Sources/OrgApp/CaptureInbox.swift
57 lines · 2385 bytes
6 symbols in this file
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}