Skip to content

Commit a0db45a

Browse files
fix(file-provider): static DateFormatter + edge-case tests
- Extract DateFormatter in FileProviderItem.filename (saveStateFile) to a private static lazy property — eliminates repeated allocation on every filename access without changing output behaviour - Add edge-case tests: parseSaveStateID/parseSaveStateGameMD5 with empty content after prefix, parseScreenshotID must not match sc-game: prefix, screenshotItemIdentifier round-trip with zero index Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ab94423 commit a0db45a

File tree

2 files changed

+27
-5
lines changed

2 files changed

+27
-5
lines changed

Extensions/ROM File Provider/FileProviderItem.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,7 @@ final class FileProviderItem: NSObject, NSFileProviderItem {
251251
case .saveStateFile(let id, _, let date, let isAutosave, let userDescription, let fileURL, _):
252252
if let name = fileURL?.lastPathComponent, !name.isEmpty { return sanitize(name) }
253253
let label = isAutosave ? "Auto" : (userDescription ?? "")
254-
let formatter = DateFormatter()
255-
formatter.locale = Locale(identifier: "en_US_POSIX")
256-
formatter.timeZone = TimeZone(secondsFromGMT: 0)
257-
formatter.dateFormat = "yyyy-MM-dd HH-mm-ss"
258-
let dateStr = formatter.string(from: date)
254+
let dateStr = FileProviderItem.saveStateDateFormatter.string(from: date)
259255
let prefix = label.isEmpty ? dateStr : "\(label) \(dateStr)"
260256
return sanitize("\(prefix) \(id.prefix(8)).pvs")
261257
case .screenshotGameFolder(let game, _):
@@ -425,6 +421,15 @@ final class FileProviderItem: NSObject, NSFileProviderItem {
425421
return attributes?[.modificationDate] as? Date
426422
}()
427423

424+
/// Shared formatter for save state filenames — created once (DateFormatter is expensive).
425+
private static let saveStateDateFormatter: DateFormatter = {
426+
let f = DateFormatter()
427+
f.locale = Locale(identifier: "en_US_POSIX")
428+
f.timeZone = TimeZone(secondsFromGMT: 0)
429+
f.dateFormat = "yyyy-MM-dd HH-mm-ss"
430+
return f
431+
}()
432+
428433
/// Sanitizes a raw name for safe use as a file provider filename.
429434
private func sanitize(_ raw: String) -> String {
430435
let withoutSeparators = raw

PVPrimitives/Tests/PVPrimitivesTests/RomFileProviderVirtualPathTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ struct RomFileProviderVirtualPathTests {
8181
@Test func parseSaveStateID_invalidPrefix() {
8282
#expect(RomFileProviderVirtualPath.parseSaveStateID(from: "game:ABCD") == nil)
8383
#expect(RomFileProviderVirtualPath.parseSaveStateGameMD5(from: "rating:5") == nil)
84+
// Empty id after prefix
85+
#expect(RomFileProviderVirtualPath.parseSaveStateID(from: "ss:") == nil)
86+
// Empty MD5 after folder prefix
87+
#expect(RomFileProviderVirtualPath.parseSaveStateGameMD5(from: "ss-game:") == nil)
8488
}
8589

8690
// MARK: - Screenshot identifiers
@@ -105,6 +109,19 @@ struct RomFileProviderVirtualPathTests {
105109
@Test func parseScreenshotID_invalidPrefix() {
106110
#expect(RomFileProviderVirtualPath.parseScreenshotID(from: "game:ABCD") == nil)
107111
#expect(RomFileProviderVirtualPath.parseScreenshotGameMD5(from: "year:1998") == nil)
112+
// Empty MD5 after folder prefix
113+
#expect(RomFileProviderVirtualPath.parseScreenshotGameMD5(from: "sc-game:") == nil)
114+
// sc-game: prefix must NOT be parsed as a screenshot item (sc: prefix)
115+
let scGameRaw = "sc-game:" + String(repeating: "A", count: 32)
116+
#expect(RomFileProviderVirtualPath.parseScreenshotID(from: scGameRaw) == nil)
117+
}
118+
119+
@Test func screenshotItemIdentifier_zeroIndex() {
120+
let md5 = String(repeating: "D", count: 32)
121+
let id = RomFileProviderVirtualPath.screenshotItemIdentifier(gameMD5: md5, index: 0)
122+
let parsed = RomFileProviderVirtualPath.parseScreenshotID(from: id)
123+
#expect(parsed?.gameMD5 == md5.uppercased())
124+
#expect(parsed?.index == 0)
108125
}
109126

110127
// MARK: - Root categories

0 commit comments

Comments
 (0)