refactor: extract TrackFileStats shared stat/hash helper

feat/music-streaming
Laurent 1 month ago
parent c04533d0a8
commit 1970ab58c2
  1. 4
      Music.xcodeproj/project.pbxproj
  2. 10
      Music/Services/ScannerService.swift
  3. 22
      Music/Services/TrackFileStats.swift
  4. 26
      MusicTests/TrackFileStatsTests.swift

@ -443,7 +443,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 21; CURRENT_PROJECT_VERSION = 24;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 526E96RFNP; DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
@ -488,7 +488,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 21; CURRENT_PROJECT_VERSION = 24;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 526E96RFNP; DEVELOPMENT_TEAM = 526E96RFNP;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;

@ -159,9 +159,7 @@ final class ScannerService {
} }
} }
let attrs = try FileManager.default.attributesOfItem(atPath: url.path) let stats = try TrackFileStats.compute(for: url)
let fileSize = attrs[.size] as? Int64 ?? 0
let modDate = attrs[.modificationDate] as? Date ?? Date()
return Track( return Track(
fileURL: url.absoluteString, fileURL: url.absoluteString,
@ -179,13 +177,13 @@ final class ScannerService {
fileFormat: url.pathExtension.lowercased(), fileFormat: url.pathExtension.lowercased(),
bitrate: bitrate, bitrate: bitrate,
sampleRate: sampleRate, sampleRate: sampleRate,
fileSize: fileSize, fileSize: stats.fileSize,
playCount: 0, playCount: 0,
lastPlayedAt: nil, lastPlayedAt: nil,
rating: 0, rating: 0,
dateAdded: Date(), dateAdded: Date(),
dateModified: modDate, dateModified: stats.dateModified,
fileHash: Track.computeHash(fileSize: fileSize, modificationDate: modDate) fileHash: stats.fileHash
) )
} catch { } catch {
print("Failed to extract metadata from \(url.lastPathComponent): \(error)") print("Failed to extract metadata from \(url.lastPathComponent): \(error)")

@ -0,0 +1,22 @@
import Foundation
// Reads a file's size + modification date and derives the library fileHash.
// Centralizes the computation so ScannerService (import) and TrackEditService
// (post-writeback refresh) can never drift. Hash uses Track.computeHash so the
// format stays identical to import-time hashes.
nonisolated struct TrackFileStats: Sendable {
let fileSize: Int64
let dateModified: Date
let fileHash: String
static func compute(for url: URL) throws -> TrackFileStats {
let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
let fileSize = attrs[.size] as? Int64 ?? 0
let modDate = attrs[.modificationDate] as? Date ?? Date()
return TrackFileStats(
fileSize: fileSize,
dateModified: modDate,
fileHash: Track.computeHash(fileSize: fileSize, modificationDate: modDate)
)
}
}

@ -0,0 +1,26 @@
import Foundation
import Testing
@testable import Music
// Verifies the shared file-stat helper reads size/mod-date from disk and
// produces a fileHash identical to Track.computeHash (the existing canonical formula).
struct TrackFileStatsTests {
@Test func compute_matchesTrackComputeHash() throws {
// Step 1: write a temp file with known bytes.
let url = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString + ".bin")
try Data(repeating: 0xAB, count: 1234).write(to: url)
defer { try? FileManager.default.removeItem(at: url) }
// Step 2: compute stats via the helper.
let stats = try TrackFileStats.compute(for: url)
// Step 3: independently read attrs and assert the helper agrees.
let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
let size = attrs[.size] as? Int64 ?? -1
let mod = attrs[.modificationDate] as? Date ?? Date.distantPast
#expect(stats.fileSize == size)
#expect(stats.dateModified == mod)
#expect(stats.fileHash == Track.computeHash(fileSize: size, modificationDate: mod))
}
}
Loading…
Cancel
Save