sync app filled in
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
public actor UploadManager {
|
||||
private let client: APIClient
|
||||
private let config: SyncConfig
|
||||
|
||||
public init(client: APIClient, config: SyncConfig) {
|
||||
self.client = client
|
||||
self.config = config
|
||||
}
|
||||
|
||||
public func uploadFile(
|
||||
_ url: URL,
|
||||
duplicateAction: UploadDuplicateAction? = nil,
|
||||
onProgress: UploadProgressHandler? = nil
|
||||
) async throws -> UploadResult {
|
||||
let response = try await client.uploadFile(
|
||||
url: url,
|
||||
folder: config.defaultUploadFolder,
|
||||
duplicateAction: duplicateAction,
|
||||
onProgress: onProgress
|
||||
)
|
||||
let fullURL = resolvedURL(response.url ?? response.attachmentUrl)
|
||||
|
||||
// Copy URL to clipboard
|
||||
await MainActor.run {
|
||||
NSPasteboard.general.clearContents()
|
||||
NSPasteboard.general.setString(fullURL.absoluteString, forType: .string)
|
||||
}
|
||||
|
||||
return UploadResult(
|
||||
filename: url.lastPathComponent,
|
||||
hash: response.hash,
|
||||
url: fullURL,
|
||||
copiedToClipboard: true
|
||||
)
|
||||
}
|
||||
|
||||
public func uploadData(
|
||||
_ data: Data,
|
||||
filename: String,
|
||||
duplicateAction: UploadDuplicateAction? = nil,
|
||||
onProgress: UploadProgressHandler? = nil
|
||||
) async throws -> UploadResult {
|
||||
let response = try await client.uploadData(
|
||||
data,
|
||||
filename: filename,
|
||||
folder: config.defaultUploadFolder,
|
||||
duplicateAction: duplicateAction,
|
||||
onProgress: onProgress
|
||||
)
|
||||
let fullURL = resolvedURL(response.url ?? response.attachmentUrl)
|
||||
|
||||
// Copy URL to clipboard
|
||||
await MainActor.run {
|
||||
NSPasteboard.general.clearContents()
|
||||
NSPasteboard.general.setString(fullURL.absoluteString, forType: .string)
|
||||
}
|
||||
|
||||
return UploadResult(
|
||||
filename: filename,
|
||||
hash: response.hash,
|
||||
url: fullURL,
|
||||
copiedToClipboard: true
|
||||
)
|
||||
}
|
||||
|
||||
public func createNote(from text: String, title: String? = nil) async throws -> DocumentSaveResponse {
|
||||
let noteTitle = title ?? generateTitle(from: text)
|
||||
let folder = config.defaultUploadFolder ?? ""
|
||||
let path = folder.isEmpty ? "\(sanitizeFilename(noteTitle)).md" : "\(folder)/\(sanitizeFilename(noteTitle)).md"
|
||||
|
||||
let content = "# \(noteTitle)\n\n\(text)"
|
||||
|
||||
return try await client.saveDocument(path: path, content: content)
|
||||
}
|
||||
|
||||
private func generateTitle(from text: String) -> String {
|
||||
let lines = text.split(separator: "\n", omittingEmptySubsequences: true)
|
||||
if let firstLine = lines.first {
|
||||
let title = String(firstLine).trimmingCharacters(in: .whitespaces)
|
||||
if title.count > 60 {
|
||||
return String(title.prefix(60)) + "..."
|
||||
}
|
||||
return title
|
||||
}
|
||||
return "Note \(ISO8601DateFormatter().string(from: Date()))"
|
||||
}
|
||||
|
||||
private func sanitizeFilename(_ name: String) -> String {
|
||||
let invalidChars = CharacterSet(charactersIn: ":/\\?%*|\"<>").union(.controlCharacters)
|
||||
return name.components(separatedBy: invalidChars).joined(separator: "-")
|
||||
}
|
||||
|
||||
private func resolvedURL(_ path: String) -> URL {
|
||||
URL(string: path, relativeTo: client.baseURL)?.absoluteURL ?? client.baseURL.appendingPathComponent(path)
|
||||
}
|
||||
}
|
||||
|
||||
public struct UploadResult {
|
||||
public let filename: String
|
||||
public let hash: String
|
||||
public let url: URL
|
||||
public let copiedToClipboard: Bool
|
||||
}
|
||||
|
||||
public struct UploadCancelledError: LocalizedError {
|
||||
public init() {}
|
||||
|
||||
public var errorDescription: String? { "Upload cancelled" }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public func promptForUploadDuplicate(_ error: APIError, filename: String) -> UploadDuplicateAction? {
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .warning
|
||||
|
||||
if error.conflictType == "document" {
|
||||
alert.messageText = "A document named \(filename) already exists"
|
||||
alert.informativeText = "Choose whether to replace the inbox document or keep both files."
|
||||
alert.addButton(withTitle: "Keep Both")
|
||||
alert.addButton(withTitle: "Replace")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
|
||||
switch alert.runModal() {
|
||||
case .alertFirstButtonReturn:
|
||||
return .rename
|
||||
case .alertSecondButtonReturn:
|
||||
return .overwrite
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
alert.messageText = "\(filename) was already uploaded"
|
||||
alert.informativeText = "The server already has the same file content. Use its existing URL?"
|
||||
alert.addButton(withTitle: "Copy Existing URL")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
return alert.runModal() == .alertFirstButtonReturn ? .reuse : nil
|
||||
}
|
||||
Reference in New Issue
Block a user