WP-0: SwiftPM scaffold, shared model, signed app bundle script
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import Foundation
|
||||
|
||||
/// The only type in the app that knows the on-disk directory layout.
|
||||
public struct AppSupportPaths: Sendable {
|
||||
public let root: URL
|
||||
public let spool: URL
|
||||
public let archive: URL
|
||||
public let outbox: URL
|
||||
public let watchFolder: URL
|
||||
|
||||
/// Production paths.
|
||||
public static func standard() throws -> AppSupportPaths {
|
||||
let fileManager = FileManager.default
|
||||
let appSupportParent = try fileManager.url(
|
||||
for: .applicationSupportDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: true
|
||||
)
|
||||
let desktop = try fileManager.url(
|
||||
for: .desktopDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: true
|
||||
)
|
||||
let downloads = try fileManager.url(
|
||||
for: .downloadsDirectory,
|
||||
in: .userDomainMask,
|
||||
appropriateFor: nil,
|
||||
create: true
|
||||
)
|
||||
let root = appSupportParent.appendingPathComponent("Shotdeck", isDirectory: true)
|
||||
return try AppSupportPaths(root: root, outbox: desktop, watchFolder: downloads)
|
||||
}
|
||||
|
||||
/// Test paths rooted anywhere. Every directory is created if missing.
|
||||
public init(root: URL, outbox: URL, watchFolder: URL) throws {
|
||||
self.root = root
|
||||
self.spool = root.appendingPathComponent("spool", isDirectory: true)
|
||||
self.archive = root.appendingPathComponent("archive", isDirectory: true)
|
||||
self.outbox = outbox
|
||||
self.watchFolder = watchFolder
|
||||
|
||||
try Self.createDirectory(self.root)
|
||||
try Self.createDirectory(self.spool)
|
||||
try Self.createDirectory(self.archive)
|
||||
try Self.createDirectory(self.outbox)
|
||||
try Self.createDirectory(self.watchFolder)
|
||||
}
|
||||
|
||||
public func sessionDirectory(_ id: UUID) -> URL {
|
||||
spool.appendingPathComponent(id.uuidString, isDirectory: true)
|
||||
}
|
||||
|
||||
public func archiveDirectory(_ id: UUID) -> URL {
|
||||
archive.appendingPathComponent(id.uuidString, isDirectory: true)
|
||||
}
|
||||
|
||||
private static func createDirectory(_ url: URL) throws {
|
||||
try FileManager.default.createDirectory(
|
||||
at: url,
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import Foundation
|
||||
|
||||
public enum DubaiTime {
|
||||
private static let stampFormatter = LockedDateFormatter(dateFormat: "d MMM yyyy, HH:mm 'Dubai'")
|
||||
private static let fileStampFormatter = LockedDateFormatter(dateFormat: "yyyyMMdd-HHmmss")
|
||||
|
||||
public static func stamp(_ date: Date) -> String {
|
||||
stampFormatter.string(from: date)
|
||||
}
|
||||
|
||||
public static func fileStamp(_ date: Date) -> String {
|
||||
fileStampFormatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
||||
/// DateFormatter is not Sendable. This holder is the only shared mutable state
|
||||
/// around a formatter: every read is serialized by the lock, so concurrent
|
||||
/// calls (one per PDF page) cannot race.
|
||||
private final class LockedDateFormatter: @unchecked Sendable {
|
||||
private let lock = NSLock()
|
||||
private let formatter: DateFormatter
|
||||
|
||||
init(dateFormat: String) {
|
||||
let formatter = DateFormatter()
|
||||
formatter.calendar = Calendar(identifier: .gregorian)
|
||||
formatter.locale = Locale(identifier: "en_GB")
|
||||
guard let dubai = TimeZone(identifier: "Asia/Dubai") else {
|
||||
preconditionFailure("The Asia/Dubai time zone is missing from this system.")
|
||||
}
|
||||
formatter.timeZone = dubai
|
||||
formatter.dateFormat = dateFormat
|
||||
self.formatter = formatter
|
||||
}
|
||||
|
||||
func string(from date: Date) -> String {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return formatter.string(from: date)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user