diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 0f49507e03..3987250fa0 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1050,7 +1050,16 @@ processChatCommand = \case withStore' (\db -> setContactDeleted db user ct) `catchChatError` (toView . CRChatError (Just user)) pure $ map aConnId conns - CTLocal -> error "TODO: APIDeleteChat.CTLocal" + CTLocal -> do + nf <- withStore $ \db -> getNoteFolder db user chatId + filesInfo <- withStore' $ \db -> getNoteFolderFileInfo db user nf + withChatLock "deleteChat local" . procCmd $ do + mapM_ (deleteFile user) filesInfo + -- functions below are called in separate transactions to prevent crashes on android + -- (possibly, race condition on integrity check?) + withStore' $ \db -> deleteNoteFolderFiles db userId nf + withStore' $ \db -> deleteNoteFolder db user nf + pure $ CRNoteFolderDeleted user nf CTContactRequest -> pure $ chatCmdError (Just user) "not supported" APIClearChat (ChatRef cType chatId) -> withUser $ \user -> case cType of CTDirect -> do @@ -1853,7 +1862,10 @@ processChatCommand = \case NewNoteFolder displayName -> withUser $ \user@User {userId} -> do -- processChatCommand $ APINewLocalChat userId localChatProfile checkValidName displayName - withStore $ \db -> CRLocalChatCreated user <$> createNewNoteFolder db userId displayName + withStore $ \db -> CRNoteFolderCreated user <$> createNewNoteFolder db userId displayName + DeleteNoteFolder displayName -> withUser $ \user -> do + folderId <- withStore $ \db -> getNoteFolderIdByName db user displayName + processChatCommand $ APIDeleteChat (ChatRef CTLocal folderId) True LastChats count_ -> withUser' $ \user -> do let count = fromMaybe 5000 count_ (errs, previews) <- partitionEithers <$> withStore' (\db -> getChatPreviews db user False (PTLast count) clqNoFilters) @@ -6153,7 +6165,7 @@ chatCommandP = ("/remove " <|> "/rm ") *> char_ '#' *> (RemoveMember <$> displayName <* A.space <* char_ '@' <*> displayName), ("/leave " <|> "/l ") *> char_ '#' *> (LeaveGroup <$> displayName), ("/delete #" <|> "/d #") *> (DeleteGroup <$> displayName), - -- TODO: ("/delete $" <|> "/d $") *> (DeleteNoteFolder <$> displayName), + ("/delete $" <|> "/d $") *> (DeleteNoteFolder <$> displayName), ("/delete " <|> "/d ") *> char_ '@' *> (DeleteContact <$> displayName), -- TODO: "/clear $" *> (ClearNoteFolder <$> displayName), "/clear #" *> (ClearGroup <$> displayName), diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 24c385d7ba..67d4768b33 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -408,6 +408,7 @@ data ChatCommand | SendGroupMessageQuote {groupName :: GroupName, contactName_ :: Maybe ContactName, quotedMsg :: Text, message :: Text} -- | APINewLocalChat UserId LocalChatProfile | NewNoteFolder NoteFolderName + | DeleteNoteFolder NoteFolderName | LastChats (Maybe Int) -- UserId (not used in UI) | LastMessages (Maybe ChatName) Int (Maybe String) -- UserId (not used in UI) | LastChatItemId (Maybe ChatName) Int -- UserId (not used in UI) @@ -556,7 +557,8 @@ data ChatResponse | CRUserDeletedMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} | CRGroupsList {user :: User, groups :: [(GroupInfo, GroupSummary)]} | CRSentGroupInvitation {user :: User, groupInfo :: GroupInfo, contact :: Contact, member :: GroupMember} - | CRLocalChatCreated {user :: User, noteFolder :: NoteFolder} + | CRNoteFolderCreated {user :: User, noteFolder :: NoteFolder} + | CRNoteFolderDeleted {user :: User, noteFolder :: NoteFolder} | CRFileTransferStatus User (FileTransfer, [Integer]) -- TODO refactor this type to FileTransferStatus | CRFileTransferStatusXFTP User AChatItem | CRUserProfile {user :: User, profile :: Profile} diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index 8fe97896c8..20a9e928a0 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -72,6 +72,7 @@ module Simplex.Chat.Store.Files getSndFileTransfer, getSndFileTransfers, getContactFileInfo, + getNoteFolderFileInfo, getLocalCryptoFile, updateDirectCIFileStatus, ) @@ -895,6 +896,11 @@ getContactFileInfo db User {userId} Contact {contactId} = map toFileInfo <$> DB.query db (fileInfoQuery <> " WHERE i.user_id = ? AND i.contact_id = ?") (userId, contactId) +getNoteFolderFileInfo :: DB.Connection -> User -> NoteFolder -> IO [CIFileInfo] +getNoteFolderFileInfo db User {userId} NoteFolder {noteFolderId} = + map toFileInfo + <$> DB.query db (fileInfoQuery <> " WHERE i.user_id = ? AND i.note_folder_id = ?") (userId, noteFolderId) + getLocalCryptoFile :: DB.Connection -> UserId -> Int64 -> Bool -> ExceptT StoreError IO CryptoFile getLocalCryptoFile db userId fileId sent = liftIO (getFileTransferRow_ db userId fileId) >>= \case diff --git a/src/Simplex/Chat/Store/NoteFolders.hs b/src/Simplex/Chat/Store/NoteFolders.hs index 1a29cecd8d..6755c9043c 100644 --- a/src/Simplex/Chat/Store/NoteFolders.hs +++ b/src/Simplex/Chat/Store/NoteFolders.hs @@ -68,3 +68,19 @@ getNoteFolder db User {userId} noteFolderId = where toNoteFolder (displayName, localDisplayName, createdAt, updatedAt, chatTs, favorite, unread) = NoteFolder {noteFolderId, userId, displayName, localDisplayName, createdAt, updatedAt, chatTs, favorite, unread} + +deleteNoteFolderFiles :: DB.Connection -> UserId -> NoteFolder -> IO () +deleteNoteFolderFiles db userId NoteFolder {noteFolderId} = do + DB.execute + db + [sql| + DELETE FROM files + WHERE user_id = ? + AND chat_item_id IN ( + SELECT chat_item_id FROM chat_items WHERE user_id = ? AND note_folder_id = ? + ) + |] (userId, userId, noteFolderId) + +deleteNoteFolder :: DB.Connection -> User -> NoteFolder -> IO () +deleteNoteFolder db User {userId} NoteFolder {noteFolderId} = + DB.execute db [sql| DELETE FROM note_folders WHERE user_id = ? AND note_folder_id = ? |] (userId, noteFolderId) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index f7efa39fa3..8fe6c5bf6c 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -262,7 +262,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRMemberSubError u g m e -> ttyUser u [ttyGroup' g <> " member " <> ttyMember m <> " error: " <> sShow e] CRMemberSubSummary u summary -> ttyUser u $ viewErrorsSummary (filter (isJust . memberError) summary) " group member errors" CRGroupSubscribed u g -> ttyUser u $ viewGroupSubscribed g - CRLocalChatCreated u NoteFolder {displayName} -> ttyUser u ["new note folder created, write to $" <> plain displayName <> " to add notes"] + CRNoteFolderCreated u NoteFolder {displayName} -> ttyUser u ["new note folder created, write to $" <> plain displayName <> " to add notes"] + CRNoteFolderDeleted u NoteFolder {displayName} -> ttyUser u ["note folder " <> plain displayName <> " deleted"] CRPendingSubSummary u _ -> ttyUser u [] CRSndFileSubError u SndFileTransfer {fileId, fileName} e -> ttyUser u ["sent file " <> sShow fileId <> " (" <> plain fileName <> ") error: " <> sShow e] @@ -1895,6 +1896,7 @@ viewChatError logLevel testView = \case SEDuplicateGroupMessage {groupId, sharedMsgId} | testView -> ["duplicate group message, group id: " <> sShow groupId <> ", message id: " <> sShow sharedMsgId] | otherwise -> [] + SENoteFolderNotFoundByName f -> ["no folder " <> ttyLocal f] e -> ["chat db error: " <> sShow e] ChatErrorDatabase err -> case err of DBErrorEncrypted -> ["error: chat database is already encrypted"] @@ -2009,6 +2011,9 @@ ttyGroup g = styled (colored Blue) $ "#" <> viewName g ttyGroup' :: GroupInfo -> StyledString ttyGroup' = ttyGroup . groupName' +ttyLocal :: NoteFolderName -> StyledString +ttyLocal l = styled (colored Green) $ "$" <> viewName l + viewContactName :: Contact -> Text viewContactName = viewName . localDisplayName' diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index b185b21aee..0e1c3d9e4b 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -26,6 +26,9 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice <## "message deleted" alice ##> "/tail" + alice ##> "/delete $self" + alice <## "note folder self deleted" + testUserNotes :: FilePath -> IO () testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/note folder self" @@ -42,5 +45,7 @@ testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/note folder gossip" alice <## "new note folder created, write to $gossip to add notes" - alice ##> "/tail" + + alice ##> "/_delete item $1 1 internal" + alice <## "chat db error: SENoteFolderNotFound {noteFolderId = 1}"