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
41 changes: 35 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,56 @@
- SPDX-License-Identifier: GPL-2.0-or-later
-->
# Agents.md
This `AGENTS.md` file provides guidelines for OpenAI Codex and other AI agents interacting with this codebase, including which directories are safe to read from or write to.

You are an experienced engineer specialized on C++ and Qt and familiar with the platform-specific details of Windows, macOS and Linux.

## Your Role

- You implement features and fix bugs.
- Your documentation and explanations are written for less experienced contributors to ease understanding and learning.
- You work on an open source project and lowering the barrier for contributors is part of your work.

## Project Overview

The Nextcloud Desktop Client is a tool to synchronize files from Nextcloud Server with your computer.
Qt, C++, CMake and KDE Craft are the key technologies used for building the app on Windows, macOS and Linux.
Beyond that, there are platform-specific extensions of the multi-platform app in the `./shell_integration` directory.

## Project Structure: AI Agent Handling Guidelines

| Directory | Description | Agent Action |
|-----------------|-----------------------------------------------------|----------------------|
| `/translations` | Translation files from Transifex. | Do not modify |
| `./admin/osx/mac-crafter` | Build tool for macOS | Ignore unless the build process must be updated |
| `./shell_integration/MacOSX/NextcloudIntegration` | Xcode project for macOS app extensions | Look here first for changes in context of the file provider extension |
| `./translations` | Translation files from Transifex. | Do not modify |

## General Guidance

