将 otg_swift OTG 引擎迁入 Core/CameraOTG,并接入旅拍有线传图与历史导入。

恢复 USB 相机连接、边拍边传、三品牌驱动与 SwiftUI 历史导入页,通过适配层对接现有本地上传与 OSS 管道。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 11:22:07 +08:00
parent d7fec35715
commit be9c844bd3
50 changed files with 5721 additions and 20 deletions

View File

@ -0,0 +1,82 @@
//
// OTGPhotoStorageTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
/// OTGPhotoFileWriter scoped
final class OTGPhotoStorageTests: XCTestCase {
private let accountKey = "test_account"
private let albumID = 12345
override func tearDown() {
let directory = OTGPhotoFileWriter.originalsDirectory(accountKey: accountKey, albumID: albumID)
try? FileManager.default.removeItem(at: directory.deletingLastPathComponent())
super.tearDown()
}
/// scoped
func testOriginalsDirectoryScopedByAccountAndAlbum() {
let directory = OTGPhotoFileWriter.originalsDirectory(accountKey: accountKey, albumID: albumID)
XCTAssertTrue(directory.path.contains("CameraDownloads"))
XCTAssertTrue(directory.path.contains("\(albumID)"))
XCTAssertTrue(directory.lastPathComponent == "originals")
XCTAssertTrue(FileManager.default.fileExists(atPath: directory.path))
}
/// writeImage
func testWriteImageReturnsRelativePath() throws {
let data = Data(repeating: 0xAB, count: 64)
let fileURL = try OTGPhotoFileWriter.writeImage(
data,
filename: "DSC_0001.JPG",
accountKey: accountKey,
albumID: albumID
)
XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path))
let relativePath = OTGPhotoFileWriter.relativePath(for: fileURL)
XCTAssertFalse(relativePath.hasPrefix("/"))
XCTAssertTrue(relativePath.contains("DSC_0001.JPG"))
let resolved = CameraDownloadStorage.resolveLocalURL(from: relativePath)
XCTAssertEqual(resolved?.standardizedFileURL, fileURL.standardizedFileURL)
}
///
func testUniqueFileURLAppendsCounterForDuplicates() throws {
let data = Data(repeating: 0xCD, count: 16)
let firstURL = try OTGPhotoFileWriter.writeImage(
data,
filename: "duplicate.JPG",
accountKey: accountKey,
albumID: albumID
)
let secondURL = try OTGPhotoFileWriter.uniqueFileURL(
filename: "duplicate.JPG",
accountKey: accountKey,
albumID: albumID
)
XCTAssertNotEqual(firstURL.lastPathComponent, secondURL.lastPathComponent)
XCTAssertTrue(secondURL.lastPathComponent.contains("duplicate_1"))
}
/// moveDownloadedFile scoped
func testMoveDownloadedFileIntoScopedDirectory() throws {
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("otg-move-test.JPG")
try Data(repeating: 0xEF, count: 32).write(to: tempURL)
defer { try? FileManager.default.removeItem(at: tempURL) }
let destination = try OTGPhotoFileWriter.moveDownloadedFile(
from: tempURL,
filename: "imported.JPG",
accountKey: accountKey,
albumID: albumID
)
XCTAssertTrue(FileManager.default.fileExists(atPath: destination.path))
XCTAssertFalse(FileManager.default.fileExists(atPath: tempURL.path))
}
}

View File

