Skip to content

Commit 64cb52c

Browse files
authored
Merge pull request #10063 from nextcloud/i2h3/10054-inheritance
New remote items inherit content policy
2 parents e70b0a5 + de9e98c commit 64cb52c

3 files changed

Lines changed: 150 additions & 7 deletions

File tree

shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Database/FilesDatabaseManager.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,36 @@ public final class FilesDatabaseManager: Sendable {
212212
.toUnmanagedResults()
213213
}
214214

215+
///
216+
/// Resolve the parent's "Always keep downloaded" flag for a metadata
217+
/// that is about to be persisted as a fresh row.
218+
///
219+
/// Mirrors the inheritance applied to locally-created items in
220+
/// `Item+Create.swift` so a sibling appearing via remote enumeration
221+
/// acquires the same `contentPolicy` and Finder overlay without the user
222+
/// having to re-toggle the parent (#10054).
223+
///
224+
/// Checking only the immediate parent is sufficient: the recursive
225+
/// enable in `Item.set(keepDownloaded:domain:)` sets the flag on every
226+
/// then-known descendant of the pinned ancestor, so every intermediate
227+
/// directory between the pin root and this new item is itself pinned.
228+
///
229+
/// Falls back to the root container when no parent row exists at the
230+
/// item's `serverUrl` — items directly under the user's home are stored
231+
/// against a synthesised root keyed by ocId, not by serverUrl/fileName.
232+
///
233+
func inheritedKeepDownloaded(for metadata: SendableItemMetadata) -> Bool {
234+
if let parent = parentDirectoryMetadataForItem(metadata) {
235+
return parent.keepDownloaded
236+
}
237+
238+
if let root = itemMetadata(ocId: NSFileProviderItemIdentifier.rootContainer.rawValue) {
239+
return root.keepDownloaded
240+
}
241+
242+
return false
243+
}
244+
215245
private func processItemMetadatasToDelete(
216246
existingMetadatas: Results<RealmItemMetadata>,
217247
updatedMetadatas: [SendableItemMetadata]
@@ -272,6 +302,9 @@ public final class FilesDatabaseManager: Sendable {
272302
}
273303

274304
} else { // This is a new metadata
305+
// Inherit the parent's "Always keep downloaded" flag so a file surfacing here via remote enumeration acquires the same pin as its already-pinned siblings (#10054).
306+
updatedMetadata.keepDownloaded = inheritedKeepDownloaded(for: updatedMetadata)
307+
275308
returningNewMetadatas.append(updatedMetadata)
276309

277310
logger.debug("Created new item metadata during update.", [.item: updatedMetadata.ocId])
@@ -374,6 +407,8 @@ public final class FilesDatabaseManager: Sendable {
374407
}
375408
} else {
376409
logger.info("Depth 1 read target is new: \(readTargetMetadata.ocId)")
410+
// Inherit from the parent so a directory appearing here via remote enumeration (e.g. created on the server while the user already pinned its parent) picks up the same pin as siblings (#10054).
411+
readTargetMetadata.keepDownloaded = inheritedKeepDownloaded(for: readTargetMetadata)
377412
metadatasToCreate.insert(readTargetMetadata, at: 0)
378413
}
379414
}
@@ -522,6 +557,7 @@ public final class FilesDatabaseManager: Sendable {
522557
&& !$0.deleted
523558
&& !$0.isLockFileOfLocalOrigin
524559
}
560+
525561
if logicalCandidates.count == 1, let existing = logicalCandidates.first {
526562
toWrite.downloaded = existing.downloaded
527563
toWrite.keepDownloaded = existing.keepDownloaded
@@ -531,6 +567,13 @@ public final class FilesDatabaseManager: Sendable {
531567
}
532568

533569
toWrite.lockToken = existing.lockToken
570+
} else {
571+
// No prior row at this ocId or logical address: this is a
572+
// genuinely new item. Inherit the parent's "Always keep
573+
// downloaded" flag so a file surfacing here via remote
574+
// enumeration acquires the same pin as its already-pinned
575+
// siblings (#10054).
576+
toWrite.keepDownloaded = inheritedKeepDownloaded(for: metadata)
534577
}
535578
}
536579

shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Enumeration/Enumerator+SyncEngine.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,15 @@ extension Enumerator {
212212
metadata.lockToken = existing?.lockToken
213213
let updatedItems: [SendableItemMetadata] = isNew ? [] : [metadata]
214214
metadata.downloaded = existing?.downloaded == true
215-
metadata.keepDownloaded = existing?.keepDownloaded == true
215+
216+
if let existing {
217+
metadata.keepDownloaded = existing.keepDownloaded
218+
} else {
219+
// Genuinely new item: inherit the parent's pin so the
220+
// overlay and `contentPolicy` match its siblings (#10054).
221+
metadata.keepDownloaded = dbManager.inheritedKeepDownloaded(for: metadata)
222+
}
223+
216224
dbManager.addItemMetadata(metadata)
217225
return ([metadata], newItems, updatedItems, [], nextPage, nil)
218226
}
@@ -228,7 +236,15 @@ extension Enumerator {
228236
metadata.lockToken = existing?.lockToken
229237
metadata.visitedDirectory = existing?.visitedDirectory == true
230238
metadata.downloaded = existing?.downloaded == true
231-
metadata.keepDownloaded = existing?.keepDownloaded == true
239+
240+
if let existing {
241+
metadata.keepDownloaded = existing.keepDownloaded
242+
} else {
243+
// Genuinely new item: inherit the parent's pin so the
244+
// overlay and `contentPolicy` match its siblings (#10054).
245+
metadata.keepDownloaded = dbManager.inheritedKeepDownloaded(for: metadata)
246+
}
247+
232248
dbManager.addItemMetadata(metadata)
233249

234250
return ([metadata], newMetadatas, updatedMetadatas, [], nextPage, nil)

shell_integration/MacOSX/NextcloudFileProviderKit/Tests/NextcloudFileProviderKitTests/FilesDatabaseManagerTests.swift

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -978,13 +978,14 @@ final class FilesDatabaseManagerTests: NextcloudFileProviderKitTestCase {
978978
XCTAssertTrue(updatedMetadata.downloaded, "downloaded should be retained when keepExistingDownloadState is true")
979979
}
980980

981-
func testKeepDownloadedNotSetForNewMetadata() throws {
981+
func testKeepDownloadedNotInheritedWhenNoPinnedAncestor() throws {
982982
let account = Account(user: "test", id: "t", serverUrl: "https://example.com", password: "")
983983

984-
// Create completely new metadata (not existing in database)
984+
// Create completely new metadata (not existing in database) with no
985+
// pinned ancestor: neither a parent row nor a pinned root container.
985986
var newMetadata = SendableItemMetadata(ocId: "new-item", fileName: "new.txt", account: account)
986987
newMetadata.etag = "initial-etag"
987-
newMetadata.keepDownloaded = false // Should remain false for new items
988+
newMetadata.keepDownloaded = false
988989

989990
let result = Self.dbManager.depth1ReadUpdateItemMetadatas(
990991
account: account.ncKitAccount,
@@ -997,11 +998,94 @@ final class FilesDatabaseManagerTests: NextcloudFileProviderKitTestCase {
997998
XCTAssertEqual(result.updatedMetadatas?.isEmpty, true, "Should not update any metadata")
998999

9991000
let createdMetadata = try XCTUnwrap(result.newMetadatas?.first)
1000-
XCTAssertFalse(createdMetadata.keepDownloaded, "keepDownloaded should remain false for new items")
1001+
XCTAssertFalse(createdMetadata.keepDownloaded, "keepDownloaded should remain false when no pinned ancestor exists")
10011002

10021003
// Verify in database
10031004
let dbMetadata = try XCTUnwrap(Self.dbManager.itemMetadata(ocId: "new-item"))
1004-
XCTAssertFalse(dbMetadata.keepDownloaded, "keepDownloaded should be false in database for new items")
1005+
XCTAssertFalse(dbMetadata.keepDownloaded, "keepDownloaded should be false in database when no pinned ancestor exists")
1006+
}
1007+
1008+
func testKeepDownloadedInheritedFromPinnedParentForNewMetadata() throws {
1009+
let account = Account(user: "test", id: "t", serverUrl: "https://example.com", password: "")
1010+
1011+
// Parent folder under the user's home, pinned.
1012+
var parentFolder = SendableItemMetadata(ocId: "pinned-parent", fileName: "pinned", account: account)
1013+
parentFolder.directory = true
1014+
parentFolder.uploaded = true
1015+
parentFolder.keepDownloaded = true
1016+
parentFolder.etag = "parent-etag"
1017+
1018+
Self.dbManager.addItemMetadata(parentFolder)
1019+
1020+
// New child appearing remotely inside the pinned folder. Its
1021+
// `serverUrl` is the parent's full path. `keepDownloaded` starts
1022+
// `false`, mirroring what `NKFile.toItemMetadata()` produces.
1023+
let parentRemotePath = account.davFilesUrl + "/pinned"
1024+
var newChild = SendableItemMetadata(ocId: "fresh-child", fileName: "child.txt", account: account)
1025+
newChild.serverUrl = parentRemotePath
1026+
newChild.etag = "child-etag"
1027+
newChild.keepDownloaded = false
1028+
1029+
let result = Self.dbManager.depth1ReadUpdateItemMetadatas(
1030+
account: account.ncKitAccount,
1031+
serverUrl: parentRemotePath,
1032+
updatedMetadatas: [parentFolder, newChild],
1033+
keepExistingDownloadState: true
1034+
)
1035+
1036+
let createdChild = try XCTUnwrap(result.newMetadatas?.first(where: { $0.ocId == "fresh-child" }))
1037+
XCTAssertTrue(createdChild.keepDownloaded, "keepDownloaded should be inherited from pinned parent for new items")
1038+
1039+
let dbChild = try XCTUnwrap(Self.dbManager.itemMetadata(ocId: "fresh-child"))
1040+
XCTAssertTrue(dbChild.keepDownloaded, "Inherited keepDownloaded should be persisted to the database")
1041+
}
1042+
1043+
func testAddItemMetadataPreservingLocalState_InheritsPinnedParentForNewItem() throws {
1044+
let account = Account(user: "test", id: "t", serverUrl: "https://example.com", password: "")
1045+
1046+
// Pinned parent folder.
1047+
var parentFolder = SendableItemMetadata(ocId: "preserving-parent", fileName: "pinned", account: account)
1048+
parentFolder.directory = true
1049+
parentFolder.uploaded = true
1050+
parentFolder.keepDownloaded = true
1051+
1052+
Self.dbManager.addItemMetadata(parentFolder)
1053+
1054+
// Fresh child inside the pinned folder, exactly as it would arrive
1055+
// from `NKFile.toItemMetadata()` (keepDownloaded = false).
1056+
var freshChild = SendableItemMetadata(ocId: "preserving-child", fileName: "child.txt", account: account)
1057+
freshChild.serverUrl = account.davFilesUrl + "/pinned"
1058+
freshChild.keepDownloaded = false
1059+
1060+
let written = Self.dbManager.addItemMetadataPreservingLocalState(freshChild)
1061+
1062+
XCTAssertTrue(written.keepDownloaded, "keepDownloaded should be inherited from pinned parent on first insert")
1063+
1064+
let dbChild = try XCTUnwrap(Self.dbManager.itemMetadata(ocId: "preserving-child"))
1065+
XCTAssertTrue(dbChild.keepDownloaded, "Inherited keepDownloaded should be persisted to the database")
1066+
}
1067+
1068+
func testAddItemMetadataPreservingLocalState_DoesNotInheritFromUnpinnedParent() throws {
1069+
let account = Account(user: "test", id: "t", serverUrl: "https://example.com", password: "")
1070+
1071+
// Unpinned parent folder.
1072+
var parentFolder = SendableItemMetadata(ocId: "unpinned-parent", fileName: "regular", account: account)
1073+
parentFolder.directory = true
1074+
parentFolder.uploaded = true
1075+
parentFolder.keepDownloaded = false
1076+
1077+
Self.dbManager.addItemMetadata(parentFolder)
1078+
1079+
var freshChild = SendableItemMetadata(ocId: "unpinned-child", fileName: "child.txt", account: account)
1080+
freshChild.serverUrl = account.davFilesUrl + "/regular"
1081+
freshChild.keepDownloaded = false
1082+
1083+
let written = Self.dbManager.addItemMetadataPreservingLocalState(freshChild)
1084+
1085+
XCTAssertFalse(written.keepDownloaded, "keepDownloaded should remain false when parent is not pinned")
1086+
1087+
let dbChild = try XCTUnwrap(Self.dbManager.itemMetadata(ocId: "unpinned-child"))
1088+
XCTAssertFalse(dbChild.keepDownloaded, "Unpinned parent must not flip the child's keepDownloaded in the database")
10051089
}
10061090

10071091
func testKeepDownloadedRetainedWithMultipleItems() throws {

0 commit comments

Comments
 (0)