diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 6c4947dd..963a7532 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -425,6 +425,8 @@ BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; }; D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; }; D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; }; + D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; }; + D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; }; D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; }; D78525252A7B2EA4002FA637 /* NoteContentViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78525242A7B2EA4002FA637 /* NoteContentViewTests.swift */; }; D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7870BC02AC4750B0080BA88 /* MentionView.swift */; }; @@ -1105,6 +1107,8 @@ BA37598B2ABCCE500018D73B /* PhotoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureProcessor.swift; sourceTree = ""; }; BA37598C2ABCCE500018D73B /* VideoCaptureProcessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoCaptureProcessor.swift; sourceTree = ""; }; BA3759962ABCCF360018D73B /* CameraPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraPreview.swift; sourceTree = ""; }; + D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = ""; }; + D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = ""; }; BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEmojiView.swift; sourceTree = ""; }; BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiListItemView.swift; sourceTree = ""; }; BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = ""; }; @@ -1300,6 +1304,7 @@ 3A5E47C42A4A6CF400C0D090 /* Trie.swift */, 3A90B1802A4EA3AF00000D94 /* UserSearchCache.swift */, D723C38D2AB8D83400065664 /* ContentFilters.swift */, + D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */, ); path = Models; sourceTree = ""; @@ -2164,6 +2169,7 @@ 4C684A562A7FFAE6005E6031 /* UrlTests.swift */, D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */, D71DC1EB2A9129C3006E207C /* PostViewTests.swift */, + D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */, ); path = damusTests; sourceTree = ""; @@ -2681,6 +2687,7 @@ 4C32B95C2A9AD44700DC3548 /* String+extension.swift in Sources */, 4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */, 4CA352AA2A76BF3A003BB08B /* LocalNotificationNotify.swift in Sources */, + D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */, 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */, 4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, @@ -2938,6 +2945,7 @@ 4C7D097E2A0C58B900943473 /* WalletConnectTests.swift in Sources */, 4CB883AA297612FF00DC99E7 /* ZapTests.swift in Sources */, 4CB8839A297322D200DC99E7 /* DMTests.swift in Sources */, + D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */, 4C9054852A6AEAA000811EEC /* NdbTests.swift in Sources */, 75AD872B2AA23A460085EF2C /* Block+Tests.swift in Sources */, F944F56E29EA9CCC0067B3BF /* DamusParseContentTests.swift in Sources */, diff --git a/damus/Models/DamusCacheManager.swift b/damus/Models/DamusCacheManager.swift new file mode 100644 index 00000000..00413817 --- /dev/null +++ b/damus/Models/DamusCacheManager.swift @@ -0,0 +1,59 @@ +// +// DamusCacheManager.swift +// damus +// +// Created by Daniel D’Aquino on 2023-10-04. +// + +import Foundation +import Kingfisher + +struct DamusCacheManager { + static var shared: DamusCacheManager = DamusCacheManager() + + func clear_cache(damus_state: DamusState, completion: (() -> Void)? = nil) { + Log.info("Clearing all caches", for: .storage) + clear_kingfisher_cache(completion: { + clear_cache_folder(completion: { + Log.info("All caches cleared", for: .storage) + completion?() + }) + }) + } + + func clear_kingfisher_cache(completion: (() -> Void)? = nil) { + Log.info("Clearing Kingfisher cache", for: .storage) + KingfisherManager.shared.cache.clearMemoryCache() + KingfisherManager.shared.cache.clearDiskCache { + Log.info("Kingfisher cache cleared", for: .storage) + completion?() + } + } + + func clear_cache_folder(completion: (() -> Void)? = nil) { + Log.info("Clearing entire cache folder", for: .storage) + let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + + do { + let fileNames = try FileManager.default.contentsOfDirectory(atPath: cacheURL.path) + + for fileName in fileNames { + let filePath = cacheURL.appendingPathComponent(fileName) + + // Prevent issues by double-checking if files are in use, and do not delete them if they are. + // This is not perfect. There is still a small chance for a race condition if a file is opened between this check and the file removal. + let isBusy = (!(access(filePath.path, F_OK) == -1 && errno == ETXTBSY)) + if isBusy { + continue + } + + try FileManager.default.removeItem(at: filePath) + } + + Log.info("Cache folder cleared successfully.", for: .storage) + completion?() + } catch { + Log.error("Could not clear cache folder", for: .storage) + } + } +} diff --git a/damus/Util/Log.swift b/damus/Util/Log.swift index fb753c0a..7811f90c 100644 --- a/damus/Util/Log.swift +++ b/damus/Util/Log.swift @@ -12,6 +12,7 @@ import os.log enum LogCategory: String { case nav case render + case storage } /// Damus structured logger @@ -44,4 +45,12 @@ class Log { static func info(_ msg: StaticString, for logcat: LogCategory, _ args: CVarArg...) { Log.log(msg, for: logger(for: logcat), type: OSLogType.info, args) } + + static func debug(_ msg: StaticString, for logcat: LogCategory, _ args: CVarArg...) { + Log.log(msg, for: logger(for: logcat), type: OSLogType.debug, args) + } + + static func error(_ msg: StaticString, for logcat: LogCategory, _ args: CVarArg...) { + Log.log(msg, for: logger(for: logcat), type: OSLogType.error, args) + } } diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift index 4a51208a..76129b56 100644 --- a/damus/Views/ConfigView.swift +++ b/damus/Views/ConfigView.swift @@ -174,23 +174,3 @@ func handle_string_amount(new_value: String) -> Int? { return amt } - -func clear_kingfisher_cache(completion: (() -> Void)? = nil) { - KingfisherManager.shared.cache.clearMemoryCache() - - let group = DispatchGroup() - - group.enter() - KingfisherManager.shared.cache.clearDiskCache { - group.leave() - } - - group.enter() - KingfisherManager.shared.cache.cleanExpiredDiskCache { - group.leave() - } - - group.notify(queue: .main) { - completion?() - } -} diff --git a/damus/Views/Settings/AppearanceSettingsView.swift b/damus/Views/Settings/AppearanceSettingsView.swift index 59317632..0ee6311b 100644 --- a/damus/Views/Settings/AppearanceSettingsView.swift +++ b/damus/Views/Settings/AppearanceSettingsView.swift @@ -115,7 +115,7 @@ struct AppearanceSettingsView: View { let group = DispatchGroup() group.enter() - clear_kingfisher_cache(completion: { + DamusCacheManager.shared.clear_cache(damus_state: self.damus_state, completion: { group.leave() }) diff --git a/damusTests/DamusCacheManagerTests.swift b/damusTests/DamusCacheManagerTests.swift new file mode 100644 index 00000000..dc90beea --- /dev/null +++ b/damusTests/DamusCacheManagerTests.swift @@ -0,0 +1,31 @@ +// +// DamusCacheManagerTests.swift +// damusTests +// +// Created by Daniel D’Aquino on 2023-10-04. +// + +import Foundation + +import Foundation +import XCTest +@testable import damus +import SwiftUI + +final class DamusCacheManagerTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + /// Simple smoke test to check if clearing cache will crash the system + func testCacheManagerSmoke() throws { + for _ in Range(0...20) { + DamusCacheManager.shared.clear_cache(damus_state: test_damus_state) + } + } +}