142 lines
4.7 KiB
Swift
142 lines
4.7 KiB
Swift
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
|
|
}
|