Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Brand/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ import Foundation
//
let databaseName = "nextcloud.realm"
let tableAccountBackup = "tableAccountBackup.json"
let databaseSchemaVersion: UInt64 = 412
let databaseSchemaVersion: UInt64 = 413
58 changes: 47 additions & 11 deletions Nextcloud.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions iOSClient/Data/NCManageDatabase+MediaPreviewBackfill.swift
Original file line number Diff line number Diff line change
@@ -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<String> {
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)
}
}
}
3 changes: 3 additions & 0 deletions iOSClient/Data/NCManageDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions iOSClient/NCGlobal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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..<maximumConcurrentRequests {
guard let metadata = iterator.next() else {
break
}

group.addTask {
await hydrate(metadata)
}
}

while let completed = await group.next() {
if completed {
succeeded += 1
} else {
failed += 1
}

guard !Task.isCancelled else {
group.cancelAll()
continue
}

guard let metadata = iterator.next() else {
continue
}

group.addTask {
await hydrate(metadata)
}
}
}

guard !Task.isCancelled else {
return .cancelled(account: account.account, total: total, succeeded: succeeded, failed: failed)
}

await update(succeeded)

return .completed(account: account.account, total: total, succeeded: succeeded, failed: failed)
}
}
Loading
Loading