@ -0,0 +1,76 @@
//
// PTPUtilitiesTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
/// PTP Container
final class PTPUtilitiesTests: XCTestCase {
/// buildCommand parseContainer
func testBuildAndParseCommandRoundTrip() throws {
let command = PTPHelper.buildCommand(
code: PTPStandardCommand.getObject,
transactionID: 42,
parameters: [1, 2, 3]
)
let parsed = try PTPHelper.parseContainer(command)
XCTAssertEqual(parsed.length, UInt32(12 + 4 * 3))
XCTAssertEqual(parsed.type, PTPContainerType.command.rawValue)
XCTAssertEqual(parsed.code, PTPStandardCommand.getObject)
XCTAssertEqual(parsed.transactionID, 42)
XCTAssertEqual(parsed.params, [1, 2, 3])
}
/// tooShort
func testParseContainerTooShortThrows() {
XCTAssertThrowsError(try PTPHelper.parseContainer(Data([0x01, 0x02]))) { error in
XCTAssertEqual(error as? PTPParseError, .tooShort)
}
}
/// length buffer invalidLength
func testParseContainerInvalidLengthThrows() {
var data = Data(count: 12)
data[0] = 32
XCTAssertThrowsError(try PTPHelper.parseContainer(data)) { error in
XCTAssertEqual(error as? PTPParseError, .invalidLength)
}
}
/// objectSignature
func testObjectSignature() {
XCTAssertEqual(PTPHelper.objectSignature(filename: "DSC_0001.JPG", size: 2048), "DSC_0001.JPG_2048")
}
/// sanitizeFilename
func testSanitizeFilename() {
XCTAssertEqual(PTPHelper.sanitizeFilename("DSC/0001.JPG"), "DSC_0001.JPG")
XCTAssertEqual(PTPHelper.sanitizeFilename(" "), "photo.jpg")
}
/// ObjectInfo
func testParseObjectCompressedSize() {
var data = Data(count: 12)
data[8] = 0x00
data[9] = 0x10
data[10] = 0x00
data[11] = 0x00
XCTAssertEqual(PTPHelper.parseObjectCompressedSize(data), 4096)
}
/// ResponseOK
func testIsResponseOK() throws {
let okResponse = PTPHelper.buildCommand(
code: PTPStandardCommand.responseOK,
transactionID: 1,
parameters: []
)
var responseData = okResponse
responseData[4] = UInt8(PTPContainerType.response.rawValue & 0xFF)
responseData[5] = UInt8((PTPContainerType.response.rawValue >> 8) & 0xFF)
XCTAssertTrue(PTPHelper.isResponseOK(responseData))
}
}

View File

@ -0,0 +1,95 @@
//
// PlatformDetectorTests.swift
// suixinkanTests
//
import XCTest
@testable import suixinkan
/// PlatformDetector USB VID
final class PlatformDetectorTests: XCTestCase {
/// Sony USB Vendor ID
func testDetectSonyByVendorID() {
let info = CameraDeviceInfo(
name: "USB Device",
manufacturer: nil,
model: nil,
serialNumber: nil,
usbVendorID: 0x054C,
usbProductID: 0x1234
)
XCTAssertEqual(PlatformDetector.detect(from: info), .sony)
}
/// Canon USB Vendor ID
func testDetectCanonByVendorID() {
let info = CameraDeviceInfo(
name: "USB Device",
manufacturer: nil,
model: nil,
serialNumber: nil,
usbVendorID: 0x04A9,
usbProductID: nil
)
XCTAssertEqual(PlatformDetector.detect(from: info), .canon)
}
/// Nikon USB Vendor ID
func testDetectNikonByVendorID() {
let info = CameraDeviceInfo(
name: "USB Device",
manufacturer: nil,
model: nil,
serialNumber: nil,
usbVendorID: 0x04B0,
usbProductID: nil
)
XCTAssertEqual(PlatformDetector.detect(from: info), .nikon)
}
/// VID
func testDetectByDeviceNameKeyword() {
let sony = CameraDeviceInfo(
name: "ILCE-7M4",
manufacturer: nil,
model: nil,
serialNumber: nil,
usbVendorID: nil,
usbProductID: nil
)
XCTAssertEqual(PlatformDetector.detect(from: sony), .sony)
let canon = CameraDeviceInfo(
name: "Canon EOS R5",
manufacturer: nil,
model: nil,
serialNumber: nil,
usbVendorID: nil,
usbProductID: nil
)
XCTAssertEqual(PlatformDetector.detect(from: canon), .canon)
let nikon = CameraDeviceInfo(
name: "Nikon Z 8",
manufacturer: nil,
model: nil,
serialNumber: nil,
usbVendorID: nil,
usbProductID: nil
)
XCTAssertEqual(PlatformDetector.detect(from: nikon), .nikon)
}
/// unknown
func testDetectUnknownWhenNoMatch() {
let info = CameraDeviceInfo(
name: "Generic Webcam",
manufacturer: nil,
model: nil,
serialNumber: nil,
usbVendorID: nil,
usbProductID: nil
)
XCTAssertEqual(PlatformDetector.detect(from: info), .unknown)
}
}