diff --git a/Gifski.xcodeproj/project.pbxproj b/Gifski.xcodeproj/project.pbxproj index e2ee6b28..fd2e4aaf 100644 --- a/Gifski.xcodeproj/project.pbxproj +++ b/Gifski.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ E3D72324229B9A7500989BDF /* CircularProgress+CheckmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D72323229B9A7500989BDF /* CircularProgress+CheckmarkView.swift */; }; E3DF3E88203BD2B900055855 /* EditVideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DF3E86203BD2B900055855 /* EditVideoViewController.swift */; }; E3DF3E89203BD2B900055855 /* EditVideoViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E3DF3E87203BD2B900055855 /* EditVideoViewController.xib */; }; + E3FC365C2377FA0000CF7C59 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3FC365B2377FA0000CF7C59 /* Shared.swift */; }; + E3FC365E2377FA9F00CF7C59 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3FC365B2377FA0000CF7C59 /* Shared.swift */; }; E3FEF31922C52819003AEFED /* ConversionCompletedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E3FEF31422C52819003AEFED /* ConversionCompletedViewController.xib */; }; /* End PBXBuildFile section */ @@ -147,6 +149,7 @@ E3D72323229B9A7500989BDF /* CircularProgress+CheckmarkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "CircularProgress+CheckmarkView.swift"; sourceTree = ""; usesTabs = 1; }; E3DF3E86203BD2B900055855 /* EditVideoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = EditVideoViewController.swift; sourceTree = ""; usesTabs = 1; }; E3DF3E87203BD2B900055855 /* EditVideoViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditVideoViewController.xib; sourceTree = ""; }; + E3FC365B2377FA0000CF7C59 /* Shared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = Shared.swift; path = Gifski/Shared.swift; sourceTree = SOURCE_ROOT; usesTabs = 1; }; E3FD6190201BCBC30087160A /* Gifski-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "Gifski-Bridging-Header.h"; sourceTree = ""; usesTabs = 1; }; E3FD61A4201BD2DA0087160A /* gifski.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = gifski.h; path = "gifski-api/gifski.h"; sourceTree = ""; }; E3FEF31422C52819003AEFED /* ConversionCompletedViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ConversionCompletedViewController.xib; sourceTree = ""; }; @@ -255,6 +258,7 @@ children = ( E3AE62861E5CD2F300035A2F /* AppDelegate.swift */, E3A6BD102245345C00F62256 /* Constants.swift */, + E3FC365B2377FA0000CF7C59 /* Shared.swift */, C2AFA91B204FFEFD00FC5A7F /* MainWindowController.swift */, 858380E522BFD0E30086BC98 /* VideoDropViewController.swift */, E3DF3E86203BD2B900055855 /* EditVideoViewController.swift */, @@ -468,6 +472,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E3FC365E2377FA9F00CF7C59 /* Shared.swift in Sources */, 0E7925202329BDBE00058B94 /* ShareViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -494,6 +499,7 @@ E3A940122182DCE5006981D5 /* CustomButton.swift in Sources */, E3DF3E88203BD2B900055855 /* EditVideoViewController.swift in Sources */, 6D86841721FD283B0044F6FE /* ConversionCompletedViewController.swift in Sources */, + E3FC365C2377FA0000CF7C59 /* Shared.swift in Sources */, B576D25422294F9900A9B75C /* CircularProgress+Util.swift in Sources */, 858380E622BFD0E30086BC98 /* VideoDropViewController.swift in Sources */, 8548806E22B82D1400E97401 /* MenuPopUpButton.swift in Sources */, diff --git a/Gifski/AppDelegate.swift b/Gifski/AppDelegate.swift index 4e09df24..5d5c5b9b 100644 --- a/Gifski/AppDelegate.swift +++ b/Gifski/AppDelegate.swift @@ -45,8 +45,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate { } } + /// Returns `nil` if it should not continue. + func extractSharedVideoUrlIfAny(from url: URL) -> URL? { + guard url.host == "shareExtension" else { + return url + } + + guard + let path = url.queryDictionary["path"], + let appGroupShareVideoUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Shared.videoShareGroupIdentifier)?.appendingPathComponent(path) + else { + NSAlert.showModal( + for: mainWindowController.window, + message: "Could not retrieve the shared video." + ) + return nil + } + + return appGroupShareVideoUrl + } + func application(_ application: NSApplication, open urls: [URL]) { - guard urls.count == 1, let videoUrl = urls.first else { + guard + urls.count == 1, + let videoUrl = urls.first + else { NSAlert.showModal( for: mainWindowController.window, message: "Gifski can only convert a single file at the time." @@ -54,30 +77,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate { return } - var sharedVideoUrl = videoUrl - - if videoUrl.host == "shareExtension" { - if let path = videoUrl.queryParameters["path"], - let appIdentifierPrefix = Bundle.main.infoDictionary?["AppIdentifierPrefix"] as? String, - let videoUrlString = path.removingPercentEncoding, - let appGroupShareVideoUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "\(appIdentifierPrefix).gifski_video_share_group")?.appendingPathComponent(videoUrlString) { - sharedVideoUrl = appGroupShareVideoUrl - } else { - NSAlert.showModal( - for: mainWindowController.window, - message: "Could not retrieve shared video" - ) - return - } + guard let videoUrl2 = extractSharedVideoUrlIfAny(from: videoUrl) else { + return } // TODO: Simplify this. Make a function that calls the input when the app finished launching, or right away if it already has. if hasFinishedLaunching { - mainWindowController.convert(sharedVideoUrl.absoluteURL) + mainWindowController.convert(videoUrl2) } else { // This method is called before `applicationDidFinishLaunching`, // so we buffer it up a video is "Open with" this app - urlToConvertOnLaunch = sharedVideoUrl.absoluteURL + urlToConvertOnLaunch = videoUrl2 } } diff --git a/Gifski/Gifski.entitlements b/Gifski/Gifski.entitlements index de255f7d..c9387c94 100644 --- a/Gifski/Gifski.entitlements +++ b/Gifski/Gifski.entitlements @@ -6,7 +6,7 @@ com.apple.security.application-groups - $(TeamIdentifierPrefix).gifski_video_share_group + $(TeamIdentifierPrefix)gifski_video_share_group com.apple.security.files.user-selected.read-write diff --git a/Gifski/Shared.swift b/Gifski/Shared.swift new file mode 100644 index 00000000..5d7e83fb --- /dev/null +++ b/Gifski/Shared.swift @@ -0,0 +1,6 @@ +import Foundation + +struct Shared { + static let appIdentifierPrefix = Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String + static let videoShareGroupIdentifier = "\(appIdentifierPrefix)gifski_video_share_group" +} diff --git a/Gifski/util.swift b/Gifski/util.swift index 09178fa8..b10ec680 100644 --- a/Gifski/util.swift +++ b/Gifski/util.swift @@ -2358,19 +2358,22 @@ extension URL { func setMetadata(key: MetadataKey, value: T) throws { try attributes.set("com.apple.metadata:\(key.attributeKey)", value: value) } +} - var queryParameters: [String: String] { - guard - let components = URLComponents(url: self, resolvingAgainstBaseURL: true), - let queryItems = components.queryItems - else { - return [:] - } - - return queryItems.reduce(into: [String: String]()) { result, item in +extension URLComponents { + var queryDictionary: [String: String] { + queryItems?.reduce(into: [String: String]()) { result, item in result[item.name] = item.value - } + } ?? [:] + } +} + +extension URL { + var components: URLComponents? { + URLComponents(url: self, resolvingAgainstBaseURL: true) } + + var queryDictionary: [String: String] { components?.queryDictionary ?? [:] } } extension NSViewController { diff --git a/ShareExtension/Base.lproj/ShareViewController.xib b/ShareExtension/Base.lproj/ShareViewController.xib index 0ff47c17..6171183f 100644 --- a/ShareExtension/Base.lproj/ShareViewController.xib +++ b/ShareExtension/Base.lproj/ShareViewController.xib @@ -23,17 +23,16 @@ - + - - + + + diff --git a/ShareExtension/ShareExtension.entitlements b/ShareExtension/ShareExtension.entitlements index 82b99312..47cbc759 100644 --- a/ShareExtension/ShareExtension.entitlements +++ b/ShareExtension/ShareExtension.entitlements @@ -6,7 +6,7 @@ com.apple.security.application-groups - $(TeamIdentifierPrefix).gifski_video_share_group + $(TeamIdentifierPrefix)gifski_video_share_group diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift index f542c0a3..b3b69470 100644 --- a/ShareExtension/ShareViewController.swift +++ b/ShareExtension/ShareViewController.swift @@ -17,7 +17,7 @@ final class ShareViewController: NSViewController { guard let item = (extensionContext?.inputItems[0] as? NSExtensionItem)?.attachments?.first else { - presentError(message: "The shared item does not contain an attachment") + presentError(message: "The shared item does not contain an attachment.") return } @@ -29,7 +29,7 @@ final class ShareViewController: NSViewController { } else if item.hasItemConformingToTypeIdentifier("com.apple.quicktime-movie") { typeIdentifier = "com.apple.quicktime-movie" } else { - presentError(message: "The shared item is not in a valid video format") + presentError(message: "The shared item is not in a supported video format.") return } @@ -39,19 +39,19 @@ final class ShareViewController: NSViewController { return } - let shareUrl = "\(url.lastPathComponent)" - let appIdentifierPrefix = Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String + let shareUrl = url.lastPathComponent guard - let appGroupShareVideUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "\(appIdentifierPrefix).gifski_video_share_group")?.appendingPathComponent(shareUrl) + let appGroupShareVideoUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Shared.videoShareGroupIdentifier)?.appendingPathComponent(shareUrl) else { - self.presentError(message: "Could not share the video with the main app") + self.presentError(message: "Could not share the video with the main app.") return } - try? FileManager.default.removeItem(at: appGroupShareVideUrl) + try? FileManager.default.removeItem(at: appGroupShareVideoUrl) + do { - try FileManager.default.copyItem(at: url, to: appGroupShareVideUrl) + try FileManager.default.copyItem(at: url, to: appGroupShareVideoUrl) } catch { self.presentError(message: error.localizedDescription) return @@ -60,14 +60,11 @@ final class ShareViewController: NSViewController { guard let gifski = self.createMainAppUrl( queryItems: [ - URLQueryItem( - name: "path", - value: shareUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! - ) + URLQueryItem(name: "path", value: shareUrl) ] ) else { - self.presentError(message: "Could not share the video with the main app") + self.presentError(message: "Could not share the video with the main app.") return } @@ -84,7 +81,7 @@ final class ShareViewController: NSViewController { errorLabel.stringValue = message } - private func createMainAppUrl(queryItems: [URLQueryItem] = []) -> URL? { + private func createMainAppUrl(queryItems: [URLQueryItem]) -> URL? { var components = URLComponents() components.scheme = "gifski" components.host = "shareExtension"