Skip to content

Commit 24ddef2

Browse files
authored
Merge pull request #9971 from nextcloud/i2h3/fix/9827-bundles
Exclude Bundles and Packages from Synchronization
2 parents d70d58e + a88418c commit 24ddef2

52 files changed

Lines changed: 775 additions & 1482 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/nextcloudfileproviderkit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ jobs:
3939
xcode-version: latest-stable
4040

4141
- name: Run Tests
42-
run: xcodebuild clean build test -scheme NextcloudFileProviderKit -destination "platform=macOS,name=My Mac"
42+
run: xcodebuild clean build test -scheme NextcloudFileProviderKit-Package -destination "platform=macOS,name=My Mac"

REUSE.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
6060
SPDX-License-Identifier = "GPL-2.0-or-later"
6161

6262
[[annotations]]
63-
path = ["shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Localizable.xcstrings", "shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Localizable.xcstrings", "shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Assets.xcassets/*", "shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Assets.xcassets/FileProviderDomainSymbol.symbolset/*", "shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Assets.xcassets/*", "shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Assets.xcassets/FileProviderDomainSymbol.symbolset/*"]
63+
path = ["shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Localizable.xcstrings", "shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Assets.xcassets/*", "shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Assets.xcassets/FileProviderDomainSymbol.symbolset/*", "shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Assets.xcassets/*", "shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/Assets.xcassets/FileProviderDomainSymbol.symbolset/*"]
6464
precedence = "aggregate"
6565
SPDX-FileCopyrightText = "2015 ownCloud GmbH, 2022 Nextcloud GmbH and Nextcloud contributors"
6666
SPDX-License-Identifier = "GPL-2.0-or-later"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
2+
# SPDX-License-Identifier: GPL-2.0-or-later
3+
4+
[main]
5+
host = https://app.transifex.com
6+
# Complete mapping from language codes on Transifex to the ones configured in the Xcode project for those where the codes diverge.
7+
lang_map = bg_BG: bg, bn_BD: bn, cs_CZ: cs, cy_GB: cy, es_AR: es-AR, es_DO: es-DO, es_EC: es-EC, es_SV: es-SV, es_GT: es-GT, es_HN: es-HN, es_419: es-419, es_MX: es-MX, es_NI: es-NI, es_PA: es-PA, es_PY: es-PY, es_PE: es-PE, es_PR: es-PR, es_UY: es-UY, et_EE: et, fi_FI: fi, hi_IN: hi, hu_HU: hu, ja_JP: ja, ka_GE: ka, lt_LT: lt, ms_MY: ms, nb_NO: nb-NO, nn_NO: nn-NO, pt_BR: pt-BR, pt_PT: pt-PT, sk_SK: sk, th_TH: th, ur_PK: ur, zh_CN: zh-Hans, zh_HK: zh-HK, zh_TW: zh-Hant-TW, zu_ZA: zu
8+
9+
[o:nextcloud:p:nextcloud:r:client-fileprovider]
10+
file_filter = Sources/NextcloudFileProviderKit/Resources/Localizable.xcstrings
11+
source_file = Sources/NextcloudFileProviderKit/Resources/Localizable.xcstrings
12+
source_lang = en
13+
type = XCSTRINGS

