Skip to content

Instantly share code, notes, and snippets.

@dreymonde
Created July 4, 2024 20:47
Show Gist options
  • Save dreymonde/793a8a7c2ed5443b1594f528bb7c88a7 to your computer and use it in GitHub Desktop.
Save dreymonde/793a8a7c2ed5443b1594f528bb7c88a7 to your computer and use it in GitHub Desktop.
import Foundation
// MARK: - Extensions
extension URL {
var isDirectory: Bool {
(try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
}
}
// MARK: - Errors
enum CreateZipError: Swift.Error {
case urlNotADirectory(URL)
case failedToCreateZIP(Swift.Error)
case failedToGetDataFromZipURL
}
// MARK: - FileToZip
enum FileToZip {
case data(Data, filename: String)
case existingFile(URL)
case renamedFile(URL, toFilename: String)
}
extension FileToZip {
static func text(_ text: String, filename: String) -> FileToZip {
.data(text.data(using: .utf8) ?? Data(), filename: filename)
}
}
extension FileToZip {
func prepareInDirectory(directoryURL: URL) throws {
switch self {
case .data(let data, filename: let filename):
let fileURL = directoryURL.appendingPathComponent(filename)
try data.write(to: fileURL)
case .existingFile(let existingFileURL):
let filename = existingFileURL.lastPathComponent
let newFileURL = directoryURL.appendingPathComponent(filename)
try FileManager.default.copyItem(at: existingFileURL, to: newFileURL)
case .renamedFile(let existingFileURL, toFilename: let filename):
let newFileURL = directoryURL.appendingPathComponent(filename)
try FileManager.default.copyItem(at: existingFileURL, to: newFileURL)
}
}
}
// MARK: - ZipService
final class ZipService {
init() { }
var shouldOverwriteIfNecessary: Bool = false
func createZip(
zipFinalURL: URL,
fromDirectory directoryURL: URL
) throws -> URL {
// see URL extension below
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
) { zipAccessURL in
do {
if shouldOverwriteIfNecessary {
try FileManager.default.replaceItemAt(zipFinalURL, withItemAt: zipAccessURL)
} else {
try FileManager.default.moveItem(at: zipAccessURL, to: zipFinalURL)
}
} catch {
fileManagerError = error
}
}
if let error = coordinatorError ?? fileManagerError {
throw CreateZipError.failedToCreateZIP(error)
}
return zipFinalURL
}
func createZipAtTmp(
zipFilename: String,
zipExtension: String = "zip",
fromDirectory directoryURL: URL
) throws -> URL {
let finalURL = FileManager.default.temporaryDirectory
.appending(path: zipFilename)
.appendingPathExtension(zipExtension)
return try createZip(
zipFinalURL: finalURL,
fromDirectory: directoryURL
)
}
func createZipAtTmp(
zipFilename: String,
zipExtension: String = "zip",
filesToZip: [FileToZip]
) throws -> URL {
let directoryToZipURL = FileManager.default.temporaryDirectory
.appending(path: UUID().uuidString)
.appending(path: zipFilename)
try FileManager.default.createDirectory(at: directoryToZipURL, withIntermediateDirectories: true, attributes: [:])
for fileToZip in filesToZip {
try fileToZip.prepareInDirectory(directoryURL: directoryToZipURL)
}
return try createZipAtTmp(
zipFilename: zipFilename,
zipExtension: zipExtension,
fromDirectory: directoryToZipURL
)
}
private func getZipData(zipFileURL: URL) throws -> Data {
if let data = FileManager.default.contents(atPath: zipFileURL.path) {
return data
} else {
throw CreateZipError.failedToGetDataFromZipURL
}
}
func getZipData(
zipFilename: String = UUID().uuidString,
fromDirectory directoryURL: URL
) throws -> Data {
let zipURL = try createZipAtTmp(
zipFilename: zipFilename,
fromDirectory: directoryURL
)
return try getZipData(zipFileURL: zipURL)
}
func getZipData(
zipFilename: String = UUID().uuidString,
filesToZip: [FileToZip]
) throws -> Data {
let zipURL = try createZipAtTmp(
zipFilename: zipFilename,
filesToZip: filesToZip
)
return try getZipData(zipFileURL: zipURL)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment