Merge branch 'stable'

This commit is contained in:
Evgeny Poberezkin 2025-01-13 17:42:14 +00:00
commit 49bf3cc673
No known key found for this signature in database
GPG key ID: 494BDDD9A28B577D
8 changed files with 99 additions and 27 deletions

View file

@ -987,6 +987,7 @@ final class ChatModel: ObservableObject {
if let i = getChatIndex(id) {
let removed = chats.remove(at: i)
ChatTagsModel.shared.removePresetChatTags(removed.chatInfo, removed.chatStats)
removeWallpaperFilesFromChat(removed)
}
}
}
@ -1025,6 +1026,23 @@ final class ChatModel: ObservableObject {
_ = upsertGroupMember(groupInfo, updatedMember)
}
}
func removeWallpaperFilesFromChat(_ chat: Chat) {
if case let .direct(contact) = chat.chatInfo {
removeWallpaperFilesFromTheme(contact.uiThemes)
} else if case let .group(groupInfo) = chat.chatInfo {
removeWallpaperFilesFromTheme(groupInfo.uiThemes)
}
}
func removeWallpaperFilesFromAllChats(_ user: User) {
// Currently, only removing everything from currently active user is supported. Inactive users are TODO
if user.userId == currentUser?.userId {
chats.forEach {
removeWallpaperFilesFromChat($0)
}
}
}
}
struct ShowingInvitation {

View file

@ -298,6 +298,7 @@ struct UserProfilesView: View {
private func removeUser(_ user: User, _ delSMPQueues: Bool, viewPwd: String?) async {
do {
if user.activeUser {
ChatModel.shared.removeWallpaperFilesFromAllChats(user)
if let newActive = m.users.first(where: { u in !u.user.activeUser && !u.user.hidden }) {
try await changeActiveUserAsync_(newActive.user.userId, viewPwd: nil)
try await deleteUser()
@ -323,6 +324,7 @@ struct UserProfilesView: View {
func deleteUser() async throws {
try await apiDeleteUser(user.userId, delSMPQueues, viewPwd: viewPwd)
removeWallpaperFilesFromTheme(user.uiThemes)
await MainActor.run { withAnimation { m.removeUser(user) } }
}
}

View file

@ -267,17 +267,26 @@ public func saveWallpaperFile(image: UIImage) -> String? {
public func removeWallpaperFile(fileName: String? = nil) {
do {
try FileManager.default.contentsOfDirectory(atPath: getWallpaperDirectory().path).forEach {
if URL(fileURLWithPath: $0).lastPathComponent == fileName { try FileManager.default.removeItem(atPath: $0) }
try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: getWallpaperDirectory().path), includingPropertiesForKeys: nil, options: []).forEach { url in
if url.lastPathComponent == fileName {
try FileManager.default.removeItem(at: url)
}
}
} catch {
logger.error("FileUtils.removeWallpaperFile error: \(error.localizedDescription)")
logger.error("FileUtils.removeWallpaperFile error: \(error)")
}
if let fileName {
WallpaperType.cachedImages.removeValue(forKey: fileName)
}
}
public func removeWallpaperFilesFromTheme(_ theme: ThemeModeOverrides?) {
if let theme {
removeWallpaperFile(fileName: theme.light?.wallpaper?.imageFile)
removeWallpaperFile(fileName: theme.dark?.wallpaper?.imageFile)
}
}
public func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String {
uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath)
}

View file

@ -726,17 +726,11 @@ object ChatModel {
}
fun removeChat(rhId: Long?, id: String) {
var removed: Chat? = null
chats.removeAll {
val found = it.id == id && it.remoteHostId == rhId
if (found) {
removed = it
}
found
}
removed?.let {
removePresetChatTags(it.chatInfo, it.chatStats)
val i = getChatIndex(rhId, id)
if (i != -1) {
val chat = chats.removeAt(i)
removePresetChatTags(chat.chatInfo, chat.chatStats)
removeWallpaperFilesFromChat(chat)
}
}

View file

@ -10,6 +10,7 @@ import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.ThemeModeOverrides
import chat.simplex.common.ui.theme.ThemeOverrides
import chat.simplex.common.views.chatlist.connectIfOpenedViaUri
import chat.simplex.res.MR
@ -316,6 +317,30 @@ fun removeWallpaperFile(fileName: String? = null) {
WallpaperType.cachedImages.remove(fileName)
}
fun removeWallpaperFilesFromTheme(theme: ThemeModeOverrides?) {
if (theme != null) {
removeWallpaperFile(theme.light?.wallpaper?.imageFile)
removeWallpaperFile(theme.dark?.wallpaper?.imageFile)
}
}
fun removeWallpaperFilesFromChat(chat: Chat) {
if (chat.chatInfo is ChatInfo.Direct) {
removeWallpaperFilesFromTheme(chat.chatInfo.contact.uiThemes)
} else if (chat.chatInfo is ChatInfo.Group) {
removeWallpaperFilesFromTheme(chat.chatInfo.groupInfo.uiThemes)
}
}
fun removeWallpaperFilesFromAllChats(user: User) {
// Currently, only removing everything from currently active user is supported. Inactive users are TODO
if (user.userId == chatModel.currentUser.value?.userId) {
chatModel.chats.value.forEach {
removeWallpaperFilesFromChat(it)
}
}
}
fun <T> createTmpFileAndDelete(dir: File = tmpDir, onCreated: (File) -> T): T {
val tmpFile = File(dir, UUID.randomUUID().toString())
tmpFile.parentFile.mkdirs()

View file

@ -347,6 +347,7 @@ private suspend fun doRemoveUser(m: ChatModel, user: User, users: List<User>, de
try {
when {
user.activeUser -> {
removeWallpaperFilesFromAllChats(user)
val newActive = users.firstOrNull { u -> !u.activeUser && !u.hidden }
if (newActive != null) {
m.controller.changeActiveUser_(user.remoteHostId, newActive.userId, null)
@ -366,6 +367,7 @@ private suspend fun doRemoveUser(m: ChatModel, user: User, users: List<User>, de
m.controller.apiDeleteUser(user, delSMPQueues, viewPwd)
}
}
removeWallpaperFilesFromTheme(user.uiThemes)
m.removeUser(user)
ntfManager.cancelNotificationsForUser(user.userId)
} catch (e: Exception) {

View file

@ -106,7 +106,7 @@ import Simplex.Messaging.Version
import Simplex.RemoteControl.Invitation (RCInvitation (..), RCSignedInvitation (..))
import Simplex.RemoteControl.Types (RCCtrlAddress (..))
import System.Exit (ExitCode, exitSuccess)
import System.FilePath (takeFileName, (</>))
import System.FilePath (takeExtension, takeFileName, (</>))
import System.IO (Handle, IOMode (..))
import System.Random (randomRIO)
import UnliftIO.Async
@ -146,6 +146,15 @@ imageExtensions = [".jpg", ".jpeg", ".png", ".gif"]
fixedImagePreview :: ImageData
fixedImagePreview = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="
imageFilePrefix :: String
imageFilePrefix = "IMG_"
voiceFilePrefix :: String
voiceFilePrefix = "voice_"
videoFilePrefix :: String
videoFilePrefix = "video_"
-- enableSndFiles has no effect when mainApp is True
startChatController :: Bool -> Bool -> CM' (Async ())
startChatController mainApp enableSndFiles = do
@ -897,7 +906,8 @@ processChatCommand' vr = \case
ifM
(doesFileExist fsFromPath)
( do
fsNewPath <- liftIO $ filesFolder `uniqueCombine` fileName
newFileName <- liftIO $ maybe (pure fileName) (generateNewFileName fileName) $ mediaFilePrefix mc
fsNewPath <- liftIO $ filesFolder `uniqueCombine` newFileName
liftIO $ B.writeFile fsNewPath "" -- create empty file
encrypt <- chatReadVar encryptLocalFiles
cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing
@ -934,6 +944,17 @@ processChatCommand' vr = \case
when (B.length ch /= chSize') $ throwError $ CF.FTCEFileIOError "encrypting file: unexpected EOF"
liftIO . CF.hPut w $ LB.fromStrict ch
when (size' > 0) $ copyChunks r w size'
mediaFilePrefix :: MsgContent -> Maybe FilePath
mediaFilePrefix = \case
MCImage {} -> Just imageFilePrefix
MCVoice {} -> Just voiceFilePrefix
MCVideo {} -> Just videoFilePrefix
_ -> Nothing
generateNewFileName fileName prefix = do
currentDate <- liftIO getCurrentTime
let formattedDate = formatTime defaultTimeLocale "%Y%m%d_%H%M%S" currentDate
let ext = takeExtension fileName
pure $ prefix <> formattedDate <> ext
APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user
UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId
APIChatRead chatRef@(ChatRef cType chatId) -> withUser $ \_ -> case cType of

View file

@ -785,8 +785,9 @@ testMultiForwardFiles =
bob <## " message without file"
bob <# "@cath <- @alice"
bob <## " test_1.jpg"
bob <# "/f @cath test_1.jpg"
jpgFileName <- T.unpack . T.strip . T.pack <$> getTermLine bob
bob <# ("/f @cath " <> jpgFileName)
bob <## "use /fc 5 to cancel sending"
bob <# "@cath <- @alice"
@ -808,8 +809,8 @@ testMultiForwardFiles =
cath <## " message without file"
cath <# "bob> -> forwarded"
cath <## " test_1.jpg"
cath <# "bob> sends file test_1.jpg (136.5 KiB / 139737 bytes)"
cath <## (" " <> jpgFileName)
cath <# ("bob> sends file " <> jpgFileName <> " (136.5 KiB / 139737 bytes)")
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
cath <# "bob> -> forwarded"
@ -824,15 +825,15 @@ testMultiForwardFiles =
cath <## ""
-- file transfer
bob <## "completed uploading file 5 (test_1.jpg) for cath"
bob <## ("completed uploading file 5 (" <> jpgFileName <> ") for cath")
bob <## "completed uploading file 6 (test_1.pdf) for cath"
cath ##> "/fr 1"
cath
<### [ "saving file 1 from bob to test_1.jpg",
"started receiving file 1 (test_1.jpg) from bob"
<### [ ConsoleString $ "saving file 1 from bob to " <> jpgFileName,
ConsoleString $ "started receiving file 1 (" <> jpgFileName <> ") from bob"
]
cath <## "completed receiving file 1 (test_1.jpg) from bob"
cath <## ("completed receiving file 1 (" <> jpgFileName <> ") from bob")
cath ##> "/fr 2"
cath
@ -841,9 +842,9 @@ testMultiForwardFiles =
]
cath <## "completed receiving file 2 (test_1.pdf) from bob"
src1B <- B.readFile "./tests/tmp/bob_app_files/test_1.jpg"
src1B <- B.readFile ("./tests/tmp/bob_app_files/" <> jpgFileName)
src1B `shouldBe` dest1
dest1C <- B.readFile "./tests/tmp/cath_app_files/test_1.jpg"
dest1C <- B.readFile ("./tests/tmp/cath_app_files/" <> jpgFileName)
dest1C `shouldBe` src1B
src2B <- B.readFile "./tests/tmp/bob_app_files/test_1.pdf"
@ -886,5 +887,5 @@ testMultiForwardFiles =
checkActionDeletesFile "./tests/tmp/bob_app_files/test.jpg" $ do
bob ##> "/clear alice"
bob <## "alice: all messages are removed locally ONLY"
fwdFileExists <- doesFileExist "./tests/tmp/bob_app_files/test_1.jpg"
fwdFileExists <- doesFileExist ("./tests/tmp/bob_app_files/" <> jpgFileName)
fwdFileExists `shouldBe` True