diff --git a/File Provider Extension/FileProviderExtension.swift b/File Provider Extension/FileProviderExtension.swift index ae13826a6a..c8926c09a0 100644 --- a/File Provider Extension/FileProviderExtension.swift +++ b/File Provider Extension/FileProviderExtension.swift @@ -220,7 +220,7 @@ class FileProviderExtension: NSFileProviderExtension { assert(pathComponents.count > 2) let itemIdentifier = NSFileProviderItemIdentifier(pathComponents[pathComponents.count - 2]) let fileName = pathComponents[pathComponents.count - 1] - guard let metadata = self.database.getMetadataFromOcIdAndocIdTransfer(itemIdentifier.rawValue) else { + guard let metadata = await self.database.getMetadataFromOcIdAndocIdTransferAsync(itemIdentifier.rawValue) else { return } let serverUrlFileName = metadata.serverUrl + "/" + fileName diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 64681acf33..8f0301e11c 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5872,7 +5872,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5938,7 +5938,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; diff --git a/iOSClient/Data/NCManageDatabase+Account.swift b/iOSClient/Data/NCManageDatabase+Account.swift index 0e0b993a1d..4ad7f94746 100644 --- a/iOSClient/Data/NCManageDatabase+Account.swift +++ b/iOSClient/Data/NCManageDatabase+Account.swift @@ -518,6 +518,21 @@ extension NCManageDatabase { } ?? NCBrandOptions.shared.folderDefaultAutoUpload } + func getAccountAutoUploadFileNameAsync(account: String) async -> String { + let result: String? = await performRealmReadAsync { realm in + guard let record = realm.objects(tableAccount.self) + .filter("account == %@", account) + .first + else { + return nil + } + + return record.autoUploadFileName.isEmpty ? nil : record.autoUploadFileName + } + + return result ?? NCBrandOptions.shared.folderDefaultAutoUpload + } + func getAccountAutoUploadDirectory(session: NCSession.Session) -> String { return getAccountAutoUploadDirectory(account: session.account, urlBase: session.urlBase, userId: session.userId) } @@ -535,10 +550,29 @@ extension NCManageDatabase { } ?? homeServer } + func getAccountAutoUploadDirectoryAsync(account: String, urlBase: String, userId: String) async -> String { + let homeServer = utilityFileSystem.getHomeServer(urlBase: urlBase, userId: userId) + + let directory: String? = await performRealmReadAsync { realm in + realm.objects(tableAccount.self) + .filter("account == %@", account) + .first? + .autoUploadDirectory + } + + return directory.flatMap { dir in + (dir.isEmpty || dir.contains("/webdav")) ? homeServer : dir + } ?? homeServer + } + func getAccountAutoUploadServerUrlBase(session: NCSession.Session) -> String { return getAccountAutoUploadServerUrlBase(account: session.account, urlBase: session.urlBase, userId: session.userId) } + func getAccountAutoUploadServerUrlBaseAsync(session: NCSession.Session) async -> String { + return await getAccountAutoUploadServerUrlBaseAsync(account: session.account, urlBase: session.urlBase, userId: session.userId) + } + func getAccountAutoUploadServerUrlBase(account: String, urlBase: String, userId: String) -> String { let cameraFileName = self.getAccountAutoUploadFileName(account: account) let cameraDirectory = self.getAccountAutoUploadDirectory(account: account, urlBase: urlBase, userId: userId) @@ -546,6 +580,13 @@ extension NCManageDatabase { return folderPhotos } + func getAccountAutoUploadServerUrlBaseAsync(account: String, urlBase: String, userId: String) async -> String { + let cameraFileName = await self.getAccountAutoUploadFileNameAsync(account: account) + let cameraDirectory = await self.getAccountAutoUploadDirectoryAsync(account: account, urlBase: urlBase, userId: userId) + let folderPhotos = utilityFileSystem.stringAppendServerUrl(cameraDirectory, addFileName: cameraFileName) + return folderPhotos + } + func getAccountAutoUploadSubfolderGranularity() -> Int { performRealmRead { realm in realm.objects(tableAccount.self) diff --git a/iOSClient/Data/NCManageDatabase+AutoUpload.swift b/iOSClient/Data/NCManageDatabase+AutoUpload.swift index 4b70f4d71a..e2bab0e8ba 100644 --- a/iOSClient/Data/NCManageDatabase+AutoUpload.swift +++ b/iOSClient/Data/NCManageDatabase+AutoUpload.swift @@ -57,7 +57,8 @@ extension NCManageDatabase { // MARK: - Realm Read - func fetchSkipFileNames(account: String, autoUploadServerUrlBase: String) async -> Set { + func fetchSkipFileNames(account: String, + autoUploadServerUrlBase: String) async -> Set { let result: Set? = await performRealmReadAsync { realm in let metadatas = realm.objects(tableMetadata.self) .filter("account == %@ AND autoUploadServerUrlBase == %@ AND status IN %@", account, autoUploadServerUrlBase, NCGlobal.shared.metadataStatusUploadingAllMode) @@ -78,7 +79,8 @@ extension NCManageDatabase { /// - account: The account identifier. /// - autoUploadServerUrlBase: The server base URL for auto-upload. /// - Returns: The most recent upload `Date`, or `nil` if no entry exists. - func fetchLastAutoUploadedDateAsync(account: String, autoUploadServerUrlBase: String) async -> Date? { + func fetchLastAutoUploadedDateAsync(account: String, + autoUploadServerUrlBase: String) async -> Date? { await performRealmReadAsync { realm in realm.objects(tableAutoUploadTransfer.self) .filter("account == %@ AND serverUrlBase == %@", account, autoUploadServerUrlBase) @@ -87,11 +89,21 @@ extension NCManageDatabase { } } - func existsAutoUpload(account: String, autoUploadServerUrlBase: String) -> Bool { + func existsAutoUpload(account: String, + autoUploadServerUrlBase: String) -> Bool { return performRealmRead { realm in realm.objects(tableAutoUploadTransfer.self) .filter("account == %@ AND serverUrlBase == %@", account, autoUploadServerUrlBase) .first != nil } ?? false } + + func existsAutoUploadAsync(account: String, + autoUploadServerUrlBase: String) async -> Bool { + return await performRealmReadAsync { realm in + realm.objects(tableAutoUploadTransfer.self) + .filter("account == %@ AND serverUrlBase == %@", account, autoUploadServerUrlBase) + .first != nil + } ?? false + } } diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index bf117f907e..1d5fe2d28a 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -471,14 +471,6 @@ extension NCManageDatabase { } } - func deleteMetadatas(_ metadatas: [tableMetadata], sync: Bool = true) { - let detached = metadatas.map { $0.detachedCopy() } - - performRealmWrite(sync: sync) { realm in - realm.delete(detached) - } - } - // Asynchronously deletes an array of `tableMetadata` entries from the Realm database. /// - Parameter metadatas: The `tableMetadata` objects to be deleted. func deleteMetadatasAsync(_ metadatas: [tableMetadata]) async { @@ -1017,16 +1009,27 @@ extension NCManageDatabase { func getMetadatasAsync(predicate: NSPredicate, sortedByKeyPath: String, - ascending: Bool = false) async -> [tableMetadata]? { + ascending: Bool = false, + limit: Int? = nil) async -> [tableMetadata]? { return await performRealmReadAsync { realm in - realm.objects(tableMetadata.self) + let results = realm.objects(tableMetadata.self) .filter(predicate) - .sorted(byKeyPath: sortedByKeyPath, ascending: ascending) - .map { $0.detachedCopy() } + .sorted(byKeyPath: sortedByKeyPath, + ascending: ascending) + + if let limit { + let sliced = results.prefix(limit) + return sliced.map { $0.detachedCopy() } + } else { + return results.map { $0.detachedCopy() } + } } } - func getMetadatas(predicate: NSPredicate, numItems: Int, sorted: String, ascending: Bool) -> [tableMetadata] { + func getMetadatas(predicate: NSPredicate, + numItems: Int, + sorted: String, + ascending: Bool) -> [tableMetadata] { return performRealmRead { realm in let results = realm.objects(tableMetadata.self) .filter(predicate) @@ -1058,17 +1061,6 @@ extension NCManageDatabase { } } - func getMetadataFromOcIdAndocIdTransfer(_ ocId: String?) -> tableMetadata? { - guard let ocId else { return nil } - - return performRealmRead { realm in - realm.objects(tableMetadata.self) - .filter("ocId == %@ OR ocIdTransfer == %@", ocId, ocId) - .first - .map { $0.detachedCopy() } - } - } - func getMetadataFromOcIdAndocIdTransferAsync(_ ocId: String?) async -> tableMetadata? { guard let ocId else { return nil @@ -1215,13 +1207,16 @@ extension NCManageDatabase { } ?? [] } - func getMetadatas(predicate: NSPredicate, sortedByKeyPath: String, ascending: Bool, arraySlice: Int) -> [tableMetadata] { + func getMetadatas(predicate: NSPredicate, + sortedByKeyPath: String, + ascending: Bool, + limit: Int) -> [tableMetadata] { return performRealmRead { realm in let results = realm.objects(tableMetadata.self) .filter(predicate) .sorted(byKeyPath: sortedByKeyPath, ascending: ascending) .map { $0.detachedCopy() } - .prefix(arraySlice) + .prefix(limit) return Array(results) } ?? [] } diff --git a/iOSClient/Data/NCManageDatabase+SecurityGuard.swift b/iOSClient/Data/NCManageDatabase+SecurityGuard.swift index 2eb31a1b7a..cb0d993c40 100644 --- a/iOSClient/Data/NCManageDatabase+SecurityGuard.swift +++ b/iOSClient/Data/NCManageDatabase+SecurityGuard.swift @@ -32,20 +32,6 @@ extension NCManageDatabase { // MARK: - Realm write - func addDiagnostic(account: String, issue: String, error: String? = nil, sync: Bool = true) { - performRealmWrite(sync: sync) { realm in - let primaryKey = account + issue + (error ?? "") - - if let result = realm.object(ofType: TableSecurityGuardDiagnostics.self, forPrimaryKey: primaryKey) { - result.counter += 1 - result.oldest = Date().timeIntervalSince1970 - } else { - let table = TableSecurityGuardDiagnostics(account: account, issue: issue, error: error, date: Date()) - realm.add(table) - } - } - } - func addDiagnosticAsync(account: String, issue: String, error: String? = nil) async { @@ -78,23 +64,13 @@ extension NCManageDatabase { // MARK: - Realm read - func existsDiagnostics(account: String) -> Bool { - var exists = false - performRealmRead { realm in + func existsDiagnosticsAsync(account: String) async -> Bool { + let exists: Bool? = await performRealmReadAsync { realm in let results = realm.objects(TableSecurityGuardDiagnostics.self) .filter("account == %@", account) - exists = !results.isEmpty - } - return exists - } - - func getDiagnostics(account: String, issue: String) -> Results? { - var results: Results? - performRealmRead { realm in - results = realm.objects(TableSecurityGuardDiagnostics.self) - .filter("account == %@ AND issue == %@", account, issue) + return !results.isEmpty } - return results + return exists ?? false } func getDiagnosticsAsync(account: String) async -> [TableSecurityGuardDiagnostics]? { diff --git a/iOSClient/Data/NCManageDatabase.swift b/iOSClient/Data/NCManageDatabase.swift index fb06594132..7f546313a0 100644 --- a/iOSClient/Data/NCManageDatabase.swift +++ b/iOSClient/Data/NCManageDatabase.swift @@ -96,6 +96,9 @@ final class NCManageDatabase: @unchecked Sendable { let dirGroup = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: NCBrandOptions.shared.capabilitiesGroup) let databaseFileUrl = dirGroup?.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud + "/" + databaseName) + // now you can read/write in Realm + isAppSuspending = false + Realm.Configuration.defaultConfiguration = Realm.Configuration(fileURL: databaseFileUrl, schemaVersion: databaseSchemaVersion, migrationBlock: { migration, oldSchemaVersion in @@ -107,12 +110,11 @@ final class NCManageDatabase: @unchecked Sendable { if let url = realm.configuration.fileURL { nkLog(start: "Realm is located at: \(url.path)") } + return true } catch { nkLog(error: "Realm error: \(error)") return false } - - return true } private func openRealmAppex() { @@ -200,6 +202,11 @@ final class NCManageDatabase: @unchecked Sendable { @discardableResult func performRealmRead(_ block: @escaping (Realm) throws -> T?, sync: Bool = true, completion: ((T?) -> Void)? = nil) -> T? { + // Skip execution if app is suspending + guard !isAppSuspending else { + completion?(nil) + return nil + } let isOnRealmQueue = DispatchQueue.getSpecific(key: NCManageDatabase.realmQueueKey) != nil if sync { @@ -241,6 +248,11 @@ final class NCManageDatabase: @unchecked Sendable { } func performRealmWrite(sync: Bool = true, _ block: @escaping (Realm) throws -> Void) { + // Skip execution if app is suspending + guard !isAppSuspending + else { + return + } let isOnRealmQueue = DispatchQueue.getSpecific(key: NCManageDatabase.realmQueueKey) != nil let executionBlock: @Sendable () -> Void = { @@ -271,7 +283,12 @@ final class NCManageDatabase: @unchecked Sendable { // MARK: - performRealmRead async/await, performRealmWrite async/await func performRealmReadAsync(_ block: @escaping (Realm) throws -> T?) async -> T? { - await withCheckedContinuation { continuation in + // Skip execution if app is suspending + guard !isAppSuspending else { + return nil + } + + return await withCheckedContinuation { continuation in realmQueue.async { autoreleasepool { do { @@ -288,6 +305,11 @@ final class NCManageDatabase: @unchecked Sendable { } func performRealmWriteAsync(_ block: @escaping (Realm) throws -> Void) async { + // Skip execution if app is suspending + if isAppSuspending { + return + } + await withCheckedContinuation { continuation in realmQueue.async { autoreleasepool { diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 22a073ed0e..50098bd2f1 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -244,7 +244,10 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let dropInteraction = UIDropInteraction(delegate: self) self.navigationController?.navigationItem.leftBarButtonItems?.first?.customView?.addInteraction(dropInteraction) - NotificationCenter.default.addObserver(self, selector: #selector(changeTheming(_:)), name: NSNotification.Name(rawValue: global.notificationCenterChangeTheming), object: nil) + NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: self.global.notificationCenterChangeTheming), object: nil, queue: .main) { [weak self] _ in + guard let self else { return } + self.collectionView.reloadData() + } DispatchQueue.main.async { self.collectionView?.collectionViewLayout.invalidateLayout() @@ -528,12 +531,6 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS self.resetPlusButtonAlpha() } - @objc func changeTheming(_ notification: NSNotification) { - Task { - await self.reloadDataSource() - } - } - @objc func closeRichWorkspaceWebView() { Task { await self.reloadDataSource() diff --git a/iOSClient/NCAppStateManager.swift b/iOSClient/NCAppStateManager.swift index c48bb0abe1..2a947aead0 100644 --- a/iOSClient/NCAppStateManager.swift +++ b/iOSClient/NCAppStateManager.swift @@ -8,6 +8,9 @@ import NextcloudKit /// Global flag indicating whether the app has ever become active since launch. var hasBecomeActiveOnce: Bool = false +/// Global flag used to control Realm write/read operations during app suspension. +var isAppSuspending: Bool = false + /// Global flag indicating whether the app is currently in background mode. var isAppInBackground: Bool = true @@ -16,6 +19,7 @@ var isAppInBackground: Bool = true /// This class observes system notifications related to app lifecycle events and updates global flags accordingly: /// /// - `hasBecomeActiveOnce`: set to `true` the first time the app enters foreground. +/// - `isAppSuspending`: set to `true` when the app enters background (useful to safely close Realm writes). /// - `isAppInBackground`: indicates whether the app is currently running in background. /// /// Additionally, it logs lifecycle transitions using `nkLog(debug:)`. @@ -25,6 +29,7 @@ final class NCAppStateManager { private init() { NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { _ in hasBecomeActiveOnce = true + isAppSuspending = false isAppInBackground = false nkLog(debug: "Application will enter in foreground") @@ -35,6 +40,9 @@ final class NCAppStateManager { } NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { _ in + isAppSuspending = true + isAppInBackground = true + nkLog(debug: "Application did enter in background") } } diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index a5d80edbc5..6ee684f233 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -238,7 +238,7 @@ final class NCGlobal: Sendable { let metadataStatusUploadingAllMode = [1,2,3] let metadataStatusDownloadingAllMode = [-1, -2, -3] - let metadataStatusInTransfer = [-1, -2, 1, 2] + let metadataStatusForScreenAwake = [-1, -2, 1, 2] let metadataStatusHideInView = [1, 2, 3, 11] let metadataStatusWaitWebDav = [10, 11, 12, 13, 14, 15] diff --git a/iOSClient/NCImageCache.swift b/iOSClient/NCImageCache.swift index 0f09c5b404..138f9f6fe2 100644 --- a/iOSClient/NCImageCache.swift +++ b/iOSClient/NCImageCache.swift @@ -39,30 +39,31 @@ final class NCImageCache: @unchecked Sendable { NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil) { _ in #if !EXTENSION - guard !self.isLoadingCache else { - return - } - self.isDidEnterBackground = false + Task { + guard !self.isLoadingCache else { + return + } + self.isDidEnterBackground = false - var files: [NCFiles] = [] - var cost: Int = 0 + var files: [NCFiles] = [] + var cost: Int = 0 - if let activeTableAccount = NCManageDatabase.shared.getActiveTableAccount(), - NCImageCache.shared.cache.count == 0 { - let session = NCSession.shared.getSession(account: activeTableAccount.account) + if let activeTableAccount = await NCManageDatabase.shared.getActiveTableAccountAsync(), + NCImageCache.shared.cache.count == 0 { + let session = NCSession.shared.getSession(account: activeTableAccount.account) - for mainTabBarController in SceneManager.shared.getControllers() { - if let currentVC = mainTabBarController.selectedViewController as? UINavigationController, - let file = currentVC.visibleViewController as? NCFiles { - files.append(file) + for mainTabBarController in SceneManager.shared.getControllers() { + if let currentVC = await mainTabBarController.selectedViewController as? UINavigationController, + let file = await currentVC.visibleViewController as? NCFiles { + files.append(file) + } } - } - DispatchQueue.global().async { self.isLoadingCache = true - /// MEDIA - if let metadatas = NCManageDatabase.shared.getResultsMetadatas(predicate: self.getMediaPredicate(filterLivePhotoFile: true, session: session, showOnlyImages: false, showOnlyVideos: false), sortedByKeyPath: "datePhotosOriginal", freeze: true)?.prefix(self.countLimit) { + // MEDIA + let predicate = self.getMediaPredicate(filterLivePhotoFile: true, session: session, showOnlyImages: false, showOnlyVideos: false) + if let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate, sortedByKeyPath: "datePhotosOriginal", limit: self.countLimit) { autoreleasepool { self.cache.removeAllValues() @@ -79,11 +80,12 @@ final class NCImageCache: @unchecked Sendable { } } - /// FILE + // FILE if !self.isDidEnterBackground { - for file in files where !file.serverUrl.isEmpty { + for file in files where await !file.serverUrl.isEmpty { + let serverUrl = await file.serverUrl NCNetworking.shared.notifyAllDelegates { delegate in - delegate.transferReloadData(serverUrl: file.serverUrl, status: nil) + delegate.transferReloadData(serverUrl: serverUrl, status: nil) } } } @@ -91,6 +93,7 @@ final class NCImageCache: @unchecked Sendable { self.isLoadingCache = false } } + #endif } } diff --git a/iOSClient/Networking/NCAutoUpload.swift b/iOSClient/Networking/NCAutoUpload.swift index fb70b6933b..10f2c4893f 100644 --- a/iOSClient/Networking/NCAutoUpload.swift +++ b/iOSClient/Networking/NCAutoUpload.swift @@ -57,7 +57,7 @@ class NCAutoUpload: NSObject { private func uploadAssets(controller: NCMainTabBarController? = nil, tblAccount: tableAccount, assets: [PHAsset], fileNames: [String]) async -> Int { let session = NCSession.shared.getSession(account: tblAccount.account) - let autoUploadServerUrlBase = self.database.getAccountAutoUploadServerUrlBase(account: tblAccount.account, urlBase: tblAccount.urlBase, userId: tblAccount.userId) + let autoUploadServerUrlBase = await self.database.getAccountAutoUploadServerUrlBaseAsync(account: tblAccount.account, urlBase: tblAccount.urlBase, userId: tblAccount.userId) var metadatas: [tableMetadata] = [] let formatCompatibility = NCKeychain().formatCompatibility let keychainLivePhoto = NCKeychain().livePhoto @@ -155,7 +155,7 @@ class NCAutoUpload: NSObject { guard hasPermission else { return (nil, nil) } - let autoUploadServerUrlBase = self.database.getAccountAutoUploadServerUrlBase(account: tblAccount.account, urlBase: tblAccount.urlBase, userId: tblAccount.userId) + let autoUploadServerUrlBase = await self.database.getAccountAutoUploadServerUrlBaseAsync(account: tblAccount.account, urlBase: tblAccount.urlBase, userId: tblAccount.userId) var mediaPredicates: [NSPredicate] = [] var datePredicates: [NSPredicate] = [] let fetchOptions = PHFetchOptions() diff --git a/iOSClient/Networking/NCNetworking+Recommendations.swift b/iOSClient/Networking/NCNetworking+Recommendations.swift index fe21ade295..f8fd2fc0aa 100644 --- a/iOSClient/Networking/NCNetworking+Recommendations.swift +++ b/iOSClient/Networking/NCNetworking+Recommendations.swift @@ -40,8 +40,8 @@ extension NCNetworking { } } } - await self.database.createRecommendedFilesAsync(account: session.account, recommendations: recommendationsToInsert) + await self.database.createRecommendedFilesAsync(account: session.account, recommendations: recommendationsToInsert) await collectionView.reloadData() } } diff --git a/iOSClient/Networking/NCNetworking+Task.swift b/iOSClient/Networking/NCNetworking+Task.swift index 62340f83cf..7ca6c50589 100644 --- a/iOSClient/Networking/NCNetworking+Task.swift +++ b/iOSClient/Networking/NCNetworking+Task.swift @@ -61,7 +61,6 @@ extension NCNetworking { func cancelAllTaskForGoInBackground() { cancelAllQueue() - cancelAllDataTask() cancelDownloadTasks() cancelUploadTasks() } diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 5e9bf0e727..56a1facb8c 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -403,7 +403,7 @@ extension NCNetworking { if capabilities.termsOfService { termsOfService(metadata: metadata) } else { - uploadForbidden(metadata: metadata, error: error) + await uploadForbidden(metadata: metadata, error: error) } #endif } @@ -463,43 +463,46 @@ extension NCNetworking { } #if !EXTENSION + @MainActor func uploadForbidden(metadata: tableMetadata, error: NKError) { let newFileName = self.utilityFileSystem.createFileName(metadata.fileName, serverUrl: metadata.serverUrl, account: metadata.account) let alertController = UIAlertController(title: error.errorDescription, message: NSLocalizedString("_change_upload_filename_", comment: ""), preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: String(format: NSLocalizedString("_save_file_as_", comment: ""), newFileName), style: .default, handler: { _ in - let atpath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName - let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + newFileName - self.utilityFileSystem.moveFile(atPath: atpath, toPath: toPath) - self.database.setMetadataSession(ocId: metadata.ocId, - newFileName: newFileName, - sessionTaskIdentifier: 0, - sessionError: "", - status: self.global.metadataStatusWaitUpload, - errorCode: error.errorCode) - })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("_discard_changes_", comment: ""), style: .destructive, handler: { _ in - Task { - await self.uploadCancelFile(metadata: metadata) - } - })) - - // Select UIWindowScene active in serverUrl - var controller = UIApplication.shared.firstWindow?.rootViewController - let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } - for windowScene in windowScenes { - if let rootViewController = windowScene.keyWindow?.rootViewController as? NCMainTabBarController, - rootViewController.currentServerUrl() == metadata.serverUrl { - controller = rootViewController - break - } - } - controller?.present(alertController, animated: true) - // Client Diagnostic - self.database.addDiagnostic(account: metadata.account, - issue: self.global.diagnosticIssueProblems, - error: self.global.diagnosticProblemsForbidden) + alertController.addAction(UIAlertAction(title: String(format: NSLocalizedString("_save_file_as_", comment: ""), newFileName), style: .default, handler: { _ in + let atpath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + metadata.fileName + let toPath = self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId) + "/" + newFileName + self.utilityFileSystem.moveFile(atPath: atpath, toPath: toPath) + self.database.setMetadataSession(ocId: metadata.ocId, + newFileName: newFileName, + sessionTaskIdentifier: 0, + sessionError: "", + status: self.global.metadataStatusWaitUpload, + errorCode: error.errorCode) + })) + alertController.addAction(UIAlertAction(title: NSLocalizedString("_discard_changes_", comment: ""), style: .destructive, handler: { _ in + Task { + await self.uploadCancelFile(metadata: metadata) + } + })) + + // Select UIWindowScene active in serverUrl + var controller = UIApplication.shared.firstWindow?.rootViewController + let windowScenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } + for windowScene in windowScenes { + if let rootViewController = windowScene.keyWindow?.rootViewController as? NCMainTabBarController, + rootViewController.currentServerUrl() == metadata.serverUrl { + controller = rootViewController + break + } + } + controller?.present(alertController, animated: true) + // Client Diagnostic + Task { + await self.database.addDiagnosticAsync(account: metadata.account, + issue: self.global.diagnosticIssueProblems, + error: self.global.diagnosticProblemsForbidden) + } } func termsOfService(metadata: tableMetadata) { @@ -541,10 +544,11 @@ extension NCNetworking { controller?.present(alertController, animated: true) // Client Diagnostic - self.database.addDiagnostic(account: metadata.account, - issue: self.global.diagnosticIssueProblems, - error: self.global.diagnosticProblemsForbidden, - sync: false) + Task { + await self.database.addDiagnosticAsync(account: metadata.account, + issue: self.global.diagnosticIssueProblems, + error: self.global.diagnosticProblemsForbidden) + } } } } diff --git a/iOSClient/Networking/NCNetworkingProcess.swift b/iOSClient/Networking/NCNetworkingProcess.swift index 3055cd4253..aba932ef98 100644 --- a/iOSClient/Networking/NCNetworkingProcess.swift +++ b/iOSClient/Networking/NCNetworkingProcess.swift @@ -29,28 +29,32 @@ actor NCNetworkingProcess { NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterPlayerIsPlaying), object: nil, queue: nil) { [weak self] _ in guard let self else { return } - Task { await self.setScreenAwake(false) } + Task { + await self.setScreenAwake(false) + } } NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterPlayerStoppedPlaying), object: nil, queue: nil) { [weak self] _ in guard let self else { return } - Task { await self.setScreenAwake(true) } + Task { + await self.setScreenAwake(true) + } } NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in guard let self else { return } - Task { await self.stopTimer() } + Task { + await self.stopTimer() + } } NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in guard let self else { return } Task { - if !isAppInBackground { - await self.startTimer(interval: self.maxInterval) - } + await self.startTimer(interval: self.maxInterval) } } } @@ -64,6 +68,13 @@ actor NCNetworkingProcess { } func startTimer(interval: TimeInterval) async { + let isActive = await MainActor.run { + UIApplication.shared.applicationState == .active + } + guard isActive else { + return + } + await stopTimer() lastUsedInterval = interval @@ -72,7 +83,9 @@ actor NCNetworkingProcess { newTimer.setEventHandler { [weak self] in guard let self else { return } - Task { await self.handleTimerTick() } + Task { + await self.handleTimerTick() + } } timer = newTimer @@ -106,10 +119,10 @@ actor NCNetworkingProcess { if let metadatas, !metadatas.isEmpty { let tasks = await networking.getAllDataTask() let hasSyncTask = tasks.contains { $0.taskDescription == global.taskDescriptionSynchronization } - let resultsTransfer = metadatas.filter { global.metadataStatusInTransfer.contains($0.status) } + let resultsScreenAwake = metadatas.filter { global.metadataStatusForScreenAwake.contains($0.status) } if enableControllingScreenAwake { - ScreenAwakeManager.shared.mode = resultsTransfer.isEmpty && !hasSyncTask ? .off : NCKeychain().screenAwakeMode + ScreenAwakeManager.shared.mode = resultsScreenAwake.isEmpty && !hasSyncTask ? .off : NCKeychain().screenAwakeMode } await runMetadataPipelineAsync() @@ -128,7 +141,6 @@ actor NCNetworkingProcess { private func removeUploadedAssetsIfNeeded() async { guard NCKeychain().removePhotoCameraRoll, - !isAppInBackground, let localIdentifiers = await self.database.getAssetLocalIdentifiersUploadedAsync(), !localIdentifiers.isEmpty else { return @@ -156,8 +168,8 @@ actor NCNetworkingProcess { /// ------------------------ WEBDAV let waitWebDav = metadatas.filter { self.global.metadataStatusWaitWebDav.contains($0.status) } if !waitWebDav.isEmpty { - let error = await metadataStatusWaitWebDav(metadatas: Array(waitWebDav)) - if error != .success { + let (status, error) = await metadataStatusWaitWebDav(metadatas: Array(waitWebDav)) + if (error == .cancelled) || (status == global.metadataStatusWaitDelete && error != .success) { return } } @@ -190,7 +202,9 @@ actor NCNetworkingProcess { let isWiFi = self.networking.networkReachability == NKTypeReachability.reachableEthernetOrWiFi let sessionUploadSelectors = [self.global.selectorUploadFileNODelete, self.global.selectorUploadFile, self.global.selectorUploadAutoUpload] var counterUploading = metadatas.filter { $0.status == self.global.metadataStatusUploading }.count - for sessionSelector in sessionUploadSelectors where counterUploading < httpMaximumConnectionsPerHostInUpload { + for sessionSelector in sessionUploadSelectors { + guard counterUploading < httpMaximumConnectionsPerHostInUpload else { return } + let limitUpload = max(0, httpMaximumConnectionsPerHostInUpload - counterUploading) let filteredUpload = metadatas .filter { $0.sessionSelector == sessionSelector && $0.status == NCGlobal.shared.metadataStatusWaitUpload } @@ -202,19 +216,23 @@ actor NCNetworkingProcess { nkLog(debug: "PROCESS (UPLOAD) find \(metadatasWaitUpload.count) items") } - for metadata in metadatasWaitUpload where counterUploading < httpMaximumConnectionsPerHostInUpload { + for metadata in metadatasWaitUpload { + guard counterUploading < httpMaximumConnectionsPerHostInUpload else { return } let metadatas = await NCCameraRoll().extractCameraRoll(from: metadata) + // no extract photo if metadatas.isEmpty { await self.database.deleteMetadataOcIdAsync(metadata.ocId) } - for metadata in metadatas where counterUploading < httpMaximumConnectionsPerHostInUpload { + for metadata in metadatas { + guard counterUploading < httpMaximumConnectionsPerHostInUpload, + timer != nil else { return } + /// isE2EE let isInDirectoryE2EE = metadata.isDirectoryE2EE /// NO WiFi if !isWiFi && metadata.session == networking.sessionUploadBackgroundWWan { continue } - if isAppInBackground && (isInDirectoryE2EE || metadata.chunk > 0) { continue } await self.database.setMetadataStatusAsync(ocId: metadata.ocId, status: global.metadataStatusUploading) @@ -271,12 +289,16 @@ actor NCNetworkingProcess { return } - private func metadataStatusWaitWebDav(metadatas: [tableMetadata]) async -> NKError { + private func metadataStatusWaitWebDav(metadatas: [tableMetadata]) async -> (status: Int?, error: NKError) { /// ------------------------ CREATE FOLDER /// let metadatasWaitCreateFolder = metadatas.filter { $0.status == global.metadataStatusWaitCreateFolder }.sorted { $0.serverUrl < $1.serverUrl } for metadata in metadatasWaitCreateFolder { + guard timer != nil else { + return (global.metadataStatusWaitCreateFolder, .cancelled) + } + let resultsCreateFolder = await networking.createFolder(fileName: metadata.fileName, serverUrl: metadata.serverUrl, overwrite: true, @@ -299,7 +321,7 @@ actor NCNetworkingProcess { } if resultsCreateFolder.error != .success { - return resultsCreateFolder.error + return (global.metadataStatusWaitCreateFolder, resultsCreateFolder.error) } } @@ -307,6 +329,10 @@ actor NCNetworkingProcess { /// let metadatasWaitCopy = metadatas.filter { $0.status == global.metadataStatusWaitCopy }.sorted { $0.serverUrl < $1.serverUrl } for metadata in metadatasWaitCopy { + guard timer != nil else { + return (global.metadataStatusWaitCopy, .cancelled) + } + let serverUrlTo = metadata.serverUrlTo let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName var serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName @@ -335,7 +361,7 @@ actor NCNetworkingProcess { } if resultCopy.error != .success { - return resultCopy.error + return (global.metadataStatusWaitCopy, resultCopy.error) } } @@ -343,6 +369,10 @@ actor NCNetworkingProcess { /// let metadatasWaitMove = metadatas.filter { $0.status == global.metadataStatusWaitMove }.sorted { $0.serverUrl < $1.serverUrl } for metadata in metadatasWaitMove { + guard timer != nil else { + return (global.metadataStatusWaitMove, .cancelled) + } + let serverUrlTo = metadata.serverUrlTo let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName @@ -387,7 +417,7 @@ actor NCNetworkingProcess { } if resultMove.error != .success { - return resultMove.error + return (global.metadataStatusWaitMove, resultMove.error) } } @@ -395,6 +425,10 @@ actor NCNetworkingProcess { /// let metadatasWaitFavorite = metadatas.filter { $0.status == global.metadataStatusWaitFavorite }.sorted { $0.serverUrl < $1.serverUrl } for metadata in metadatasWaitFavorite { + guard timer != nil else { + return (global.metadataStatusWaitFavorite, .cancelled) + } + let session = NCSession.Session(account: metadata.account, urlBase: metadata.urlBase, user: metadata.user, userId: metadata.userId) let fileName = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session) let resultsFavorite = await NextcloudKit.shared.setFavoriteAsync(fileName: fileName, favorite: metadata.favorite, account: metadata.account) @@ -419,7 +453,7 @@ actor NCNetworkingProcess { } if resultsFavorite.error != .success { - return resultsFavorite.error + return (global.metadataStatusWaitFavorite, resultsFavorite.error) } } @@ -427,6 +461,10 @@ actor NCNetworkingProcess { /// let metadatasWaitRename = metadatas.filter { $0.status == global.metadataStatusWaitRename }.sorted { $0.serverUrl < $1.serverUrl } for metadata in metadatasWaitRename { + guard timer != nil else { + return (global.metadataStatusWaitRename, .cancelled) + } + let serverUrlFileNameSource = metadata.serveUrlFileName let serverUrlFileNameDestination = metadata.serverUrl + "/" + metadata.fileName let resultRename = await NextcloudKit.shared.moveFileOrFolderAsync(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: false, account: metadata.account) @@ -444,7 +482,7 @@ actor NCNetworkingProcess { } if resultRename.error != .success { - return resultRename.error + return (global.metadataStatusWaitRename, resultRename.error) } } @@ -456,6 +494,10 @@ actor NCNetworkingProcess { var returnError = NKError() for metadata in metadatasWaitDelete { + guard timer != nil else { + return (global.metadataStatusWaitDelete, .cancelled) + } + let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName let resultDelete = await NextcloudKit.shared.deleteFileOrFolderAsync(serverUrlFileName: serverUrlFileName, account: metadata.account) @@ -492,10 +534,10 @@ actor NCNetworkingProcess { } if returnError != .success { - return returnError + return (global.metadataStatusWaitDelete, returnError) } } - return .success + return (nil, .success) } } diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index 8e8cda3c85..6dd1bfda7a 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -245,7 +245,7 @@ class NCService: NSObject { func sendClientDiagnosticsRemoteOperation(account: String) async { let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) guard capabilities.securityGuardDiagnostics, - self.database.existsDiagnostics(account: account) else { + await self.database.existsDiagnosticsAsync(account: account) else { return } diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index f1181ca762..979a7fbdc4 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -29,12 +29,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { self.window?.overrideUserInterfaceStyle = NCKeychain().appearanceInterfaceStyle } - if let activeTableAccount = self.database.getActiveTableAccount() { - nkLog(debug: "Account active \(activeTableAccount.account)") + if let activeTblAccount = self.database.getActiveTableAccount() { + nkLog(debug: "Account active \(activeTblAccount.account)") + // set capabilities + self.database.applyCachedCapabilitiesBlocking(account: activeTblAccount.account) + // set theming color + NCBrandColor.shared.settingThemingColor(account: activeTblAccount.account) - NCBrandColor.shared.settingThemingColor(account: activeTableAccount.account) Task { - await NCNetworkingProcess.shared.setCurrentAccount(activeTableAccount.account) + await NCNetworkingProcess.shared.setCurrentAccount(activeTblAccount.account) } for tableAccount in self.database.getAllTableAccount() { NextcloudKit.shared.appendSession(account: tableAccount.account, @@ -57,7 +60,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as? NCMainTabBarController { SceneManager.shared.register(scene: scene, withRootViewController: controller) /// Set the ACCOUNT - controller.account = activeTableAccount.account + controller.account = activeTblAccount.account /// window?.rootViewController = controller window?.makeKeyAndVisible() diff --git a/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift b/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift index 01b7dc7db3..25b5157e67 100644 --- a/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift +++ b/iOSClient/Settings/AutoUpload/NCAutoUploadModel.swift @@ -241,9 +241,8 @@ class NCAutoUploadModel: ObservableObject, ViewOnAppearHandling { func deleteAutoUploadTransfer() { Task { - let autoUploadServerUrlBase = NCManageDatabase.shared.getAccountAutoUploadServerUrlBase(session: session) - await NCManageDatabase.shared.deleteAutoUploadTransferAsync(account: session.account, - autoUploadServerUrlBase: autoUploadServerUrlBase) + let autoUploadServerUrlBase = await NCManageDatabase.shared.getAccountAutoUploadServerUrlBaseAsync(session: session) + await NCManageDatabase.shared.deleteAutoUploadTransferAsync(account: session.account, autoUploadServerUrlBase: autoUploadServerUrlBase) } } diff --git a/iOSClient/Transfers/NCTransfers.swift b/iOSClient/Transfers/NCTransfers.swift index 6b6168e27d..6015b979d7 100644 --- a/iOSClient/Transfers/NCTransfers.swift +++ b/iOSClient/Transfers/NCTransfers.swift @@ -77,8 +77,12 @@ class NCTransfers: NCCollectionViewCommon, NCTransferCellDelegate { // MARK: TAP EVENT override func tapMoreGridItem(with ocId: String, ocIdTransfer: String, image: UIImage?, sender: Any) { - guard let metadata = self.database.getMetadataFromOcIdAndocIdTransfer(ocIdTransfer) else { return } - NCNetworking.shared.cancelTask(metadata: metadata) + Task { + guard let metadata = await self.database.getMetadataFromOcIdAndocIdTransferAsync(ocIdTransfer) else { + return + } + NCNetworking.shared.cancelTask(metadata: metadata) + } } override func longPressMoreListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { @@ -101,13 +105,15 @@ class NCTransfers: NCCollectionViewCommon, NCTransferCellDelegate { override func longPressListItem(with ocId: String, ocIdTransfer: String, gestureRecognizer: UILongPressGestureRecognizer) { if gestureRecognizer.state != .began { return } - if let metadata = self.database.getMetadataFromOcIdAndocIdTransfer(ocIdTransfer) { - metadataTemp = metadata - let touchPoint = gestureRecognizer.location(in: collectionView) - becomeFirstResponder() - let startTaskItem = UIMenuItem(title: NSLocalizedString("_force_start_", comment: ""), action: #selector(startTask(_:))) - UIMenuController.shared.menuItems = [startTaskItem] - UIMenuController.shared.showMenu(from: collectionView, rect: CGRect(x: touchPoint.x, y: touchPoint.y, width: 0, height: 0)) + Task { + if let metadata = await self.database.getMetadataFromOcIdAndocIdTransferAsync(ocIdTransfer) { + metadataTemp = metadata + let touchPoint = gestureRecognizer.location(in: collectionView) + becomeFirstResponder() + let startTaskItem = UIMenuItem(title: NSLocalizedString("_force_start_", comment: ""), action: #selector(startTask(_:))) + UIMenuController.shared.menuItems = [startTaskItem] + UIMenuController.shared.showMenu(from: collectionView, rect: CGRect(x: touchPoint.x, y: touchPoint.y, width: 0, height: 0)) + } } } diff --git a/iOSClient/Utility/NCCameraRoll.swift b/iOSClient/Utility/NCCameraRoll.swift index 44657484f0..892940c948 100644 --- a/iOSClient/Utility/NCCameraRoll.swift +++ b/iOSClient/Utility/NCCameraRoll.swift @@ -151,12 +151,7 @@ final class NCCameraRoll: CameraRollExtractor { /// - originalMetadata: Metadata describing the asset /// - modifyMetadataForUpload: Whether to update metadata for upload and store it in the database /// - Returns: An `ExtractedAsset` containing the updated metadata and path to the extracted file - func extractImageVideoFromAssetLocalIdentifier( - metadata originalMetadata: tableMetadata, - modifyMetadataForUpload: Bool - ) async throws -> ExtractedAsset { - var metadata = originalMetadata.detachedCopy() - + func extractImageVideoFromAssetLocalIdentifier(metadata: tableMetadata, modifyMetadataForUpload: Bool) async throws -> ExtractedAsset { // Determine the appropriate chunk size based on the current network connection let chunkSize = NCNetworking.shared.networkReachability == .reachableEthernetOrWiFi ? NCGlobal.shared.chunkSizeMBEthernetOrWiFi @@ -219,10 +214,11 @@ final class NCCameraRoll: CameraRollExtractor { // Optionally update metadata for upload and persist it if modifyMetadataForUpload { - updateMetadataForUpload(&metadata, size: Int(metadata.size), chunkSize: chunkSize) + let metadata = updateMetadataForUpload(metadata: metadata, size: Int(metadata.size), chunkSize: chunkSize) + return ExtractedAsset(metadata: metadata, filePath: filePath) + } else { + return ExtractedAsset(metadata: metadata, filePath: filePath) } - - return ExtractedAsset(metadata: metadata, filePath: filePath) } private func metadataUpdatedFilename(for asset: PHAsset, original: String, ext: String, native: Bool) -> String { @@ -239,14 +235,14 @@ final class NCCameraRoll: CameraRollExtractor { return nil } - private func updateMetadataForUpload(_ metadata: inout tableMetadata, size: Int, chunkSize: Int) { + private func updateMetadataForUpload(metadata: tableMetadata, size: Int, chunkSize: Int) -> tableMetadata { metadata.chunk = size > chunkSize ? chunkSize : 0 metadata.e2eEncrypted = metadata.isDirectoryE2EE if metadata.chunk > 0 || metadata.e2eEncrypted { metadata.session = NCNetworking.shared.sessionUpload } metadata.isExtractFile = true - metadata = self.database.addAndReturnMetadata(metadata) + return self.database.addAndReturnMetadata(metadata) } private func extractImage(asset: PHAsset, ext: String, filePath: String, compatibilityFormat: Bool) async throws {