diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 47c9894..4146728 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; + EBCC27F92D80C9F90079C478 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC27F82D80C9F70079C478 /* Zip.swift */; }; EBD2A2172D2860C100BCB8E2 /* WelcomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */; }; /* End PBXBuildFile section */ @@ -212,6 +213,7 @@ EB96ED882D7F79B9004B3FF1 /* ShareViewControllerSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewControllerSwiftUI.swift; sourceTree = ""; }; EB96ED8A2D7F7CF4004B3FF1 /* QrCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeView.swift; sourceTree = ""; }; EB96ED8E2D7F8103004B3FF1 /* Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = ""; }; + EBCC27F82D80C9F70079C478 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = ""; }; EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = ""; }; EBD2A2182D28619200BCB8E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ @@ -263,6 +265,7 @@ children = ( EB96ED882D7F79B9004B3FF1 /* ShareViewControllerSwiftUI.swift */, 699DEBA52AB0573200115D22 /* ShareViewController.swift */, + EBCC27F82D80C9F70079C478 /* Zip.swift */, EB96ED8A2D7F7CF4004B3FF1 /* QrCodeView.swift */, 691F53B92ABB70840089FD92 /* DeviceListCell.swift */, 691F53BA2ABB70840089FD92 /* DeviceListCell.xib */, @@ -522,6 +525,7 @@ EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */, 699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */, EB96ED8B2D7F7CF7004B3FF1 /* QrCodeView.swift in Sources */, + EBCC27F92D80C9F90079C478 /* Zip.swift in Sources */, EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */, 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */, ); diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift index 5f95351..faf4164 100644 --- a/ShareExtension/ShareViewController.swift +++ b/ShareExtension/ShareViewController.swift @@ -164,25 +164,33 @@ class ShareViewController: NSViewController, ShareExtensionDelegate{ let isDirectory=UnsafeMutablePointer.allocate(capacity: 1) if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue{ - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - log("Canceling share request because URL \(url) is a directory") - - let alert = NSAlert() - alert.alertStyle = .critical - - alert.messageText = "TypeNotSupported".localized() - alert.informativeText = "TypeNotSupportedDescription".localized() - alert.addButton(withTitle: "TypeNotSupportedButton".localized()) - - let _ = alert.runModal() - let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) + do { + let zipUrl = try createZipAtTmp(zipFilename: url.lastPathComponent, fromDirectory: url) + let index = urls.firstIndex(of: url) + urls[index!] = zipUrl + } + catch { + log("Error creating zip file: \(error)") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + log("Canceling share request because URL \(url) is a directory") + + let alert = NSAlert() + alert.alertStyle = .critical + + alert.messageText = "TypeNotSupported".localized() + alert.informativeText = "TypeNotSupportedDescription".localized() + alert.addButton(withTitle: "TypeNotSupportedButton".localized()) + + let _ = alert.runModal() + let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil) + self.extensionContext!.cancelRequest(withError: cancelError) } + + return } - - return } } } diff --git a/ShareExtension/Zip.swift b/ShareExtension/Zip.swift new file mode 100644 index 0000000..d1c81d4 --- /dev/null +++ b/ShareExtension/Zip.swift @@ -0,0 +1,65 @@ +// +// Zip.swift +// QuickDrop +// +// Created by Leon Böttger on 11.03.25. +// + +import Foundation + +enum CreateZipError: Swift.Error { + case urlNotADirectory(URL) + case failedToCreateZIP(Swift.Error) +} + + +func createZip(zipFinalURL: URL, fromDirectory directoryURL: URL) throws -> URL { + + guard directoryURL.isDirectory else { + throw CreateZipError.urlNotADirectory(directoryURL) + } + + var fileManagerError: Swift.Error? + var coordinatorError: NSError? + let coordinator = NSFileCoordinator() + + coordinator.coordinate(readingItemAt: directoryURL, options: .forUploading, error: &coordinatorError) { zipCreatedURL in + do { + // clean if file exists + if FileManager.default.fileExists(atPath: zipFinalURL.path) { + try FileManager.default.removeItem(at: zipFinalURL) + } + + try FileManager.default.moveItem(at: zipCreatedURL, to: zipFinalURL) + + } catch { + fileManagerError = error + } + } + + if let error = coordinatorError ?? fileManagerError { + throw CreateZipError.failedToCreateZIP(error) + } + + return zipFinalURL +} + + +extension URL { + var isDirectory: Bool { + (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true + } +} + + +func createZipAtTmp(zipFilename: String, zipExtension: String = "zip", fromDirectory directoryURL: URL) throws -> URL { + + let finalURL = FileManager.default.temporaryDirectory + .appendingPathComponent(zipFilename) + .appendingPathExtension(zipExtension) + + return try createZip( + zipFinalURL: finalURL, + fromDirectory: directoryURL + ) +}