diff --git a/Brand/Database.swift b/Brand/Database.swift index b829712b0d..3e692dd6b7 100644 --- a/Brand/Database.swift +++ b/Brand/Database.swift @@ -8,4 +8,4 @@ import Foundation // let databaseName = "nextcloud.realm" let tableAccountBackup = "tableAccountBackup.json" -let databaseSchemaVersion: UInt64 = 412 +let databaseSchemaVersion: UInt64 = 413 diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 5302517d6b..ff4545ed5c 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -763,6 +763,14 @@ F7BD0A022C4689A4003A4A6D /* NCMedia+CollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BD0A012C4689A4003A4A6D /* NCMedia+CollectionViewDelegate.swift */; }; F7BD0A042C4689E9003A4A6D /* NCMedia+MediaLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BD0A032C4689E9003A4A6D /* NCMedia+MediaLayout.swift */; }; F7BD71E62636EAFC00643C34 /* NCNetworkingE2EE.swift in Sources */ = {isa = PBXBuildFile; fileRef = F785EE9C246196DF00B3F945 /* NCNetworkingE2EE.swift */; }; + F7BDC1D2300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1D1300F440600C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift */; }; + F7BDC1D3300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1D1300F440600C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift */; }; + F7BDC1D4300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1D1300F440600C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift */; }; + F7BDC1D5300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1D1300F440600C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift */; }; + F7BDC1D6300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1D1300F440600C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift */; }; + F7BDC1D8300F4E8E00C5D9FA /* NCMediaMetadataBackfillProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1D7300F4E8A00C5D9FA /* NCMediaMetadataBackfillProcessor.swift */; }; + F7BDC1DA300F4F2800C5D9FA /* NCMediaPlaceholderHydrationProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1D9300F4F2500C5D9FA /* NCMediaPlaceholderHydrationProcessor.swift */; }; + F7BDC1DC300F4F9700C5D9FA /* NCMediaPreviewBackfillProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BDC1DB300F4F9500C5D9FA /* NCMediaPreviewBackfillProcessor.swift */; }; F7BF9D822934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */; }; F7BF9D832934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */; }; F7BF9D842934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */; }; @@ -789,7 +797,6 @@ F7C55CC92FB5CE74004A974F /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C55CC32FB5CE74004A974F /* ActionViewController.swift */; }; F7C55CCA2FB5CE74004A974F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F7C55CC42FB5CE74004A974F /* Images.xcassets */; }; F7C55CCC2FB5CE74004A974F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7C55CC72FB5CE74004A974F /* MainInterface.storyboard */; }; - F7C630712FFE206100257EEB /* NCMediaMetadataBackgroundProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C630702FFE206100257EEB /* NCMediaMetadataBackgroundProcessor.swift */; }; F7C6307F2FFE4F8000257EEB /* UIWindowScene+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C6307E2FFE4F8000257EEB /* UIWindowScene+Extension.swift */; }; F7C630802FFE4F8000257EEB /* UIWindowScene+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C6307E2FFE4F8000257EEB /* UIWindowScene+Extension.swift */; }; F7C630872FFF6DE100257EEB /* NCMetadataDownloadTranfersSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7C630832FFF6DDF00257EEB /* NCMetadataDownloadTranfersSuccess.swift */; }; @@ -1756,6 +1763,10 @@ F7BD09FF2C468925003A4A6D /* NCMedia+CollectionViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMedia+CollectionViewDataSource.swift"; sourceTree = ""; }; F7BD0A012C4689A4003A4A6D /* NCMedia+CollectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMedia+CollectionViewDelegate.swift"; sourceTree = ""; }; F7BD0A032C4689E9003A4A6D /* NCMedia+MediaLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCMedia+MediaLayout.swift"; sourceTree = ""; }; + F7BDC1D1300F440600C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+MediaPreviewBackfill.swift"; sourceTree = ""; }; + F7BDC1D7300F4E8A00C5D9FA /* NCMediaMetadataBackfillProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaMetadataBackfillProcessor.swift; sourceTree = ""; }; + F7BDC1D9300F4F2500C5D9FA /* NCMediaPlaceholderHydrationProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaPlaceholderHydrationProcessor.swift; sourceTree = ""; }; + F7BDC1DB300F4F9500C5D9FA /* NCMediaPreviewBackfillProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaPreviewBackfillProcessor.swift; sourceTree = ""; }; F7BE7C25290AC8C9002ABB61 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intent.strings; sourceTree = ""; }; F7BE7C27290ADEFD002ABB61 /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Intent.strings; sourceTree = ""; }; F7BE7C29290ADEFD002ABB61 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Intent.strings; sourceTree = ""; }; @@ -1812,7 +1823,6 @@ F7C55CC32FB5CE74004A974F /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; }; F7C55CC42FB5CE74004A974F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; F7C55CC62FB5CE74004A974F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; - F7C630702FFE206100257EEB /* NCMediaMetadataBackgroundProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMediaMetadataBackgroundProcessor.swift; sourceTree = ""; }; F7C6307E2FFE4F8000257EEB /* UIWindowScene+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindowScene+Extension.swift"; sourceTree = ""; }; F7C630832FFF6DDF00257EEB /* NCMetadataDownloadTranfersSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMetadataDownloadTranfersSuccess.swift; sourceTree = ""; }; F7C687E82D22BD46004757BC /* NCManageDatabase+RecommendedFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+RecommendedFiles.swift"; sourceTree = ""; }; @@ -3094,16 +3104,15 @@ F7BAAD951ED5A63D00B7EAD4 /* Data */ = { isa = PBXGroup; children = ( - F76340F32EBDE9740056F538 /* NCManageDatabaseCore.swift */, F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */, AF4BF613275629E20081CEEF /* NCManageDatabase+Account.swift */, AF4BF61D27562B3F0081CEEF /* NCManageDatabase+Activity.swift */, F7D7A76B2DCDD437003D2007 /* NCManageDatabase+AutoUpload.swift */, F749B650297B0F2400087535 /* NCManageDatabase+Avatar.swift */, - F7D61EA52EBF168E007F865B /* NCManageDatabase+TableCapabilities.swift */, F763D29C2A249C4500A3C901 /* NCManageDatabase+Capabilities.swift */, F74B6D942A7E239A00F03C5F /* NCManageDatabase+Chunk.swift */, F73EF7A62B0223900087E6E9 /* NCManageDatabase+Comments.swift */, + F7CF06822E11273F0063AD04 /* NCManageDatabase+CreateMetadata.swift */, F7D68FCB28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift */, F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */, AA8D31522D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift */, @@ -3111,26 +3120,47 @@ F73EF7B62B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift */, F73EF7BE2B02250B0087E6E9 /* NCManageDatabase+GPS.swift */, F757CC8129E7F88B00F31428 /* NCManageDatabase+Groupfolders.swift */, - F764C3E02FFB7DF800029FD5 /* NCManageDatabase+MediaMetadataBackfill.swift */, F7BF9D812934CA21009EE9A6 /* NCManageDatabase+LayoutForView.swift */, F77E8C1E2E79717D00EAE68F /* NCManageDatabase+LivePhoto.swift */, F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */, + F764C3E02FFB7DF800029FD5 /* NCManageDatabase+MediaMetadataBackfill.swift */, + F7BDC1D1300F440600C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift */, AF4BF61827562A4B0081CEEF /* NCManageDatabase+Metadata.swift */, - F7CF06822E11273F0063AD04 /* NCManageDatabase+CreateMetadata.swift */, F7B769A72B7A0B2000C1AAEB /* NCManageDatabase+Metadata+Session.swift */, F7C687E82D22BD46004757BC /* NCManageDatabase+RecommendedFiles.swift */, F7C9B91C2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift */, F749B649297B0CBB00087535 /* NCManageDatabase+Share.swift */, + F7D61EA52EBF168E007F865B /* NCManageDatabase+TableCapabilities.swift */, F76340FB2EBDF64A0056F538 /* NCManageDatabase+Tag.swift */, F73EF7D62B0226080087E6E9 /* NCManageDatabase+Tip.swift */, F73EF7DE2B02266C0087E6E9 /* NCManageDatabase+Trash.swift */, F7E98C1527E0D0FC001F9F19 /* NCManageDatabase+Video.swift */, - F7CADEFA2EA1591D0057849E /* NCMetadataUploadTranfersSuccess.swift */, + F76340F32EBDE9740056F538 /* NCManageDatabaseCore.swift */, F7C630832FFF6DDF00257EEB /* NCMetadataDownloadTranfersSuccess.swift */, + F7CADEFA2EA1591D0057849E /* NCMetadataUploadTranfersSuccess.swift */, ); path = Data; sourceTree = ""; }; + F7BDC1DD300F4FF800C5D9FA /* Processor */ = { + isa = PBXGroup; + children = ( + F7110AE32F9774130095AA5C /* AppDelegate+AppProcessing.swift */, + F7BDC1D7300F4E8A00C5D9FA /* NCMediaMetadataBackfillProcessor.swift */, + F7BDC1D9300F4F2500C5D9FA /* NCMediaPlaceholderHydrationProcessor.swift */, + F7BDC1DB300F4F9500C5D9FA /* NCMediaPreviewBackfillProcessor.swift */, + ); + path = Processor; + sourceTree = ""; + }; + F7BDC1DE300F502C00C5D9FA /* Refresh */ = { + isa = PBXGroup; + children = ( + F7110ADF2F9773210095AA5C /* AppDelegate+AppRefresh.swift */, + ); + path = Refresh; + sourceTree = ""; + }; F7BFFA621A24D7300044ED85 /* Login */ = { isa = PBXGroup; children = ( @@ -3525,16 +3555,15 @@ isa = PBXGroup; children = ( AA517BB42D66149900F8D37C /* .tx */, + F7BDC1DD300F4FF800C5D9FA /* Processor */, + F7BDC1DE300F502C00C5D9FA /* Refresh */, F702F2CC25EE5B4F008F8E80 /* AppDelegate.swift */, - F7110ADF2F9773210095AA5C /* AppDelegate+AppRefresh.swift */, - F7110AE32F9774130095AA5C /* AppDelegate+AppProcessing.swift */, F7CAFE1A2F16AA8600DB35A5 /* main.swift */, F794E13E2BBC0F70003693D7 /* SceneDelegate.swift */, F7CF067A2E0FF38F0063AD04 /* NCAppStateManager.swift */, F77DD6A72C5CC093009448FB /* NCSession.swift */, F76B649B2ADFFAED00014640 /* NCImageCache.swift */, F702F2CE25EE5B5C008F8E80 /* NCGlobal.swift */, - F7C630702FFE206100257EEB /* NCMediaMetadataBackgroundProcessor.swift */, F718E2572DF2D5C3004038AF /* NCBackgroundLocationUploadManager.swift */, F7E402282BA85D1D007E5609 /* PrivacyInfo.xcprivacy */, F73CB5771ED46807005F2A5A /* NCBridgeSwift.h */, @@ -4364,6 +4393,7 @@ F7D7A7712DCDD437003D2007 /* NCManageDatabase+AutoUpload.swift in Sources */, AF4BF62127562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */, F702F2D225EE5B5C008F8E80 /* NCGlobal.swift in Sources */, + F7BDC1D3300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */, F7B82F182EBFA3B700F5F242 /* NCNetworking.swift in Sources */, F72FD3BA297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */, F3E173C62C9B1067006D177A /* AwakeMode.swift in Sources */, @@ -4571,6 +4601,7 @@ AF22B206277B4E4C00DAB0CC /* NCCreateFormUploadConflict.swift in Sources */, F74D50362C9856D300BBBF4C /* NCCollectionViewDataSource.swift in Sources */, F7A573692E190387009C9257 /* NCShareExtensionData.swift in Sources */, + F7BDC1D4300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */, F7BD71E62636EAFC00643C34 /* NCNetworkingE2EE.swift in Sources */, F7F878AF1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */, F7327E3B2B73B8D600A462C7 /* Array+Extension.swift in Sources */, @@ -4649,6 +4680,7 @@ F711D63128F44801003F43C8 /* IntentHandler.swift in Sources */, F760A4952FE95D33001B212E /* NetworkingTasks.swift in Sources */, F76DEE9728F808AF0041B1C9 /* LockscreenData.swift in Sources */, + F7BDC1D6300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */, F72EA95A28B7BD0D00C88F0C /* FilesWidgetView.swift in Sources */, F768823C2C0DD231001CF441 /* NCPreferences.swift in Sources */, F71F6D082B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */, @@ -4820,7 +4852,6 @@ F7CDB5C52FA33CA300F72306 /* NCMediaViewerModel.swift in Sources */, F7CDB5C62FA33CA300F72306 /* NCMediaViewerView.swift in Sources */, F7CDB5CC2FA33CA300F72306 /* NCNextcloudMediaViewerLoader.swift in Sources */, - F7C630712FFE206100257EEB /* NCMediaMetadataBackgroundProcessor.swift in Sources */, F76341182EBE0BC60056F538 /* NCNetworking+NextcloudKitDelegate.swift in Sources */, F79377052FBD86AF00DE56DE /* NCMediaViewerFloatingTitleView.swift in Sources */, F78A18B823CDE2B300F681F3 /* NCViewerRichWorkspace.swift in Sources */, @@ -4828,6 +4859,7 @@ F34E1AD92ECC839100FA10C3 /* EmojiTextField.swift in Sources */, F768822E2C0DD1E7001CF441 /* NCSettingsBundleHelper.swift in Sources */, F72408332B8A27C900F128E2 /* NCMedia+Command.swift in Sources */, + F7BDC1DC300F4F9700C5D9FA /* NCMediaPreviewBackfillProcessor.swift in Sources */, F755CB402B8CB13C00CE27E9 /* NCMediaLayout.swift in Sources */, F73EF7B72B0224AB0087E6E9 /* NCManageDatabase+ExternalSites.swift in Sources */, AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, @@ -5087,6 +5119,7 @@ F7327E352B73AEDE00A462C7 /* NCNetworking+LivePhoto.swift in Sources */, F76687072B7D067400779E3F /* NCAudioRecorderViewController.swift in Sources */, F77B0F893008ABDC00EE4AE1 /* NCBlurEffectsTestView.swift in Sources */, + F7BDC1D8300F4E8E00C5D9FA /* NCMediaMetadataBackfillProcessor.swift in Sources */, AA8E03DA2D2ED83300E7E89C /* TransientShare.swift in Sources */, F7A03E2F2D425A14007AA677 /* NCFavoriteNavigationController.swift in Sources */, F343A4BB2A1E734600DDA874 /* Optional+Extension.swift in Sources */, @@ -5111,6 +5144,7 @@ F7C6307F2FFE4F8000257EEB /* UIWindowScene+Extension.swift in Sources */, F75CA1472962F13700B01130 /* NCHUDView.swift in Sources */, F77BB748289985270090FC19 /* UITabBarController+Extension.swift in Sources */, + F7BDC1DA300F4F2800C5D9FA /* NCMediaPlaceholderHydrationProcessor.swift in Sources */, F7DA0F672F66AA0D0033020C /* ShowBanner.swift in Sources */, F752BA052E58C05200616A26 /* Maintenance.swift in Sources */, F763D29D2A249C4500A3C901 /* NCManageDatabase+Capabilities.swift in Sources */, @@ -5145,6 +5179,7 @@ F3E173C02C9B1067006D177A /* AwakeMode.swift in Sources */, F7CAFE182F164B9500DB35A5 /* NCCollectionViewCommon+CellDelegate.swift in Sources */, F711A4DC2AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */, + F7BDC1D5300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */, AF4BF61E27562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5197,6 +5232,7 @@ F768823B2C0DD231001CF441 /* NCPreferences.swift in Sources */, AA8D31532D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift in Sources */, F7A8D74228F18261008BBE1C /* NCUtility.swift in Sources */, + F7BDC1D2300F440A00C5D9FA /* NCManageDatabase+MediaPreviewBackfill.swift in Sources */, F7A8D73A28F17E28008BBE1C /* NCManageDatabase+Video.swift in Sources */, F7CAFE1F2F17A37C00DB35A5 /* ProgressQuantizer.swift in Sources */, F7D61EA72EBF1694007F865B /* NCManageDatabase+TableCapabilities.swift in Sources */, diff --git a/iOSClient/Data/NCManageDatabase+MediaPreviewBackfill.swift b/iOSClient/Data/NCManageDatabase+MediaPreviewBackfill.swift new file mode 100644 index 0000000000..dc916df550 --- /dev/null +++ b/iOSClient/Data/NCManageDatabase+MediaPreviewBackfill.swift @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2023 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import UIKit +import RealmSwift +import NextcloudKit + +final class tableMediaPreviewBackfill: Object { + @Persisted(primaryKey: true) var key: String = "" + + @Persisted var account: String + @Persisted var ocId: String + + convenience init(account: String, ocId: String) { + self.init() + + self.key = "\(account)-\(ocId)" + self.account = account + self.ocId = ocId + } +} + +extension NCManageDatabase { + + // MARK: - Media preview backfill Realm read + + func getFailedMediaPreviewOcIdsAsync(account: String) async -> Set { + await core.performRealmReadAsync { realm in + let results = realm.objects(tableMediaPreviewBackfill.self) + .where { + $0.account == account + } + + return Set(results.map(\.ocId)) + } ?? [] + } + + // MARK: - Media preview backfill Realm write + + func addMediaPreviewBackfillFailureAsync(account: String, ocId: String) async { + await core.performRealmWriteAsync { realm in + let item = tableMediaPreviewBackfill() + item.key = "\(account)-\(ocId)" + item.account = account + item.ocId = ocId + + realm.add(item, update: .modified) + } + } +} diff --git a/iOSClient/Data/NCManageDatabase.swift b/iOSClient/Data/NCManageDatabase.swift index 83d15f4f79..8e2c57b477 100644 --- a/iOSClient/Data/NCManageDatabase.swift +++ b/iOSClient/Data/NCManageDatabase.swift @@ -207,6 +207,8 @@ final class NCManageDatabase: @unchecked Sendable { self.clearTable(tableExternalSites.self) self.clearTable(tableLivePhoto.self) self.clearTable(tableLocalFile.self) + self.clearTable(tableMediaMetadataBackfill.self) + self.clearTable(tableMediaPreviewBackfill.self) self.clearTable(tableMetadata.self) self.clearTable(tableMetadataTag.self) self.clearTable(tableRecommendedFiles.self) @@ -238,6 +240,7 @@ final class NCManageDatabase: @unchecked Sendable { self.clearTable(tableLivePhoto.self, account: account) self.clearTable(tableLocalFile.self, account: account) self.clearTable(tableMediaMetadataBackfill.self, account: account) + self.clearTable(tableMediaPreviewBackfill.self, account: account) self.clearTable(tableMetadata.self, account: account) self.clearTable(tableMetadataTag.self, account: account) self.clearTable(tableRecommendedFiles.self, account: account) diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 91fecd360d..7bc8e895ad 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -393,6 +393,7 @@ final class NCGlobal: Sendable { let logTagViewer = "VIEWERS" let logTagMediaBackfill = "MEDIA BACKFILL" let logTagMediaPlaceholder = "MEDIA PLACEHOLDER" + let logTagMediaPreview = "MEDIA PREVIEW" // USER DEFAULTS // diff --git a/iOSClient/AppDelegate+AppProcessing.swift b/iOSClient/Processor/AppDelegate+AppProcessing.swift similarity index 84% rename from iOSClient/AppDelegate+AppProcessing.swift rename to iOSClient/Processor/AppDelegate+AppProcessing.swift index 51ce75f833..7f36ae7707 100644 --- a/iOSClient/AppDelegate+AppProcessing.swift +++ b/iOSClient/Processor/AppDelegate+AppProcessing.swift @@ -95,13 +95,11 @@ extension AppDelegate { return false } - let mediaProcessor = NCMediaMetadataBackgroundProcessor() - nkLog(tag: self.global.logTagMediaBackfill, emoji: .start, message: "Start media metadata backfill for account \(activeAccount.account)") - let backfillStatus = await mediaProcessor.runBackfill( + let backfillStatus = await NCMediaMetadataBackfillProcessor().runBackfill( account: activeAccount, limit: 250 ) { offset, inserted, updated in @@ -123,7 +121,7 @@ extension AppDelegate { emoji: .start, message: "Start media metadata placeholder hydration for account \(account.account)") - let hydrationStatus = await mediaProcessor.runPlaceholderHydration( + let hydrationStatus = await NCMediaPlaceholderHydrationProcessor().runPlaceholderHydration( account: account, limit: 100 ) { succeeded in @@ -139,6 +137,27 @@ extension AppDelegate { guard !Task.isCancelled else { return false } + + nkLog(tag: self.global.logTagMediaPreview, + emoji: .start, + message: "Start media preview backfill for account \(account.account)") + + let previewStatus = await NCMediaPreviewBackfillProcessor().runPreviewBackfill( + account: account, + limit: 100 + ) { succeeded, failed in + nkLog(tag: self.global.logTagMediaPreview, + emoji: .info, + message: "Media preview backfill progress: succeeded \(succeeded) - failed \(failed) account \(account.account)") + } + + nkLog(tag: self.global.logTagMediaPreview, + emoji: previewStatus.isSuccessful ? .stop : .error, + message: previewStatus.logMessage) + + guard !Task.isCancelled else { + return false + } } return true diff --git a/iOSClient/NCMediaMetadataBackgroundProcessor.swift b/iOSClient/Processor/NCMediaMetadataBackfillProcessor.swift similarity index 52% rename from iOSClient/NCMediaMetadataBackgroundProcessor.swift rename to iOSClient/Processor/NCMediaMetadataBackfillProcessor.swift index 2563878f55..9aacb48b57 100644 --- a/iOSClient/NCMediaMetadataBackgroundProcessor.swift +++ b/iOSClient/Processor/NCMediaMetadataBackfillProcessor.swift @@ -5,7 +5,7 @@ import Foundation import NextcloudKit -final class NCMediaMetadataBackgroundProcessor { +final class NCMediaMetadataBackfillProcessor { enum BackfillStatus { case skippedAlreadyCompleted(account: String) case completed(account: String, processed: Int, inserted: Int, updated: Int) @@ -38,36 +38,6 @@ final class NCMediaMetadataBackgroundProcessor { } } - enum PlaceholderHydrationStatus { - case skippedNoPlaceholders(account: String) - case completed(account: String, total: Int, succeeded: Int, failed: Int) - case cancelled(account: String, total: Int, succeeded: Int, failed: Int) - - var isSuccessful: Bool { - switch self { - case .skippedNoPlaceholders, .completed: - return true - case .cancelled: - return false - } - } - - var logMessage: String { - switch self { - case .skippedNoPlaceholders(let account): - return "Media metadata placeholder hydration skipped for account \(account): no placeholders found" - - case .completed(let account, let total, let succeeded, let failed): - let pending = max(0, total - succeeded - failed) - return "Media metadata placeholder hydration completed for account \(account): total \(total) - succeeded \(succeeded) - failed \(failed) - pending \(pending)" - - case .cancelled(let account, let total, let succeeded, let failed): - let pending = max(0, total - succeeded - failed) - return "Media metadata placeholder hydration cancelled for account \(account): total \(total) - succeeded \(succeeded) - failed \(failed) - pending \(pending)" - } - } - } - /// Progressively scans the media archive and creates missing metadata placeholders. func runBackfill( account: tableAccount, @@ -155,106 +125,4 @@ final class NCMediaMetadataBackgroundProcessor { return .cancelled(account: account.account, processed: processed, inserted: inserted, updated: updated) } - - /// Completes media metadata placeholders by retrieving and storing their full properties. - func runPlaceholderHydration( - account: tableAccount, - limit: Int, - update: @escaping (_ succeeded: Int) async -> Void - ) async -> PlaceholderHydrationStatus { - let database = NCManageDatabase.shared - let maximumConcurrentRequests = min(8, NCBrandOptions.shared.httpMaximumConnectionsPerHost) - - var succeeded = 0 - var failed = 0 - - guard let metadatas = await database.getMetadatasAsync( - predicate: NSPredicate( - format: "account == %@ AND placeholder == true", - account.account - ), - sortedByKeyPath: "date", - ascending: false, - limit: limit - ), !metadatas.isEmpty else { - return .skippedNoPlaceholders(account: account.account) - } - - let total = metadatas.count - - func hydrate(_ metadata: tableMetadata) async -> Bool { - guard !Task.isCancelled else { - return false - } - - let result = await NextcloudKit.shared.readFileOrFolderAsync( - serverUrlFileName: metadata.serverUrlFileName, - depth: "0", - account: metadata.account - ) - - guard !Task.isCancelled else { - return false - } - - switch result.error.errorCode { - case 0: - if let file = result.files?.first { - let metadata = await NCManageDatabaseCreateMetadata().convertFileToMetadataAsync(file) - await database.addMetadataAsync(metadata) - } - return true - - case 404: - await database.deleteMetadataAsync(ocId: metadata.ocId) - return true - - default: - return false - } - } - - await withTaskGroup(of: Bool.self) { group in - var iterator = metadatas.makeIterator() - - for _ in 0.. Void + ) async -> PlaceholderHydrationStatus { + let database = NCManageDatabase.shared + let maximumConcurrentRequests = min(8, NCBrandOptions.shared.httpMaximumConnectionsPerHost) + + var succeeded = 0 + var failed = 0 + + guard let metadatas = await database.getMetadatasAsync( + predicate: NSPredicate( + format: "account == %@ AND placeholder == true", + account.account + ), + sortedByKeyPath: "date", + ascending: false, + limit: limit + ), !metadatas.isEmpty else { + return .skippedNoPlaceholders(account: account.account) + } + + let total = metadatas.count + + func hydrate(_ metadata: tableMetadata) async -> Bool { + guard !Task.isCancelled else { + return false + } + + let result = await NextcloudKit.shared.readFileOrFolderAsync( + serverUrlFileName: metadata.serverUrlFileName, + depth: "0", + account: metadata.account + ) + + guard !Task.isCancelled else { + return false + } + + switch result.error.errorCode { + case 0: + if let file = result.files?.first { + let metadata = await NCManageDatabaseCreateMetadata().convertFileToMetadataAsync(file) + await database.addMetadataAsync(metadata) + } + return true + + case 404: + await database.deleteMetadataAsync(ocId: metadata.ocId) + return true + + default: + return false + } + } + + await withTaskGroup(of: Bool.self) { group in + var iterator = metadatas.makeIterator() + + for _ in 0.. Void + ) async -> PreviewBackfillStatus { + let database = NCManageDatabase.shared + let utilityFileSystem = NCUtilityFileSystem() + let maximumConcurrentRequests = min(8, NCBrandOptions.shared.httpMaximumConnectionsPerHost) + let session = NCSession.Session(account: account.account, urlBase: account.urlBase, user: account.user, userId: account.userId) + let mediaPredicate = NCImageCache.shared.getMediaPredicate( + session: session, + mediaPath: account.mediaPath, + showOnlyImages: false, + showOnlyVideos: false) + guard let metadatasMedia = await database.getMetadatasAsync(predicate: mediaPredicate, sortedByKeyPath: "date", ascending: false) else { + return .skippedNoPreviews(account: account.account) + } + + let failedOcIds = await database.getFailedMediaPreviewOcIdsAsync(account: account.account) + var metadatas: [tableMetadata] = [] + metadatas.reserveCapacity(limit) + + for metadata in metadatasMedia { + guard !Task.isCancelled else { + break + } + guard !failedOcIds.contains(metadata.ocId) else { + continue + } + let imageExists = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag, userId: metadata.userId, urlBase: metadata.urlBase) + + guard !imageExists else { + continue + } + + metadatas.append(metadata) + if metadatas.count >= limit { + break + } + } + + guard !metadatas.isEmpty else { + await database.clearTableAsync(tableMediaPreviewBackfill.self, account: account.account) + return .skippedNoPreviews(account: account.account) + } + + let total = metadatas.count + + var succeeded = 0 + var failed = 0 + + enum PreviewResult { + case succeeded + case failed + case cancelled + } + + func process(_ metadata: tableMetadata) async -> PreviewResult { + guard !Task.isCancelled else { + return .cancelled + } + + let error = await requestPreview(metadata: metadata) + + guard !Task.isCancelled else { + return .cancelled + } + + guard error.errorCode == 0 else { + await database.addMediaPreviewBackfillFailureAsync(account: metadata.account, ocId: metadata.ocId) + return .failed + } + + return .succeeded + } + + await withTaskGroup(of: PreviewResult.self) { group in + var iterator = metadatas.makeIterator() + + for _ in 0.. NKError { + guard !Task.isCancelled else { + return NKError( + errorCode: NSURLErrorCancelled, + errorDescription: "Cancelled" + ) + } + + let result = await NextcloudKit.shared.downloadPreviewAsync( + fileId: metadata.fileId, + etag: metadata.etag, + account: metadata.account + ) + + guard !Task.isCancelled else { + return NKError( + errorCode: NSURLErrorCancelled, + errorDescription: "Cancelled" + ) + } + + guard result.error == .success, + let data = result.responseData?.data else { + return result.error + } + + NCUtility().createImageFileFrom(data: data, metadata: metadata) + + return .success + } +} diff --git a/iOSClient/AppDelegate+AppRefresh.swift b/iOSClient/Refresh/AppDelegate+AppRefresh.swift similarity index 100% rename from iOSClient/AppDelegate+AppRefresh.swift rename to iOSClient/Refresh/AppDelegate+AppRefresh.swift