From a1b3f50dd707e3b0cfbada21bdbdfd7853b03158 Mon Sep 17 00:00:00 2001 From: "Alexander A. Kropotin" Date: Wed, 1 Sep 2021 18:15:46 +0500 Subject: [PATCH 1/4] feature(FileLinkManager.swift) implement adapter for FileManager util --- .../QuickSymlinkAction.swift | 40 --------- commons/link/FileLinkManager.swift | 82 +++++++++++++++++++ .../action}/CopyPathAction.swift | 0 .../action}/CreateLinkAction.swift | 15 ++-- .../action}/PasteLinkAction.swift | 14 ++-- commons/link/action/QuickSymlinkAction.swift | 14 ++++ .../action}/ReplaceWithLinkAction.swift | 15 ++-- commons/{FileSystem => path}/Path.swift | 0 .../{FileSystem => path}/ResourcePath.swift | 2 - quick-symlink-extension/FinderSync.swift | 6 +- quick-symlink.xcodeproj/project.pbxproj | 26 ++++-- 11 files changed, 137 insertions(+), 77 deletions(-) delete mode 100644 commons/QuickSymlinkActions/QuickSymlinkAction.swift create mode 100644 commons/link/FileLinkManager.swift rename commons/{QuickSymlinkActions => link/action}/CopyPathAction.swift (100%) rename commons/{QuickSymlinkActions => link/action}/CreateLinkAction.swift (57%) rename commons/{QuickSymlinkActions => link/action}/PasteLinkAction.swift (69%) create mode 100644 commons/link/action/QuickSymlinkAction.swift rename commons/{QuickSymlinkActions => link/action}/ReplaceWithLinkAction.swift (66%) rename commons/{FileSystem => path}/Path.swift (100%) rename commons/{FileSystem => path}/ResourcePath.swift (96%) diff --git a/commons/QuickSymlinkActions/QuickSymlinkAction.swift b/commons/QuickSymlinkActions/QuickSymlinkAction.swift deleted file mode 100644 index 12e81aa..0000000 --- a/commons/QuickSymlinkActions/QuickSymlinkAction.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// QuickSymlinkAction.swift -// quick-symlink -// -// Created by Alexander A. Kropotin on 15/07/2021. -// Copyright © 2021 Alexander A. Kropotin. All rights reserved. -// - -import Foundation - -public protocol QuickSymlinkAction { - - func execute(); -} - -internal extension QuickSymlinkAction { - - internal func getTargetPath(_ from: URL!, to: URL!) -> URL! { - let originSourceName = from.absoluteURL.deletingPathExtension().lastPathComponent; - let fileType = from.absoluteURL.pathExtension; - - var fileExtention = fileType; - if !fileType.isEmpty { - fileExtention = ".\(fileType)" - } - - var fileName = "\(originSourceName)\(fileExtention)"; - var counter = 1 - var targetPath = to; - targetPath = targetPath?.appendingPathComponent(fileName); - - while FileManager.default.fileExists(atPath: (targetPath?.path)!) { - fileName = "\(originSourceName)-\(counter)\(fileExtention)"; - counter += 1; - targetPath = to.appendingPathComponent(fileName); - } - - return targetPath!; - } -} diff --git a/commons/link/FileLinkManager.swift b/commons/link/FileLinkManager.swift new file mode 100644 index 0000000..f456fc3 --- /dev/null +++ b/commons/link/FileLinkManager.swift @@ -0,0 +1,82 @@ +// +// FileManagerAdapter.swift +// quick-symlink +// +// Created by Alexander A. Kropotin on 01/09/2021. +// Copyright © 2021 Alexander A. Kropotin. All rights reserved. +// + +import Foundation + +public protocol FileLinkManager { + + func linkWith(of: URL!, with: URL!); + + func replaceWith(of: URL!, with: URL!); +} + +public extension FileLinkManager { + + public func getTargetPath(_ from: URL!, to: URL!) -> URL! { + let originSourceName = from.absoluteURL.deletingPathExtension().lastPathComponent; + let fileType = from.absoluteURL.pathExtension; + + var fileExtention = fileType; + if !fileType.isEmpty { + fileExtention = ".\(fileType)" + } + + var fileName = "\(originSourceName)\(fileExtention)"; + var counter = 1 + var targetPath = to; + targetPath = targetPath?.appendingPathComponent(fileName); + + while FileManager.default.fileExists(atPath: (targetPath?.path)!) { + fileName = "\(originSourceName)-\(counter)\(fileExtention)"; + counter += 1; + targetPath = to.appendingPathComponent(fileName); + } + + return targetPath!; + } +} + +public class SoftLinkManager: FileLinkManager { + + public func linkWith(of: URL!, with: URL!) { + do { + try FileManager.default.createSymbolicLink(at: with!, withDestinationURL: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with?.deletingLastPathComponent())).toUrl()!); + } catch let error as NSError { + NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); + } + } + + public func replaceWith(of: URL!, with: URL!) { + do { + try FileManager.default.moveItem(at: with, to: of!); + try FileManager.default.createSymbolicLink(at: with, withDestinationURL: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with.deletingLastPathComponent())).toUrl()!); + } catch let error as NSError { + NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); + } + } +} + +public class HardLinkManager: FileLinkManager { + + public func linkWith(of: URL!, with: URL!) { + do { + try FileManager.default.linkItem(at: with!, to: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with?.deletingLastPathComponent())).toUrl()!); + } catch let error as NSError { + NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); + } + } + + public func replaceWith(of: URL!, with: URL!) { + do { + try FileManager.default.moveItem(at: with, to: of!); + try FileManager.default.linkItem(at: with, to: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with.deletingLastPathComponent())).toUrl()!); + } catch let error as NSError { + NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); + } + } +} diff --git a/commons/QuickSymlinkActions/CopyPathAction.swift b/commons/link/action/CopyPathAction.swift similarity index 100% rename from commons/QuickSymlinkActions/CopyPathAction.swift rename to commons/link/action/CopyPathAction.swift diff --git a/commons/QuickSymlinkActions/CreateLinkAction.swift b/commons/link/action/CreateLinkAction.swift similarity index 57% rename from commons/QuickSymlinkActions/CreateLinkAction.swift rename to commons/link/action/CreateLinkAction.swift index ac97835..2dcffed 100644 --- a/commons/QuickSymlinkActions/CreateLinkAction.swift +++ b/commons/link/action/CreateLinkAction.swift @@ -13,8 +13,11 @@ public class CreateLinkAction: QuickSymlinkAction { private var finderController: FIFinderSyncController; - public init() { + private var fileLinkManager: FileLinkManager; + + public init(fileLinkManager: FileLinkManager!) { self.finderController = FIFinderSyncController.default(); + self.fileLinkManager = fileLinkManager; } public func execute() { @@ -26,14 +29,8 @@ public class CreateLinkAction: QuickSymlinkAction { } for path in target { - let targetPath = self.getTargetPath(path, to: path.deletingLastPathComponent()); - - do { - try FileManager.default.createSymbolicLink(at: targetPath!, withDestinationURL: ResourcePath.of(url: path).relativize(to: ResourcePath.of(url: targetPath?.deletingLastPathComponent())).toUrl()!); - } catch let error as NSError { - NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); - } + let targetPath = self.fileLinkManager.getTargetPath(path, to: path.deletingLastPathComponent()); + self.fileLinkManager.linkWith(of: path, with: targetPath); } } } - diff --git a/commons/QuickSymlinkActions/PasteLinkAction.swift b/commons/link/action/PasteLinkAction.swift similarity index 69% rename from commons/QuickSymlinkActions/PasteLinkAction.swift rename to commons/link/action/PasteLinkAction.swift index 991fbbe..aee04bb 100644 --- a/commons/QuickSymlinkActions/PasteLinkAction.swift +++ b/commons/link/action/PasteLinkAction.swift @@ -13,8 +13,11 @@ public class PasteLinkAction: QuickSymlinkAction { private var finderController: FIFinderSyncController; - public init() { + private var fileLinkManager: FileLinkManager; + + public init(fileLinkManager: FileLinkManager!) { self.finderController = FIFinderSyncController.default(); + self.fileLinkManager = fileLinkManager; } public func execute() { @@ -35,13 +38,8 @@ public class PasteLinkAction: QuickSymlinkAction { let paths = pathsFromClipboard.components(separatedBy: ";"); for path in paths { let pathUrl = URL(fileURLWithPath: path); - let targetPath = self.getTargetPath(pathUrl, to: target); - - do { - try FileManager.default.createSymbolicLink(at: targetPath!, withDestinationURL: ResourcePath.of(url: pathUrl).relativize(to: ResourcePath.of(url: targetPath?.deletingLastPathComponent())).toUrl()!); - } catch let error as NSError { - NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); - } + let targetPath = self.fileLinkManager.getTargetPath(pathUrl, to: target); + self.fileLinkManager.linkWith(of: pathUrl, with: targetPath); } } } diff --git a/commons/link/action/QuickSymlinkAction.swift b/commons/link/action/QuickSymlinkAction.swift new file mode 100644 index 0000000..2b99d99 --- /dev/null +++ b/commons/link/action/QuickSymlinkAction.swift @@ -0,0 +1,14 @@ +// +// QuickSymlinkAction.swift +// quick-symlink +// +// Created by Alexander A. Kropotin on 15/07/2021. +// Copyright © 2021 Alexander A. Kropotin. All rights reserved. +// + +import Foundation + +public protocol QuickSymlinkAction { + + func execute(); +} diff --git a/commons/QuickSymlinkActions/ReplaceWithLinkAction.swift b/commons/link/action/ReplaceWithLinkAction.swift similarity index 66% rename from commons/QuickSymlinkActions/ReplaceWithLinkAction.swift rename to commons/link/action/ReplaceWithLinkAction.swift index d2a9c1b..f852cb8 100644 --- a/commons/QuickSymlinkActions/ReplaceWithLinkAction.swift +++ b/commons/link/action/ReplaceWithLinkAction.swift @@ -13,8 +13,11 @@ public class ReplaceWithLinkAction: QuickSymlinkAction { private var finderController: FIFinderSyncController; - public init() { + private var fileLinkManager: FileLinkManager; + + public init(fileLinkManager: FileLinkManager!) { self.finderController = FIFinderSyncController.default(); + self.fileLinkManager = fileLinkManager; } public func execute() { @@ -35,14 +38,8 @@ public class ReplaceWithLinkAction: QuickSymlinkAction { let paths = pathsFromClipboard.components(separatedBy: ";"); for path in paths { let pathUrl = URL(fileURLWithPath: path); - let targetPath = self.getTargetPath(pathUrl, to: target); - - do { - try FileManager.default.moveItem(at: pathUrl, to: targetPath!); - try FileManager.default.createSymbolicLink(at: pathUrl, withDestinationURL: ResourcePath.of(url: targetPath).relativize(to: ResourcePath.of(url: pathUrl.deletingLastPathComponent())).toUrl()!); - } catch let error as NSError { - NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); - } + let targetPath = self.fileLinkManager.getTargetPath(pathUrl, to: target); + self.fileLinkManager.replaceWith(of: pathUrl, with: targetPath); } } } diff --git a/commons/FileSystem/Path.swift b/commons/path/Path.swift similarity index 100% rename from commons/FileSystem/Path.swift rename to commons/path/Path.swift diff --git a/commons/FileSystem/ResourcePath.swift b/commons/path/ResourcePath.swift similarity index 96% rename from commons/FileSystem/ResourcePath.swift rename to commons/path/ResourcePath.swift index fa4fb6c..f05468c 100644 --- a/commons/FileSystem/ResourcePath.swift +++ b/commons/path/ResourcePath.swift @@ -50,8 +50,6 @@ public class ResourcePath: Path { destinationPath.appendPathComponent(pathFragment); } - //pathFragments!.append(contentsOf: targetPathFragments!); - return ResourcePath.of(url: destinationPath); } diff --git a/quick-symlink-extension/FinderSync.swift b/quick-symlink-extension/FinderSync.swift index 5b6626d..89e2ed9 100644 --- a/quick-symlink-extension/FinderSync.swift +++ b/quick-symlink-extension/FinderSync.swift @@ -13,9 +13,9 @@ class FinderSync: FIFinderSync { let quickSymlinkToolbarItemImage = NSImage(named:NSImage.Name(rawValue: "quick-symlink-toolbar-item-image")); let copyPathAction = CopyPathAction.init(); - let pasteLinkAction = PasteLinkAction.init(); - let replaceWithLinkAction = ReplaceWithLinkAction.init(); - let createSymlink = CreateLinkAction.init(); + let pasteLinkAction = PasteLinkAction.init(fileLinkManager: SoftLinkManager.init()); + let replaceWithLinkAction = ReplaceWithLinkAction.init(fileLinkManager: SoftLinkManager.init()); + let createSymlink = CreateLinkAction.init(fileLinkManager: SoftLinkManager.init()); override init() { super.init() diff --git a/quick-symlink.xcodeproj/project.pbxproj b/quick-symlink.xcodeproj/project.pbxproj index 1de801c..b59292c 100644 --- a/quick-symlink.xcodeproj/project.pbxproj +++ b/quick-symlink.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ A345C9C226A0B30F004FBF0F /* CopyPathAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9C126A0B30F004FBF0F /* CopyPathAction.swift */; }; A345C9C526A0B49C004FBF0F /* PasteLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9C426A0B49C004FBF0F /* PasteLinkAction.swift */; }; A345C9C826A0B552004FBF0F /* ReplaceWithLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9C726A0B552004FBF0F /* ReplaceWithLinkAction.swift */; }; + A36F9B0626DFAC27009E95CE /* FileLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36F9B0526DFAC27009E95CE /* FileLinkManager.swift */; }; + A36F9B0726DFB127009E95CE /* FileLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36F9B0526DFAC27009E95CE /* FileLinkManager.swift */; }; A3D93EB526A09B5A004E068D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A3D93EB726A09B5A004E068D /* Localizable.strings */; }; /* End PBXBuildFile section */ @@ -89,6 +91,7 @@ A345C9C126A0B30F004FBF0F /* CopyPathAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyPathAction.swift; sourceTree = ""; }; A345C9C426A0B49C004FBF0F /* PasteLinkAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteLinkAction.swift; sourceTree = ""; }; A345C9C726A0B552004FBF0F /* ReplaceWithLinkAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplaceWithLinkAction.swift; sourceTree = ""; }; + A36F9B0526DFAC27009E95CE /* FileLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLinkManager.swift; sourceTree = ""; }; A3D93EA426A08EDF004E068D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; A3D93EA826A0904F004E068D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; A3D93EA926A09053004E068D /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Main.strings"; sourceTree = ""; }; @@ -123,7 +126,7 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - A307B41426D21DBE002EEF58 /* QuickSymlinkActions */ = { + A307B41426D21DBE002EEF58 /* action */ = { isa = PBXGroup; children = ( A316477626B7B403001DD969 /* CreateLinkAction.swift */, @@ -132,16 +135,16 @@ A345C9C126A0B30F004FBF0F /* CopyPathAction.swift */, A345C9BE26A0B200004FBF0F /* QuickSymlinkAction.swift */, ); - path = QuickSymlinkActions; + path = action; sourceTree = ""; }; - A307B41526D21E23002EEF58 /* FileSystem */ = { + A307B41526D21E23002EEF58 /* path */ = { isa = PBXGroup; children = ( A307B41626D21E39002EEF58 /* Path.swift */, A307B41926D22116002EEF58 /* ResourcePath.swift */, ); - path = FileSystem; + path = path; sourceTree = ""; }; A307B42526D255FB002EEF58 /* quick-symlink-tests */ = { @@ -203,12 +206,21 @@ A345C9BD26A0B12C004FBF0F /* commons */ = { isa = PBXGroup; children = ( - A307B41526D21E23002EEF58 /* FileSystem */, - A307B41426D21DBE002EEF58 /* QuickSymlinkActions */, + A36F9B0426DFAA4B009E95CE /* link */, + A307B41526D21E23002EEF58 /* path */, ); path = commons; sourceTree = ""; }; + A36F9B0426DFAA4B009E95CE /* link */ = { + isa = PBXGroup; + children = ( + A307B41426D21DBE002EEF58 /* action */, + A36F9B0526DFAC27009E95CE /* FileLinkManager.swift */, + ); + path = link; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -370,6 +382,7 @@ A345C9BF26A0B200004FBF0F /* QuickSymlinkAction.swift in Sources */, A30B9AAB265CA63300ACAA63 /* AppDelegate.swift in Sources */, A345C9C526A0B49C004FBF0F /* PasteLinkAction.swift in Sources */, + A36F9B0626DFAC27009E95CE /* FileLinkManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -385,6 +398,7 @@ A316477826B7B403001DD969 /* CreateLinkAction.swift in Sources */, A30D4A4D26A0C18B00BA775B /* QuickSymlinkAction.swift in Sources */, A30D4A4A26A0C14400BA775B /* CopyPathAction.swift in Sources */, + A36F9B0726DFB127009E95CE /* FileLinkManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 19f9767ce3daab62c2da54292f1ccffa3863cfb6 Mon Sep 17 00:00:00 2001 From: "Alexander A. Kropotin" Date: Thu, 2 Sep 2021 18:40:06 +0500 Subject: [PATCH 2/4] fix(FinderSync.swift) fix replace symlink action --- commons/link/FileLinkManager.swift | 9 +- .../Assets.xcassets/Contents.json | 6 + .../Contents.json | 33 ++++ .../quick-symlink-app-icon-28.png | Bin 0 -> 2068 bytes .../quick-symlink-app-icon-29.png | Bin 0 -> 2068 bytes .../quick-symlink-app-icon-56.png | Bin 0 -> 4107 bytes .../quick-symlink-app-icon-57.png | Bin 0 -> 4107 bytes .../quick-symlink-app-icon-84.png | Bin 0 -> 6396 bytes hard-link-action-extension/FinderSync.swift | 140 ++++++++++++++++ hard-link-action-extension/Info.plist | 41 +++++ .../hard-link-action-extension.entitlements | 20 +++ .../Base.lproj/Localizable.strings | 10 +- quick-symlink-extension/FinderSync.swift | 6 +- quick-symlink-extension/Info.plist | 2 +- .../en-GB.lproj/Localizable.strings | 10 +- .../en.lproj/Localizable.strings | 10 +- .../quick_symlink_extension.entitlements | 5 - .../ru.lproj/Localizable.strings | 10 +- quick-symlink.xcodeproj/project.pbxproj | 157 ++++++++++++++++-- 19 files changed, 428 insertions(+), 31 deletions(-) create mode 100644 hard-link-action-extension/Assets.xcassets/Contents.json create mode 100644 hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/Contents.json create mode 100644 hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-28.png create mode 100644 hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-29.png create mode 100644 hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-56.png create mode 100644 hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-57.png create mode 100644 hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-84.png create mode 100644 hard-link-action-extension/FinderSync.swift create mode 100644 hard-link-action-extension/Info.plist create mode 100644 hard-link-action-extension/hard-link-action-extension.entitlements delete mode 100644 quick-symlink-extension/quick_symlink_extension.entitlements diff --git a/commons/link/FileLinkManager.swift b/commons/link/FileLinkManager.swift index f456fc3..70cfe5f 100644 --- a/commons/link/FileLinkManager.swift +++ b/commons/link/FileLinkManager.swift @@ -53,8 +53,9 @@ public class SoftLinkManager: FileLinkManager { public func replaceWith(of: URL!, with: URL!) { do { - try FileManager.default.moveItem(at: with, to: of!); - try FileManager.default.createSymbolicLink(at: with, withDestinationURL: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with.deletingLastPathComponent())).toUrl()!); + //FIXME: Add checking for existance of file & resolving this case with symply pastle link + try FileManager.default.moveItem(at: of, to: with); + try FileManager.default.createSymbolicLink(at: of, withDestinationURL: ResourcePath.of(url: with).relativize(to: ResourcePath.of(url: of.deletingLastPathComponent())).toUrl()!); } catch let error as NSError { NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); } @@ -65,7 +66,7 @@ public class HardLinkManager: FileLinkManager { public func linkWith(of: URL!, with: URL!) { do { - try FileManager.default.linkItem(at: with!, to: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with?.deletingLastPathComponent())).toUrl()!); + try FileManager.default.createSymbolicLink(at: with!, withDestinationURL: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with?.deletingLastPathComponent())).toUrl()!); } catch let error as NSError { NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); } @@ -73,7 +74,7 @@ public class HardLinkManager: FileLinkManager { public func replaceWith(of: URL!, with: URL!) { do { - try FileManager.default.moveItem(at: with, to: of!); + try FileManager.default.moveItem(at: of, to: with); try FileManager.default.linkItem(at: with, to: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with.deletingLastPathComponent())).toUrl()!); } catch let error as NSError { NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); diff --git a/hard-link-action-extension/Assets.xcassets/Contents.json b/hard-link-action-extension/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/hard-link-action-extension/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/Contents.json b/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/Contents.json new file mode 100644 index 0000000..1553d71 --- /dev/null +++ b/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/Contents.json @@ -0,0 +1,33 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "quick-symlink-app-icon-28.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "quick-symlink-app-icon-56.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "quick-symlink-app-icon-84.png", + "scale" : "3x" + }, + { + "idiom" : "mac", + "filename" : "quick-symlink-app-icon-29.png", + "scale" : "1x" + }, + { + "idiom" : "mac", + "filename" : "quick-symlink-app-icon-57.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-28.png b/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-28.png new file mode 100644 index 0000000000000000000000000000000000000000..84841b0e750d6fe03af2c26a4ab0094fe6ea5e8f GIT binary patch literal 2068 zcmY*adpr~B8y~SWw$n(G#Y}36&E3I9VJ_2R*kLDRHkZXr+ce}dmvT=<9G5yJbx0q_ zy%uuGs1dTFC<>8EoK(X~XR2Sn-|zi=p7(v;=lwq4@AJIxA8&^HK_@V1I|u*(fL)v& zJftgZ-4uY*nFXH62LNR9==S#RF820tcNR049v%b$AkV~~?sb(VZt1o)GwM_j;uO_3 zkoSU3k_#%66o7$1vcdMA2T%?y*ikQ3y@9UX@TzvMF;sM`2{j7bPn3I5op^C%7tLy< zXfP8KX=!`=ct3wOfft|Uo$ukj8QFWQI1_hsrB8M?_N0n`v|&<$|6qQkaS=ekT~pWM z*3`zS1k|sMbF;@wD>m_RbS@}wA?^0xrTO~<)ozV0&2t5a@gQHrmF%O#)3Not&Sxr5 zD6^BhX45Cqza*Cz$XJ&w^qymvkNrg`P3w9YU4-Um`+@}>2l~B2;H<1tHyj{U8vhe01yri&;A;jL?v_37q4sYq6 zp?{jpi1}cJdiU%Zis`A!6=+58Rqn8U>C+8!QShVkZ|67Y$j7qhv~q8iXp(I>*K6ZG z%Qj7Lm&Te0!O~aS6zNQ60{|$Ub(4v{VtY*bW@NgT566daz>3ObpeQtEU=S*r5h+Cj z0GMbismloBP~g#w@Cde5G#2pvMfech;r2{c5Znx9iZVplfZ%XA zhD8gu@^EnckuKd~5uqGTq!k*?<#JJ66BLsbf;O_Wv_u;kqm7LXqzD6cOazA#Z4klM z`A+g59)}<{l|_%_(3uhNbzVv!Gm3*nAl8L`j_-AH=)wP0ieUd}OWGiM{RC}oWZTX4TSlUwyeF2XU^!F6y4iK_wWo2cTr^EP6udg)_8_Y9(z6UTzDd)D`&C^ zC#P2Ja81!n5$YEz7(Jn}XWM?Hg94#S&dk<|J|pgH9$9EE+CzQSkU{+AsrVgMT)Gr5 ze#%}r-u_j82E1TU-F_hslyKuQ8UX2jJDPCg=vNi%r5VCkeZEbwQgMF=#LkE(hMc)y zru8~oHUQT&Bz#-xXxAmw>__a3*B1Dc^E5HxM!Li5SK7V-HG|9F!n z!<#@YNr7vz!N`l#SvQ-bJL#j=e>$4y5wgJ#XWWX0pJ9PvQ;whsJoFa_#oJ@`eHZDb zJiLZzxqqOaMhw?LT8Q(#vZ=JQ+gyWZSUrD>bm4kTvExjW46%Fp*YWssLms|5TR-7j z1b~==%$j!_w-#vI?Ffmprdd|J6D!n`I7Ezkm+@{a8#hpp- zF@9}zAXoA!C+rc2{3M8ca`WoLj4mW2w(rlY_jTlRdt5#!d^G5uugSPe zehmp~u3~sxI(}Iwv6Gnh-)?^cQ@dMthp?mUU^|rfC(1wOVT1bDb=%l?$qt$X7}ocHay$MWUwk{i}5_f!c>TEZDOP|&F|$ioe3hZ8nh@HBh7RgW94jdKsJxH@0YLSMTT|8d3ckPvrD2$tXQ(VZSuX8U>!^tBIxuy=abqNb;jvwJ3TY zO_Y};AA~~hg|^1nwJWf*rS?@3XOt2L9y|Fl!iH}2s^wU=4_wWnkdA-d2V<8{U2oeC zN;HX4D}>a&zT^Gb^%VFdo*j2MK*AMd+qMK|P6bvUOJPhh6;=!4U&rLUPm5_I97W23 r5)aG9H7i~8EoK(X~XR2Sn-|zi=p7(v;=lwq4@AJIxA8&^HK_@V1I|u*(fL)v& zJftgZ-4uY*nFXH62LNR9==S#RF820tcNR049v%b$AkV~~?sb(VZt1o)GwM_j;uO_3 zkoSU3k_#%66o7$1vcdMA2T%?y*ikQ3y@9UX@TzvMF;sM`2{j7bPn3I5op^C%7tLy< zXfP8KX=!`=ct3wOfft|Uo$ukj8QFWQI1_hsrB8M?_N0n`v|&<$|6qQkaS=ekT~pWM z*3`zS1k|sMbF;@wD>m_RbS@}wA?^0xrTO~<)ozV0&2t5a@gQHrmF%O#)3Not&Sxr5 zD6^BhX45Cqza*Cz$XJ&w^qymvkNrg`P3w9YU4-Um`+@}>2l~B2;H<1tHyj{U8vhe01yri&;A;jL?v_37q4sYq6 zp?{jpi1}cJdiU%Zis`A!6=+58Rqn8U>C+8!QShVkZ|67Y$j7qhv~q8iXp(I>*K6ZG z%Qj7Lm&Te0!O~aS6zNQ60{|$Ub(4v{VtY*bW@NgT566daz>3ObpeQtEU=S*r5h+Cj z0GMbismloBP~g#w@Cde5G#2pvMfech;r2{c5Znx9iZVplfZ%XA zhD8gu@^EnckuKd~5uqGTq!k*?<#JJ66BLsbf;O_Wv_u;kqm7LXqzD6cOazA#Z4klM z`A+g59)}<{l|_%_(3uhNbzVv!Gm3*nAl8L`j_-AH=)wP0ieUd}OWGiM{RC}oWZTX4TSlUwyeF2XU^!F6y4iK_wWo2cTr^EP6udg)_8_Y9(z6UTzDd)D`&C^ zC#P2Ja81!n5$YEz7(Jn}XWM?Hg94#S&dk<|J|pgH9$9EE+CzQSkU{+AsrVgMT)Gr5 ze#%}r-u_j82E1TU-F_hslyKuQ8UX2jJDPCg=vNi%r5VCkeZEbwQgMF=#LkE(hMc)y zru8~oHUQT&Bz#-xXxAmw>__a3*B1Dc^E5HxM!Li5SK7V-HG|9F!n z!<#@YNr7vz!N`l#SvQ-bJL#j=e>$4y5wgJ#XWWX0pJ9PvQ;whsJoFa_#oJ@`eHZDb zJiLZzxqqOaMhw?LT8Q(#vZ=JQ+gyWZSUrD>bm4kTvExjW46%Fp*YWssLms|5TR-7j z1b~==%$j!_w-#vI?Ffmprdd|J6D!n`I7Ezkm+@{a8#hpp- zF@9}zAXoA!C+rc2{3M8ca`WoLj4mW2w(rlY_jTlRdt5#!d^G5uugSPe zehmp~u3~sxI(}Iwv6Gnh-)?^cQ@dMthp?mUU^|rfC(1wOVT1bDb=%l?$qt$X7}ocHay$MWUwk{i}5_f!c>TEZDOP|&F|$ioe3hZ8nh@HBh7RgW94jdKsJxH@0YLSMTT|8d3ckPvrD2$tXQ(VZSuX8U>!^tBIxuy=abqNb;jvwJ3TY zO_Y};AA~~hg|^1nwJWf*rS?@3XOt2L9y|Fl!iH}2s^wU=4_wWnkdA-d2V<8{U2oeC zN;HX4D}>a&zT^Gb^%VFdo*j2MK*AMd+qMK|P6bvUOJPhh6;=!4U&rLUPm5_I97W23 r5)aG9H7i~C#C60U`7%MS3R?#F#)Jp(H>65$PSJ2I^tu}v-@jfO^xr+({RuL004S@J#F)gRpF0aroNcV zqLjD+0J2o1mX@i$mKLum#uI@=!2tk~Fp3PeWX0(;9}kg#35;Tf`b&idLr2oQWHJFgxN~+MTy0$t^Sy?1 z5-x$1Q+6dJQ6P_r#Yz||#BD#y+RNF^osQ=x!5yTI6TBx^0vm*KFg{sc9@i#M}FTH5;98r=oMJkdbs2cGxgK!3E?1sVWQ_6J|M zXt<9vuRj{)fd%`k@c)efU-*B*Ab#GzAwKRZ{I*7>yjq?ZIIkQ~7AVE9O2f;`tBiSo z0Gn&;{+oWWQ{i{@@$mwKKsX!@h`R;!#JGT@6%`dhQZgVJ8OaNTBsRdq$Jt-f11sB0Ml*ZIDuua62p{~w|Mj(_U(K_dQF$piauTNe$2{+xiMfl{FV za$lq>|A~T4Fi7}?@*jRxY309>|1b8hjxy+v`2Q*9pHBadUbL!8qYV1*wW-qFeJ5-O z0MNbG*VeFfrTU)bj&iWM+V|xf$??Yt>#X-(b0wqCV~s+Qs)YskkzjS@Zedf)@+FuW zpCF|sIUn~Lvz{KW;CtccyNop~Wnz?W8dl;bnvvIBuT&dwI%LWEq56g$YucZ7{dm09 zPMmpZq2wJW?wfK7c0mx&T-JsZ534_rikg(S@t4l?xxF}o{HI->w0F%F3amBFK7$=* zDFt?O5MQIG+C?GJ?Zc?J9rDb!!?&5&%MgQ0qqW$?Veahj8|bg7x+u8{;;Z2#%m4T>wI-Qg|6Vt zN=Z+)we?01B+NReqHN-A!5LGjpHPqrNXxr-&311iKt{bErt5*75^;?Ncs9quXqGhv zdv%GCFEEz-;)2IC!jgt&Zr=@ly!PydQrJZnC-vvkuDb&a-YPQ%woX%w`Ta~0Yr)Fi ztp^(-lb1Esi)7Z}(L4#CzZsjok`!p=Y$f6QxS>!J=Fl9le1R2!HJ6D7I1t!cL9Y`j zX!OZ*LR~g=R@ZP&2A+;{85cXYmLcqR46M-` zg+p1(i<>!3?kz*Qh#}pK4=w9@@S9VpDH?TOiB4a@Vm2di3>!F#dd0y&N;C_Hm7C1x=r!ZbF1cjCEYZ!D$uhSS@If+( z1115^z5{MpDMLEwhw*-S2GOPJ}i+uX!Fxj zt_I)}$_V9-MAJZ%&~3gPLD&Q+rPAX{g{1fXeP{6naa4FWC|pU(GH7u`lse!UqFg$HgOwcqX4&*$$Z z+N2w8%clAOWvb&Q(2gV;-avef+Me9?Zz-e&-+_bs>_L8Uq435cel08g0E)}JeaCLa zQ~9bPVw-Vs^4qOaWvQ~04SKCIFL8y~>(ND$DH#L^0G#<3pAqu9S7%qhM#uA>OL_P1jj+su<8#!xbv;0sWq3m!WzMu_`sBiHAZ^~nBX;q~oLSbCMVdqY zjiRPdemdlvYF{?;%PLa$6q;>X)d_;_V?B3LI&1dLn`BmWWs0LEPOgtHYw%CTax1zq z7GPe{KJmJsO`>ME<60Y)$-R$OX*4_3Oj`$GIj{O~?nKS7q!n{}T3};WL4T8}Y1)a^ zh}fn9jGiw*2bfN;lX!`&E$sD-@upD{UMxmsThu?xdZXEySYZau>9Q~%>6%j)N;*lR zrO-1SjHJ$9SLWI=Su^ylrj>|Q-*3$0%bwric|&FZV~JMN z-J6x6`{pk$moX8?r1-*J4OM_0&-I(c1x+z&@@zMAN^ZI_5RMg=B@11mwB$6drnjv< z0INVtWPYcuBEk+%F!-j!tE22E>4~DXjFbWP*ag7_Xx1gY+g9b#t0AWw+miFMj0;gm z6{6vHTx}xcgthljiw4d93=5ua2YxBl3-*ReQr`0=A(L;cZh9uJ{sAYwF9K$EvOD zo~Lgt_{*O5&On^sWUaWJ#r%iq!Gv7Hk-H59<~MZL-tddgq)zWpaz5?aZ_DKs`Ko)A zR8r}q#gHqxv|A?BHU2(aEkY5S1EJp)@ZIH3)&GG3|_0gvdji~ZxiG?mq&?;oTwNe)MN@?sS z*Qa7%)8#?^lvI-Rew;NK9etr9)JfLag#U;vp@WdoE{0v98WZFP2X^_#T1LR(=I$w9 zlL}>Y*^{?)RzIYD1gWy)iIN8di7e~`cM-7LRGtk)85nwp_a-RUnXlCGQN4|l%#K0L zx*R~O7@QJbr)gOuTn>UURQ$l0%g0hN)ct(mI?B6#Q#00DMG&n&XaF8#)&qB!z$6Lk zS*Me`)x!Dj2i7e8Cb;M*r1fEN>3BfdB8UZVBbuy&QHo#n$u+@9)MkZm9^Mr*@=EsV z#ktfJ7H^s|`90C7nn379r@($ovNK^sJsEf_|q zW5%QB;gJ+tYe#$|N9{wU$q~ObT?M=#qpvx5qvY`K&xRm;G)5wk%^j%7<6GBq*&9}w zLH4U3%W^Xn;PDKrmhwB z=LEM$uq5mHesZ%YtvXq1K<(VoY89P1$YTBJ1kb$x@XR@byfKwg=xV}ajTbi8P(Pav zpXHyK(A{gtx@WxJGVJV3Bd&gU%G2~y>o(-$Xd-j}yj8N3$y?6Zl6TBpqIq9t zh~VnlC+-Zhp`hQ>;2%eA$K^IdHt*M5`V@O@9|(OS4)N&>=&Q!z+P+%H2mZ9vf~4j~ zN>b_rl3e>8nWp$z4hf+Px}e^r!IKke$v6d@P{yxTR~1xhs{& zi6ifg_PaJsl2v&E^PAQ_MDTt(n{DRAT*Kj&Gzp)p@rz6KtF|0P)lkKLillZnrR5tM zGbu1;LyeW9N*N1ImWuU3`SY)>==1fa>94i3^73gC454=9-Ub$yxk)W$#fB59RdlKji?ER)XBg4*72<=+F5J?!Sps z^;X!+bUH_@gzlUMAE7&M_~bsKSG$#0$DxF5-|L1z*WA`NzHO0OzmBK!?5=)NS|758 zp3M%t-+3HVJpKDEY+?)pDXAj#Dt*+g8{fX3Ur%gZ4Fpd08@E`)MxShqJ5)^07>Rx; zo2HPcoR&cGiJ4&9lfxy0oRjv&_Sw@go+23P=?I-QP8N&b zx}%^vutOIOr8q!FP1&Euu(mqY^=$NA#vl&z!$(?~W7L+@*yl@UYE>iN+%PP<-YNRR z?#TMwOQ)5aFh9fMa7s5wa zX`R}ssDvwb0@+u_W$vVncCdG6MBLt1XLOJ$y_v5|UjD7rSv{P_>+>lkZSYu*d{ E52-t`V*mgE literal 0 HcmV?d00001 diff --git a/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-57.png b/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-57.png new file mode 100644 index 0000000000000000000000000000000000000000..be4fb4c957f93ae4c182c754100ba48cef486c43 GIT binary patch literal 4107 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 E52-t`V*mgE literal 0 HcmV?d00001 diff --git a/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-84.png b/hard-link-action-extension/Assets.xcassets/quick-symlink-toolbar-item-image.imageset/quick-symlink-app-icon-84.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbe65788cc7fe3592accb608870fce74ee48729 GIT binary patch literal 6396 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 Ar2qf` literal 0 HcmV?d00001 diff --git a/hard-link-action-extension/FinderSync.swift b/hard-link-action-extension/FinderSync.swift new file mode 100644 index 0000000..aede09e --- /dev/null +++ b/hard-link-action-extension/FinderSync.swift @@ -0,0 +1,140 @@ +// +// FinderSync.swift +// hard-link-action-extension +// +// Created by Alexander A. Kropotin on 02/09/2021. +// Copyright © 2021 Alexander A. Kropotin. All rights reserved. +// + +import Cocoa +import FinderSync + +class FinderSync: FIFinderSync { + + let quickSymlinkToolbarItemImage = NSImage(named:NSImage.Name(rawValue: "quick-symlink-toolbar-item-image")); + + let copyPathAction = CopyPathAction.init(); + let pasteLinkAction = PasteLinkAction.init(fileLinkManager: HardLinkManager.init()); + let replaceWithLinkAction = ReplaceWithLinkAction.init(fileLinkManager: HardLinkManager.init()); + let createSymlink = CreateLinkAction.init(fileLinkManager: HardLinkManager.init()); + + override init() { + super.init() + + NSLog("FinderSync() launched from %@", Bundle.main.bundlePath as NSString) + + // Set up the directory we are syncing. + let finderSync = FIFinderSyncController.default(); + + // Shared group preferences required + _ = UserDefaults.init(suiteName: "org.ololx.quick-symlink") + + if let mountedVolumes = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: nil, + options: .skipHiddenVolumes) { + finderSync.directoryURLs = Set(mountedVolumes); + } + + let notificationCenter = NSWorkspace.shared.notificationCenter + notificationCenter.addObserver(forName: NSWorkspace.didMountNotification, object: nil, queue: .main) { + (notification) in + if let volumeURL = notification.userInfo?[NSWorkspace.volumeURLUserInfoKey] as? URL { + finderSync.directoryURLs.insert(volumeURL); + } + } + } + + // MARK: - Primary Finder Sync protocol methods + + override func beginObservingDirectory(at url: URL) { + // The user is now seeing the container's contents. + // If they see it in more than one view at a time, we're only told once. + NSLog("beginObservingDirectoryAtURL: %@", url.path as NSString) + } + + + override func endObservingDirectory(at url: URL) { + // The user is no longer seeing the container's contents. + NSLog("endObservingDirectoryAtURL: %@", url.path as NSString) + } + + // MARK: - Menu and toolbar item support + + override var toolbarItemName: String { + return NSLocalizedString("HARD_LINK_ACTIONS_EXTENTION_NAME", comment: ""); + } + + override var toolbarItemToolTip: String { + return NSLocalizedString("HARD_LINK_ACTIONS_EXTENTION_TOOL_TIP", comment: ""); + } + + override var toolbarItemImage: NSImage { + return quickSymlinkToolbarItemImage!; + } + + override func menu(for menuKind: FIMenuKind) -> NSMenu { + // Produce a menu for the extension (to be shown when right clicking a folder in Finder) + let quickSymlinkMenu = NSMenu(title: ""); + quickSymlinkMenu.addItem( + withTitle: NSLocalizedString("CREATE_LINK_ACTION_NAME", comment: ""), + action: #selector(createSymlink(_:)), + keyEquivalent: "" + ); + + quickSymlinkMenu.addItem( + withTitle: NSLocalizedString("COPY_PATH_ACTION_NAME", comment: ""), + action: #selector(copyPathToClipboard(_:)), + keyEquivalent: "" + ); + + let pastleSymlinkFromClipboardMenuItem = NSMenuItem.init( + title: NSLocalizedString("PASTE_LINK_ACTION_NAME", comment: ""), + action: #selector(pastleSymlinkFromClipboard(_:)), + keyEquivalent: "" + ); + quickSymlinkMenu.addItem(pastleSymlinkFromClipboardMenuItem); + + let replaceFileWithSymlinkFromClipboardMenuItem = NSMenuItem.init( + title: NSLocalizedString("REPLACE_WITH_LINK_ACTION_NAME", comment: ""), + action: #selector(replaceFileWithSymlinkFromClipboard(_:)), + keyEquivalent: "" + ); + quickSymlinkMenu.addItem(replaceFileWithSymlinkFromClipboardMenuItem); + + if (NSPasteboard.init(name: NSPasteboard.Name.init(rawValue: "qs")).string(forType: NSPasteboard.PasteboardType.string) ?? "").isEmpty { + pastleSymlinkFromClipboardMenuItem.isEnabled = false; + replaceFileWithSymlinkFromClipboardMenuItem.isEnabled = false; + } + + if menuKind.rawValue == 3 { + return quickSymlinkMenu; + } else { + let quickSymLinkMainMenu = NSMenu(title: ""); + let quickSymlinkMenuItem = NSMenuItem( + title: NSLocalizedString("HARD_LINK_ACTIONS_EXTENTION_NAME", comment: ""), + action: nil, + keyEquivalent: "" + ); + quickSymLinkMainMenu.setSubmenu(quickSymlinkMenu, for: quickSymlinkMenuItem); + quickSymLinkMainMenu.addItem(quickSymlinkMenuItem); + + return quickSymLinkMainMenu; + } + } + + @IBAction func copyPathToClipboard(_ sender: AnyObject?) { + self.copyPathAction.execute(); + } + + @IBAction func replaceFileWithSymlinkFromClipboard(_ sender: AnyObject?) { + self.replaceWithLinkAction.execute(); + } + + @IBAction func pastleSymlinkFromClipboard(_ sender: AnyObject?) { + self.pasteLinkAction.execute(); + } + + @IBAction func createSymlink(_ sender: AnyObject?) { + self.createSymlink.execute(); + } +} + diff --git a/hard-link-action-extension/Info.plist b/hard-link-action-extension/Info.plist new file mode 100644 index 0000000..e03c840 --- /dev/null +++ b/hard-link-action-extension/Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + hard-link-action-extension + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + LSUIElement + + NSExtension + + NSExtensionAttributes + + NSExtensionPointIdentifier + com.apple.FinderSync + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).FinderSync + + NSHumanReadableCopyright + Copyright © 2021 Alexander A. Kropotin. All rights reserved. + NSPrincipalClass + NSApplication + + diff --git a/hard-link-action-extension/hard-link-action-extension.entitlements b/hard-link-action-extension/hard-link-action-extension.entitlements new file mode 100644 index 0000000..604e60f --- /dev/null +++ b/hard-link-action-extension/hard-link-action-extension.entitlements @@ -0,0 +1,20 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.folders.user-selected.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.temporary-exception.files.absolute-path.read-write + + / + + com.apple.security.temporary-exception.files.home-relative-path.read-write + + / + + + diff --git a/quick-symlink-extension/Base.lproj/Localizable.strings b/quick-symlink-extension/Base.lproj/Localizable.strings index 2a48615..1de47e6 100644 --- a/quick-symlink-extension/Base.lproj/Localizable.strings +++ b/quick-symlink-extension/Base.lproj/Localizable.strings @@ -7,10 +7,16 @@ */ /* Class = "NSMenuItem"; */ -"EXTENTION_NAME" = "Quick Symlink"; +"SOFT_LINK_ACTIONS_EXTENTION_NAME" = "Soft link actions"; /* Class = "NSMenuItem"; */ -"EXTENTION_TOOL_TIP" = "Create symbolic links for selected files and folders"; +"SOFT_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Create symbolic links for selected files and folders"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_NAME" = "Hard link actions"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Create hard links for selected files and folders"; /* Class = "NSMenuItem"; */ "COPY_PATH_ACTION_NAME" = "Copy path from here"; diff --git a/quick-symlink-extension/FinderSync.swift b/quick-symlink-extension/FinderSync.swift index 89e2ed9..fe10be9 100644 --- a/quick-symlink-extension/FinderSync.swift +++ b/quick-symlink-extension/FinderSync.swift @@ -59,11 +59,11 @@ class FinderSync: FIFinderSync { // MARK: - Menu and toolbar item support override var toolbarItemName: String { - return NSLocalizedString("EXTENTION_NAME", comment: ""); + return NSLocalizedString("SOFT_LINK_ACTIONS_EXTENTION_NAME", comment: ""); } override var toolbarItemToolTip: String { - return NSLocalizedString("EXTENTION_TOOL_TIP", comment: ""); + return NSLocalizedString("SOFT_LINK_ACTIONS_EXTENTION_TOOL_TIP", comment: ""); } override var toolbarItemImage: NSImage { @@ -109,7 +109,7 @@ class FinderSync: FIFinderSync { } else { let quickSymLinkMainMenu = NSMenu(title: ""); let quickSymlinkMenuItem = NSMenuItem( - title: NSLocalizedString("EXTENTION_NAME", comment: ""), + title: NSLocalizedString("SOFT_LINK_ACTIONS_EXTENTION_NAME", comment: ""), action: nil, keyEquivalent: "" ); diff --git a/quick-symlink-extension/Info.plist b/quick-symlink-extension/Info.plist index 8f22540..99c4c3b 100644 --- a/quick-symlink-extension/Info.plist +++ b/quick-symlink-extension/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en_GB + $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName quick-symlink-extension CFBundleExecutable diff --git a/quick-symlink-extension/en-GB.lproj/Localizable.strings b/quick-symlink-extension/en-GB.lproj/Localizable.strings index 2a48615..1de47e6 100644 --- a/quick-symlink-extension/en-GB.lproj/Localizable.strings +++ b/quick-symlink-extension/en-GB.lproj/Localizable.strings @@ -7,10 +7,16 @@ */ /* Class = "NSMenuItem"; */ -"EXTENTION_NAME" = "Quick Symlink"; +"SOFT_LINK_ACTIONS_EXTENTION_NAME" = "Soft link actions"; /* Class = "NSMenuItem"; */ -"EXTENTION_TOOL_TIP" = "Create symbolic links for selected files and folders"; +"SOFT_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Create symbolic links for selected files and folders"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_NAME" = "Hard link actions"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Create hard links for selected files and folders"; /* Class = "NSMenuItem"; */ "COPY_PATH_ACTION_NAME" = "Copy path from here"; diff --git a/quick-symlink-extension/en.lproj/Localizable.strings b/quick-symlink-extension/en.lproj/Localizable.strings index 2a48615..1de47e6 100644 --- a/quick-symlink-extension/en.lproj/Localizable.strings +++ b/quick-symlink-extension/en.lproj/Localizable.strings @@ -7,10 +7,16 @@ */ /* Class = "NSMenuItem"; */ -"EXTENTION_NAME" = "Quick Symlink"; +"SOFT_LINK_ACTIONS_EXTENTION_NAME" = "Soft link actions"; /* Class = "NSMenuItem"; */ -"EXTENTION_TOOL_TIP" = "Create symbolic links for selected files and folders"; +"SOFT_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Create symbolic links for selected files and folders"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_NAME" = "Hard link actions"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Create hard links for selected files and folders"; /* Class = "NSMenuItem"; */ "COPY_PATH_ACTION_NAME" = "Copy path from here"; diff --git a/quick-symlink-extension/quick_symlink_extension.entitlements b/quick-symlink-extension/quick_symlink_extension.entitlements deleted file mode 100644 index 0c67376..0000000 --- a/quick-symlink-extension/quick_symlink_extension.entitlements +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/quick-symlink-extension/ru.lproj/Localizable.strings b/quick-symlink-extension/ru.lproj/Localizable.strings index 361f830..18900c5 100644 --- a/quick-symlink-extension/ru.lproj/Localizable.strings +++ b/quick-symlink-extension/ru.lproj/Localizable.strings @@ -7,10 +7,16 @@ */ /* Class = "NSMenuItem"; */ -"EXTENTION_NAME" = "Быстрый симлинк"; +"SOFT_LINK_ACTIONS_EXTENTION_NAME" = "Действия с симлинками"; /* Class = "NSMenuItem"; */ -"EXTENTION_TOOL_TIP" = "Создать символьные ссылки для выбранных файлов и папок"; +"SOFT_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Создать символьные ссылки для выбранных файлов и папок"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_NAME" = "Действия с псевдонимами"; + +/* Class = "NSMenuItem"; */ +"HARD_LINK_ACTIONS_EXTENTION_TOOL_TIP" = "Создать псевдонимы для выбранных файлов и папок"; /* Class = "NSMenuItem"; */ "COPY_PATH_ACTION_NAME" = "Скопировать путь отсюда"; diff --git a/quick-symlink.xcodeproj/project.pbxproj b/quick-symlink.xcodeproj/project.pbxproj index b59292c..9853a2e 100644 --- a/quick-symlink.xcodeproj/project.pbxproj +++ b/quick-symlink.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ A30D4A4D26A0C18B00BA775B /* QuickSymlinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9BE26A0B200004FBF0F /* QuickSymlinkAction.swift */; }; A316477726B7B403001DD969 /* CreateLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A316477626B7B403001DD969 /* CreateLinkAction.swift */; }; A316477826B7B403001DD969 /* CreateLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A316477626B7B403001DD969 /* CreateLinkAction.swift */; }; + A31904A426E104FA00D7F69D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A31904A326E104FA00D7F69D /* Assets.xcassets */; }; A32EE8E0265D4D05008648AA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A32EE8DF265D4D05008648AA /* Assets.xcassets */; }; A345C9BF26A0B200004FBF0F /* QuickSymlinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9BE26A0B200004FBF0F /* QuickSymlinkAction.swift */; }; A345C9C226A0B30F004FBF0F /* CopyPathAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9C126A0B30F004FBF0F /* CopyPathAction.swift */; }; @@ -34,6 +35,17 @@ A36F9B0626DFAC27009E95CE /* FileLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36F9B0526DFAC27009E95CE /* FileLinkManager.swift */; }; A36F9B0726DFB127009E95CE /* FileLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36F9B0526DFAC27009E95CE /* FileLinkManager.swift */; }; A3D93EB526A09B5A004E068D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A3D93EB726A09B5A004E068D /* Localizable.strings */; }; + A3E4D35526E0FD2900C9F175 /* FinderSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3E4D35426E0FD2900C9F175 /* FinderSync.swift */; }; + A3E4D35A26E0FD2900C9F175 /* hard-link-action-extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = A3E4D35226E0FD2900C9F175 /* hard-link-action-extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + A3E4D35E26E0FEE700C9F175 /* FileLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A36F9B0526DFAC27009E95CE /* FileLinkManager.swift */; }; + A3E4D35F26E0FEEA00C9F175 /* QuickSymlinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9BE26A0B200004FBF0F /* QuickSymlinkAction.swift */; }; + A3E4D36026E0FEED00C9F175 /* CopyPathAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9C126A0B30F004FBF0F /* CopyPathAction.swift */; }; + A3E4D36126E0FEEF00C9F175 /* PasteLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9C426A0B49C004FBF0F /* PasteLinkAction.swift */; }; + A3E4D36226E0FEF200C9F175 /* ReplaceWithLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A345C9C726A0B552004FBF0F /* ReplaceWithLinkAction.swift */; }; + A3E4D36326E0FEF400C9F175 /* CreateLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A316477626B7B403001DD969 /* CreateLinkAction.swift */; }; + A3E4D36426E0FF8100C9F175 /* ResourcePath.swift in Sources */ = {isa = PBXBuildFile; fileRef = A307B41926D22116002EEF58 /* ResourcePath.swift */; }; + A3E4D36526E0FF8600C9F175 /* Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = A307B41626D21E39002EEF58 /* Path.swift */; }; + A3E4D36626E101B000C9F175 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A3D93EB726A09B5A004E068D /* Localizable.strings */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -51,6 +63,13 @@ remoteGlobalIDString = A30B9ABD265CA68900ACAA63; remoteInfo = "quick-symlink-extension"; }; + A3E4D35826E0FD2900C9F175 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A30B9A9F265CA63300ACAA63 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A3E4D35126E0FD2900C9F175; + remoteInfo = "hard-link-action-extension"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -60,6 +79,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + A3E4D35A26E0FD2900C9F175 /* hard-link-action-extension.appex in Embed App Extensions */, A30B9AC6265CA68900ACAA63 /* quick-symlink-extension.appex in Embed App Extensions */, ); name = "Embed App Extensions"; @@ -83,9 +103,9 @@ A30B9ABE265CA68900ACAA63 /* quick-symlink-extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "quick-symlink-extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; A30B9AC0265CA68900ACAA63 /* FinderSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderSync.swift; sourceTree = ""; }; A30B9AC2265CA68900ACAA63 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A30B9AC3265CA68900ACAA63 /* quick_symlink_extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = quick_symlink_extension.entitlements; sourceTree = ""; }; A30B9ACB265CA78600ACAA63 /* quick-symlink-extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "quick-symlink-extension.entitlements"; sourceTree = ""; }; A316477626B7B403001DD969 /* CreateLinkAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLinkAction.swift; sourceTree = ""; }; + A31904A326E104FA00D7F69D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A32EE8DF265D4D05008648AA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A345C9BE26A0B200004FBF0F /* QuickSymlinkAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSymlinkAction.swift; sourceTree = ""; }; A345C9C126A0B30F004FBF0F /* CopyPathAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyPathAction.swift; sourceTree = ""; }; @@ -99,6 +119,10 @@ A3D93EB826A09B61004E068D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; A3D93EB926A09B72004E068D /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; }; A3D93EBA26A09B73004E068D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + A3E4D35226E0FD2900C9F175 /* hard-link-action-extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "hard-link-action-extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + A3E4D35426E0FD2900C9F175 /* FinderSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderSync.swift; sourceTree = ""; }; + A3E4D35626E0FD2900C9F175 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A3E4D35726E0FD2900C9F175 /* hard-link-action-extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "hard-link-action-extension.entitlements"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -123,6 +147,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A3E4D34F26E0FD2900C9F175 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -164,6 +195,7 @@ A30B9AA9265CA63300ACAA63 /* quick-symlink */, A30B9ABF265CA68900ACAA63 /* quick-symlink-extension */, A307B42526D255FB002EEF58 /* quick-symlink-tests */, + A3E4D35326E0FD2900C9F175 /* hard-link-action-extension */, A30B9AA8265CA63300ACAA63 /* Products */, ); sourceTree = ""; @@ -174,6 +206,7 @@ A30B9AA7265CA63300ACAA63 /* quick-symlink.app */, A30B9ABE265CA68900ACAA63 /* quick-symlink-extension.appex */, A307B42426D255FB002EEF58 /* quick-symlink-tests.xctest */, + A3E4D35226E0FD2900C9F175 /* hard-link-action-extension.appex */, ); name = Products; sourceTree = ""; @@ -198,7 +231,6 @@ A30B9AC0265CA68900ACAA63 /* FinderSync.swift */, A32EE8DF265D4D05008648AA /* Assets.xcassets */, A30B9AC2265CA68900ACAA63 /* Info.plist */, - A30B9AC3265CA68900ACAA63 /* quick_symlink_extension.entitlements */, ); path = "quick-symlink-extension"; sourceTree = ""; @@ -221,6 +253,17 @@ path = link; sourceTree = ""; }; + A3E4D35326E0FD2900C9F175 /* hard-link-action-extension */ = { + isa = PBXGroup; + children = ( + A3E4D35426E0FD2900C9F175 /* FinderSync.swift */, + A3E4D35626E0FD2900C9F175 /* Info.plist */, + A3E4D35726E0FD2900C9F175 /* hard-link-action-extension.entitlements */, + A31904A326E104FA00D7F69D /* Assets.xcassets */, + ); + path = "hard-link-action-extension"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -255,6 +298,7 @@ ); dependencies = ( A30B9AC5265CA68900ACAA63 /* PBXTargetDependency */, + A3E4D35926E0FD2900C9F175 /* PBXTargetDependency */, ); name = "quick-symlink"; productName = "quick-symlink"; @@ -278,6 +322,23 @@ productReference = A30B9ABE265CA68900ACAA63 /* quick-symlink-extension.appex */; productType = "com.apple.product-type.app-extension"; }; + A3E4D35126E0FD2900C9F175 /* hard-link-action-extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = A3E4D35B26E0FD2900C9F175 /* Build configuration list for PBXNativeTarget "hard-link-action-extension" */; + buildPhases = ( + A3E4D34E26E0FD2900C9F175 /* Sources */, + A3E4D34F26E0FD2900C9F175 /* Frameworks */, + A3E4D35026E0FD2900C9F175 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "hard-link-action-extension"; + productName = "hard-link-action-extension"; + productReference = A3E4D35226E0FD2900C9F175 /* hard-link-action-extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -306,6 +367,10 @@ }; }; }; + A3E4D35126E0FD2900C9F175 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = A30B9AA2265CA63300ACAA63 /* Build configuration list for PBXProject "quick-symlink" */; @@ -326,6 +391,7 @@ A30B9AA6265CA63300ACAA63 /* quick-symlink */, A30B9ABD265CA68900ACAA63 /* quick-symlink-extension */, A307B42326D255FB002EEF58 /* quick-symlink-tests */, + A3E4D35126E0FD2900C9F175 /* hard-link-action-extension */, ); }; /* End PBXProject section */ @@ -356,6 +422,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A3E4D35026E0FD2900C9F175 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A31904A426E104FA00D7F69D /* Assets.xcassets in Resources */, + A3E4D36626E101B000C9F175 /* Localizable.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -402,6 +477,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A3E4D34E26E0FD2900C9F175 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A3E4D36226E0FEF200C9F175 /* ReplaceWithLinkAction.swift in Sources */, + A3E4D36326E0FEF400C9F175 /* CreateLinkAction.swift in Sources */, + A3E4D36426E0FF8100C9F175 /* ResourcePath.swift in Sources */, + A3E4D35526E0FD2900C9F175 /* FinderSync.swift in Sources */, + A3E4D36026E0FEED00C9F175 /* CopyPathAction.swift in Sources */, + A3E4D35F26E0FEEA00C9F175 /* QuickSymlinkAction.swift in Sources */, + A3E4D36126E0FEEF00C9F175 /* PasteLinkAction.swift in Sources */, + A3E4D36526E0FF8600C9F175 /* Path.swift in Sources */, + A3E4D35E26E0FEE700C9F175 /* FileLinkManager.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -415,6 +506,11 @@ target = A30B9ABD265CA68900ACAA63 /* quick-symlink-extension */; targetProxy = A30B9AC4265CA68900ACAA63 /* PBXContainerItemProxy */; }; + A3E4D35926E0FD2900C9F175 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A3E4D35126E0FD2900C9F175 /* hard-link-action-extension */; + targetProxy = A3E4D35826E0FD2900C9F175 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -452,7 +548,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "quick-symlink-tests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; @@ -468,7 +564,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "quick-symlink-tests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; @@ -525,7 +621,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -577,7 +673,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -594,7 +690,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "quick-symlink/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; @@ -611,7 +707,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "quick-symlink/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; @@ -626,7 +722,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "quick-symlink-extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink.quick-symlink-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -642,7 +738,7 @@ COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = "quick-symlink-extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink.quick-symlink-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -650,6 +746,38 @@ }; name = Release; }; + A3E4D35C26E0FD2900C9F175 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "hard-link-action-extension/hard-link-action-extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "hard-link-action-extension/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink.hard-link-action-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + A3E4D35D26E0FD2900C9F175 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "hard-link-action-extension/hard-link-action-extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "hard-link-action-extension/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + PRODUCT_BUNDLE_IDENTIFIER = "org.ololx.quick-symlink.hard-link-action-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -689,6 +817,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + A3E4D35B26E0FD2900C9F175 /* Build configuration list for PBXNativeTarget "hard-link-action-extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A3E4D35C26E0FD2900C9F175 /* Debug */, + A3E4D35D26E0FD2900C9F175 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = A30B9A9F265CA63300ACAA63 /* Project object */; From 17d6d0203fd8d87522fc15ff01a8e7d2691116b2 Mon Sep 17 00:00:00 2001 From: "Alexander A. Kropotin" Date: Thu, 2 Sep 2021 20:19:25 +0500 Subject: [PATCH 3/4] feature(FileLinkManager.swift) implement hard link menu --- commons/link/FileLinkManager.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/commons/link/FileLinkManager.swift b/commons/link/FileLinkManager.swift index 70cfe5f..359e268 100644 --- a/commons/link/FileLinkManager.swift +++ b/commons/link/FileLinkManager.swift @@ -7,6 +7,7 @@ // import Foundation +import FinderSync public protocol FileLinkManager { @@ -65,8 +66,13 @@ public class SoftLinkManager: FileLinkManager { public class HardLinkManager: FileLinkManager { public func linkWith(of: URL!, with: URL!) { + + let pasteboard = NSPasteboard.general; + pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil); + do { - try FileManager.default.createSymbolicLink(at: with!, withDestinationURL: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with?.deletingLastPathComponent())).toUrl()!); + try FileManager.default.linkItem(at: of, to: with); + } catch let error as NSError { NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); } @@ -75,7 +81,7 @@ public class HardLinkManager: FileLinkManager { public func replaceWith(of: URL!, with: URL!) { do { try FileManager.default.moveItem(at: of, to: with); - try FileManager.default.linkItem(at: with, to: ResourcePath.of(url: of).relativize(to: ResourcePath.of(url: with.deletingLastPathComponent())).toUrl()!); + try FileManager.default.linkItem(at: with, to: of); } catch let error as NSError { NSLog("FileManager.createSymbolicLink() failed to create file: %@", error.description as NSString); } From 5206a22043de11a5c03f26cdb64f93fd68b9bb8c Mon Sep 17 00:00:00 2001 From: "Alexander A. Kropotin" Date: Thu, 2 Sep 2021 22:11:55 +0500 Subject: [PATCH 4/4] chore(FinderSync.swift) delete unusable menu item for the hard link actions extention --- CHANGELOG.md | 20 ++++++-- README.md | 51 +++++++++++++++------ TODO.md | 2 +- hard-link-action-extension/FinderSync.swift | 13 ------ 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b158d0f..d35e9c0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - Refactor application. +## [0.8.0] - 2021-09-02 + +### Added + +- Develop additional `Finder extension` which allows to create a `hard links` for selected folders and files via contextual menu. + +### Changed + +- Change build os version to 10.10. + +### Fixed + +- Fix soft link replace with function. + ## [0.7.0] - 2021-08-22 ### Added -- Added new classes `Path` for working with path's and creating relative path from specified directory. +- Add new classes `Path` for working with path's and creating relative path from specified directory. - Add unit-tests cases for the `Path` class. ### Changed @@ -25,13 +39,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Changed -- Changed the activity property of some menu items according to the rule: +- Change the activity property of some menu items according to the rule: - if no object was copied, then the menu items "Paste link to here" an "Move it here and replace with a link" are not active. - if at least one object was not copied, then the menu items "Paste link to here" an "Move it here and replace with a link" are inactive. ### Added -- Added cleaning clipboard after inserting links. +- Add cleaning clipboard after inserting links. ## [0.5.0] - 2021-08-02 diff --git a/README.md b/README.md index e09474a..6d58702 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ The Quick Symlink is a `Finder extension` which provides a `contextual menu item` for the symbolic links creation on macOS. -[![status](https://img.shields.io/badge/status-active-active?style=flat-square)](BADGES_GUIDE.md#status) [![version](https://img.shields.io/badge/version-0.6.0-informational?style=flat-square)](BADGES_GUIDE.md#version) [![oss lifecycle](https://img.shields.io/badge/oss_lifecycle-active-important?style=flat-square)](BADGES_GUIDE.md#oss-lifecycle) [![maintenance](https://img.shields.io/badge/maintenance-yes-informational?style=flat-square)](BADGES_GUIDE.md#maintenance) [![last release](https://img.shields.io/badge/last_release-August_22,_2021-informational?style=flat-square)](BADGES_GUIDE.md#release-date) [![last commit](https://img.shields.io/badge/last_commit-August_22,_2021-informational?style=flat-square)](BADGES_GUIDE.md#commit-date) +[![status](https://img.shields.io/badge/status-active-active?style=flat-square)](BADGES_GUIDE.md#status) [![version](https://img.shields.io/badge/version-0.8.0-informational?style=flat-square)](BADGES_GUIDE.md#version) [![oss lifecycle](https://img.shields.io/badge/oss_lifecycle-active-important?style=flat-square)](BADGES_GUIDE.md#oss-lifecycle) [![maintenance](https://img.shields.io/badge/maintenance-yes-informational?style=flat-square)](BADGES_GUIDE.md#maintenance) [![last release](https://img.shields.io/badge/last_release-September_02,_2021-informational?style=flat-square)](BADGES_GUIDE.md#release-date) [![last commit](https://img.shields.io/badge/last_commit-September_02,_2021-informational?style=flat-square)](BADGES_GUIDE.md#commit-date) [![license](https://img.shields.io/badge/license-MIT-informational?style=flat-square)](LICENSE) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg?style=flat-square)](CODE_OF_CONDUCT.md) -[![platform](https://img.shields.io/badge/platform-OS_X_10.11+-important?style=flat-square)](https://en.wikipedia.org/wiki/Computing_platform) +[![platform](https://img.shields.io/badge/platform-OS_X_10.10+-important?style=flat-square)](https://en.wikipedia.org/wiki/Computing_platform) --- @@ -55,10 +55,14 @@ This GIF demonstrates how the `Quick Symlink` allows quite simple to copy files ## 🎚 Features - Create a symbolic links in a several clicks via the context menu instead of the terminal promt: - - Select files or folders and create symlink for them. - - Copy files or folders and paste symlink somewhere. + - Select files or folders and create symlinks for them. + - Copy files or folders and paste symlinks somewhere. - Copy files or folders, paste them somewhere, and replace them with symlinks. +- Create a hard links in a several clicks via the context menu instead of the terminal promt: + - Select files or folders and create hard links for them. + - Copy files or folders and paste hard links somewhere. + ### To Do - For more information on an upcoming development, please read the [todo](TODO.md) list. @@ -75,12 +79,12 @@ These instructions allow to get a copy of this project and run it on a local mac Before using it, make sure that follows software are installed on the local machine: -- **[OS X 10.11+](https://www.apple.com/ru/macos/what-is/)** - the operating system under which the extention is executing. +- **[OS X 10.10+](https://www.apple.com/ru/macos/what-is/)** - the operating system under which the extention is executing. If any of the listed programs is not installed, then it can be installed by instruction as described below. -1. #### OS X 10.11+ - - Install macOS 10.11+ by [this](https://support.apple.com/ht201372) instruction. +1. #### OS X 10.10+ + - Install macOS 10.10+ by [this](https://support.apple.com/ht201372) instruction. ### Installing @@ -105,7 +109,9 @@ In order to install it is quite simple to: open quick-symlink.app ``` -4. Open up `System Preferences > Extensions` and enable the extension `quick-symlink`. +4. Open up `System Preferences > Extensions` and enable the follows extensions: + 4.1. `quick-symlink-extension` - for the symlink actions. + 4.2. `hard-link-actions-extension` - for the hard link actions. **Otherwise, it's possible to install and remove the extention using the actual extension bundled into the app.** @@ -113,12 +119,14 @@ In order to install it is quite simple to: ```bash pluginkit -a quick-symlink.app/Contents/PlugIns/quick-symlink-extension.appex/ +pluginkit -a quick-symlink.app/Contents/PlugIns/hard-link-action-extension.appex/ ``` 2. To remove it, run this: ```bash pluginkit -r quick-symlink.app/Contents/PlugIns/quick-symlink-extension.appex/ +pluginkit -r quick-symlink.app/Contents/PlugIns/hard-link-action-extension.appex/ ``` ### Downloading @@ -144,27 +152,44 @@ This tool allows to: Create symlinks in the current directory 1. Select folders or files for which a symbolic link is needed.
2. Call the contextual menu by the right-clicking on selected.
-3. Select menu item `Quick Symlink --> Create symlink for`.
+3. Select menu item `Symlink actions --> Create symlink for`.
Create symlinks in another directory 1. Select folders or files for which a symbolic link is needed.
2. Call the contextual menu by the right-clicking on selected.
-3. Select menu item `Quick Symlink --> Copy path from here`.
+3. Select menu item `Symlink actions --> Copy path from here`.
4. Go to a destination folder.
5. Call the contextual menu by right-clicking on the filder.
-6. Select menu item `Quick Symlink --> Paste to here`.
+6. Select menu item `Symlink actions --> Paste to here`.
Replace objects with symbolic links 1. Select folders or files for which a symbolic link is needed.
2. Call the contextual menu by the right-clicking on selected.
-3. Select menu item `Quick Symlink --> Copy to clipboard`.
+3. Select menu item `Symlink actions --> Copy to clipboard`.
+4. Go to a destination folder.
+5. Call the contextual menu by right-clicking on the filder.
+6. Select menu item `Symlink actions --> Move it here and replace with a link`.
+
+ +
+ Create hard links in the current directory +1. Select folders or files for which a symbolic link is needed.
+2. Call the contextual menu by the right-clicking on selected.
+3. Select menu item `Hard link actions --> Create symlink for`.
+
+ +
+ Create symlinks in another directory +1. Select folders or files for which a symbolic link is needed.
+2. Call the contextual menu by the right-clicking on selected.
+3. Select menu item `Hard link actions --> Copy path from here`.
4. Go to a destination folder.
5. Call the contextual menu by right-clicking on the filder.
-6. Select menu item `Quick Symlink --> Move it here and replace with a link`.
+6. Select menu item `Hard link actions --> Paste to here`.
## 🛠 Built With diff --git a/TODO.md b/TODO.md index a3ffbc5..914d65f 100755 --- a/TODO.md +++ b/TODO.md @@ -7,7 +7,7 @@ - [x] Add the new menu item for creating symlink in a parent directory (parent for target objects). - [x] Optional feature - use relative path instead absolute path in symlink target URL (if target and link located in one volume) - [ ] Refactor code in `commons/*` part and add unit-tests -- [ ] Develop additional `Finder extension` which allows to create a `hard links` for selected folders and files via contextual menu +- [x] Develop additional `Finder extension` which allows to create a `hard links` for selected folders and files via contextual menu - [ ] Develop the action panel for created symbolic links and hard lonks (in the app window): - [ ] a) (if broken) process to browse finder for 'Find/fix missing target' - [ ] b) Modify existing paths and symbolic link features diff --git a/hard-link-action-extension/FinderSync.swift b/hard-link-action-extension/FinderSync.swift index aede09e..6320399 100644 --- a/hard-link-action-extension/FinderSync.swift +++ b/hard-link-action-extension/FinderSync.swift @@ -15,7 +15,6 @@ class FinderSync: FIFinderSync { let copyPathAction = CopyPathAction.init(); let pasteLinkAction = PasteLinkAction.init(fileLinkManager: HardLinkManager.init()); - let replaceWithLinkAction = ReplaceWithLinkAction.init(fileLinkManager: HardLinkManager.init()); let createSymlink = CreateLinkAction.init(fileLinkManager: HardLinkManager.init()); override init() { @@ -93,16 +92,8 @@ class FinderSync: FIFinderSync { ); quickSymlinkMenu.addItem(pastleSymlinkFromClipboardMenuItem); - let replaceFileWithSymlinkFromClipboardMenuItem = NSMenuItem.init( - title: NSLocalizedString("REPLACE_WITH_LINK_ACTION_NAME", comment: ""), - action: #selector(replaceFileWithSymlinkFromClipboard(_:)), - keyEquivalent: "" - ); - quickSymlinkMenu.addItem(replaceFileWithSymlinkFromClipboardMenuItem); - if (NSPasteboard.init(name: NSPasteboard.Name.init(rawValue: "qs")).string(forType: NSPasteboard.PasteboardType.string) ?? "").isEmpty { pastleSymlinkFromClipboardMenuItem.isEnabled = false; - replaceFileWithSymlinkFromClipboardMenuItem.isEnabled = false; } if menuKind.rawValue == 3 { @@ -125,10 +116,6 @@ class FinderSync: FIFinderSync { self.copyPathAction.execute(); } - @IBAction func replaceFileWithSymlinkFromClipboard(_ sender: AnyObject?) { - self.replaceWithLinkAction.execute(); - } - @IBAction func pastleSymlinkFromClipboard(_ sender: AnyObject?) { self.pasteLinkAction.execute(); }