Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ce40c93
Add syncTime and deleted properties to item metadata
claucambra Jun 17, 2025
f3d2e69
Add method to get items modified since a given date in files database…
claucambra Jun 17, 2025
69f6fa7
Add test for database pending working set changes database method
claucambra Jun 17, 2025
9e1d7bf
Return all modified items since sync anchor date for changes enumeration
claucambra Jul 1, 2025
b630c03
Do not actually delete deleted metadatas
claucambra Jul 1, 2025
7112baa
Use simpler syntax for going to last array index
claucambra Jul 1, 2025
3263c9d
During depth 1 read handling, mark deleted files as deleted
claucambra Jul 1, 2025
1d2ae02
Fix changed folder enumerator test
claucambra Jul 1, 2025
406deb2
Adapt FIlesDatabaseManagerTests to deleted item changes
claucambra Jul 1, 2025
1c6097f
Add passable anchor to enumeate changes in mock change observer
claucambra Jul 1, 2025
3b7e65a
Amend testWorkingSetEnumerateChanges to test current behaviour of wor…
claucambra Jul 1, 2025
24cc81c
Perform a check of the working set's items via the remote change obse…
claucambra Jul 1, 2025
fe2b69e
Implement tests for new behaviour in remote change observer
claucambra Jul 1, 2025
f1a8f67
Store deletion state in db
claucambra Jul 1, 2025
69afa7a
Assert database state in remote change observer test
claucambra Jul 2, 2025
a95f0d6
Expand remote change observer detected change test
claucambra Jul 2, 2025
7c24e92
Add more logging regarding parent missing in enumerator
claucambra Jul 2, 2025
e550fb3
Add sync anchor logging
claucambra Jul 2, 2025
cc269c4
Complete change enumeration with correct sync anchor
claucambra Jul 2, 2025
ceeb717
Add more logging to remote change observer change calculation
claucambra Jul 2, 2025
ae7b6df
Log sync time on items
claucambra Jul 2, 2025
cf723b0
Mark items as deleted, do not delete actual db items
claucambra Jul 2, 2025
059ee9f
In pending working set changes also return children to updated/delete…
claucambra Jul 2, 2025
f008cd3
Improve testPendingWorkingSetChanges
claucambra Jul 2, 2025
0453b5e
Improve new item metadata creation during update logging
claucambra Jul 2, 2025
054a335
Ensure sync time is updated during deletion
claucambra Jul 2, 2025
cc28569
TEMP: Debug logging
claucambra Jul 2, 2025
398ee72
Properly handle root item server url
claucambra Jul 2, 2025
d9a91e7
Fix handling of visitedDirectory state
claucambra Jul 2, 2025
7195d1a
Handle deleted files properly in pending working set retrieval
claucambra Jul 2, 2025
9bf90bd
Ensure marking of deleted files in depth1 handling
claucambra Jul 2, 2025
b841e59
Fix crash from improper metadata modification
claucambra Jul 2, 2025
ee4eeff
Fix tests to conform to new item metadata deletion approach
claucambra Jul 4, 2025
f2c2a57
Improve testPendingWorkingSetChanges
claucambra Jul 4, 2025
11b7c94
Test for updated sync time on deleted item during remote change obser…
claucambra Jul 4, 2025
b6ead3e
Test for visitedDirectory state in enumerator tests
claucambra Jul 4, 2025
b25a3a1
Add method to invalidate the RCO
claucambra Jul 7, 2025
43fd059
Use weak self in websocket reconnect task
claucambra Jul 7, 2025
aca7968
Also handle invalidation after server read
claucambra Jul 8, 2025
e65461a
Ensure unchanged directories don't get examined even if they are mate…
claucambra Jul 8, 2025
1350e18
Add RCO optimisation test
claucambra Jul 8, 2025
67de21f
Remove duplicate dbManager from RCO tests, just use one
claucambra Jul 8, 2025
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
Expand Up @@ -90,7 +90,7 @@ extension FilesDatabaseManager {

let database = ncDatabase()
do {
try database.write { database.delete(directoryMetadata) }
try database.write { directoryMetadata.deleted = true }
} catch let error {
Self.logger.error(
"""
Expand All @@ -113,7 +113,7 @@ extension FilesDatabaseManager {
for result in results {
let inactiveItemMetadata = SendableItemMetadata(value: result)
do {
try database.write { database.delete(result) }
try database.write { result.deleted = true }
deletedMetadatas.append(inactiveItemMetadata)
} catch let error {
Self.logger.error(
Expand Down
114 changes: 100 additions & 14 deletions Sources/NextcloudFileProviderKit/Database/FilesDatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public final class FilesDatabaseManager: Sendable {

for existingMetadata in existingMetadatas {
guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
let metadataToDelete = itemMetadatas.where({ $0.ocId == existingMetadata.ocId }).first
var metadataToDelete = itemMetadatas.where({ $0.ocId == existingMetadata.ocId }).first
else { continue }

deletedMetadatas.append(metadataToDelete)
Expand All @@ -245,6 +245,7 @@ public final class FilesDatabaseManager: Sendable {
ocID: \(existingMetadata.ocId, privacy: .public)
etag: \(existingMetadata.etag, privacy: .public)
fileName: \(existingMetadata.fileName, privacy: .public)"
syncTime: \(existingMetadata.syncTime, privacy: .public)
"""
)
}
Expand Down Expand Up @@ -282,6 +283,7 @@ public final class FilesDatabaseManager: Sendable {
if keepExistingDownloadState {
updatedMetadata.downloaded = existingMetadata.downloaded
}
updatedMetadata.visitedDirectory = existingMetadata.visitedDirectory

returningUpdatedMetadatas.append(updatedMetadata)

Expand All @@ -291,6 +293,7 @@ public final class FilesDatabaseManager: Sendable {
ocID: \(updatedMetadata.ocId, privacy: .public)
etag: \(updatedMetadata.etag, privacy: .public)
fileName: \(updatedMetadata.fileName, privacy: .public)
syncTime: \(updatedMetadata.syncTime, privacy: .public)
"""
)
} else {
Expand All @@ -300,6 +303,7 @@ public final class FilesDatabaseManager: Sendable {
ocID: \(updatedMetadata.ocId, privacy: .public)
etag: \(updatedMetadata.etag, privacy: .public)
fileName: \(updatedMetadata.fileName, privacy: .public)
syncTime: \(updatedMetadata.syncTime, privacy: .public)
"""
)
}
Expand All @@ -313,6 +317,23 @@ public final class FilesDatabaseManager: Sendable {
ocID: \(updatedMetadata.ocId, privacy: .public)
etag: \(updatedMetadata.etag, privacy: .public)
fileName: \(updatedMetadata.fileName, privacy: .public)
parentDirectoryUrl: \(updatedMetadata.serverUrl, privacy: .public)
account: \(updatedMetadata.account, privacy: .public)
content type: \(updatedMetadata.contentType, privacy: .public)
is directory: \(updatedMetadata.directory, privacy: .public)
creation date: \(updatedMetadata.creationDate, privacy: .public)
date: \(updatedMetadata.date, privacy: .public)
lock: \(updatedMetadata.lock, privacy: .public)
lockTimeOut: \(updatedMetadata.lockTimeOut?.description ?? "", privacy: .public)
lockOwner: \(updatedMetadata.lockOwner ?? "", privacy: .public)
permissions: \(updatedMetadata.permissions, privacy: .public)
size: \(updatedMetadata.size, privacy: .public)
trashbinFileName: \(updatedMetadata.trashbinFileName, privacy: .public)
downloaded: \(updatedMetadata.downloaded, privacy: .public)
uploaded: \(updatedMetadata.uploaded, privacy: .public)
visitedDirectory: \(updatedMetadata.visitedDirectory, privacy: .public)
deleted: \(updatedMetadata.deleted, privacy: .public)
syncTime: \(updatedMetadata.syncTime, privacy: .public)
"""
)
}
Expand Down Expand Up @@ -368,12 +389,14 @@ public final class FilesDatabaseManager: Sendable {
readTargetMetadata = nil
}

// NOTE: These metadatas are managed -- be careful!
let metadatasToDelete = processItemMetadatasToDelete(
existingMetadatas: existingMetadatas,
updatedMetadatas: updatedChildMetadatas
)
let metadatasToDeleteCopy = metadatasToDelete.map { SendableItemMetadata(value: $0) }
).map {
var metadata = SendableItemMetadata(value: $0)
metadata.deleted = true
return metadata
}

let metadatasToChange = processItemMetadatasToUpdate(
existingMetadatas: existingMetadatas,
Expand All @@ -396,32 +419,34 @@ public final class FilesDatabaseManager: Sendable {
}

if var readTargetMetadata {
if readTargetMetadata.directory {
readTargetMetadata.visitedDirectory = true
}

if let existing = itemMetadata(ocId: readTargetMetadata.ocId) {
if existing.status == Status.normal.rawValue,
!existing.isInSameDatabaseStoreableRemoteState(readTargetMetadata)
{
Self.logger.info("Depth 1 read target changed: \(readTargetMetadata.ocId)")
Self.logger.info("Depth 1 read target changed: \(readTargetMetadata.ocId, privacy: .public)")
if keepExistingDownloadState {
readTargetMetadata.downloaded = existing.downloaded
}
if readTargetMetadata.directory {
readTargetMetadata.visitedDirectory = true
}
metadatasToUpdate.insert(readTargetMetadata, at: 0)
}
} else {
Self.logger.info("Depth 1 read target is new: \(readTargetMetadata.ocId)")
Self.logger.info("Depth 1 read target is new: \(readTargetMetadata.ocId, privacy: .public)")
metadatasToCreate.insert(readTargetMetadata, at: 0)
}
}

try database.write {
database.delete(metadatasToDelete)
// Do not delete the metadatas that have been deleted
database.add(metadatasToDelete.map { RealmItemMetadata(value: $0) }, update: .modified)
database.add(metadatasToUpdate.map { RealmItemMetadata(value: $0) }, update: .modified)
database.add(metadatasToCreate.map { RealmItemMetadata(value: $0) }, update: .all)
}

return (metadatasToCreate, metadatasToUpdate, metadatasToDeleteCopy)
return (metadatasToCreate, metadatasToUpdate, metadatasToDelete)
} catch {
Self.logger.error(
"""
Expand Down Expand Up @@ -467,6 +492,7 @@ public final class FilesDatabaseManager: Sendable {
ocID: \(metadata.ocId, privacy: .public)
etag: \(metadata.etag, privacy: .public)
fileName: \(metadata.fileName, privacy: .public)
syncTime: \(metadata.syncTime, privacy: .public)
"""
)
}
Expand Down Expand Up @@ -513,6 +539,8 @@ public final class FilesDatabaseManager: Sendable {
downloaded: \(metadata.downloaded, privacy: .public)
uploaded: \(metadata.uploaded, privacy: .public)
visitedDirectory: \(metadata.visitedDirectory, privacy: .public)
deleted: \(metadata.deleted, privacy: .public)
syncTime: \(metadata.syncTime, privacy: .public)
"""
)
}
Expand All @@ -523,6 +551,7 @@ public final class FilesDatabaseManager: Sendable {
ocID: \(metadata.ocId, privacy: .public)
etag: \(metadata.etag, privacy: .public)
fileName: \(metadata.fileName, privacy: .public)
syncTime: \(metadata.syncTime, privacy: .public)
received error: \(error.localizedDescription, privacy: .public)
"""
)
Expand All @@ -535,7 +564,7 @@ public final class FilesDatabaseManager: Sendable {
let database = ncDatabase()
try database.write {
Self.logger.debug("Deleting item metadata. \(ocId, privacy: .public)")
database.delete(results)
results.forEach { $0.deleted = true }
}
return true
} catch {
Expand Down Expand Up @@ -614,6 +643,7 @@ public final class FilesDatabaseManager: Sendable {
fileName: \(metadata.fileName, privacy: .public),
serverUrl: \(metadata.serverUrl, privacy: .public),
account: \(metadata.account, privacy: .public),
syncTime: \(metadata.syncTime, privacy: .public)
"""
)
return nil
Expand Down Expand Up @@ -652,12 +682,68 @@ public final class FilesDatabaseManager: Sendable {
return NSFileProviderItemIdentifier(parentMetadata.ocId)
}

public func materialisedItemMetadatas(account: String) -> [SendableItemMetadata] {
private func managedMaterialisedItemMetadatas(account: String) -> Results<RealmItemMetadata> {
itemMetadatas
.where {
$0.account == account &&
(($0.directory && $0.visitedDirectory) || (!$0.directory && $0.downloaded))
}
.toUnmanagedResults()
}

public func materialisedItemMetadatas(account: String) -> [SendableItemMetadata] {
managedMaterialisedItemMetadatas(account: account).toUnmanagedResults()
}

public func pendingWorkingSetChanges(
account: Account, since date: Date
) -> (updated: [SendableItemMetadata], deleted: [SendableItemMetadata]) {
let accId = account.ncKitAccount
let pending = managedMaterialisedItemMetadatas(account: accId).where { $0.syncTime > date }
var updated = pending.where { !$0.deleted }.toUnmanagedResults()
var deleted = pending.where { $0.deleted }.toUnmanagedResults()

var handledUpdateOcIds = Set(updated.map(\.ocId))
updated
.map {
var serverUrl = $0.serverUrl + "/" + $0.fileName
if serverUrl.last == "/" { serverUrl.removeLast() }
return serverUrl
}
.forEach { serverUrl in
Self.logger.debug("Checking (updated) \(serverUrl, privacy: .public)")
itemMetadatas
.where { $0.serverUrl == serverUrl && $0.syncTime > date }
.forEach { metadata in
Self.logger.debug("Checking item: \(metadata.fileName, privacy: .public)")
guard !handledUpdateOcIds.contains(metadata.ocId) else { return }
handledUpdateOcIds.insert(metadata.ocId)
let sendableMetadata = SendableItemMetadata(value: metadata)
if metadata.deleted {
deleted.append(sendableMetadata)
} else {
updated.append(sendableMetadata)
}
Self.logger.debug("Appended item: \(metadata.fileName, privacy: .public)")
}
}

var handledDeleteOcIds = Set(deleted.map(\.ocId))
deleted
.map {
var serverUrl = $0.serverUrl + "/" + $0.fileName
if serverUrl.last == "/" { serverUrl.removeLast() }
return serverUrl
}
.forEach { serverUrl in
Self.logger.debug("Checking (deletion) \(serverUrl, privacy: .public)")
itemMetadatas
.where { $0.serverUrl.starts(with: serverUrl) && $0.syncTime > date }
.forEach { metadata in
guard !handledDeleteOcIds.contains(metadata.ocId) else { return }
deleted.append(SendableItemMetadata(value: metadata))
}
}

return (updated, deleted)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension Enumerator {
}
dbManager.addItemMetadata(metadata)
}
let metadatas = files[startIndex..<files.count].map { $0.toItemMetadata() }
let metadatas = files[startIndex...].map { $0.toItemMetadata() }
metadatas.forEach { dbManager.addItemMetadata($0) }
return (metadatas, nil)
}
Expand Down
Loading