shell_integration/MacOSX/NextcloudFileProviderKit/Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ let package = Package(
1515
.library(
1616
name: "NextcloudFileProviderKit",
1717
targets: ["NextcloudFileProviderKit"]
18+
),
19+
.library(
20+
name: "NextcloudFileProviderXPC",
21+
targets: ["NextcloudFileProviderXPC"]
1822
)
1923
],
2024
dependencies: [
@@ -27,12 +31,21 @@ let package = Package(
2731
targets: [
2832
// Targets are the basic building blocks of a package, defining a module or a test suite.
2933
// Targets can depend on other targets in this package and products from dependencies.
34+
.target(
35+
name: "NextcloudFileProviderXPC",
36+
path: "Sources/NextcloudFileProviderXPC",
37+
publicHeadersPath: "include"
38+
),
3039
.target(
3140
name: "NextcloudFileProviderKit",
3241
dependencies: [
42+
"NextcloudFileProviderXPC",
3343
.product(name: "NextcloudCapabilitiesKit", package: "NextcloudCapabilitiesKit"),
3444
.product(name: "NextcloudKit", package: "NextcloudKit"),
3545
.product(name: "RealmSwift", package: "realm-swift")
46+
],
47+
resources: [
48+
.process("Resources")
3649
]
3750
),
3851
.target(

shell_integration/MacOSX/NextcloudFileProviderKit/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,55 @@ You can run the following command in the root of the package repository clone:
4949
swift package plugin --allow-writing-to-package-directory swiftformat --verbose --cache ignore
5050
```
5151

52+
### Localization
53+
54+
Transifex is used for localization.
55+
These localizations are excluded from our usual and automated translation flow due to how Transifex synchronizes Xcode string catalogs and the danger of data loss.
56+
To pull updated localizations from Transifex into the Xcode project manually, follow the steps below.
57+
58+
#### Configuration
59+
60+
The dedicated [`.tx/config`](.tx/config) file is used.
61+
62+
### Pulling Translations
63+
64+
Run this in the "NextcloudFileProviderKit" package folder of your repository clone:
65+
66+
```sh
67+
tx pull --force --all --mode=translator
68+
```
69+
70+
The `translator` mode is important here and for later, so unreviewed strings are included and not accidentally deleted.
71+
See [the official Transifex documentation on Xcode string catalogs and download modes](https://help.transifex.com/en/articles/9459174-xcode-strings-catalogs-xcstrings#h_786f60d73b).
72+
Do not commit the changes string catalogs, they need to be processed first.
73+
74+
#### Sanitize Translations
75+
76+
Transifex returns empty strings for keys with untranslated localizations.
77+
To remove them, we use the Swift command-line utility [TransifexStringCatalogSanitizer](../TransifexStringCatalogSanitizer/).
78+
See its dedicated README for usage instructions.
79+
Use it for all updated Xcode string catalogs.
80+
81+
#### Summary
82+
83+
```sh
84+
tx pull --force --all --mode=translator
85+
swift run --package-path=../TransifexStringCatalogSanitizer TransifexStringCatalogSanitizer ./Sources/NextcloudFileProviderKit/Resources/Localizable.xcstrings
86+
```
87+
88+
### Pushing Translations
89+
90+
**Follow this section carefully to the end before performing any steps of it.**
91+
The way Transifex handles Xcode string catalogs poses a high risk of accidentally deleting already available and finished translations on Transifex because pushing an Xcode string catalog overwrites the online state with the catalog as it is.
92+
This means that changes on Transifex must be integrated locally first to avoid data loss, before the then updated local Xcode string catalog can be pushed to Transifex.
93+
94+
1. Perform the steps in the previous section about pulling translations.
95+
2. Build the extensions in Xcode. This causes the compiler to update the string catalogs based on the current source code by automatically recognizing localizable texts. As of writing, the "desktopclient" scheme is a good choice because it builds both file provider extensions as dependencies.
96+
3. Run the `TransifexStringCatalogSanitizer` over both string catalogs as in the previous section.
97+
4. Inspect the changes in the string catalogs in a Git diff or whatever tool you use for that task. Verify the plausibility of each change.
98+
5. Run `tx push` in the "NextcloudIntegration" directory.
99+
6. Check Transifex to have received the new keys and deleted the obsolete ones.
100+
52101
## License
53102

54103
This project is licensed under the LGPLv3 License. See the [LICENSE](LICENSE) file for more details.

shell_integration/MacOSX/NextcloudFileProviderKit/REUSE.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ path = [
2121
".swiftformat",
2222
".swift-version",
2323
"Package.swift",
24-
"Sources/NextcloudFileProviderKit/Documentation.docc/theme-settings.json"
24+
"Sources/NextcloudFileProviderKit/Documentation.docc/theme-settings.json",
25+
"Sources/NextcloudFileProviderKit/Resources/Localizable.xcstrings"
2526
]
2627
precedence = "aggregate"
2728
SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"

shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ChangeNotificationInterface.swift renamed to shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Extension/FileProviderExtension+ChangeNotificationInterface.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
// SPDX-License-Identifier: GPL-2.0-or-later
33

44
import FileProvider
5-
import NextcloudFileProviderKit
65

76
extension FileProviderExtension: ChangeNotificationInterface {
8-
func notifyChange() {
7+
public func notifyChange() {
98
guard let fpManager = NSFileProviderManager(for: domain) else {
10-
logger.error("Could not get file provider manager for domain \(self.domain.displayName), cannot notify changes")
9+
logger.error("Could not get file provider manager for domain \(domain.displayName), cannot notify changes")
1110
return
1211
}
1312

shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientCommunicationProtocol.swift renamed to shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Extension/FileProviderExtension+ClientCommunicationProtocol.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// SPDX-License-Identifier: GPL-2.0-or-later
33

44
import FileProvider
5-
import NextcloudFileProviderKit
5+
import NextcloudFileProviderXPC
66

77
extension FileProviderExtension: ClientCommunicationProtocol {
8-
func hasDirtyUserData(completionHandler: ((Bool) -> Void)?) {
8+
public func hasDirtyUserData(completionHandler: ((Bool) -> Void)?) {
99
logger.debug("Dirty user data check is requested.")
1010

1111
guard let completionHandler else {
@@ -25,23 +25,23 @@ extension FileProviderExtension: ClientCommunicationProtocol {
2525
enumerator.enumerateItems(for: observer, startingAt: page)
2626
}
2727

28-
func getFileProviderDomainIdentifier(completionHandler: @escaping (String?, Error?) -> Void) {
28+
public func getFileProviderDomainIdentifier(completionHandler: @escaping (String?, Error?) -> Void) {
2929
logger.debug("Returning file provider domain identifier.", [.domain: domain.identifier.rawValue])
3030
completionHandler(domain.identifier.rawValue, nil)
3131
}
3232

33-
func configureAccount(withUser user: String, userId: String, serverUrl: String, password: String, userAgent: String) {
33+
public func configureAccount(withUser user: String, userId: String, serverUrl: String, password: String, userAgent: String) {
3434
logger.info("Received account to configure.")
3535
setupDomainAccount(user: user, userId: userId, serverUrl: serverUrl, password: password, userAgent: userAgent)
3636
}
3737

38-
func removeAccountConfig() {
38+
public func removeAccountConfig() {
3939
logger.info("Received request to remove account data.")
4040
dbManager = nil
4141
ncAccount = nil
4242
}
4343

44-
func setIgnoreList(_ ignoreList: [String]) {
44+
public func setIgnoreList(_ ignoreList: [String]) {
4545
ignoredFiles = IgnoredFilesMatcher(ignoreList: ignoreList, log: log)
4646
logger.info("Ignore list updated.")
4747
}

shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+CustomActions.swift renamed to shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Extension/FileProviderExtension+CustomActions.swift

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,73 @@
11
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
22
// SPDX-License-Identifier: GPL-2.0-or-later
33

4-
import FileProvider
5-
import NextcloudFileProviderKit
4+
@preconcurrency import FileProvider
65
import OSLog
76

87
extension FileProviderExtension: NSFileProviderCustomAction {
9-
func performAction(
8+
public func performAction(
109
identifier actionIdentifier: NSFileProviderExtensionActionIdentifier,
1110
onItemsWithIdentifiers itemIdentifiers: [NSFileProviderItemIdentifier],
12-
completionHandler: @escaping ((any Error)?) -> Void
11+
completionHandler: @Sendable @escaping ((any Error)?) -> Void
1312
) -> Progress {
1413
switch actionIdentifier.rawValue {
15-
case "com.nextcloud.desktopclient.FileProviderExt.FileActionsAction":
16-
guard let itemIdentifier = itemIdentifiers.first else {
17-
logger.error("Failed to get first item identifier for file actions action.")
18-
completionHandler(NSFileProviderError(.noSuchItem))
19-
return Progress()
20-
}
21-
22-
guard let dbManager else {
23-
logger.error("Cannot fetch metadata for item file actions due to database manager not being available.", [.item: itemIdentifier])
24-
completionHandler(NSFileProviderError(.cannotSynchronize))
25-
return Progress()
26-
}
27-
28-
Task {
29-
guard let userVisibleURL = try await manager?.getUserVisibleURL(for: itemIdentifier) else {
30-
logger.error("Failed to get user-visible URL for item.", [.item: itemIdentifier])
14+
case "com.nextcloud.desktopclient.FileProviderExt.FileActionsAction":
15+
guard let itemIdentifier = itemIdentifiers.first else {
16+
logger.error("Failed to get first item identifier for file actions action.")
3117
completionHandler(NSFileProviderError(.noSuchItem))
32-
return
18+
return Progress()
3319
}
3420

35-
guard let metadata = dbManager.itemMetadata(itemIdentifier) else {
36-
logger.error("Failed to get metadata for item.", [.item: itemIdentifier])
21+
guard let dbManager else {
22+
logger.error("Cannot fetch metadata for item file actions due to database manager not being available.", [.item: itemIdentifier])
3723
completionHandler(NSFileProviderError(.cannotSynchronize))
38-
return
24+
return Progress()
3925
}
4026

41-
let path = userVisibleURL.path
42-
let domainIdentifier = domain.identifier.rawValue
43-
logger.info("Telling main app to present file actions.", [.item: path, .domain: domainIdentifier])
44-
app?.presentFileActions(metadata.ocId, path: path, remoteItemPath: metadata.path, withDomainIdentifier: domainIdentifier)
45-
completionHandler(nil)
46-
}
27+
Task {
28+
guard let userVisibleURL = try await manager?.getUserVisibleURL(for: itemIdentifier) else {
29+
logger.error("Failed to get user-visible URL for item.", [.item: itemIdentifier])
30+
completionHandler(NSFileProviderError(.noSuchItem))
31+
return
32+
}
4733

48-
return Progress()
49-
case "com.nextcloud.desktopclient.FileProviderExt.KeepDownloadedAction":
50-
return performKeepDownloadedAction(
51-
keepDownloaded: true,
52-
onItemsWithIdentifiers: itemIdentifiers,
53-
completionHandler: completionHandler
54-
)
55-
case "com.nextcloud.desktopclient.FileProviderExt.AutoEvictAction":
56-
return performKeepDownloadedAction(
57-
keepDownloaded: false,
58-
onItemsWithIdentifiers: itemIdentifiers,
59-
completionHandler: completionHandler
60-
)
61-
default:
62-
logger.error("Unsupported action: \(actionIdentifier.rawValue)")
63-
completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError))
64-
return Progress()
34+
guard let metadata = dbManager.itemMetadata(itemIdentifier) else {
35+
logger.error("Failed to get metadata for item.", [.item: itemIdentifier])
36+
completionHandler(NSFileProviderError(.cannotSynchronize))
37+
return
38+
}
39+
40+
let path = userVisibleURL.path
41+
let domainIdentifier = domain.identifier.rawValue
42+
logger.info("Telling main app to present file actions.", [.item: path, .domain: domainIdentifier])
43+
app?.presentFileActions(metadata.ocId, path: path, remoteItemPath: metadata.path, withDomainIdentifier: domainIdentifier)
44+
completionHandler(nil)
45+
}
46+
47+
return Progress()
48+
case "com.nextcloud.desktopclient.FileProviderExt.KeepDownloadedAction":
49+
return performKeepDownloadedAction(
50+
keepDownloaded: true,
51+
onItemsWithIdentifiers: itemIdentifiers,
52+
completionHandler: completionHandler
53+
)
54+
case "com.nextcloud.desktopclient.FileProviderExt.AutoEvictAction":
55+
return performKeepDownloadedAction(
56+
keepDownloaded: false,
57+
onItemsWithIdentifiers: itemIdentifiers,
58+
completionHandler: completionHandler
59+
)
60+
default:
61+
logger.error("Unsupported action: \(actionIdentifier.rawValue)")
62+
completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError))
63+
return Progress()
6564
}
6665
}
6766

6867
private func performKeepDownloadedAction(
6968
keepDownloaded: Bool,
7069
onItemsWithIdentifiers itemIdentifiers: [NSFileProviderItemIdentifier],
71-
completionHandler: @escaping ((any Error)?) -> Void
70+
completionHandler: @Sendable @escaping ((any Error)?) -> Void
7271
) -> Progress {
7372
guard let ncAccount else {
7473
logger.error("Not setting keep offline for items, account not set up yet.")
@@ -123,7 +122,7 @@ extension FileProviderExtension: NSFileProviderCustomAction {
123122
}
124123
logger.info("All items successfully processed for keepDownloaded=\(keepDownloaded)")
125124
completionHandler(nil)
126-
} catch let error {
125+
} catch {
127126
logger.error("Error during keepDownloaded=\(keepDownloaded) action: \(error.localizedDescription)")
128127
completionHandler(error)
129128
}

shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+NSFileProviderServiceSource.swift renamed to shell_integration/MacOSX/NextcloudFileProviderKit/Sources/NextcloudFileProviderKit/Extension/FileProviderExtension+NSFileProviderServiceSource.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import FileProvider
55

66
extension FileProviderExtension: NSFileProviderServiceSource {
7-
func makeListenerEndpoint() throws -> NSXPCListenerEndpoint {
7+
public func makeListenerEndpoint() throws -> NSXPCListenerEndpoint {
88
logger.info("Making listener endpoint...")
99

1010
// Invalidate existing listener.

0 commit comments

Comments
 (0)