diff --git a/indexedDb/implementation.go b/indexedDb/implementation.go index aba249271e5ef308e16d043576268c8cd8e525df..f546a822f003f656e7bde8355354db91a4ae9004 100644 --- a/indexedDb/implementation.go +++ b/indexedDb/implementation.go @@ -20,12 +20,12 @@ import ( "github.com/hack-pad/go-indexeddb/idb" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/xxdk-wasm/utils" "gitlab.com/elixxir/client/channels" "gitlab.com/elixxir/client/cmix/rounds" cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/xxdk-wasm/utils" "gitlab.com/xx_network/primitives/id" ) @@ -141,9 +141,60 @@ func (w *wasmModel) LeaveChannel(channelID *id.ID) { "Deleting Channel failed: %+v", err)) return } + + // Clean up lingering data + err = w.deleteMsgByChannel(channelID) + if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Deleting Channel's Message data failed: %+v", err)) + return + } jww.DEBUG.Printf("Successfully deleted channel: %s", channelID) } +// deleteMsgByChannel is a private helper that uses messageStoreChannelIndex +// to delete all Message with the given Channel ID. +func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error { + parentErr := errors.New("failed to deleteMsgByChannel") + + // Prepare the Transaction + txn, err := w.db.Transaction(idb.TransactionReadWrite, messageStoreName) + if err != nil { + return errors.WithMessagef(parentErr, + "Unable to create Transaction: %+v", err) + } + store, err := txn.ObjectStore(messageStoreName) + if err != nil { + return errors.WithMessagef(parentErr, + "Unable to get ObjectStore: %+v", err) + } + index, err := store.Index(messageStoreChannelIndex) + if err != nil { + return errors.WithMessagef(parentErr, + "Unable to get Index: %+v", err) + } + + // Perform the operation + channelIdStr := base64.StdEncoding.EncodeToString(channelID.Marshal()) + keyRange, err := idb.NewKeyRangeOnly(js.ValueOf(channelIdStr)) + cursorRequest, err := index.OpenCursorRange(keyRange, idb.CursorNext) + if err != nil { + return errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err) + } + ctx, cancel := newContext() + err = cursorRequest.Iter(ctx, + func(cursor *idb.CursorWithValue) error { + _, err := cursor.Delete() + return err + }) + cancel() + if err != nil { + return errors.WithMessagef(parentErr, + "Unable to delete Message data: %+v", err) + } + return nil +} + // ReceiveMessage is called whenever a message is received on a given channel. // // It may be called multiple times on the same message; it is incumbent on the @@ -159,7 +210,7 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, uuid, err := w.receiveHelper(msgToInsert) if err != nil { - jww.ERROR.Printf("Failed to receiver message: %+v", err) + jww.ERROR.Printf("Failed to receive Message: %+v", err) } go w.receivedMessageCB(uuid, channelID, false) diff --git a/indexedDb/implementation_test.go b/indexedDb/implementation_test.go index e76b2737aef2b3f854d12b7c319f0c8b86e6e375..61c550330aa47fbd603d7dc0adf2a7f7c5a9c268 100644 --- a/indexedDb/implementation_test.go +++ b/indexedDb/implementation_test.go @@ -13,6 +13,7 @@ import ( "encoding/json" "fmt" "os" + "strconv" "testing" "time" @@ -210,3 +211,59 @@ func TestWasmModel_DuplicateReceives(t *testing.T) { } } } + +// TestWasmModel_deleteMsgByChannel is a happy path test. Inserts many messages, +// deletes some, and checks that the final result is as expected. +func TestWasmModel_deleteMsgByChannel(t *testing.T) { + testString := "test_deleteMsgByChannel" + totalMessages := 10 + expectedMessages := 5 + eventModel, err := newWASMModel(testString, dummyCallback) + if err != nil { + t.Fatalf("%+v", err) + } + + // Create a test channel id + deleteChannel := id.NewIdFromString("deleteMe", id.Generic, t) + keepChannel := id.NewIdFromString("dontDeleteMe", id.Generic, t) + + // Store some test messages + cid := channel.Identity{} + for i := 0; i < totalMessages; i++ { + testStr := testString + strconv.Itoa(i) + + // Interleave the channel id to ensure cursor is behaving intelligently + thisChannel := deleteChannel + if i%2 == 0 { + thisChannel = keepChannel + } + + testMsgId := channel.MakeMessageID([]byte(testStr), &id.ID{1}) + eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, + testStr, cid, time.Now(), time.Second, rounds.Round{ID: id.Round(0)}, 0, channels.Sent) + } + + // Check pre-results + result, err := eventModel.dump(messageStoreName) + if err != nil { + t.Fatalf("%+v", err) + } + if len(result) != totalMessages { + t.Errorf("Expected %d messages, got %d", totalMessages, len(result)) + } + + // Do delete + err = eventModel.deleteMsgByChannel(deleteChannel) + if err != nil { + t.Error(err) + } + + // Check final results + result, err = eventModel.dump(messageStoreName) + if err != nil { + t.Fatalf("%+v", err) + } + if len(result) != expectedMessages { + t.Errorf("Expected %d messages, got %d", expectedMessages, len(result)) + } +} diff --git a/indexedDb/init.go b/indexedDb/init.go index 81676053b9998d89e81bc1136b9bdba4b8092360..38c742315076d2c6fad74e5c2e6fc70bfb60c9cd 100644 --- a/indexedDb/init.go +++ b/indexedDb/init.go @@ -102,13 +102,12 @@ func v1Upgrade(db *idb.Database) error { if err != nil { return err } - - messageStoreMessageIndexOpts := idb.IndexOptions{ - Unique: true, - MultiEntry: false, - } _, err = messageStore.CreateIndex(messageStoreMessageIndex, - js.ValueOf(messageStoreMessage), messageStoreMessageIndexOpts) + js.ValueOf(messageStoreMessage), + idb.IndexOptions{ + Unique: true, + MultiEntry: false, + }) if err != nil { return err } diff --git a/indexedDb/model.go b/indexedDb/model.go index 7204468bfc14b71ce5b7af6caf9a22329592735c..0e869277a8ea82965b54283c96ff6af706a99670 100644 --- a/indexedDb/model.go +++ b/indexedDb/model.go @@ -27,7 +27,6 @@ const ( messageStoreParentIndex = "parent_message_id_index" messageStoreTimestampIndex = "timestamp_index" messageStorePinnedIndex = "pinned_index" - messageStorePubkeyIndex = "pubkey_index" // Message keyPath names (must match json struct tags). messageStoreMessage = "message_id"