dnote/core/reducer.go
2018-08-23 08:11:39 +10:00

265 lines
5.8 KiB
Go

package core
import (
"encoding/json"
"sort"
"github.com/dnote/cli/infra"
"github.com/pkg/errors"
)
type AddNoteData struct {
NoteUUID string `json:"note_uuid"`
BookName string `json:"book_name"`
Content string `json:"content"`
}
type EditNoteData struct {
NoteUUID string `json:"note_uuid"`
FromBook string `json:"from_book"`
ToBook string `json:"to_book"`
Content string `json:"content"`
}
type RemoveNoteData struct {
NoteUUID string `json:"note_uuid"`
BookName string `json:"book_name"`
}
type AddBookData struct {
BookName string `json:"book_name"`
}
type RemoveBookData struct {
BookName string `json:"book_name"`
}
// ReduceAll reduces all actions
func ReduceAll(ctx infra.DnoteCtx, actions []Action) error {
for _, action := range actions {
if err := Reduce(ctx, action); err != nil {
return errors.Wrap(err, "Failed to reduce action")
}
}
return nil
}
// Reduce transitions the local dnote state by consuming the action returned
// from the server
func Reduce(ctx infra.DnoteCtx, action Action) error {
var err error
switch action.Type {
case ActionAddNote:
err = handleAddNote(ctx, action)
case ActionRemoveNote:
err = handleRemoveNote(ctx, action)
case ActionEditNote:
err = handleEditNote(ctx, action)
case ActionAddBook:
err = handleAddBook(ctx, action)
case ActionRemoveBook:
err = handleRemoveBook(ctx, action)
default:
return errors.Errorf("Unsupported action %s", action.Type)
}
if err != nil {
return errors.Wrapf(err, "Failed to process the action %s", action.Type)
}
return nil
}
func handleAddNote(ctx infra.DnoteCtx, action Action) error {
var data AddNoteData
err := json.Unmarshal(action.Data, &data)
if err != nil {
return errors.Wrap(err, "Failed to parse the action data")
}
note := infra.Note{
UUID: data.NoteUUID,
Content: data.Content,
AddedOn: action.Timestamp,
}
dnote, err := GetDnote(ctx)
if err != nil {
return errors.Wrap(err, "Failed to get dnote")
}
book, ok := dnote[data.BookName]
if !ok {
return errors.Errorf("Book with a name %s is not found", data.BookName)
}
// Check duplicate
for _, note := range book.Notes {
if note.UUID == data.NoteUUID {
return errors.New("Duplicate note exists")
}
}
notes := append(dnote[book.Name].Notes, note)
sort.SliceStable(notes, func(i, j int) bool {
return notes[i].AddedOn < notes[j].AddedOn
})
dnote[book.Name] = GetUpdatedBook(dnote[book.Name], notes)
err = WriteDnote(ctx, dnote)
if err != nil {
return errors.Wrap(err, "Failed to write dnote")
}
return nil
}
func handleRemoveNote(ctx infra.DnoteCtx, action Action) error {
var data RemoveNoteData
err := json.Unmarshal(action.Data, &data)
if err != nil {
return errors.Wrap(err, "Failed to parse the action data")
}
dnote, err := GetDnote(ctx)
if err != nil {
return errors.Wrap(err, "Failed to get dnote")
}
book, ok := dnote[data.BookName]
if !ok {
return errors.Errorf("Book with a name %s is not found", data.BookName)
}
notes := FilterNotes(book.Notes, func(note infra.Note) bool {
return note.UUID != data.NoteUUID
})
dnote[book.Name] = GetUpdatedBook(dnote[book.Name], notes)
err = WriteDnote(ctx, dnote)
if err != nil {
return errors.Wrap(err, "Failed to write dnote")
}
return nil
}
func handleEditNote(ctx infra.DnoteCtx, action Action) error {
var data EditNoteData
err := json.Unmarshal(action.Data, &data)
if err != nil {
return errors.Wrap(err, "Failed to parse the action data")
}
dnote, err := GetDnote(ctx)
if err != nil {
return errors.Wrap(err, "Failed to get dnote")
}
fromBook, ok := dnote[data.FromBook]
if !ok {
return errors.Errorf("Origin book with a name %s is not found", data.FromBook)
}
if data.ToBook == "" {
for idx, note := range fromBook.Notes {
if note.UUID == data.NoteUUID {
note.Content = data.Content
note.EditedOn = action.Timestamp
dnote[fromBook.Name].Notes[idx] = note
}
}
} else {
// Change the book
toBook, ok := dnote[data.ToBook]
if !ok {
return errors.Errorf("Destination book with a name %s is not found", data.FromBook)
}
var index int
var note infra.Note
// Find the note
for idx := range fromBook.Notes {
note = fromBook.Notes[idx]
if note.UUID == data.NoteUUID {
index = idx
}
}
note.Content = data.Content
note.EditedOn = action.Timestamp
dnote[fromBook.Name] = GetUpdatedBook(dnote[fromBook.Name], append(fromBook.Notes[:index], fromBook.Notes[index+1:]...))
dnote[toBook.Name] = GetUpdatedBook(dnote[toBook.Name], append(toBook.Notes, note))
}
err = WriteDnote(ctx, dnote)
if err != nil {
return errors.Wrap(err, "Failed to write dnote")
}
return nil
}
func handleAddBook(ctx infra.DnoteCtx, action Action) error {
var data AddBookData
err := json.Unmarshal(action.Data, &data)
if err != nil {
return errors.Wrap(err, "Failed to parse the action data")
}
dnote, err := GetDnote(ctx)
if err != nil {
return errors.Wrap(err, "Failed to get dnote")
}
_, exists := dnote[data.BookName]
if exists {
// If book already exists, another machine added a book with the same name.
// noop
return nil
}
book := infra.Book{
Name: data.BookName,
Notes: []infra.Note{},
}
dnote[data.BookName] = book
err = WriteDnote(ctx, dnote)
if err != nil {
return errors.Wrap(err, "Failed to write dnote")
}
return nil
}
func handleRemoveBook(ctx infra.DnoteCtx, action Action) error {
var data RemoveBookData
err := json.Unmarshal(action.Data, &data)
if err != nil {
return errors.Wrap(err, "Failed to parse the action data")
}
dnote, err := GetDnote(ctx)
if err != nil {
return errors.Wrap(err, "Failed to get dnote")
}
for bookName := range dnote {
if bookName == data.BookName {
delete(dnote, bookName)
}
}
err = WriteDnote(ctx, dnote)
if err != nil {
return errors.Wrap(err, "Failed to write dnote")
}
return nil
}