Every new file needs to get a SPDX header in the first rows according to this template.
The year needs to be adjusted accordingly. The commenting signs need to be used depending on the file type.
```
SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
The year in the first line must be replaced with the year when the file is created (for example, 2026 for files first added in 2026).
The commenting signs need to be used depending on the file type.

```plaintext
SPDX-FileCopyrightText: <YEAR> Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: GPL-2.0-or-later
```

## Commit & PR Guidelines
## Commit and Pull Request Guidelines

- **Commits**: Follow Conventional Commits format. Use `feat: ...`, `fix: ...`, or `refactor: ...` as appropriate in the commit message prefix.
- Include a short summary of what changed. *Example:* `fix: prevent crash on empty todo title`.
- **Pull Request**: When the agent creates a PR, it should include a description summarizing the changes and why they were made. If a GitHub issue exists, reference it (e.g., “Closes #123”).

## macOS Specifics

The following details are important when working on the desktop client on macOS.

- Latest stable Xcode available is required to be installed in the development environment.
- There is a self-contained and independent build tool called mac-crafter in `./admin/osx/mac-crafter` implemented as a Swift package which builds as an executable.
- To enable a macOS app build, the file `./shell_integration/MacOSX/NextcloudIntegration/NextcloudDev/Build.xcconfig` must be created if not existent already and it must contain the Xcode build setting `CODE_SIGN_IDENTITY=Apple Development`.
- To verify that the project builds successfully on macOS, mac-crafter can be run in its own directory with these arguments: `swift run mac-crafter --build-path=DerivedData --product-path=/Applications --build-type=Debug --dev --disable-auto-updater --build-file-provider-module`
- The macOS app includes a FinderSync extension.
- The macOS app can be built to include a file provider extension and file provider UI extension.
- The macOS extensions bundled with the main app are built in the Xcode project in `./shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj`. The build system later copies the built extension bundles into the main app bundle on its own. The Xcode project does not build the main app.
- The main app manages file provider domains and the communication with them via XPC in source code files located in `./src/gui/macOS` and usually are written in Objective-C++ (implementation files with `.mm` extension, sometimes having a `_mac` suffix in their name while their corresponding header files do not). The PIMPL pattern is an established convention here.
- When writing code in Swift, respect strict concurrency rules and Swift 6 compatibility.
7 changes: 7 additions & 0 deletions shell_integration/MacOSX/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ if(APPLE)
endif()

if (BUILD_OWNCLOUD_OSX_BUNDLE)
# Set debug entitlements conditionally based on build type
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DEBUG_ENTITLEMENTS "\t<key>com.apple.security.get-task-allow</key>\n\t<true/>")
else()
set(DEBUG_ENTITLEMENTS "")
endif()

set(OSX_PLUGINS_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${XCODE_TARGET_CONFIGURATION})
set(OSX_PLUGINS_INSTALL_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/PlugIns)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
<true/>
<key>com.apple.security.network.server</key>
<true/>
@DEBUG_ENTITLEMENTS@
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
<true/>
<key>com.apple.security.network.server</key>
<true/>
@DEBUG_ENTITLEMENTS@
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
<array>
<string>@DEVELOPMENT_TEAM@.@APPLICATION_REV_DOMAIN@</string>
</array>
@DEBUG_ENTITLEMENTS@
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import Foundation

public class IgnoredFilesMatcher {
private let logger: FileProviderLogger
private let regexes: [NSRegularExpression]

private static func patternToRegex(_ pattern: String, wildcardsMatchSlash: Bool) -> String {
Expand Down Expand Up @@ -42,7 +43,10 @@ public class IgnoredFilesMatcher {
return hasSlash ? "^\(regex)$" : "(^|/)" + regex + "$"
}

public init(ignoreList: [String], wildcardsMatchSlash: Bool = false) {
public init(ignoreList: [String], wildcardsMatchSlash: Bool = false, log: any FileProviderLogging) {
logger = FileProviderLogger(category: "IgnoredFilesMatcher", log: log)
logger.debug("Initializing with ignore list:\n\n\(ignoreList.map { "- \"\($0)\"" }.joined(separator: "\n"))")

regexes = ignoreList
.map { Self.patternToRegex($0, wildcardsMatchSlash: wildcardsMatchSlash) }
.compactMap { try? NSRegularExpression(pattern: $0, options: [.caseInsensitive]) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

@testable import NextcloudFileProviderKit
import NextcloudFileProviderKitMocks
import Testing

struct IgnoredFilesMatcherTests {
Expand All @@ -14,7 +15,7 @@ struct IgnoredFilesMatcherTests {
"deep/**"
]

let matcher = IgnoredFilesMatcher(ignoreList: patterns)
let matcher = IgnoredFilesMatcher(ignoreList: patterns, log: FileProviderLogMock())

#expect(matcher.isExcluded("foo.tmp"))
#expect(matcher.isExcluded("a/b/c/hello.tmp"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ final class ItemCreateTests: NextcloudFileProviderKitTestCase {
}

func testCreateDoesNotPropagateIgnoredFile() async throws {
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["*.tmp", "/build/"])
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["*.tmp", "/build/"], log: FileProviderLogMock())
let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem)

// We'll create a file that matches the ignored pattern
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ final class ItemDeleteTests: NextcloudFileProviderKitTestCase {
}

func testDeleteDoesNotPropagateIgnoredFile() async throws {
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["*.log", "/tmp/"])
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["*.log", "/tmp/"], log: FileProviderLogMock())
let metadata = SendableItemMetadata(
ocId: "ignored-file-id",
fileName: "debug.log",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1324,7 +1324,7 @@ final class ItemModifyTests: NextcloudFileProviderKitTestCase {
}

func testModifyDoesNotPropagateIgnoredFile() async throws {
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["*.bak", "/logs/"])
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["*.bak", "/logs/"], log: FileProviderLogMock())
let metadata = SendableItemMetadata(
ocId: "ignored-modify-id",
fileName: "error.bak",
Expand Down Expand Up @@ -1355,7 +1355,7 @@ final class ItemModifyTests: NextcloudFileProviderKitTestCase {

func testModifyCreatesFileThatWasPreviouslyIgnoredWithContentsUrlProvided() async throws {
let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem)
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["/logs/"])
let ignoredMatcher = IgnoredFilesMatcher(ignoreList: ["/logs/"], log: FileProviderLogMock())

let tempFileName = UUID().uuidString
let tempUrl = FileManager.default.temporaryDirectory.appendingPathComponent(tempFileName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#ifndef FileProviderExt_Bridging_Header_h
#define FileProviderExt_Bridging_Header_h

#import "Services/AppProtocol.h"
#import "Services/ClientCommunicationProtocol.h"

#endif /* FileProviderExt_Bridging_Header_h */
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-2.0-or-later

import FileProvider
import NextcloudFileProviderKit

extension FileProviderExtension: ChangeNotificationInterface {
func notifyChange() {
guard let fpManager = NSFileProviderManager(for: domain) else {
logger.error("Could not get file provider manager for domain \(self.domain.displayName), cannot notify changes")
return
}

fpManager.signalEnumerator(for: .workingSet) { error in
if error != nil {
self.logger.error("Error signalling enumerator for working set, received error: \(error!.localizedDescription)")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
// SPDX-License-Identifier: GPL-2.0-or-later

import NextcloudFileProviderKit

extension FileProviderExtension: ClientCommunicationProtocol {
func getFileProviderDomainIdentifier(completionHandler: @escaping (String?, Error?) -> Void) {
logger.debug("Returning file provider domain identifier.", [.domain: domain.identifier.rawValue])
completionHandler(domain.identifier.rawValue, nil)
}

func configureAccount(withUser user: String, userId: String, serverUrl: String, password: String, userAgent: String) {
logger.info("Received account to configure.")
setupDomainAccount(user: user, userId: userId, serverUrl: serverUrl, password: password, userAgent: userAgent)
}

func removeAccountConfig() {
logger.info("Received request to remove account data.")
dbManager = nil
ncAccount = nil
}

func setIgnoreList(_ ignoreList: [String]) {
ignoredFiles = IgnoredFilesMatcher(ignoreList: ignoreList, log: log)
logger.info("Ignore list updated.")
}
}
Loading
Loading