Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// FilesDatabaseManager+KeepOffline.swift
// NextcloudFileProviderKit
//
// Created by Claudio Cambra on 13/5/25.
//

import Foundation
import RealmSwift

public extension FilesDatabaseManager {
func set(
keepOffline: Bool, for metadata: SendableItemMetadata
) throws -> SendableItemMetadata? {
guard #available(macOS 13.0, iOS 16.0, visionOS 1.0, *) else {
let errorString = """
Could not update keepOffline status for item: \(metadata.fileName)
as the system does not support this state.
"""
Self.logger.error("\(errorString, privacy: .public)")
throw NSError(
domain: Self.errorDomain,
code: NSFeatureUnsupportedError,
userInfo: [NSLocalizedDescriptionKey: errorString]
)

Check warning on line 25 in Sources/NextcloudFileProviderKit/Database/FilesDatabaseManager+KeepOffline.swift

View check run for this annotation

Codecov / codecov/patch

Sources/NextcloudFileProviderKit/Database/FilesDatabaseManager+KeepOffline.swift#L16-L25

Added lines #L16 - L25 were not covered by tests
}

guard let result = itemMetadatas.where({ $0.ocId == metadata.ocId }).first else {
let errorString = """
Did not update keepOffline for item metadata as it was not found.
ocID: \(metadata.ocId)
filename: \(metadata.fileName)
"""
Self.logger.error("\(errorString, privacy: .public)")
throw NSError(
domain: Self.errorDomain,
code: ErrorCode.metadataNotFound.rawValue,
userInfo: [NSLocalizedDescriptionKey: errorString]
)

Check warning on line 39 in Sources/NextcloudFileProviderKit/Database/FilesDatabaseManager+KeepOffline.swift

View check run for this annotation

Codecov / codecov/patch

Sources/NextcloudFileProviderKit/Database/FilesDatabaseManager+KeepOffline.swift#L29-L39

Added lines #L29 - L39 were not covered by tests
}

try ncDatabase().write {
result.keepOffline = keepOffline

Self.logger.debug(
"""
Updated keepOffline status for item metadata.
ocID: \(metadata.ocId, privacy: .public)
fileName: \(metadata.fileName, privacy: .public)
"""
)
}
return SendableItemMetadata(value: result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ public let relativeDatabaseFolderPath = "Database/"
public let databaseFilename = "fileproviderextdatabase.realm"

public final class FilesDatabaseManager: Sendable {
enum ErrorCode: Int {
case metadataNotFound = -1000
}

private static let schemaVersion = stable2_0SchemaVersion
static let errorDomain = "FilesDatabaseManager"
static let logger = Logger(subsystem: Logger.subsystem, category: "filesdatabase")
let account: Account

Expand Down
45 changes: 45 additions & 0 deletions Sources/NextcloudFileProviderKit/Item/Item+KeepOffline.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Item+KeepOffline.swift
// NextcloudFileProviderKit
//
// Created by Claudio Cambra on 13/5/25.
//

import FileProvider

public extension Item {
func toggle(keepOfflineIn domain: NSFileProviderDomain) async throws {
try await set(keepOffline: !keepOffline, domain: domain)
}

Check warning on line 13 in Sources/NextcloudFileProviderKit/Item/Item+KeepOffline.swift

View check run for this annotation

Codecov / codecov/patch

Sources/NextcloudFileProviderKit/Item/Item+KeepOffline.swift#L11-L13

Added lines #L11 - L13 were not covered by tests

func set(keepOffline: Bool, domain: NSFileProviderDomain) async throws {
try dbManager.set(keepOffline: keepOffline, for: metadata)

guard let manager = NSFileProviderManager(for: domain) else {
if #available(macOS 14.1, *) {
throw NSFileProviderError(.providerDomainNotFound)
} else {
let providerDomainNotFoundErrorCode = -2013
throw NSError(
domain: NSFileProviderErrorDomain,
code: providerDomainNotFoundErrorCode,
userInfo: [NSLocalizedDescriptionKey: "Failed to get manager for domain."]
)
}
}

if #available(macOS 13.0, iOS 16.0, visionOS 1.0, *) {
if keepOffline && !isDownloaded {
try await manager.requestDownloadForItem(withIdentifier: itemIdentifier)
} else if !keepOffline && isDownloaded {
try await manager.evictItem(identifier: itemIdentifier)
} else {
try await manager.requestModification(
of: [.lastUsedDate], forItemWithIdentifier: itemIdentifier
)
}
} else {
try await manager.signalEnumerator(for: .workingSet)
}
}

Check warning on line 44 in Sources/NextcloudFileProviderKit/Item/Item+KeepOffline.swift

View check run for this annotation

Codecov / codecov/patch

Sources/NextcloudFileProviderKit/Item/Item+KeepOffline.swift#L15-L44

Added lines #L15 - L44 were not covered by tests
}
16 changes: 10 additions & 6 deletions Sources/NextcloudFileProviderKit/Item/Item.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,17 @@
return userInfoDict
}

