Skip to content

Commit

Permalink
Merge pull request #6324 from nextcloud/work/fileprovider-xpc
Browse files Browse the repository at this point in the history
Rewrite communication between client and File Provider extensions using XPC
  • Loading branch information
claucambra authored Feb 19, 2024
2 parents 9e75ca8 + 3334b4e commit 8ab52b8
Show file tree
Hide file tree
Showing 20 changed files with 816 additions and 112 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/

#ifndef FileProviderExt_Bridging_Header_h
#define FileProviderExt_Bridging_Header_h

#import "Services/ClientCommunicationProtocol.h"

#endif /* FileProviderExt_Bridging_Header_h */
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,38 @@ import NCDesktopClientSocketKit
import NextcloudKit
import OSLog

extension FileProviderExtension {
func sendFileProviderDomainIdentifier() {
extension FileProviderExtension: NSFileProviderServicing {
/*
This FileProviderExtension extension contains everything needed to communicate with the client.
We have two systems for communicating between the extensions and the client.
Apple's XPC based File Provider APIs let us easily communicate client -> extension.
This is what ClientCommunicationService is for.
We also use sockets, because the File Provider XPC system does not let us easily talk from
extension->client.
We need this because the extension needs to be able to request account details. We can't
reliably do this via XPC because the extensions get torn down by the system, out of the control
of the app, and we can receive nil/no services from NSFileProviderManager. Once this is done
then XPC works ok.
*/
func supportedServiceSources(
for itemIdentifier: NSFileProviderItemIdentifier,
completionHandler: @escaping ([NSFileProviderServiceSource]?, Error?) -> Void
) -> Progress {
Logger.desktopClientConnection.debug("Serving supported service sources")
let clientCommService = ClientCommunicationService(fpExtension: self)
let services = [clientCommService]
completionHandler(services, nil)
let progress = Progress()
progress.cancellationHandler = {
let error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError)
completionHandler(nil, error)
}
return progress
}

@objc func sendFileProviderDomainIdentifier() {
let command = "FILE_PROVIDER_DOMAIN_IDENTIFIER_REQUEST_REPLY"
let argument = domain.identifier.rawValue
let message = command + ":" + argument + "\n"
Expand Down Expand Up @@ -57,16 +87,18 @@ extension FileProviderExtension {
}
}

func setupDomainAccount(user: String, serverUrl: String, password: String) {
ncAccount = NextcloudAccount(user: user, serverUrl: serverUrl, password: password)
@objc func setupDomainAccount(user: String, serverUrl: String, password: String) {
let newNcAccount = NextcloudAccount(user: user, serverUrl: serverUrl, password: password)
guard newNcAccount != ncAccount else { return }
ncAccount = newNcAccount
ncKit.setup(
user: ncAccount!.username,
userId: ncAccount!.username,
password: ncAccount!.password,
urlBase: ncAccount!.serverUrl,
userAgent: "Nextcloud-macOS/FileProviderExt",
nextcloudVersion: 25,
delegate: nil) // TODO: add delegate methods for self
delegate: nil) // TODO: add delegate methods for self

Logger.fileProviderExtension.info(
"Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)"
Expand All @@ -75,7 +107,7 @@ extension FileProviderExtension {
signalEnumeratorAfterAccountSetup()
}

func removeAccountConfig() {
@objc func removeAccountConfig() {
Logger.fileProviderExtension.info(
"Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NCDesktopClientSocketKit
import NextcloudKit
import OSLog

class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate {
@objc class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate {
let domain: NSFileProviderDomain
let ncKit = NextcloudKit()
let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import FileProvider
import Foundation

class NextcloudAccount: NSObject {
struct NextcloudAccount: Equatable {
static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/"
let username, password, ncKitAccount, serverUrl, davFilesUrl: String

Expand All @@ -25,7 +25,5 @@ class NextcloudAccount: NSObject {
ncKitAccount = user + " " + serverUrl
self.serverUrl = serverUrl
davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user

super.init()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/

#ifndef ClientCommunicationProtocol_h
#define ClientCommunicationProtocol_h

#import <Foundation/Foundation.h>

@protocol ClientCommunicationProtocol

- (void)getExtensionAccountIdWithCompletionHandler:(void(^)(NSString *extensionAccountId, NSError *error))completionHandler;
- (void)configureAccountWithUser:(NSString *)user
serverUrl:(NSString *)serverUrl
password:(NSString *)password;
- (void)removeAccountConfig;

@end

#endif /* ClientCommunicationProtocol_h */
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/

import Foundation
import FileProvider
import OSLog

class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, ClientCommunicationProtocol {
let listener = NSXPCListener.anonymous()
let serviceName = NSFileProviderServiceName("com.nextcloud.desktopclient.ClientCommunicationService")
let fpExtension: FileProviderExtension

init(fpExtension: FileProviderExtension) {
Logger.desktopClientConnection.debug("Instantiating client communication service")
self.fpExtension = fpExtension
super.init()
}

func makeListenerEndpoint() throws -> NSXPCListenerEndpoint {
listener.delegate = self
listener.resume()
return listener.endpoint
}

func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: ClientCommunicationProtocol.self)
newConnection.exportedObject = self
newConnection.resume()
return true
}

//MARK: - Client Communication Protocol methods

func getExtensionAccountId(completionHandler: @escaping (String?, Error?) -> Void) {
let accountUserId = self.fpExtension.domain.identifier.rawValue
Logger.desktopClientConnection.info("Sending extension account ID \(accountUserId, privacy: .public)")
completionHandler(accountUserId, nil)
}

func configureAccount(withUser user: String,
serverUrl: String,
password: String) {
Logger.desktopClientConnection.info("Received configure account information over client communication service")
self.fpExtension.setupDomainAccount(user: user,
serverUrl: serverUrl,
password: password)
}

func removeAccountConfig() {
self.fpExtension.removeAccountConfig()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand All @@ -15,6 +15,7 @@
5318AD9529BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */; };
5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */; };
5318AD9929BF58D000CBB71C /* NKError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */; };
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */; };
5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */; };
5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */; };
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */; };
Expand Down Expand Up @@ -145,6 +146,9 @@
5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudLocalFileMetadataTable.swift; sourceTree = "<group>"; };
5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderMaterialisedEnumerationObserver.swift; sourceTree = "<group>"; };
5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NKError+Extensions.swift"; sourceTree = "<group>"; };
5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClientCommunicationProtocol.h; sourceTree = "<group>"; };
5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCommunicationService.swift; sourceTree = "<group>"; };
5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FileProviderExt-Bridging-Header.h"; sourceTree = "<group>"; };
5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudFilesDatabaseManager+Directories.swift"; sourceTree = "<group>"; };
5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudFilesDatabaseManager+LocalFiles.swift"; sourceTree = "<group>"; };
5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+Thumbnailing.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -238,6 +242,15 @@
path = Database;
sourceTree = "<group>";
};
5350E4C72B0C368B00F276CB /* Services */ = {
isa = PBXGroup;
children = (
5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */,
5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */,
);
path = Services;
sourceTree = "<group>";
};
5352E85929B7BFB4002CE85C /* Extensions */ = {
isa = PBXGroup;
children = (
Expand All @@ -259,6 +272,7 @@
538E396B27F4765000FA63D5 /* FileProviderExt */ = {
isa = PBXGroup;
children = (
5350E4C72B0C368B00F276CB /* Services */,
5318AD8F29BF406500CBB71C /* Database */,
5352E85929B7BFB4002CE85C /* Extensions */,
538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */,
Expand All @@ -273,6 +287,7 @@
536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */,
538E397327F4765000FA63D5 /* FileProviderExt.entitlements */,
538E397227F4765000FA63D5 /* Info.plist */,
5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */,
);
path = FileProviderExt;
sourceTree = "<group>";
Expand Down Expand Up @@ -591,6 +606,7 @@
538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */,
5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */,
5318AD9129BF42FB00CBB71C /* NextcloudItemMetadataTable.swift in Sources */,
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */,
5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */,
5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */,
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */,
Expand Down Expand Up @@ -705,6 +721,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "FileProviderExt/FileProviderExt-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
Expand Down Expand Up @@ -755,6 +772,7 @@
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "FileProviderExt/FileProviderExt-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
Expand Down
9 changes: 8 additions & 1 deletion src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,9 @@ IF( APPLE )

if (BUILD_FILE_PROVIDER_MODULE)
list(APPEND client_SRCS
# Symlinks to files in shell_integration/MacOSX/NextcloudIntegration/
macOS/ClientCommunicationProtocol.h
# End of symlink files
macOS/fileprovider.h
macOS/fileprovider_mac.mm
macOS/fileproviderdomainmanager.h
Expand All @@ -294,7 +297,11 @@ IF( APPLE )
macOS/fileprovidersocketcontroller.cpp
macOS/fileprovidersocketserver.h
macOS/fileprovidersocketserver.cpp
macOS/fileprovidersocketserver_mac.mm)
macOS/fileprovidersocketserver_mac.mm
macOS/fileproviderxpc.h
macOS/fileproviderxpc_mac.mm
macOS/fileproviderxpc_mac_utils.h
macOS/fileproviderxpc_mac_utils.mm)
endif()

if(SPARKLE_FOUND AND BUILD_UPDATER)
Expand Down
8 changes: 4 additions & 4 deletions src/gui/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,6 @@ Application::Application(int &argc, char **argv)

connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage);

#if defined(BUILD_FILE_PROVIDER_MODULE)
_fileProvider.reset(new Mac::FileProvider);
#endif

// create accounts and folders from a legacy desktop client or from the current config file
setupAccountsAndFolders();

Expand Down Expand Up @@ -420,6 +416,10 @@ Application::Application(int &argc, char **argv)
AccountSetupCommandLineManager::instance()->setupAccountFromCommandLine();
}
AccountSetupCommandLineManager::destroy();

#if defined(BUILD_FILE_PROVIDER_MODULE)
_fileProvider.reset(new Mac::FileProvider);
#endif
}

Application::~Application()
Expand Down
1 change: 1 addition & 0 deletions src/gui/macOS/ClientCommunicationProtocol.h
5 changes: 5 additions & 0 deletions src/gui/macOS/fileprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "fileproviderdomainmanager.h"
#include "fileprovidersocketserver.h"
#include "fileproviderxpc.h"

namespace OCC {

Expand All @@ -38,9 +39,13 @@ class FileProvider : public QObject

static bool fileProviderAvailable();

private slots:
void configureXPC();

private:
std::unique_ptr<FileProviderDomainManager> _domainManager;
std::unique_ptr<FileProviderSocketServer> _socketServer;
std::unique_ptr<FileProviderXPC> _xpc;

static FileProvider *_instance;
explicit FileProvider(QObject * const parent = nullptr);
Expand Down
Loading

0 comments on commit 8ab52b8

Please sign in to comment.