mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-25 14:36:01 +03:00
Merge pull request #6960 from nextcloud/feature/macos-vfs-locking
Feature/macos vfs locking
This commit is contained in:
commit
804cb1d4d6
18 changed files with 531 additions and 81 deletions
|
@ -104,7 +104,15 @@ import OSLog
|
||||||
request _: NSFileProviderRequest,
|
request _: NSFileProviderRequest,
|
||||||
completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void
|
completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void
|
||||||
) -> Progress {
|
) -> Progress {
|
||||||
if let item = Item.storedItem(identifier: identifier, remoteInterface: ncKit) {
|
if ncAccount == nil {
|
||||||
|
Logger.fileProviderExtension.error(
|
||||||
|
"""
|
||||||
|
Not fetching item for identifier: \(identifier.rawValue, privacy: .public)
|
||||||
|
as account not set up yet.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
completionHandler(nil, NSFileProviderError(.notAuthenticated))
|
||||||
|
} else if let item = Item.storedItem(identifier: identifier, remoteInterface: ncKit) {
|
||||||
completionHandler(item, nil)
|
completionHandler(item, nil)
|
||||||
} else {
|
} else {
|
||||||
completionHandler(nil, NSFileProviderError(.noSuchItem))
|
completionHandler(nil, NSFileProviderError(.noSuchItem))
|
||||||
|
|
|
@ -36,17 +36,21 @@ class DocumentActionViewController: FPUIActionExtensionViewController {
|
||||||
) {
|
) {
|
||||||
Logger.actionViewController.info("Preparing action: \(actionIdentifier, privacy: .public)")
|
Logger.actionViewController.info("Preparing action: \(actionIdentifier, privacy: .public)")
|
||||||
|
|
||||||
if actionIdentifier == "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction" {
|
switch (actionIdentifier) {
|
||||||
|
case "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction":
|
||||||
prepare(childViewController: ShareViewController(itemIdentifiers))
|
prepare(childViewController: ShareViewController(itemIdentifiers))
|
||||||
|
case "com.nextcloud.desktopclient.FileProviderUIExt.LockFileAction":
|
||||||
|
prepare(childViewController: LockViewController(itemIdentifiers, locking: true))
|
||||||
|
case "com.nextcloud.desktopclient.FileProviderUIExt.UnlockFileAction":
|
||||||
|
prepare(childViewController: LockViewController(itemIdentifiers, locking: false))
|
||||||
|
default:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(forError error: Error) {
|
override func prepare(forError error: Error) {
|
||||||
Logger.actionViewController.info(
|
Logger.actionViewController.info(
|
||||||
"""
|
"Preparing for error: \(error.localizedDescription, privacy: .public)"
|
||||||
Preparing for error: \(error.localizedDescription, privacy: .public)
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ extension Logger {
|
||||||
private static var subsystem = Bundle.main.bundleIdentifier!
|
private static var subsystem = Bundle.main.bundleIdentifier!
|
||||||
|
|
||||||
static let actionViewController = Logger(subsystem: subsystem, category: "actionViewController")
|
static let actionViewController = Logger(subsystem: subsystem, category: "actionViewController")
|
||||||
|
static let lockViewController = Logger(subsystem: subsystem, category: "lockViewController")
|
||||||
|
static let metadataProvider = Logger(subsystem: subsystem, category: "metadataProvider")
|
||||||
static let shareCapabilities = Logger(subsystem: subsystem, category: "shareCapabilities")
|
static let shareCapabilities = Logger(subsystem: subsystem, category: "shareCapabilities")
|
||||||
static let shareController = Logger(subsystem: subsystem, category: "shareController")
|
static let shareController = Logger(subsystem: subsystem, category: "shareController")
|
||||||
static let shareeDataSource = Logger(subsystem: subsystem, category: "shareeDataSource")
|
static let shareeDataSource = Logger(subsystem: subsystem, category: "shareeDataSource")
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// FileProviderCommunication.swift
|
||||||
|
// FileProviderUIExt
|
||||||
|
//
|
||||||
|
// Created by Claudio Cambra on 30/7/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FileProvider
|
||||||
|
|
||||||
|
enum FileProviderCommunicationError: Error {
|
||||||
|
case serviceNotFound
|
||||||
|
case remoteProxyObjectInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceConnection(
|
||||||
|
url: URL, interruptionHandler: @escaping () -> Void
|
||||||
|
) async throws -> FPUIExtensionService {
|
||||||
|
let services = try await FileManager().fileProviderServicesForItem(at: url)
|
||||||
|
guard let service = services[fpUiExtensionServiceName] else {
|
||||||
|
throw FileProviderCommunicationError.serviceNotFound
|
||||||
|
}
|
||||||
|
let connection: NSXPCConnection
|
||||||
|
connection = try await service.fileProviderConnection()
|
||||||
|
connection.remoteObjectInterface = NSXPCInterface(with: FPUIExtensionService.self)
|
||||||
|
connection.interruptionHandler = interruptionHandler
|
||||||
|
connection.resume()
|
||||||
|
guard let proxy = connection.remoteObjectProxy as? FPUIExtensionService else {
|
||||||
|
throw FileProviderCommunicationError.remoteProxyObjectInvalid
|
||||||
|
}
|
||||||
|
return proxy
|
||||||
|
}
|
|
@ -12,6 +12,22 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionFileProviderActions</key>
|
<key>NSExtensionFileProviderActions</key>
|
||||||
<array>
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionFileProviderActionIdentifier</key>
|
||||||
|
<string>com.nextcloud.desktopclient.FileProviderUIExt.UnlockFileAction</string>
|
||||||
|
<key>NSExtensionFileProviderActionName</key>
|
||||||
|
<string>Unlock file</string>
|
||||||
|
<key>NSExtensionFileProviderActionActivationRule</key>
|
||||||
|
<string>SUBQUERY ( fileproviderItems, $fileproviderItem, $fileproviderItem.userInfo.locked != nil && !($fileproviderItem.contentType.identifier UTI-CONFORMS-TO "public.folder") ).@count > 0</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionFileProviderActionActivationRule</key>
|
||||||
|
<string>SUBQUERY ( fileproviderItems, $fileproviderItem, $fileproviderItem.userInfo.locked == nil && !($fileproviderItem.contentType.identifier UTI-CONFORMS-TO "public.folder") ).@count > 0</string>
|
||||||
|
<key>NSExtensionFileProviderActionName</key>
|
||||||
|
<string>Lock file</string>
|
||||||
|
<key>NSExtensionFileProviderActionIdentifier</key>
|
||||||
|
<string>com.nextcloud.desktopclient.FileProviderUIExt.LockFileAction</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionFileProviderActionActivationRule</key>
|
<key>NSExtensionFileProviderActionActivationRule</key>
|
||||||
<string>TRUEPREDICATE</string>
|
<string>TRUEPREDICATE</string>
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
//
|
||||||
|
// LockViewController.swift
|
||||||
|
// FileProviderUIExt
|
||||||
|
//
|
||||||
|
// Created by Claudio Cambra on 30/7/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
import FileProvider
|
||||||
|
import NextcloudFileProviderKit
|
||||||
|
import NextcloudKit
|
||||||
|
import OSLog
|
||||||
|
import QuickLookThumbnailing
|
||||||
|
|
||||||
|
class LockViewController: NSViewController {
|
||||||
|
let itemIdentifiers: [NSFileProviderItemIdentifier]
|
||||||
|
let locking: Bool
|
||||||
|
|
||||||
|
@IBOutlet weak var fileNameIcon: NSImageView!
|
||||||
|
@IBOutlet weak var fileNameLabel: NSTextField!
|
||||||
|
@IBOutlet weak var descriptionLabel: NSTextField!
|
||||||
|
@IBOutlet weak var closeButton: NSButton!
|
||||||
|
@IBOutlet weak var loadingIndicator: NSProgressIndicator!
|
||||||
|
@IBOutlet weak var warnImage: NSImageView!
|
||||||
|
|
||||||
|
public override var nibName: NSNib.Name? {
|
||||||
|
return NSNib.Name(self.className)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionViewController: DocumentActionViewController! {
|
||||||
|
return parent as? DocumentActionViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ itemIdentifiers: [NSFileProviderItemIdentifier], locking: Bool) {
|
||||||
|
self.itemIdentifiers = itemIdentifiers
|
||||||
|
self.locking = locking
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
guard let firstItem = itemIdentifiers.first else {
|
||||||
|
Logger.shareViewController.error("called without items")
|
||||||
|
closeAction(self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.lockViewController.info(
|
||||||
|
"""
|
||||||
|
Locking \(self.locking ? "enabled" : "disabled", privacy: .public) for items:
|
||||||
|
\(firstItem.rawValue, privacy: .public)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await processItemIdentifier(firstItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func closeAction(_ sender: Any) {
|
||||||
|
actionViewController.extensionContext.completeRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopIndicatingLoading() {
|
||||||
|
loadingIndicator.stopAnimation(self)
|
||||||
|
loadingIndicator.isHidden = true
|
||||||
|
warnImage.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func presentError(_ error: String) {
|
||||||
|
Logger.lockViewController.error("Error: \(error, privacy: .public)")
|
||||||
|
descriptionLabel.stringValue = "Error: \(error)"
|
||||||
|
stopIndicatingLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func processItemIdentifier(_ itemIdentifier: NSFileProviderItemIdentifier) async {
|
||||||
|
guard let manager = NSFileProviderManager(for: actionViewController.domain) else {
|
||||||
|
fatalError("NSFileProviderManager isn't expected to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let itemUrl = try await manager.getUserVisibleURL(for: itemIdentifier)
|
||||||
|
guard itemUrl.startAccessingSecurityScopedResource() else {
|
||||||
|
Logger.lockViewController.error("Could not access scoped resource for item url!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await updateFileDetailsDisplay(itemUrl: itemUrl)
|
||||||
|
itemUrl.stopAccessingSecurityScopedResource()
|
||||||
|
await lockOrUnlockFile(localItemUrl: itemUrl)
|
||||||
|
} catch let error {
|
||||||
|
let errorString = "Error processing item: \(error)"
|
||||||
|
Logger.lockViewController.error("\(errorString, privacy: .public)")
|
||||||
|
fileNameLabel.stringValue = "Could not lock unknown item…"
|
||||||
|
descriptionLabel.stringValue = errorString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateFileDetailsDisplay(itemUrl: URL) async {
|
||||||
|
let lockAction = locking ? "Locking" : "Unlocking"
|
||||||
|
fileNameLabel.stringValue = "\(lockAction) file \(itemUrl.lastPathComponent)…"
|
||||||
|
|
||||||
|
let request = QLThumbnailGenerator.Request(
|
||||||
|
fileAt: itemUrl,
|
||||||
|
size: CGSize(width: 48, height: 48),
|
||||||
|
scale: 1.0,
|
||||||
|
representationTypes: .icon
|
||||||
|
)
|
||||||
|
let generator = QLThumbnailGenerator.shared
|
||||||
|
let fileThumbnail = await withCheckedContinuation { continuation in
|
||||||
|
generator.generateRepresentations(for: request) { thumbnail, type, error in
|
||||||
|
if thumbnail == nil || error != nil {
|
||||||
|
Logger.lockViewController.error(
|
||||||
|
"Could not get thumbnail: \(error, privacy: .public)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
continuation.resume(returning: thumbnail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileNameIcon.image =
|
||||||
|
fileThumbnail?.nsImage ??
|
||||||
|
NSImage(systemSymbolName: "doc", accessibilityDescription: "doc")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func lockOrUnlockFile(localItemUrl: URL) async {
|
||||||
|
descriptionLabel.stringValue = "Fetching file details…"
|
||||||
|
|
||||||
|
guard let itemIdentifier = await withCheckedContinuation({
|
||||||
|
(continuation: CheckedContinuation<NSFileProviderItemIdentifier?, Never>) -> Void in
|
||||||
|
NSFileProviderManager.getIdentifierForUserVisibleFile(
|
||||||
|
at: localItemUrl
|
||||||
|
) { identifier, domainIdentifier, error in
|
||||||
|
defer { continuation.resume(returning: identifier) }
|
||||||
|
guard error == nil else {
|
||||||
|
self.presentError("No item with identifier: \(error.debugDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) else {
|
||||||
|
presentError("Could not get identifier for item, no shares can be acquired.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let connection = try await serviceConnection(url: localItemUrl, interruptionHandler: {
|
||||||
|
Logger.lockViewController.error("Service connection interrupted")
|
||||||
|
})
|
||||||
|
guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier),
|
||||||
|
let credentials = await connection.credentials() as? Dictionary<String, String>,
|
||||||
|
let account = Account(dictionary: credentials),
|
||||||
|
!account.password.isEmpty
|
||||||
|
else {
|
||||||
|
presentError("Failed to get details from File Provider Extension.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let serverPathString = serverPath as String
|
||||||
|
let kit = NextcloudKit()
|
||||||
|
kit.setup(
|
||||||
|
user: account.username,
|
||||||
|
userId: account.username,
|
||||||
|
password: account.password,
|
||||||
|
urlBase: account.serverUrl
|
||||||
|
)
|
||||||
|
// guard let capabilities = await fetchCapabilities() else {
|
||||||
|
guard let itemMetadata = await fetchItemMetadata(
|
||||||
|
itemRelativePath: serverPathString, kit: kit
|
||||||
|
) else {
|
||||||
|
presentError("Could not get item metadata.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run lock state checks
|
||||||
|
if locking {
|
||||||
|
guard !itemMetadata.lock else {
|
||||||
|
presentError("File is already locked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
guard itemMetadata.lock else {
|
||||||
|
presentError("File is already unlocked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptionLabel.stringValue =
|
||||||
|
"Communicating with server, \(locking ? "locking" : "unlocking") file…"
|
||||||
|
|
||||||
|
let serverUrlFileName = itemMetadata.serverUrl + "/" + itemMetadata.fileName
|
||||||
|
Logger.lockViewController.info(
|
||||||
|
"""
|
||||||
|
Locking file: \(serverUrlFileName, privacy: .public)
|
||||||
|
\(self.locking ? "locking" : "unlocking", privacy: .public)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
let error = await withCheckedContinuation { continuation in
|
||||||
|
kit.lockUnlockFile(
|
||||||
|
serverUrlFileName: serverUrlFileName,
|
||||||
|
shouldLock: locking,
|
||||||
|
completion: { _, error in
|
||||||
|
continuation.resume(returning: error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if error == .success {
|
||||||
|
descriptionLabel.stringValue = "File \(self.locking ? "locked" : "unlocked")!"
|
||||||
|
warnImage.image = NSImage(
|
||||||
|
systemSymbolName: "checkmark.circle.fill",
|
||||||
|
accessibilityDescription: "checkmark.circle.fill"
|
||||||
|
)
|
||||||
|
stopIndicatingLoading()
|
||||||
|
if let manager = NSFileProviderManager(for: actionViewController.domain) {
|
||||||
|
do {
|
||||||
|
try await manager.signalEnumerator(for: itemIdentifier)
|
||||||
|
} catch let error {
|
||||||
|
presentError(
|
||||||
|
"""
|
||||||
|
Could not signal lock state change in virtual file.
|
||||||
|
Changes may take a while to be reflected on your Mac.
|
||||||
|
Error: \(error.localizedDescription)
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
presentError("Could not lock file: \(error.errorDescription).")
|
||||||
|
}
|
||||||
|
} catch let error {
|
||||||
|
presentError("Could not lock file: \(error).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
|
||||||
|
<capability name="Image references" minToolsVersion="12.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="LockViewController" customModule="FileProviderUIExt" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="closeButton" destination="7qN-mr-hAh" id="NkY-2f-Ltx"/>
|
||||||
|
<outlet property="descriptionLabel" destination="DG3-ti-eu7" id="rez-CW-FUS"/>
|
||||||
|
<outlet property="fileNameIcon" destination="KlP-OW-SKo" id="Dey-vA-qIG"/>
|
||||||
|
<outlet property="fileNameLabel" destination="LDe-7m-hvL" id="AzB-UH-ndO"/>
|
||||||
|
<outlet property="loadingIndicator" destination="UWQ-uR-PJA" id="Swv-It-LT9"/>
|
||||||
|
<outlet property="view" destination="MSC-7J-Z1I" id="6cA-xC-NDA"/>
|
||||||
|
<outlet property="warnImage" destination="zlo-mM-ZNd" id="5gS-Ab-nDE"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<customView translatesAutoresizingMaskIntoConstraints="NO" id="MSC-7J-Z1I">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="500" height="87"/>
|
||||||
|
<subviews>
|
||||||
|
<progressIndicator horizontalHuggingPriority="1000" verticalHuggingPriority="1000" maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="UWQ-uR-PJA">
|
||||||
|
<rect key="frame" x="10" y="28" width="32" height="32"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="32" id="ShG-G9-AMJ"/>
|
||||||
|
<constraint firstAttribute="width" secondItem="UWQ-uR-PJA" secondAttribute="height" multiplier="1:1" id="Tcq-dQ-mHX"/>
|
||||||
|
</constraints>
|
||||||
|
</progressIndicator>
|
||||||
|
<imageView hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="zlo-mM-ZNd">
|
||||||
|
<rect key="frame" x="10" y="25" width="32" height="38"/>
|
||||||
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="exclamationmark.circle.fill" catalog="system" id="Yym-fE-Cdh"/>
|
||||||
|
</imageView>
|
||||||
|
<imageView horizontalHuggingPriority="1000" verticalHuggingPriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="KlP-OW-SKo">
|
||||||
|
<rect key="frame" x="52" y="16.5" width="48" height="55"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" secondItem="KlP-OW-SKo" secondAttribute="height" multiplier="1:1" id="u2z-cf-k5P"/>
|
||||||
|
<constraint firstAttribute="width" constant="48" id="ucZ-Lb-C49"/>
|
||||||
|
</constraints>
|
||||||
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="doc" catalog="system" id="sA8-Dn-b0s"/>
|
||||||
|
</imageView>
|
||||||
|
<stackView distribution="fillEqually" orientation="vertical" alignment="leading" spacing="10" horizontalStackHuggingPriority="250" verticalStackHuggingPriority="250" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dpJ-oy-su9">
|
||||||
|
<rect key="frame" x="110" y="21" width="338" height="45"/>
|
||||||
|
<subviews>
|
||||||
|
<textField focusRingType="none" verticalHuggingPriority="750" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="LDe-7m-hvL">
|
||||||
|
<rect key="frame" x="-2" y="26" width="342" height="19"/>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" title="Locking file filename.txt…" id="Zld-Ku-SeH">
|
||||||
|
<font key="font" metaFont="systemBold" size="16"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField focusRingType="none" verticalHuggingPriority="749" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="DG3-ti-eu7">
|
||||||
|
<rect key="frame" x="-2" y="0.0" width="342" height="16"/>
|
||||||
|
<textFieldCell key="cell" selectable="YES" title="Communicating with server..." id="tz0-OE-Too">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="DG3-ti-eu7" secondAttribute="trailing" id="8TF-VE-4z7"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="LDe-7m-hvL" secondAttribute="trailing" id="BAx-hY-Nrb"/>
|
||||||
|
<constraint firstItem="LDe-7m-hvL" firstAttribute="leading" secondItem="dpJ-oy-su9" secondAttribute="leading" id="NUj-8a-xvp"/>
|
||||||
|
<constraint firstItem="DG3-ti-eu7" firstAttribute="leading" secondItem="dpJ-oy-su9" secondAttribute="leading" id="eV4-Uv-5xX"/>
|
||||||
|
</constraints>
|
||||||
|
<visibilityPriorities>
|
||||||
|
<integer value="1000"/>
|
||||||
|
<integer value="1000"/>
|
||||||
|
</visibilityPriorities>
|
||||||
|
<customSpacing>
|
||||||
|
<real value="3.4028234663852886e+38"/>
|
||||||
|
<real value="3.4028234663852886e+38"/>
|
||||||
|
</customSpacing>
|
||||||
|
</stackView>
|
||||||
|
<button horizontalHuggingPriority="1000" verticalHuggingPriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7qN-mr-hAh">
|
||||||
|
<rect key="frame" x="458" y="23" width="32" height="43"/>
|
||||||
|
<buttonCell key="cell" type="bevel" title="Close" bezelStyle="rounded" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="7Oc-Xd-RzM">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<imageReference key="image" image="xmark.circle.fill" catalog="system" symbolScale="large"/>
|
||||||
|
</buttonCell>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" secondItem="7qN-mr-hAh" secondAttribute="height" multiplier="1:1" id="K00-Bi-dEy"/>
|
||||||
|
<constraint firstAttribute="width" constant="32" id="zVJ-h1-QXJ"/>
|
||||||
|
</constraints>
|
||||||
|
<connections>
|
||||||
|
<action selector="closeAction:" target="-2" id="E6h-U9-2eB"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="KlP-OW-SKo" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="MSC-7J-Z1I" secondAttribute="leading" constant="10" id="42d-pC-0Lq"/>
|
||||||
|
<constraint firstItem="zlo-mM-ZNd" firstAttribute="bottom" secondItem="UWQ-uR-PJA" secondAttribute="bottom" id="4kj-Yy-erm"/>
|
||||||
|
<constraint firstItem="UWQ-uR-PJA" firstAttribute="top" relation="greaterThanOrEqual" secondItem="MSC-7J-Z1I" secondAttribute="top" constant="10" id="86t-6h-ezO"/>
|
||||||
|
<constraint firstItem="dpJ-oy-su9" firstAttribute="leading" secondItem="KlP-OW-SKo" secondAttribute="trailing" constant="10" id="CkI-Rn-Ens"/>
|
||||||
|
<constraint firstItem="7qN-mr-hAh" firstAttribute="leading" secondItem="dpJ-oy-su9" secondAttribute="trailing" constant="10" id="D0y-zd-Mkx"/>
|
||||||
|
<constraint firstItem="UWQ-uR-PJA" firstAttribute="leading" secondItem="MSC-7J-Z1I" secondAttribute="leading" constant="10" id="DOR-jC-JYh"/>
|
||||||
|
<constraint firstItem="dpJ-oy-su9" firstAttribute="top" relation="greaterThanOrEqual" secondItem="MSC-7J-Z1I" secondAttribute="top" constant="10" id="EnF-gg-RXe"/>
|
||||||
|
<constraint firstItem="UWQ-uR-PJA" firstAttribute="centerY" secondItem="MSC-7J-Z1I" secondAttribute="centerY" id="GAc-W2-Bue"/>
|
||||||
|
<constraint firstItem="zlo-mM-ZNd" firstAttribute="trailing" secondItem="UWQ-uR-PJA" secondAttribute="trailing" id="Hn0-5r-n4p"/>
|
||||||
|
<constraint firstItem="KlP-OW-SKo" firstAttribute="top" relation="greaterThanOrEqual" secondItem="MSC-7J-Z1I" secondAttribute="top" constant="10" id="JEs-Z1-i2x"/>
|
||||||
|
<constraint firstItem="KlP-OW-SKo" firstAttribute="leading" secondItem="UWQ-uR-PJA" secondAttribute="trailing" constant="10" id="YHI-Z6-DqR"/>
|
||||||
|
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="KlP-OW-SKo" secondAttribute="bottom" constant="10" id="g7d-i6-GFB"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="7qN-mr-hAh" secondAttribute="trailing" constant="10" id="jyt-D2-tiy"/>
|
||||||
|
<constraint firstItem="zlo-mM-ZNd" firstAttribute="leading" secondItem="UWQ-uR-PJA" secondAttribute="leading" id="ljA-uc-fNi"/>
|
||||||
|
<constraint firstItem="7qN-mr-hAh" firstAttribute="centerY" secondItem="MSC-7J-Z1I" secondAttribute="centerY" id="tuo-AL-2Xr"/>
|
||||||
|
<constraint firstItem="KlP-OW-SKo" firstAttribute="centerY" secondItem="MSC-7J-Z1I" secondAttribute="centerY" id="upa-4o-uiG"/>
|
||||||
|
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="dpJ-oy-su9" secondAttribute="bottom" constant="10" id="xOO-EK-GWO"/>
|
||||||
|
<constraint firstItem="zlo-mM-ZNd" firstAttribute="top" secondItem="UWQ-uR-PJA" secondAttribute="top" id="xeA-Ec-B32"/>
|
||||||
|
<constraint firstItem="dpJ-oy-su9" firstAttribute="centerY" secondItem="MSC-7J-Z1I" secondAttribute="centerY" id="yKD-Bd-jC9"/>
|
||||||
|
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="UWQ-uR-PJA" secondAttribute="bottom" constant="10" id="zgg-6J-3Zy"/>
|
||||||
|
</constraints>
|
||||||
|
<point key="canvasLocation" x="-266" y="-126.5"/>
|
||||||
|
</customView>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="doc" catalog="system" width="14" height="16"/>
|
||||||
|
<image name="exclamationmark.circle.fill" catalog="system" width="15" height="15"/>
|
||||||
|
<image name="xmark.circle.fill" catalog="system" width="20" height="20"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// MetadataProvider.swift
|
||||||
|
// FileProviderUIExt
|
||||||
|
//
|
||||||
|
// Created by Claudio Cambra on 30/7/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import NextcloudKit
|
||||||
|
import OSLog
|
||||||
|
|
||||||
|
func fetchItemMetadata(itemRelativePath: String, kit: NextcloudKit) async -> NKFile? {
|
||||||
|
func slashlessPath(_ string: String) -> String {
|
||||||
|
var strCopy = string
|
||||||
|
if strCopy.hasPrefix("/") {
|
||||||
|
strCopy.removeFirst()
|
||||||
|
}
|
||||||
|
if strCopy.hasSuffix("/") {
|
||||||
|
strCopy.removeLast()
|
||||||
|
}
|
||||||
|
return strCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
let nkCommon = kit.nkCommonInstance
|
||||||
|
let urlBase = slashlessPath(nkCommon.urlBase)
|
||||||
|
let davSuffix = slashlessPath(nkCommon.dav)
|
||||||
|
let userId = nkCommon.userId
|
||||||
|
let itemRelPath = slashlessPath(itemRelativePath)
|
||||||
|
|
||||||
|
let itemFullServerPath = "\(urlBase)/\(davSuffix)/files/\(userId)/\(itemRelPath)"
|
||||||
|
return await withCheckedContinuation { continuation in
|
||||||
|
kit.readFileOrFolder(serverUrlFileName: itemFullServerPath, depth: "0") {
|
||||||
|
account, files, data, error in
|
||||||
|
guard error == .success else {
|
||||||
|
Logger.metadataProvider.error(
|
||||||
|
"Error getting item metadata: \(error.errorDescription)"
|
||||||
|
)
|
||||||
|
continuation.resume(returning: nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Logger.metadataProvider.info("Successfully retrieved item metadata")
|
||||||
|
continuation.resume(returning: files.first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,7 +66,10 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||||
}
|
}
|
||||||
|
|
||||||
func reload() async {
|
func reload() async {
|
||||||
guard let itemURL = itemURL else { return }
|
guard let itemURL else {
|
||||||
|
presentError("No item URL, cannot reload data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
guard let itemIdentifier = await withCheckedContinuation({
|
guard let itemIdentifier = await withCheckedContinuation({
|
||||||
(continuation: CheckedContinuation<NSFileProviderItemIdentifier?, Never>) -> Void in
|
(continuation: CheckedContinuation<NSFileProviderItemIdentifier?, Never>) -> Void in
|
||||||
NSFileProviderManager.getIdentifierForUserVisibleFile(
|
NSFileProviderManager.getIdentifierForUserVisibleFile(
|
||||||
|
@ -84,7 +87,9 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let connection = try await serviceConnection(url: itemURL)
|
let connection = try await serviceConnection(url: itemURL, interruptionHandler: {
|
||||||
|
Logger.sharesDataSource.error("Service connection interrupted")
|
||||||
|
})
|
||||||
guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier),
|
guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier),
|
||||||
let credentials = await connection.credentials() as? Dictionary<String, String>,
|
let credentials = await connection.credentials() as? Dictionary<String, String>,
|
||||||
let convertedAccount = Account(dictionary: credentials),
|
let convertedAccount = Account(dictionary: credentials),
|
||||||
|
@ -104,7 +109,11 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||||
presentError("Server does not support shares.")
|
presentError("Server does not support shares.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
itemMetadata = await fetchItemMetadata(itemRelativePath: serverPathString)
|
guard let kit else {
|
||||||
|
presentError("NextcloudKit instance is unavailable, cannot reload data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
itemMetadata = await fetchItemMetadata(itemRelativePath: serverPathString, kit: kit)
|
||||||
guard itemMetadata?.permissions.contains("R") == true else {
|
guard itemMetadata?.permissions.contains("R") == true else {
|
||||||
presentError("This file cannot be shared.")
|
presentError("This file cannot be shared.")
|
||||||
return
|
return
|
||||||
|
@ -118,25 +127,6 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func serviceConnection(url: URL) async throws -> FPUIExtensionService {
|
|
||||||
let services = try await FileManager().fileProviderServicesForItem(at: url)
|
|
||||||
guard let service = services[fpUiExtensionServiceName] else {
|
|
||||||
Logger.sharesDataSource.error("Couldn't get service, required service not present")
|
|
||||||
throw NSFileProviderError(.providerNotFound)
|
|
||||||
}
|
|
||||||
let connection: NSXPCConnection
|
|
||||||
connection = try await service.fileProviderConnection()
|
|
||||||
connection.remoteObjectInterface = NSXPCInterface(with: FPUIExtensionService.self)
|
|
||||||
connection.interruptionHandler = {
|
|
||||||
Logger.sharesDataSource.error("Service connection interrupted")
|
|
||||||
}
|
|
||||||
connection.resume()
|
|
||||||
guard let proxy = connection.remoteObjectProxy as? FPUIExtensionService else {
|
|
||||||
throw NSFileProviderError(.serverUnreachable)
|
|
||||||
}
|
|
||||||
return proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
private func fetch(
|
private func fetch(
|
||||||
itemIdentifier: NSFileProviderItemIdentifier, itemRelativePath: String
|
itemIdentifier: NSFileProviderItemIdentifier, itemRelativePath: String
|
||||||
) async -> [NKShare] {
|
) async -> [NKShare] {
|
||||||
|
@ -180,44 +170,6 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchItemMetadata(itemRelativePath: String) async -> NKFile? {
|
|
||||||
guard let kit = kit else {
|
|
||||||
presentError("Could not fetch item metadata as NextcloudKit instance is unavailable")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func slashlessPath(_ string: String) -> String {
|
|
||||||
var strCopy = string
|
|
||||||
if strCopy.hasPrefix("/") {
|
|
||||||
strCopy.removeFirst()
|
|
||||||
}
|
|
||||||
if strCopy.hasSuffix("/") {
|
|
||||||
strCopy.removeLast()
|
|
||||||
}
|
|
||||||
return strCopy
|
|
||||||
}
|
|
||||||
|
|
||||||
let nkCommon = kit.nkCommonInstance
|
|
||||||
let urlBase = slashlessPath(nkCommon.urlBase)
|
|
||||||
let davSuffix = slashlessPath(nkCommon.dav)
|
|
||||||
let userId = nkCommon.userId
|
|
||||||
let itemRelPath = slashlessPath(itemRelativePath)
|
|
||||||
|
|
||||||
let itemFullServerPath = "\(urlBase)/\(davSuffix)/files/\(userId)/\(itemRelPath)"
|
|
||||||
return await withCheckedContinuation { continuation in
|
|
||||||
kit.readFileOrFolder(serverUrlFileName: itemFullServerPath, depth: "0") {
|
|
||||||
account, files, data, error in
|
|
||||||
guard error == .success else {
|
|
||||||
self.presentError("Error getting item metadata: \(error.errorDescription)")
|
|
||||||
continuation.resume(returning: nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Logger.sharesDataSource.info("Successfully retrieved item metadata")
|
|
||||||
continuation.resume(returning: files.first)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentError(_ errorString: String) {
|
private func presentError(_ errorString: String) {
|
||||||
Logger.sharesDataSource.error("\(errorString, privacy: .public)")
|
Logger.sharesDataSource.error("\(errorString, privacy: .public)")
|
||||||
Task { @MainActor in self.uiDelegate?.showError(errorString) }
|
Task { @MainActor in self.uiDelegate?.showError(errorString) }
|
|
@ -25,6 +25,10 @@
|
||||||
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */; };
|
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */; };
|
||||||
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
|
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
|
||||||
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
|
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
|
||||||
|
537BD67A2C58D67800446ED0 /* LockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537BD6792C58D67800446ED0 /* LockViewController.swift */; };
|
||||||
|
537BD67C2C58D7B700446ED0 /* LockViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 537BD67B2C58D7B700446ED0 /* LockViewController.xib */; };
|
||||||
|
537BD6802C58F01B00446ED0 /* FileProviderCommunication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537BD67F2C58F01B00446ED0 /* FileProviderCommunication.swift */; };
|
||||||
|
537BD6822C58F72E00446ED0 /* MetadataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537BD6812C58F72E00446ED0 /* MetadataProvider.swift */; };
|
||||||
538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */; };
|
538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */; };
|
||||||
538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */; };
|
538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */; };
|
||||||
538E397627F4765000FA63D5 /* FileProviderExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 538E396727F4765000FA63D5 /* FileProviderExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
538E397627F4765000FA63D5 /* FileProviderExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 538E396727F4765000FA63D5 /* FileProviderExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
@ -163,6 +167,10 @@
|
||||||
537630922B85F4B00026BFAB /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
537630922B85F4B00026BFAB /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||||
537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionServiceSource.swift; sourceTree = "<group>"; };
|
537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionServiceSource.swift; sourceTree = "<group>"; };
|
||||||
537630962B860D920026BFAB /* FPUIExtensionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionService.swift; sourceTree = "<group>"; };
|
537630962B860D920026BFAB /* FPUIExtensionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionService.swift; sourceTree = "<group>"; };
|
||||||
|
537BD6792C58D67800446ED0 /* LockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockViewController.swift; sourceTree = "<group>"; };
|
||||||
|
537BD67B2C58D7B700446ED0 /* LockViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LockViewController.xib; sourceTree = "<group>"; };
|
||||||
|
537BD67F2C58F01B00446ED0 /* FileProviderCommunication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderCommunication.swift; sourceTree = "<group>"; };
|
||||||
|
537BD6812C58F72E00446ED0 /* MetadataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataProvider.swift; sourceTree = "<group>"; };
|
||||||
538E396727F4765000FA63D5 /* FileProviderExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
538E396727F4765000FA63D5 /* FileProviderExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
|
538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
|
||||||
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderExtension.swift; sourceTree = "<group>"; };
|
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderExtension.swift; sourceTree = "<group>"; };
|
||||||
|
@ -280,6 +288,31 @@
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
537BD6772C58D0C400446ED0 /* Sharing */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5374FD432B95EE1400C78D54 /* ShareController.swift */,
|
||||||
|
53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */,
|
||||||
|
53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */,
|
||||||
|
53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */,
|
||||||
|
531522812B8E01C6002E31BE /* ShareTableItemView.xib */,
|
||||||
|
53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */,
|
||||||
|
537630922B85F4B00026BFAB /* ShareViewController.swift */,
|
||||||
|
537630902B85F4980026BFAB /* ShareViewController.xib */,
|
||||||
|
53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */,
|
||||||
|
);
|
||||||
|
path = Sharing;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
537BD6782C58D0FC00446ED0 /* Locking */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
537BD6792C58D67800446ED0 /* LockViewController.swift */,
|
||||||
|
537BD67B2C58D7B700446ED0 /* LockViewController.xib */,
|
||||||
|
);
|
||||||
|
path = Locking;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
538E396827F4765000FA63D5 /* Frameworks */ = {
|
538E396827F4765000FA63D5 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -321,16 +354,11 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5376307B2B85E2E00026BFAB /* Extensions */,
|
5376307B2B85E2E00026BFAB /* Extensions */,
|
||||||
|
537BD6782C58D0FC00446ED0 /* Locking */,
|
||||||
|
537BD6772C58D0C400446ED0 /* Sharing */,
|
||||||
53B979802B84C81F002DA742 /* DocumentActionViewController.swift */,
|
53B979802B84C81F002DA742 /* DocumentActionViewController.swift */,
|
||||||
5374FD432B95EE1400C78D54 /* ShareController.swift */,
|
537BD67F2C58F01B00446ED0 /* FileProviderCommunication.swift */,
|
||||||
53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */,
|
537BD6812C58F72E00446ED0 /* MetadataProvider.swift */,
|
||||||
53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */,
|
|
||||||
53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */,
|
|
||||||
531522812B8E01C6002E31BE /* ShareTableItemView.xib */,
|
|
||||||
53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */,
|
|
||||||
537630922B85F4B00026BFAB /* ShareViewController.swift */,
|
|
||||||
537630902B85F4980026BFAB /* ShareViewController.xib */,
|
|
||||||
53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */,
|
|
||||||
53FE14572B8E3A7C006C4193 /* FileProviderUIExt.entitlements */,
|
53FE14572B8E3A7C006C4193 /* FileProviderUIExt.entitlements */,
|
||||||
53B979852B84C81F002DA742 /* Info.plist */,
|
53B979852B84C81F002DA742 /* Info.plist */,
|
||||||
);
|
);
|
||||||
|
@ -617,6 +645,7 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
537BD67C2C58D7B700446ED0 /* LockViewController.xib in Resources */,
|
||||||
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */,
|
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */,
|
||||||
537630912B85F4980026BFAB /* ShareViewController.xib in Resources */,
|
537630912B85F4980026BFAB /* ShareViewController.xib in Resources */,
|
||||||
);
|
);
|
||||||
|
@ -691,9 +720,11 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
537BD6822C58F72E00446ED0 /* MetadataProvider.swift in Sources */,
|
||||||
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */,
|
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */,
|
||||||
53FE14672B8F78B6006C4193 /* ShareOptionsView.swift in Sources */,
|
53FE14672B8F78B6006C4193 /* ShareOptionsView.swift in Sources */,
|
||||||
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */,
|
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */,
|
||||||
|
537BD67A2C58D67800446ED0 /* LockViewController.swift in Sources */,
|
||||||
53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */,
|
53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */,
|
||||||
53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */,
|
53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */,
|
||||||
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */,
|
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */,
|
||||||
|
@ -701,6 +732,7 @@
|
||||||
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */,
|
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */,
|
||||||
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */,
|
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */,
|
||||||
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */,
|
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */,
|
||||||
|
537BD6802C58F01B00446ED0 /* FileProviderCommunication.swift in Sources */,
|
||||||
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */,
|
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
Loading…
Reference in a new issue