@available(macOS 13.0, *)
@available(macOS 13.0, iOS 16.0, visionOS 1.0, *)
public var contentPolicy: NSFileProviderContentPolicy {
#if os(macOS)
.downloadLazily
#else
.downloadLazilyAndEvictOnRemoteUpdate
#endif
if metadata.keepOffline {
return .downloadEagerlyAndKeepDownloaded
}
return .inherited
}

Check warning on line 211 in Sources/NextcloudFileProviderKit/Item/Item.swift

View check run for this annotation

Codecov / codecov/patch

Sources/NextcloudFileProviderKit/Item/Item.swift#L207-L211

Added lines #L207 - L211 were not covered by tests

public var keepOffline: Bool {
guard #available(macOS 13.0, iOS 16.0, visionOS 1.0, *) else { return false }
return metadata.keepOffline

Check warning on line 215 in Sources/NextcloudFileProviderKit/Item/Item.swift

View check run for this annotation

Codecov / codecov/patch

Sources/NextcloudFileProviderKit/Item/Item.swift#L213-L215

Added lines #L213 - L215 were not covered by tests
}

public static func rootContainer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public protocol ItemMetadata: Equatable {
var tags: [String] { get set }
var downloaded: Bool { get set }
var uploaded: Bool { get set }
var keepOffline: Bool { get set }
var trashbinFileName: String { get set }
var trashbinOriginalLocation: String { get set }
var trashbinDeletionTime: Date { get set }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ internal class RealmItemMetadata: Object, ItemMetadata {
}
@Persisted public var downloaded = false
@Persisted public var uploaded = false
@Persisted public var keepOffline = false
@Persisted public var trashbinFileName = ""
@Persisted public var trashbinOriginalLocation = ""
@Persisted public var trashbinDeletionTime = Date()
Expand Down Expand Up @@ -157,6 +158,7 @@ internal class RealmItemMetadata: Object, ItemMetadata {
self.tags = value.tags
self.downloaded = value.downloaded
self.uploaded = value.uploaded
self.keepOffline = value.keepOffline
self.trashbinFileName = value.trashbinFileName
self.trashbinOriginalLocation = value.trashbinOriginalLocation
self.trashbinDeletionTime = value.trashbinDeletionTime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public struct SendableItemMetadata: ItemMetadata, Sendable {
public var tags: [String]
public var downloaded: Bool
public var uploaded: Bool
public var keepOffline: Bool
public var trashbinFileName: String
public var trashbinOriginalLocation: String
public var trashbinDeletionTime: Date
Expand Down Expand Up @@ -125,6 +126,7 @@ public struct SendableItemMetadata: ItemMetadata, Sendable {
tags: [String] = [],
downloaded: Bool = false,
uploaded: Bool = false,
keepOffline: Bool = false,
trashbinFileName: String = "",
trashbinOriginalLocation: String = "",
trashbinDeletionTime: Date = Date(),
Expand Down Expand Up @@ -186,6 +188,7 @@ public struct SendableItemMetadata: ItemMetadata, Sendable {
self.tags = tags
self.downloaded = downloaded
self.uploaded = uploaded
self.keepOffline = keepOffline
self.trashbinFileName = trashbinFileName
self.trashbinOriginalLocation = trashbinOriginalLocation
self.trashbinDeletionTime = trashbinDeletionTime
Expand Down Expand Up @@ -248,6 +251,7 @@ public struct SendableItemMetadata: ItemMetadata, Sendable {
self.status = value.status
self.downloaded = value.downloaded
self.uploaded = value.uploaded
self.keepOffline = value.keepOffline
self.tags = value.tags
self.trashbinFileName = value.trashbinFileName
self.trashbinOriginalLocation = value.trashbinOriginalLocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1031,4 +1031,28 @@ final class FilesDatabaseManagerTests: XCTestCase {

try FileManager.default.removeItem(at: tempDir)
}

func testKeepOfflineSetting() throws {
let existingMetadata = RealmItemMetadata()
existingMetadata.ocId = "id-1"
existingMetadata.fileName = "File.pdf"
existingMetadata.account = "TestAccount"
existingMetadata.serverUrl = "https://example.com"
XCTAssertFalse(existingMetadata.keepOffline)

let realm = Self.dbManager.ncDatabase()
try realm.write {
realm.add(existingMetadata)
}

let sendable = SendableItemMetadata(value: existingMetadata)
var updatedMetadata =
try XCTUnwrap(try Self.dbManager.set(keepOffline: true, for: sendable))
XCTAssertTrue(updatedMetadata.keepOffline)

updatedMetadata.keepOffline = false
let finalMetadata =
try XCTUnwrap(try Self.dbManager.set(keepOffline: false, for: updatedMetadata))
XCTAssertFalse(finalMetadata.keepOffline)
}
}