Skip to content

Commit

Permalink
Merge pull request #6643 from nextcloud/backport/stable-3.13-fp-sharing
Browse files Browse the repository at this point in the history
Backport/stable 3.13 fp sharing
  • Loading branch information
claucambra authored Apr 17, 2024
2 parents c4f3228 + fad069b commit 3dfe216
Show file tree
Hide file tree
Showing 22 changed files with 2,392 additions and 5 deletions.
19 changes: 18 additions & 1 deletion shell_integration/MacOSX/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,20 @@ if(APPLE)
COMMENT building macOS File Provider extension
VERBATIM)

add_dependencies(mac_overlayplugin mac_fileproviderplugin nextcloud) # for the ownCloud.icns to be generated
add_custom_target( mac_fileprovideruiplugin ALL
xcodebuild ARCHS=${CMAKE_OSX_ARCHITECTURES} ONLY_ACTIVE_ARCH=NO
-project ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj
-target FileProviderUIExt -configuration ${XCODE_TARGET_CONFIGURATION} "SYMROOT=${CMAKE_CURRENT_BINARY_DIR}"
"OC_APPLICATION_EXECUTABLE_NAME=${APPLICATION_EXECUTABLE}"
"OC_APPLICATION_VENDOR=${APPLICATION_VENDOR}"
"OC_APPLICATION_NAME=${APPLICATION_NAME}"
"OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN}"
"OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX}"
DEPENDS mac_fileproviderplugin
COMMENT building macOS File Provider UI extension
VERBATIM)

add_dependencies(mac_overlayplugin mac_fileproviderplugin mac_fileprovideruiplugin nextcloud) # for the ownCloud.icns to be generated
else()
add_dependencies(mac_overlayplugin nextcloud) # for the ownCloud.icns to be generated
endif()
Expand All @@ -55,6 +68,10 @@ if(APPLE)
install(DIRECTORY ${OSX_PLUGINS_BINARY_DIR}/FileProviderExt.appex
DESTINATION ${OSX_PLUGINS_INSTALL_DIR}
USE_SOURCE_PERMISSIONS)

install(DIRECTORY ${OSX_PLUGINS_BINARY_DIR}/FileProviderUIExt.appex
DESTINATION ${OSX_PLUGINS_INSTALL_DIR}
USE_SOURCE_PERMISSIONS)
endif()
endif()
endif()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ extension Logger {

static let desktopClientConnection = Logger(
subsystem: subsystem, category: "desktopclientconnection")
static let fpUiExtensionService = Logger(subsystem: subsystem, category: "fpUiExtensionService")
static let enumeration = Logger(subsystem: subsystem, category: "enumeration")
static let fileProviderExtension = Logger(
subsystem: subsystem, category: "fileproviderextension")
static let fileTransfer = Logger(subsystem: subsystem, category: "filetransfer")
static let localFileOps = Logger(subsystem: subsystem, category: "localfileoperations")
static let ncFilesDatabase = Logger(subsystem: subsystem, category: "nextcloudfilesdatabase")
static let shares = Logger(subsystem: subsystem, category: "shares")
static let ncAccount = Logger(subsystem: subsystem, category: "ncAccount")
static let materialisedFileHandling = Logger(
subsystem: subsystem, category: "materialisedfilehandling"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ extension FileProviderExtension: NSFileProviderServicing {
) -> Progress {
Logger.desktopClientConnection.debug("Serving supported service sources")
let clientCommService = ClientCommunicationService(fpExtension: self)
let services = [clientCommService]
let fpuiExtService = FPUIExtensionServiceSource(fpExtension: self)
let services: [NSFileProviderServiceSource] = [clientCommService, fpuiExtService]
completionHandler(services, nil)
let progress = Progress()
progress.cancellationHandler = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
import FileProvider
import Foundation

let ncAccountDictUsernameKey = "usernameKey"
let ncAccountDictPasswordKey = "passwordKey"
let ncAccountDictNcKitAccountKey = "ncKitAccountKey"
let ncAccountDictServerUrlKey = "serverUrlKey"
let ncAccountDictDavFilesUrlKey = "davFilesUrlKey"

struct NextcloudAccount: Equatable {
static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/"
let username, password, ncKitAccount, serverUrl, davFilesUrl: String
Expand All @@ -26,4 +32,31 @@ struct NextcloudAccount: Equatable {
self.serverUrl = serverUrl
davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user
}

init?(dictionary: Dictionary<String, String>) {
guard let username = dictionary[ncAccountDictUsernameKey],
let password = dictionary[ncAccountDictPasswordKey],
let ncKitAccount = dictionary[ncAccountDictNcKitAccountKey],
let serverUrl = dictionary[ncAccountDictServerUrlKey],
let davFilesUrl = dictionary[ncAccountDictDavFilesUrlKey]
else {
return nil
}

self.username = username
self.password = password
self.ncKitAccount = ncKitAccount
self.serverUrl = serverUrl
self.davFilesUrl = davFilesUrl
}

func dictionary() -> Dictionary<String, String> {
return [
ncAccountDictUsernameKey: username,
ncAccountDictPasswordKey: password,
ncAccountDictNcKitAccountKey: ncKitAccount,
ncAccountDictServerUrlKey: serverUrl,
ncAccountDictDavFilesUrlKey: davFilesUrl
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// FPUIExtensionCommunicationProtocol.swift
// FileProviderExt
//
// Created by Claudio Cambra on 21/2/24.
//

import FileProvider
import NextcloudKit

let fpUiExtensionServiceName = NSFileProviderServiceName(
"com.nextcloud.desktopclient.FPUIExtensionService"
)

@objc protocol FPUIExtensionService {
func credentials() async -> NSDictionary
func itemServerPath(identifier: NSFileProviderItemIdentifier) async -> NSString?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// FPUIExtensionCommunicationService.swift
// FileProviderExt
//
// Created by Claudio Cambra on 21/2/24.
//

import FileProvider
import Foundation
import NextcloudKit
import OSLog

class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, FPUIExtensionService {
let listener = NSXPCListener.anonymous()
let serviceName = fpUiExtensionServiceName
let fpExtension: FileProviderExtension

init(fpExtension: FileProviderExtension) {
Logger.fpUiExtensionService.debug("Instantiating FPUIExtensionService 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: FPUIExtensionService.self)
newConnection.exportedObject = self
newConnection.resume()
return true
}

//MARK: - FPUIExtensionService protocol methods

func credentials() async -> NSDictionary {
return (fpExtension.ncAccount?.dictionary() ?? [:]) as NSDictionary
}

func itemServerPath(identifier: NSFileProviderItemIdentifier) async -> NSString? {
let rawIdentifier = identifier.rawValue
Logger.shares.info("Fetching shares for item \(rawIdentifier, privacy: .public)")

guard let baseUrl = fpExtension.ncAccount?.davFilesUrl else {
Logger.shares.error("Could not fetch shares as ncAccount on parent extension is nil")
return nil
}

let dbManager = NextcloudFilesDatabaseManager.shared
guard let item = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier) else {
Logger.shares.error("No item \(rawIdentifier, privacy: .public) in db, no shares.")
return nil
}

let completePath = item.serverUrl + "/" + item.fileName
return completePath.replacingOccurrences(of: baseUrl, with: "") as NSString
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// DocumentActionViewController.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 20/2/24.
//

import FileProviderUI
import OSLog

class DocumentActionViewController: FPUIActionExtensionViewController {
var domain: NSFileProviderDomain {
guard let identifier = extensionContext.domainIdentifier else {
fatalError("not expected to be called with default domain")
}
return NSFileProviderDomain(
identifier: NSFileProviderDomainIdentifier(rawValue: identifier.rawValue),
displayName: ""
)
}

func prepare(childViewController: NSViewController) {
addChild(childViewController)
view.addSubview(childViewController.view)

NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: childViewController.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: childViewController.view.trailingAnchor),
view.topAnchor.constraint(equalTo: childViewController.view.topAnchor),
view.bottomAnchor.constraint(equalTo: childViewController.view.bottomAnchor)
])
}

override func prepare(
forAction actionIdentifier: String, itemIdentifiers: [NSFileProviderItemIdentifier]
) {
Logger.actionViewController.info("Preparing for action: \(actionIdentifier)")

if actionIdentifier == "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction" {
prepare(childViewController: ShareViewController(itemIdentifiers))
}

}

override func prepare(forError error: Error) {
Logger.actionViewController.info("Preparing for error: \(error.localizedDescription)")
}

override public func loadView() {
self.view = NSView()
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Logger+Extensions.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 21/2/24.
//

import OSLog

extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!

static let actionViewController = Logger(subsystem: subsystem, category: "actionViewController")
static let shareCapabilities = Logger(subsystem: subsystem, category: "shareCapabilities")
static let shareController = Logger(subsystem: subsystem, category: "shareController")
static let shareeDataSource = Logger(subsystem: subsystem, category: "shareeDataSource")
static let sharesDataSource = Logger(subsystem: subsystem, category: "sharesDataSource")
static let shareOptionsView = Logger(subsystem: subsystem, category: "shareOptionsView")
static let shareViewController = Logger(subsystem: subsystem, category: "shareViewController")
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//
// NKShare+Extensions.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//

import AppKit
import NextcloudKit

extension NKShare {
enum ShareType: Int {
case user = 0
case group = 1
case publicLink = 3
case email = 4
case federatedCloud = 6
case circle = 7
case talkConversation = 10
}

enum PermissionValues: Int {
case readShare = 1
case updateShare = 2
case createShare = 4
case deleteShare = 8
case shareShare = 16
case all = 31
}

var typeImage: NSImage? {
var image: NSImage?
switch shareType {
case ShareType.user.rawValue:
image = NSImage(
systemSymbolName: "person.circle.fill",
accessibilityDescription: "User share icon"
)
case ShareType.group.rawValue:
image = NSImage(
systemSymbolName: "person.2.circle.fill",
accessibilityDescription: "Group share icon"
)
case ShareType.publicLink.rawValue:
image = NSImage(
systemSymbolName: "link.circle.fill",
accessibilityDescription: "Public link share icon"
)
case ShareType.email.rawValue:
image = NSImage(
systemSymbolName: "envelope.circle.fill",
accessibilityDescription: "Email share icon"
)
case ShareType.federatedCloud.rawValue:
image = NSImage(
systemSymbolName: "cloud.circle.fill",
accessibilityDescription: "Federated cloud share icon"
)
case ShareType.circle.rawValue:
image = NSImage(
systemSymbolName: "circle.circle.fill",
accessibilityDescription: "Circle share icon"
)
case ShareType.talkConversation.rawValue:
image = NSImage(
systemSymbolName: "message.circle.fill",
accessibilityDescription: "Talk conversation share icon"
)
default:
return nil
}

var config = NSImage.SymbolConfiguration(textStyle: .body, scale: .large)
if #available(macOS 12.0, *) {
config = config.applying(
.init(paletteColors: [.controlBackgroundColor, .controlAccentColor])
)
}
return image?.withSymbolConfiguration(config)
}

var displayString: String {
if label != "" {
return label
}

switch shareType {
case ShareType.user.rawValue:
return "User share (\(shareWith))"
case ShareType.group.rawValue:
return "Group share (\(shareWith))"
case ShareType.publicLink.rawValue:
return "Public link share"
case ShareType.email.rawValue:
return "Email share (\(shareWith))"
case ShareType.federatedCloud.rawValue:
return "Federated cloud share (\(shareWith))"
case ShareType.circle.rawValue:
return "Circle share (\(shareWith))"
case ShareType.talkConversation.rawValue:
return "Talk conversation share (\(shareWith))"
default:
return "Unknown share"
}
}

var expirationDateString: String? {
guard let date = expirationDate else { return nil }
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
return dateFormatter.string(from: date as Date)
}

var shareesCanEdit: Bool {
get { (permissions & PermissionValues.updateShare.rawValue) != 0 }
set {
if newValue {
permissions |= NKShare.PermissionValues.updateShare.rawValue
} else {
permissions &= ~NKShare.PermissionValues.updateShare.rawValue
}
}
}

static func formattedDateString(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
return dateFormatter.string(from: date)
}
}
Loading

0 comments on commit 3dfe216

Please sign in to comment.