Update OTG transfer workflow
This commit is contained in:
@ -15,8 +15,8 @@ final class CanonRemoteCaptureService {
|
||||
|
||||
private var albumID: Int?
|
||||
private let photoRepository: PhotoRepositoryProtocol
|
||||
/// 下载成功时回调文件名,供 ConnectionManager 转发给 ViewModel。
|
||||
var onShotSaved: ((String) -> Void)?
|
||||
/// 下载成功时回调本地照片记录,供 ConnectionManager 转发给 ViewModel。
|
||||
var onShotSaved: ((TravelAlbumOTGPhotoRecord) -> Void)?
|
||||
|
||||
private var eventPollTimer: Timer?
|
||||
private var catalogPollTimer: Timer?
|
||||
@ -175,14 +175,14 @@ final class CanonRemoteCaptureService {
|
||||
|
||||
lastDownloadedSignature = objectID
|
||||
|
||||
guard await saveToAlbum(data: data, filename: filename, reason: reason) else {
|
||||
guard let record = await saveToAlbum(data: data, filename: filename, reason: reason) else {
|
||||
knownCatalogIDs.remove(objectID)
|
||||
return
|
||||
}
|
||||
|
||||
savedShotCount += 1
|
||||
OTGLog.info(.canon, "Live shot saved (#\(savedShotCount)): \(filename), bytes=\(data.count)")
|
||||
onShotSaved?(filename)
|
||||
onShotSaved?(record)
|
||||
} catch {
|
||||
knownCatalogIDs.remove(objectID)
|
||||
OTGLog.error(.canon, "\(reason): catalog download failed \(filename): \(error.localizedDescription)")
|
||||
@ -417,23 +417,23 @@ final class CanonRemoteCaptureService {
|
||||
lastDownloadedSignature = signature
|
||||
knownDownloadedHandles.insert(item.handle)
|
||||
|
||||
guard await saveToAlbum(data: result.data, filename: result.filename, reason: item.reason) else {
|
||||
guard let record = await saveToAlbum(data: result.data, filename: result.filename, reason: item.reason) else {
|
||||
return false
|
||||
}
|
||||
|
||||
savedShotCount += 1
|
||||
OTGLog.info(.canon, "Live shot saved (#\(savedShotCount)): \(result.filename), bytes=\(result.data.count)")
|
||||
onShotSaved?(result.filename)
|
||||
onShotSaved?(record)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func saveToAlbum(data: Data, filename: String, reason: String) async -> Bool {
|
||||
private func saveToAlbum(data: Data, filename: String, reason: String) async -> TravelAlbumOTGPhotoRecord? {
|
||||
guard let albumID else {
|
||||
OTGLog.warning(.canon, "\(reason): live transfer album not configured, skip save")
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
@ -442,15 +442,14 @@ final class CanonRemoteCaptureService {
|
||||
filename: filename,
|
||||
albumID: albumID
|
||||
)
|
||||
try photoRepository.addPhoto(
|
||||
return try photoRepository.addPhoto(
|
||||
albumID: albumID,
|
||||
localPath: fileURL.path,
|
||||
createdAt: Date()
|
||||
)
|
||||
return true
|
||||
} catch {
|
||||
OTGLog.error(.canon, "\(reason): save failed: \(error.localizedDescription)")
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,8 @@ final class NikonCatalogLiveCaptureService {
|
||||
|
||||
private var albumID: Int?
|
||||
private let photoRepository: PhotoRepositoryProtocol
|
||||
var onShotSaved: ((String) -> Void)?
|
||||
/// 下载成功时回调本地照片记录,供 ConnectionManager 转发给 ViewModel。
|
||||
var onShotSaved: ((TravelAlbumOTGPhotoRecord) -> Void)?
|
||||
|
||||
private var catalogPollTimer: Timer?
|
||||
private var isDownloading = false
|
||||
@ -140,14 +141,14 @@ final class NikonCatalogLiveCaptureService {
|
||||
|
||||
lastDownloadedSignature = objectID
|
||||
|
||||
guard await saveToAlbum(data: data, filename: filename, reason: reason) else {
|
||||
guard let record = await saveToAlbum(data: data, filename: filename, reason: reason) else {
|
||||
knownCatalogIDs.remove(objectID)
|
||||
return
|
||||
}
|
||||
|
||||
savedShotCount += 1
|
||||
OTGLog.info(.nikon, "Live shot saved (#\(savedShotCount)): \(filename), bytes=\(data.count)")
|
||||
onShotSaved?(filename)
|
||||
onShotSaved?(record)
|
||||
} catch {
|
||||
knownCatalogIDs.remove(objectID)
|
||||
OTGLog.error(.nikon, "\(reason): catalog download failed \(filename): \(error.localizedDescription)")
|
||||
@ -184,10 +185,10 @@ final class NikonCatalogLiveCaptureService {
|
||||
|
||||
// MARK: - Save
|
||||
|
||||
private func saveToAlbum(data: Data, filename: String, reason: String) async -> Bool {
|
||||
private func saveToAlbum(data: Data, filename: String, reason: String) async -> TravelAlbumOTGPhotoRecord? {
|
||||
guard let albumID else {
|
||||
OTGLog.warning(.nikon, "\(reason): live transfer album not configured, skip save")
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
@ -196,15 +197,14 @@ final class NikonCatalogLiveCaptureService {
|
||||
filename: filename,
|
||||
albumID: albumID
|
||||
)
|
||||
try photoRepository.addPhoto(
|
||||
return try photoRepository.addPhoto(
|
||||
albumID: albumID,
|
||||
localPath: fileURL.path,
|
||||
createdAt: Date()
|
||||
)
|
||||
return true
|
||||
} catch {
|
||||
OTGLog.error(.nikon, "\(reason): save failed: \(error.localizedDescription)")
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@ final class SonyRemoteCaptureService {
|
||||
|
||||
private var albumID: Int?
|
||||
private let photoRepository: PhotoRepositoryProtocol
|
||||
/// 下载成功时回调文件名,供 ConnectionManager 转发给 ViewModel。
|
||||
var onShotSaved: ((String) -> Void)?
|
||||
/// 下载成功时回调本地照片记录,供 ConnectionManager 转发给 ViewModel。
|
||||
var onShotSaved: ((TravelAlbumOTGPhotoRecord) -> Void)?
|
||||
|
||||
private var pollTimer: Timer?
|
||||
private var propertyCheckTask: Task<Void, Never>?
|
||||
@ -298,7 +298,7 @@ final class SonyRemoteCaptureService {
|
||||
filename: downloaded.filename,
|
||||
albumID: albumID
|
||||
)
|
||||
try photoRepository.addPhoto(
|
||||
let record = try photoRepository.addPhoto(
|
||||
albumID: albumID,
|
||||
localPath: fileURL.path,
|
||||
createdAt: Date()
|
||||
@ -307,7 +307,7 @@ final class SonyRemoteCaptureService {
|
||||
.sony,
|
||||
"Live shot saved (#\(savedShotCount + 1)): \(downloaded.filename), bytes=\(downloaded.data.count), path=\(fileURL.lastPathComponent)"
|
||||
)
|
||||
onShotSaved?(downloaded.filename)
|
||||
onShotSaved?(record)
|
||||
return true
|
||||
} catch {
|
||||
OTGLog.error(.sony, "\(reason): save failed: \(error.localizedDescription)")
|
||||
|
||||
@ -15,14 +15,14 @@ protocol ConnectionManagerDelegate: AnyObject {
|
||||
func connectionManager(_ manager: ConnectionManager, didFail error: Error)
|
||||
func connectionManager(_ manager: ConnectionManager, contentCatalogDidBecomeReady driver: CameraDriver)
|
||||
func connectionManagerDidSuggestReplug(_ manager: ConnectionManager)
|
||||
/// 边拍边传:单张照片已下载并写入目标相册。
|
||||
func connectionManager(_ manager: ConnectionManager, didSaveLiveShot filename: String, albumID: Int)
|
||||
/// 边拍边传:单张照片已下载并写入目标相册本地缓存。
|
||||
func connectionManager(_ manager: ConnectionManager, didSaveLiveShot record: TravelAlbumOTGPhotoRecord)
|
||||
}
|
||||
|
||||
extension ConnectionManagerDelegate {
|
||||
func connectionManager(_ manager: ConnectionManager, contentCatalogDidBecomeReady driver: CameraDriver) {}
|
||||
func connectionManagerDidSuggestReplug(_ manager: ConnectionManager) {}
|
||||
func connectionManager(_ manager: ConnectionManager, didSaveLiveShot filename: String, albumID: Int) {}
|
||||
func connectionManager(_ manager: ConnectionManager, didSaveLiveShot record: TravelAlbumOTGPhotoRecord) {}
|
||||
}
|
||||
|
||||
/// 有线相机连接管理抽象,便于页面 ViewModel 测试连接与目录就绪状态。
|
||||
@ -553,14 +553,14 @@ private extension ConnectionManager {
|
||||
nikonLiveCapture = nil
|
||||
}
|
||||
|
||||
private func makeLiveShotSavedHandler() -> (String) -> Void {
|
||||
{ [weak self] filename in
|
||||
private func makeLiveShotSavedHandler() -> (TravelAlbumOTGPhotoRecord) -> Void {
|
||||
{ [weak self] record in
|
||||
guard let self, let albumID = self.liveTransferAlbumID else { return }
|
||||
guard record.albumId == albumID else { return }
|
||||
self.notify {
|
||||
self.delegate?.connectionManager(
|
||||
self,
|
||||
didSaveLiveShot: filename,
|
||||
albumID: albumID
|
||||
didSaveLiveShot: record
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +67,44 @@ enum TravelAlbumOTGTransferTab: Int, CaseIterable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// OTG 传输模式,决定新照片传入 App 后是否立即上传 OSS。
|
||||
enum TravelAlbumOTGTransferMode: String, CaseIterable, Sendable {
|
||||
case liveUpload
|
||||
case postTransfer
|
||||
|
||||
/// 展示标题。
|
||||
var title: String {
|
||||
switch self {
|
||||
case .liveUpload: return "边拍边传"
|
||||
case .postTransfer: return "拍后传输"
|
||||
}
|
||||
}
|
||||
|
||||
/// 是否在新照片导入 App 后自动上传。
|
||||
var shouldAutoUploadNewImports: Bool {
|
||||
self == .liveUpload
|
||||
}
|
||||
|
||||
/// 根据展示标题解析传输模式。
|
||||
static func option(title: String) -> TravelAlbumOTGTransferMode? {
|
||||
allCases.first { $0.title == title }
|
||||
}
|
||||
}
|
||||
|
||||
/// OTG 指定上传弹窗选项。
|
||||
enum TravelAlbumOTGSpecifyUploadOption: String, CaseIterable, Sendable {
|
||||
case allPending
|
||||
case todayCaptured
|
||||
|
||||
/// 展示标题。
|
||||
var title: String {
|
||||
switch self {
|
||||
case .allPending: return "上传所有未上传的照片"
|
||||
case .todayCaptured: return "上传所有今日拍摄的照片"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TravelAlbumOTGPhotoItem {
|
||||
/// 将 Android 同款 `yyyy-MM-dd HH:mm:ss` 拍摄时间转为本地日期。
|
||||
func capturedDate() -> Date? {
|
||||
|
||||
@ -89,7 +89,8 @@ protocol PhotoRepositoryProtocol {
|
||||
func fetchPhotos(albumID: Int) throws -> [TravelAlbumOTGPhotoRecord]
|
||||
|
||||
/// 添加一张已下载到本地的照片。
|
||||
func addPhoto(albumID: Int, localPath: String, createdAt: Date) throws
|
||||
@discardableResult
|
||||
func addPhoto(albumID: Int, localPath: String, createdAt: Date) throws -> TravelAlbumOTGPhotoRecord
|
||||
|
||||
/// 删除指定照片及本地文件。
|
||||
func deletePhoto(photoID: String, albumID: Int) throws
|
||||
@ -110,12 +111,15 @@ final class TravelAlbumOTGPhotoRepository: PhotoRepositoryProtocol {
|
||||
}
|
||||
|
||||
/// 添加一张已下载到本地的照片。
|
||||
func addPhoto(albumID: Int, localPath: String, createdAt: Date) throws {
|
||||
@discardableResult
|
||||
func addPhoto(albumID: Int, localPath: String, createdAt: Date) throws -> TravelAlbumOTGPhotoRecord {
|
||||
let fileURL = URL(fileURLWithPath: localPath)
|
||||
let attributes = try? FileManager.default.attributesOfItem(atPath: localPath)
|
||||
let size = (attributes?[.size] as? NSNumber)?.int64Value ?? 0
|
||||
let userId = storage.context.userId
|
||||
guard !userId.isEmpty else { return }
|
||||
guard !userId.isEmpty else {
|
||||
throw CameraError.connectionFailed("用户信息缺失,无法保存照片")
|
||||
}
|
||||
|
||||
let record = TravelAlbumOTGPhotoRecord(
|
||||
id: TravelAlbumOTGPhotoStore.photoId(for: fileURL.lastPathComponent, createdAt: createdAt),
|
||||
@ -128,6 +132,7 @@ final class TravelAlbumOTGPhotoRepository: PhotoRepositoryProtocol {
|
||||
userId: userId
|
||||
)
|
||||
storage.upsert(record, albumId: albumID)
|
||||
return record
|
||||
}
|
||||
|
||||
/// 删除指定照片及本地文件。
|
||||
|
||||
Reference in New Issue
Block a user