Files
Commilitia-Drop/ios/CDrop/Sources/Engine/DownloadManager.swift
T
admin b55cb615a3 Mac 原生系统通知 + 接收目录可配(Phase 4b·1)
- MacNotifier:窗口非前台(!NSApp.isActive)时对「收到消息 / 收到文件」发原生系统通知(UNUserNotificationCenter,授权已由引擎启动的 PushRegistry.requestAuthorizationAndRegister 一并请求)。对齐桌面 app.ShowNotification——前台事件由应用内 NoticeOverlay / 未读红点负责,仅 app 非活跃才发系统通知,避免与应用内提示重复。引擎 handleNotify 的 "message"(新到对端消息,非当前会话)+ "transferDone"(incoming 且 completed)两处加 macOS 挂钩
- 接收目录可配:DownloadManager.downloadDir 在 macOS 读 UserDefaults macDownloadDir(设置页 NSOpenPanel 选目录),未配 / 目录已失效则回退系统「下载」目录(比「文稿」更符桌面直觉);iOS 仍固定沙盒 Documents。设置加「接收」区(显示当前保存目录 + 选择目录…)。非沙盒构建用普通路径即可;沙盒分发(Phase 6)须改安全作用域书签
- 验证:just mac-build(CommilitiaDropMac.app)+ iOS 模拟器(CommilitiaDrop.app)均 BUILD SUCCEEDED
2026-07-08 00:07:59 +08:00

135 lines
5.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
// desktop/platform/download.go app
// DocumentssaveWhole begin / append / finalizeabort
// 线 EngineController 线 IO
//
final class DownloadManager
{
private struct Active
{
let handle: FileHandle
let tmpURL: URL
}
private var active: [String: Active] = [:]
private func downloadDir() -> URL
{
#if os(macOS)
// macOSUserDefaults macDownloadDir / 退
// 稿Phase 6
//
if let path = UserDefaults.standard.string(forKey: "macDownloadDir"), !path.isEmpty
{
let url = URL(fileURLWithPath: path, isDirectory: true)
if FileManager.default.fileExists(atPath: url.path) { return url }
}
return FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
?? FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
#else
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
#endif
}
// Documents .part /
// / / Files app
func receivedFiles() -> [URL]
{
let urls = (try? FileManager.default.contentsOfDirectory(
at: downloadDir(),
includingPropertiesForKeys: [ .contentModificationDateKey ],
options: [ .skipsHiddenFiles ])) ?? []
return urls
.filter { $0.pathExtension != "part" && !$0.lastPathComponent.hasPrefix(".cdrop-") }
.sorted { a, b in modDate(a) > modDate(b) }
}
func deleteReceivedFile(_ url: URL)
{
try? FileManager.default.removeItem(at: url)
}
private func modDate(_ url: URL) -> Date
{
return (try? url.resourceValues(forKeys: [ .contentModificationDateKey ]))?
.contentModificationDate ?? .distantPast
}
// saveDownload
func saveWhole(name: String, data: Data) throws -> String
{
let url = uniqueURL(for: sanitize(name))
try data.write(to: url)
return url.path
}
func begin(sessionId: String) throws
{
cleanup(sessionId) // re-begin
let tmp = downloadDir().appendingPathComponent(".cdrop-\(sanitize(sessionId)).part")
FileManager.default.createFile(atPath: tmp.path, contents: nil)
let handle = try FileHandle(forWritingTo: tmp)
active[sessionId] = Active(handle: handle, tmpURL: tmp)
}
func append(sessionId: String, data: Data) throws
{
guard let a = active[sessionId] else { throw Err.unknownSession }
try a.handle.write(contentsOf: data)
}
func finalize(sessionId: String, name: String) throws -> String
{
guard let a = active[sessionId] else { throw Err.unknownSession }
try a.handle.close()
active[sessionId] = nil
let dest = uniqueURL(for: sanitize(name))
try FileManager.default.moveItem(at: a.tmpURL, to: dest)
return dest.path
}
func abort(sessionId: String)
{
cleanup(sessionId)
}
private func cleanup(_ sessionId: String)
{
guard let a = active[sessionId] else { return }
try? a.handle.close()
try? FileManager.default.removeItem(at: a.tmpURL)
active[sessionId] = nil
}
// (1) (2) uniquePath
private func uniqueURL(for name: String) -> URL
{
let dir = downloadDir()
let base = (name as NSString).deletingPathExtension
let ext = (name as NSString).pathExtension
var candidate = dir.appendingPathComponent(name)
var i = 1
while FileManager.default.fileExists(atPath: candidate.path)
{
let next = ext.isEmpty ? "\(base) (\(i))" : "\(base) (\(i)).\(ext)"
candidate = dir.appendingPathComponent(next)
i += 1
}
return candidate
}
// sanitize穿 sanitizeFileName
private func sanitize(_ name: String) -> String
{
let last = (name as NSString).lastPathComponent
let cleaned = last.replacingOccurrences(of: "/", with: "_")
return cleaned.isEmpty ? "download" : cleaned
}
enum Err: Error
{
case unknownSession
}
}