krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/OrgApp/CaptureInbox.swift

15f6b0709d88971fb62ed432c3e5b8032643670a
orgstar/Sources/OrgApp/CaptureInbox.swift history · blame · raw

57 lines · 2385 bytes

 1import Foundation
 2
 3/// Captures shared from other apps, waiting for the app to file them: `org-protocol` capture
 4/// links in the app group's container, which the share extension writes. The app keeps its
 5/// template list there for the extension to offer.
 6public struct CaptureInbox: Sendable {
 7    public static let group = "group.sh.krz.orgstar"
 8
 9    public struct Template: Codable, Sendable, Hashable {
10        public let key: String
11        public let name: String
12
13        public init(key: String, name: String) {
14            self.key = key
15            self.name = name
16        }
17    }
18
19    public let folder: URL
20
21    /// The app group's inbox; nil when the app isn't entitled to the group.
22    public init?(group: String = CaptureInbox.group) {
23        guard let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: group) else { return nil }
24        folder = container.appendingPathComponent("Captures")
25    }
26
27    public init(folder: URL) {
28        self.folder = folder
29    }
30
31    private var templatesFile: URL { folder.appendingPathComponent("templates.json") }
32
33    public func add(_ link: String) throws {
34        try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
35        let name = String(format: "%020.6f", Date().timeIntervalSince1970) + "-" + UUID().uuidString + ".link"
36        try Data(link.utf8).write(to: folder.appendingPathComponent(name), options: .atomic)
37    }
38
39    /// The waiting links, oldest first, each removed as it's taken.
40    public func take() -> [String] {
41        let names = ((try? FileManager.default.contentsOfDirectory(atPath: folder.path)) ?? []).filter { $0.hasSuffix(".link") }.sorted()
42        return names.compactMap { name in
43            let file = folder.appendingPathComponent(name)
44            defer { try? FileManager.default.removeItem(at: file) }
45            return (try? Data(contentsOf: file)).flatMap { String(data: $0, encoding: .utf8) }
46        }
47    }
48
49    public func setTemplates(_ templates: [Template]) throws {
50        try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
51        try JSONEncoder().encode(templates).write(to: templatesFile, options: .atomic)
52    }
53
54    public func templates() -> [Template] {
55        (try? JSONDecoder().decode([Template].self, from: Data(contentsOf: templatesFile))) ?? []
56    }
57}