diff --git a/LoopbackFS-Swift/LICENSE.txt b/LoopbackFS-Swift/LICENSE.txt index b11a31c..4a58915 100644 --- a/LoopbackFS-Swift/LICENSE.txt +++ b/LoopbackFS-Swift/LICENSE.txt @@ -1,5 +1,5 @@ Copyright 2017 KF Interactive GmbH -Copyright 2018-2023 Benjamin Fleischer +Copyright 2018-2025 Benjamin Fleischer Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/LoopbackFS-Swift/LoopbackFS.xcodeproj/project.pbxproj b/LoopbackFS-Swift/LoopbackFS.xcodeproj/project.pbxproj index a73ef7c..e1b3bcb 100644 --- a/LoopbackFS-Swift/LoopbackFS.xcodeproj/project.pbxproj +++ b/LoopbackFS-Swift/LoopbackFS.xcodeproj/project.pbxproj @@ -233,6 +233,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CODE_SIGN_FLAGS = "--timestamp"; @@ -287,6 +288,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CODE_SIGN_FLAGS = "--timestamp"; SDKROOT = macosx; @@ -312,7 +314,6 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = "io.macfuse.demo.loopbackfs-swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -338,7 +339,6 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; PRODUCT_BUNDLE_IDENTIFIER = "io.macfuse.demo.loopbackfs-swift"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/LoopbackFS-Swift/LoopbackFS/AppDelegate.swift b/LoopbackFS-Swift/LoopbackFS/AppDelegate.swift index 496910c..3ee98a4 100644 --- a/LoopbackFS-Swift/LoopbackFS/AppDelegate.swift +++ b/LoopbackFS-Swift/LoopbackFS/AppDelegate.swift @@ -4,16 +4,15 @@ // // Created by Gunnar Herzog on 27/01/2017. // Copyright © 2017 KF Interactive GmbH. All rights reserved. -// Copyright © 2019-2023 Benjamin Fleischer. All rights reserved. +// Copyright © 2019-2025 Benjamin Fleischer. All rights reserved. // import Cocoa let loopbackMountPath = "/Volumes/loop" -@NSApplicationMain +@main class AppDelegate: NSObject, NSApplicationDelegate { - @IBOutlet weak var window: NSWindow! private var notificationObservers: [NSObjectProtocol] = [] @@ -24,7 +23,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { private var userFileSystem: GMUserFileSystem? - func applicationDidFinishLaunching(_ aNotification: Notification) { + func applicationDidFinishLaunching(_ aNotification: Notification) { let panel = NSOpenPanel() panel.canChooseFiles = false panel.canChooseDirectories = true @@ -45,10 +44,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { } userFileSystem = GMUserFileSystem(delegate: self.loopFileSystem, isThreadSafe: false) - + // Do not use the 'native_xattr' mount-time option unless the underlying // file system supports native extended attributes. Typically, the user - // would be mounting an HFS+ directory through LoopbackFS, so we do want + // would be mounting an APFS directory through LoopbackFS, so we do want // this option in that case. userFileSystem!.mount(atPath: loopbackMountPath, withOptions: options) } diff --git a/LoopbackFS-Swift/LoopbackFS/LoopbackFS.swift b/LoopbackFS-Swift/LoopbackFS/LoopbackFS.swift index e9fb3c2..f0f018a 100644 --- a/LoopbackFS-Swift/LoopbackFS/LoopbackFS.swift +++ b/LoopbackFS-Swift/LoopbackFS/LoopbackFS.swift @@ -4,22 +4,21 @@ // // Created by Gunnar Herzog on 27/01/2017. // Copyright © 2017 KF Interactive GmbH. All rights reserved. -// Copyright © 2019-2020 Benjamin Fleischer. All rights reserved. +// Copyright © 2019-2025 Benjamin Fleischer. All rights reserved. // import Foundation -final class LoopbackFS: NSObject { +public final class LoopbackFS: NSObject { + private let rootPath: String - let rootPath: String - - init(rootPath: String) { + public init(rootPath: String) { self.rootPath = rootPath } // MARK: - Moving an Item - override func moveItem(atPath source: String!, toPath destination: String!, options: GMUserFileSystemMoveOption) throws { + public override func moveItem(atPath source: String, toPath destination: String, options: GMUserFileSystemMoveOption) throws { let sourcePath = (rootPath.appending(source) as NSString).utf8String! let destinationPath = (rootPath.appending(destination) as NSString).utf8String! @@ -47,7 +46,7 @@ final class LoopbackFS: NSObject { // MARK: - Removing an Item - override func removeDirectory(atPath path: String!) throws { + public override func removeDirectory(atPath path: String) throws { // We need to special-case directories here and use the bsd API since // NSFileManager will happily do a recursive remove :-( @@ -59,7 +58,7 @@ final class LoopbackFS: NSObject { } } - override func removeItem(atPath path: String!) throws { + public override func removeItem(atPath path: String) throws { let originalPath = rootPath.appending(path) return try FileManager.default.removeItem(atPath: originalPath) @@ -67,15 +66,13 @@ final class LoopbackFS: NSObject { // MARK: - Creating an Item - override func createDirectory(atPath path: String!, attributes: [AnyHashable : Any]! = [:]) throws { - guard let attributes = attributes as? [String: Any] else { throw NSError(posixErrorCode: EPERM) } - + public override func createDirectory(atPath path: String, attributes: [FileAttributeKey : Any] = [:]) throws { let originalPath = rootPath.appending(path) - try FileManager.default.createDirectory(atPath: originalPath, withIntermediateDirectories: false, attributes: convertToOptionalFileAttributeKeyDictionary(attributes)) + try FileManager.default.createDirectory(atPath: originalPath, withIntermediateDirectories: false, attributes: attributes) } - override func createFile(atPath path: String!, attributes: [AnyHashable : Any]! = [:], flags: Int32, userData: AutoreleasingUnsafeMutablePointer!) throws { + public override func createFile(atPath path: String, attributes: [FileAttributeKey : Any], flags: Int32, userData: AutoreleasingUnsafeMutablePointer) throws { guard let mode = attributes[FileAttributeKey.posixPermissions] as? mode_t else { throw NSError(posixErrorCode: EPERM) @@ -94,7 +91,7 @@ final class LoopbackFS: NSObject { // MARK: - Linking an Item - override func linkItem(atPath path: String!, toPath otherPath: String!) throws { + public override func linkItem(atPath path: String, toPath otherPath: String) throws { let originalPath = (rootPath.appending(path) as NSString).utf8String! let originalOtherPath = (rootPath.appending(otherPath) as NSString).utf8String! @@ -107,19 +104,19 @@ final class LoopbackFS: NSObject { // MARK: - Symbolic Links - override func createSymbolicLink(atPath path: String!, withDestinationPath otherPath: String!) throws { + public override func createSymbolicLink(atPath path: String, withDestinationPath otherPath: String) throws { let sourcePath = rootPath.appending(path) try FileManager.default.createSymbolicLink(atPath: sourcePath, withDestinationPath: otherPath) } - override func destinationOfSymbolicLink(atPath path: String!) throws -> String { + public override func destinationOfSymbolicLink(atPath path: String) throws -> String { let sourcePath = rootPath.appending(path) return try FileManager.default.destinationOfSymbolicLink(atPath: sourcePath) } // MARK: - File Contents - override func openFile(atPath path: String!, mode: Int32, userData: AutoreleasingUnsafeMutablePointer!) throws { + public override func openFile(atPath path: String, mode: Int32, userData: AutoreleasingUnsafeMutablePointer) throws { let originalPath = (rootPath.appending(path) as NSString).utf8String! let fileDescriptor = open(originalPath, mode) @@ -131,7 +128,7 @@ final class LoopbackFS: NSObject { userData.pointee = NSNumber(value: fileDescriptor) } - override func releaseFile(atPath path: String!, userData: Any!) { + public override func releaseFile(atPath path: String, userData: Any!) { guard let num = userData as? NSNumber else { return } @@ -140,7 +137,7 @@ final class LoopbackFS: NSObject { close(fileDescriptor) } - override func readFile(atPath path: String!, userData: Any!, buffer: UnsafeMutablePointer!, size: Int, offset: off_t, error: NSErrorPointer) -> Int32 { + public override func readFile(atPath path: String, userData: Any?, buffer: UnsafeMutablePointer, size: Int, offset: off_t, error: NSErrorPointer) -> Int32 { guard let num = userData as? NSNumber else { error?.pointee = NSError(posixErrorCode: EBADF) return -1 @@ -156,7 +153,7 @@ final class LoopbackFS: NSObject { return returnValue } - override func writeFile(atPath path: String!, userData: Any!, buffer: UnsafePointer!, size: Int, offset: off_t, error: NSErrorPointer) -> Int32 { + public override func writeFile(atPath path: String, userData: Any?, buffer: UnsafePointer, size: Int, offset: off_t, error: NSErrorPointer) -> Int32 { guard let num = userData as? NSNumber else { error?.pointee = NSError(posixErrorCode: EBADF) return -1 @@ -171,7 +168,7 @@ final class LoopbackFS: NSObject { return Int32(returnValue) } - override func preallocateFile(atPath path: String!, userData: Any!, options: Int32, offset: off_t, length: off_t) throws { + public override func preallocateFile(atPath path: String, userData: Any!, options: Int32, offset: off_t, length: off_t) throws { guard let num = userData as? NSNumber else { throw NSError(posixErrorCode: EBADF) } @@ -197,7 +194,7 @@ final class LoopbackFS: NSObject { } } - public override func exchangeDataOfItem(atPath path1: String!, withItemAtPath path2: String!) throws { + public override func exchangeDataOfItem(atPath path1: String, withItemAtPath path2: String) throws { let sourcePath = (rootPath.appending(path1) as NSString).utf8String! let destinationPath = (rootPath.appending(path2) as NSString).utf8String! @@ -209,19 +206,30 @@ final class LoopbackFS: NSObject { // MARK: - Directory Contents - override func contentsOfDirectory(atPath path: String!) throws -> [Any] { + public override func contentsOfDirectory(atPath path: String, includingAttributesForKeys keys: [FileAttributeKey]) throws -> [GMDirectoryEntry] { let originalPath = rootPath.appending(path) - return try FileManager.default.contentsOfDirectory(atPath: originalPath) + let contents = try FileManager.default.contentsOfDirectory(atPath: originalPath) + + var entries = [GMDirectoryEntry]() + for name in contents { + do { + let attributes = try FileManager.default.attributesOfItem(atPath: originalPath.appending("/\(name)")) + entries.append(GMDirectoryEntry(name: name, attributes: attributes)) + } catch { + // Skip entry + } + } + return entries } // MARK: - Getting and Setting Attributes - override func attributesOfItem(atPath path: String!, userData: Any!) throws -> [AnyHashable : Any] { + public override func attributesOfItem(atPath path: String, userData: Any?) throws -> [FileAttributeKey : Any] { let originalPath = rootPath.appending(path) return try FileManager.default.attributesOfItem(atPath: originalPath) } - override func attributesOfFileSystem(forPath path: String!) throws -> [AnyHashable : Any] { + public override func attributesOfFileSystem(forPath path: String) throws -> [FileAttributeKey : Any] { let originalPath = rootPath.appending(path) var attributes = try FileManager.default.attributesOfFileSystem(forPath: originalPath) @@ -242,20 +250,18 @@ final class LoopbackFS: NSObject { return attributes } - override func setAttributes(_ attributes: [AnyHashable : Any]!, ofItemAtPath path: String!, userData: Any!) throws { - guard let attribs = attributes as? [FileAttributeKey: Any] else { throw NSError(posixErrorCode: EINVAL) } - + public override func setAttributes(_ attributes: [FileAttributeKey : Any], ofItemAtPath path: String, userData: Any?) throws { let originalPath = rootPath.appending(path) if let pathPointer = (originalPath as NSString).utf8String { - if let offset = attributes[FileAttributeKey.size.rawValue] as? Int64 { + if let offset = attributes[FileAttributeKey.size] as? Int64 { let ret = truncate(pathPointer, offset) if ret < 0 { throw NSError(posixErrorCode: errno) } } - if let flags = attributes[kGMUserFileSystemFileFlagsKey] as? Int32 { + if let flags = attributes[FileAttributeKey(rawValue: kGMUserFileSystemFileFlagsKey)] as? Int32 { let rc = chflags(pathPointer, UInt32(flags)) if rc < 0 { throw NSError(posixErrorCode: errno) @@ -263,20 +269,16 @@ final class LoopbackFS: NSObject { } } - try FileManager.default.setAttributes(attribs, ofItemAtPath: originalPath) + try FileManager.default.setAttributes(attributes, ofItemAtPath: originalPath) } - override func setAttributes(_ attributes: [AnyHashable : Any]!, ofFileSystemAtPath path: String!) throws { + public override func setAttributes(_ attributes: [FileAttributeKey : Any], ofFileSystemAtPath path: String) throws { // Needed for kGMUserFileSystemVolumeSupportsSetVolumeNameKey } - + // MARK: - Extended Attributes - public override func extendedAttributesOfItem(atPath path: Any!) throws -> [Any] { - guard let path = path as? String else { - throw NSError(posixErrorCode: ENODEV) - } - + public override func extendedAttributesOfItem(atPath path: String) throws -> [String] { let originalUrl = URL(fileURLWithPath: rootPath.appending(path)) return try originalUrl.withUnsafeFileSystemRepresentation { fileSystemPath -> [String] in @@ -301,7 +303,7 @@ final class LoopbackFS: NSObject { } } - public override func value(ofExtendedAttribute name: String!, ofItemAtPath path: String!, position: off_t) throws -> Data { + public override func value(ofExtendedAttribute name: String, ofItemAtPath path: String, position: off_t) throws -> Data { let originalUrl = URL(fileURLWithPath: rootPath.appending(path)) return try originalUrl.withUnsafeFileSystemRepresentation { fileSystemPath -> Data in @@ -327,7 +329,7 @@ final class LoopbackFS: NSObject { } } - public override func setExtendedAttribute(_ name: String!, ofItemAtPath path: String!, value: Data!, position: off_t, options: Int32) throws { + public override func setExtendedAttribute(_ name: String, ofItemAtPath path: String, value: Data, position: off_t, options: Int32) throws { let originalUrl = URL(fileURLWithPath: rootPath.appending(path)) try originalUrl.withUnsafeFileSystemRepresentation { fileSystemPath in @@ -344,7 +346,7 @@ final class LoopbackFS: NSObject { } } - public override func removeExtendedAttribute(_ name: String!, ofItemAtPath path: String!) throws { + public override func removeExtendedAttribute(_ name: String, ofItemAtPath path: String) throws { let originalUrl = URL(fileURLWithPath: rootPath.appending(path)) try originalUrl.withUnsafeFileSystemRepresentation { fileSystemPath in @@ -353,9 +355,3 @@ final class LoopbackFS: NSObject { } } } - -// Helper function inserted by Swift 4.2 migrator. -fileprivate func convertToOptionalFileAttributeKeyDictionary(_ input: [String: Any]?) -> [FileAttributeKey: Any]? { - guard let input = input else { return nil } - return Dictionary(uniqueKeysWithValues: input.map { key, value in (FileAttributeKey(rawValue: key), value)}) -}