From 67323a41eb3124f57bdd595a33f0b3ee85c4d6e7 Mon Sep 17 00:00:00 2001 From: JRoberts <8711996+jr-simplex@users.noreply.github.com> Date: Mon, 19 Sep 2022 19:05:29 +0400 Subject: [PATCH] ios: clear storage (#1069) * wip * display current storage state * alert * fix * simplify * remove unused function * fix log * replace prints with logger * Apply suggestions from code review Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * low res will remain text Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../Shared/Views/Database/DatabaseView.swift | 38 +++++++++++++++++- apps/ios/SimpleXChat/FileUtils.swift | 40 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 5fe3b7b836..cc677f0161 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -17,6 +17,7 @@ enum DatabaseAlert: Identifiable { case deleteChat case chatDeleted case deleteLegacyDatabase + case deleteFilesAndMedia case error(title: LocalizedStringKey, error: String = "") var id: String { @@ -28,6 +29,7 @@ enum DatabaseAlert: Identifiable { case .deleteChat: return "deleteChat" case .chatDeleted: return "chatDeleted" case .deleteLegacyDatabase: return "deleteLegacyDatabase" + case .deleteFilesAndMedia: return "deleteFilesAndMedia" case let .error(title, _): return "error \(title)" } } @@ -46,6 +48,7 @@ struct DatabaseView: View { @State private var dbContainer = dbContainerGroupDefault.get() @State private var legacyDatabase = hasLegacyDatabase() @State private var useKeychain = storeDBPassphraseGroupDefault.get() + @State private var appFilesCountAndSize: (Int, Int)? var body: some View { ZStack { @@ -147,8 +150,27 @@ struct DatabaseView: View { } } } + + Section { + Button("Delete files & media", role: .destructive) { + alert = .deleteFilesAndMedia + } + } header: { + Text("Files") + } footer: { + if let (fileCount, size) = appFilesCountAndSize { + if fileCount == 0 { + Text("No received or sent files") + } else { + Text("\(fileCount) file(s) with total size of \(ByteCountFormatter().string(fromByteCount: Int64(size)))") + } + } + } + } + .onAppear { + runChat = m.chatRunning ?? true + appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) } - .onAppear { runChat = m.chatRunning ?? true } .alert(item: $alert) { item in databaseAlert(item) } .fileImporter( isPresented: $showFileImporter, @@ -222,6 +244,15 @@ struct DatabaseView: View { }, secondaryButton: .cancel() ) + case .deleteFilesAndMedia: + return Alert( + title: Text("Delete files and media?"), + message: Text("This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain."), + primaryButton: .destructive(Text("Delete")) { + deleteFiles() + }, + secondaryButton: .cancel() + ) case let .error(title, error): return Alert(title: Text(title), message: Text("\(error)")) } @@ -354,6 +385,11 @@ struct DatabaseView: View { } } } + + private func deleteFiles() { + deleteAppFiles() + appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) + } } struct DatabaseView_Previews: PreviewProvider { diff --git a/apps/ios/SimpleXChat/FileUtils.swift b/apps/ios/SimpleXChat/FileUtils.swift index 57c5a2537f..64bf6f74e0 100644 --- a/apps/ios/SimpleXChat/FileUtils.swift +++ b/apps/ios/SimpleXChat/FileUtils.swift @@ -61,6 +61,46 @@ func fileModificationDate(_ path: String) -> Date? { } } +public func deleteAppFiles() { + let fm = FileManager.default + do { + let fileNames = try fm.contentsOfDirectory(atPath: getAppFilesDirectory().path) + for fileName in fileNames { + removeFile(fileName) + } + } catch { + logger.error("FileUtils deleteAppFiles error: \(error.localizedDescription)") + } +} + +func fileSize(_ url: URL) -> Int? { // in bytes + do { + let val = try url.resourceValues(forKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey]) + return val.totalFileAllocatedSize ?? val.fileAllocatedSize + } catch { + logger.error("FileUtils fileSize error: \(error.localizedDescription)") + return nil + } +} + +public func directoryFileCountAndSize(_ dir: URL) -> (Int, Int)? { // size in bytes + let fm = FileManager.default + if let enumerator = fm.enumerator(at: dir, includingPropertiesForKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey], options: [], errorHandler: { (_, error) -> Bool in + logger.error("FileUtils directoryFileCountAndSize error: \(error.localizedDescription)") + return false + }) { + var fileCount = 0 + var bytes = 0 + for case let url as URL in enumerator { + fileCount += 1 + bytes += fileSize(url) ?? 0 + } + return (fileCount, bytes) + } else { + return nil + } +} + public func hasBackup(newerThan date: Date) -> Bool { let dbPath = getAppDatabasePath().path return hasBackupFile(dbPath + AGENT_DB_BAK, newerThan: date)