Skip to content

Commit e63b9b6

Browse files
fix(file-provider): split FileProviderExtension into extensions to fix type_body_length lint error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4ccaf60 commit e63b9b6

1 file changed

Lines changed: 158 additions & 148 deletions

File tree

Extensions/ROM File Provider/FileProviderExtension.swift

Lines changed: 158 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -432,139 +432,13 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
432432
) throws -> NSFileProviderEnumerator {
433433
return FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier)
434434
}
435+
}
435436

436-
private func performImport(from sourceURL: URL, filename: String, systemID: String) throws -> FileProviderItem {
437-
let realm = RomFileProviderLibrary.realm
438-
439-
guard let pvSystem = realm.object(ofType: PVSystem.self, forPrimaryKey: systemID),
440-
!pvSystem.isInvalidated else {
441-
throw NSFileProviderError(.noSuchItem)
442-
}
443-
444-
try Task.checkCancellation()
445-
guard let md5 = streamingMD5(for: sourceURL) else {
446-
ELOG("FileProvider: failed to compute MD5 for \(filename)")
447-
throw NSFileProviderError(.cannotSynchronize)
448-
}
449-
450-
if let existing = realm.object(ofType: PVGame.self, forPrimaryKey: md5),
451-
!existing.isInvalidated {
452-
if let existingFileURL = existing.file?.url,
453-
FileManager.default.fileExists(atPath: existingFileURL.path) {
454-
ILOG("FileProvider: ROM already exists (md5=\(md5)), skipping copy")
455-
return FileProviderItem(game: existing.asDomain(), romURL: existingFileURL)
456-
}
457-
}
458-
459-
let destDir = PVEmulatorConfiguration.romDirectory(forSystemIdentifier: systemID)
460-
try FileManager.default.createDirectory(at: destDir, withIntermediateDirectories: true, attributes: nil)
461-
462-
try Task.checkCancellation()
463-
464-
let destURL = uniqueDestinationURL(in: destDir, for: filename)
465-
try FileManager.default.copyItem(at: sourceURL, to: destURL)
466-
467-
try Task.checkCancellation()
468-
469-
if let existing = realm.object(ofType: PVGame.self, forPrimaryKey: md5),
470-
!existing.isInvalidated {
471-
ILOG("FileProvider: existing ROM record for md5=\(md5) has no valid local file; updating record with imported copy at \(destURL.lastPathComponent)")
472-
let newRomFile = PVFile(withURL: destURL)
473-
let newPartialPath = newRomFile.partialPath
474-
try realm.write {
475-
if let oldFile = existing.file {
476-
realm.delete(oldFile)
477-
}
478-
realm.add(newRomFile)
479-
existing.file = newRomFile
480-
existing.romPath = newPartialPath
481-
existing.isDownloaded = true
482-
}
483-
return FileProviderItem(game: existing.asDomain(), romURL: destURL)
484-
}
485-
486-
let romFile = PVFile(withURL: destURL)
487-
let actualFilename = destURL.lastPathComponent
488-
let game = PVGame()
489-
game.md5Hash = md5
490-
game.systemIdentifier = systemID
491-
game.system = pvSystem
492-
game.title = (actualFilename as NSString).deletingPathExtension
493-
game.requiresSync = true
494-
game.isDownloaded = true
495-
game.file = romFile
496-
game.romPath = romFile.partialPath
497-
498-
try realm.write {
499-
realm.add(romFile)
500-
realm.add(game)
501-
}
502-
503-
ILOG("FileProvider: imported \(filename) md5=\(md5) system=\(systemID)")
504-
return FileProviderItem(game: game.asDomain(), romURL: destURL)
505-
}
506-
507-
private func sanitizedFilename(from rawFilename: String) -> String {
508-
var name = (rawFilename as NSString).lastPathComponent
509-
name = name
510-
.replacingOccurrences(of: "/", with: "-")
511-
.replacingOccurrences(of: ":", with: "-")
512-
let filteredScalars = name.unicodeScalars.filter { !CharacterSet.controlCharacters.contains($0) }
513-
name = String(String.UnicodeScalarView(filteredScalars))
514-
let components = name.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }
515-
name = components.joined(separator: " ")
516-
return name.isEmpty ? "Untitled" : name
517-
}
518-
519-
private func uniqueDestinationURL(in directory: URL, for filename: String) -> URL {
520-
let safeFilename = sanitizedFilename(from: filename)
521-
let candidate = directory.appendingPathComponent(safeFilename)
522-
guard FileManager.default.fileExists(atPath: candidate.path) else {
523-
return candidate
524-
}
525-
let base = (safeFilename as NSString).deletingPathExtension
526-
let ext = (safeFilename as NSString).pathExtension
527-
var counter = 2
528-
while true {
529-
let name = ext.isEmpty ? "\(base)-\(counter)" : "\(base)-\(counter).\(ext)"
530-
let url = directory.appendingPathComponent(name)
531-
if !FileManager.default.fileExists(atPath: url.path) { return url }
532-
counter += 1
533-
}
534-
}
535-
536-
private func streamingMD5(for url: URL) -> String? {
537-
let chunkSize = 1024 * 1024
538-
guard let stream = InputStream(url: url) else { return nil }
539-
stream.open()
540-
defer { stream.close() }
541-
542-
var hasher = Insecure.MD5()
543-
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: chunkSize)
544-
defer { buffer.deallocate() }
545-
546-
while stream.hasBytesAvailable {
547-
let n = stream.read(buffer, maxLength: chunkSize)
548-
if n < 0 { return nil }
549-
if n == 0 { break }
550-
hasher.update(bufferPointer: UnsafeRawBufferPointer(start: buffer, count: n))
551-
}
552-
553-
return hasher.finalize().map { String(format: "%02X", $0) }.joined()
554-
}
437+
// MARK: - Item Resolution
555438

556-
/// Resolves symlink or canonical `game:` raw value to uppercase MD5 primary key.
557-
private func canonicalGameMD5(from raw: String) -> String? {
558-
if raw.hasPrefix(FileProviderItem.gameIdentifierPrefix) {
559-
return String(raw.dropFirst(FileProviderItem.gameIdentifierPrefix.count)).uppercased()
560-
}
561-
if raw.hasPrefix(RomFileProviderVirtualPath.symlinkPrefix) {
562-
return RomFileProviderVirtualPath.parseSymlinkMD5(from: raw)
563-
}
564-
return nil
565-
}
439+
private extension FileProviderExtension {
566440

567-
private func resolveItem(for identifier: NSFileProviderItemIdentifier) -> FileProviderItem? {
441+
func resolveItem(for identifier: NSFileProviderItemIdentifier) -> FileProviderItem? {
568442
if identifier == .rootContainer {
569443
return FileProviderItem(root: ())
570444
}
@@ -642,7 +516,7 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
642516
return nil
643517
}
644518

645-
private func resolveSystemFolder(raw: String) -> FileProviderItem? {
519+
func resolveSystemFolder(raw: String) -> FileProviderItem? {
646520
let sysID = String(raw.dropFirst(FileProviderItem.systemIdentifierPrefix.count))
647521
let realm = RomFileProviderLibrary.realm
648522
guard let pvSystem = realm.object(ofType: PVSystem.self, forPrimaryKey: sysID),
@@ -651,15 +525,15 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
651525
return FileProviderItem(system: pvSystem.asDomain(), parentItemIdentifier: parent)
652526
}
653527

654-
private func resolveCanonicalGame(raw: String) -> FileProviderItem? {
528+
func resolveCanonicalGame(raw: String) -> FileProviderItem? {
655529
let md5 = String(raw.dropFirst(FileProviderItem.gameIdentifierPrefix.count)).uppercased()
656530
let realm = RomFileProviderLibrary.realm
657531
guard let pvGame = realm.object(ofType: PVGame.self, forPrimaryKey: md5),
658532
!pvGame.isInvalidated else { return nil }
659533
return FileProviderItem(game: pvGame.asDomain(), romURL: pvGame.file?.url)
660534
}
661535

662-
private func resolveSymlink(raw: String) -> FileProviderItem? {
536+
func resolveSymlink(raw: String) -> FileProviderItem? {
663537
guard let parsed = RomFileProviderVirtualPath.parseSymlink(from: raw) else { return nil }
664538
let realm = RomFileProviderLibrary.realm
665539
guard let pvGame = realm.object(ofType: PVGame.self, forPrimaryKey: parsed.md5),
@@ -676,23 +550,36 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
676550
)
677551
}
678552

679-
private func resolvePublisherFolder(raw: String) -> FileProviderItem? {
553+
func resolvePublisherFolder(raw: String) -> FileProviderItem? {
680554
let enc = String(raw.dropFirst(RomFileProviderVirtualPath.publisherFolderPrefix.count))
681555
guard let groupingKey = RomFileProviderVirtualPath.decodeSegment(enc) else { return nil }
682556
let parent = NSFileProviderItemIdentifier(RomFileProviderRootCategory.publishers.rawIdentifier)
683557
let title = RomFileProviderLibrary.displayName(axis: .publisher, groupingKey: groupingKey)
684558
return FileProviderItem(publisherFolderGroupingKey: groupingKey, title: title, parentItemIdentifier: parent)
685559
}
686560

687-
private func resolveSaveStateGameFolder(md5: String) -> FileProviderItem? {
561+
func resolvePublisherSystemFolder(raw: String) -> FileProviderItem? {
562+
let rest = String(raw.dropFirst(RomFileProviderVirtualPath.publisherSystemPrefix.count))
563+
guard let colon = rest.firstIndex(of: ":") else { return nil }
564+
let enc = String(rest[..<colon])
565+
let systemId = String(rest[rest.index(after: colon)...])
566+
guard let groupingKey = RomFileProviderVirtualPath.decodeSegment(enc) else { return nil }
567+
let realm = RomFileProviderLibrary.realm
568+
guard let pvSystem = realm.object(ofType: PVSystem.self, forPrimaryKey: systemId),
569+
!pvSystem.isInvalidated else { return nil }
570+
let parent = NSFileProviderItemIdentifier(RomFileProviderVirtualPath.publisherFolderPrefix + enc)
571+
return FileProviderItem(publisherSystemFolderGroupingKey: groupingKey, system: pvSystem.asDomain(), parentItemIdentifier: parent)
572+
}
573+
574+
func resolveSaveStateGameFolder(md5: String) -> FileProviderItem? {
688575
let realm = RomFileProviderLibrary.realm
689576
guard let pvGame = realm.object(ofType: PVGame.self, forPrimaryKey: md5),
690577
!pvGame.isInvalidated else { return nil }
691578
let parent = NSFileProviderItemIdentifier(RomFileProviderRootCategory.saveStates.rawIdentifier)
692579
return FileProviderItem(saveStateGameFolder: pvGame.asDomain(), parentItemIdentifier: parent)
693580
}
694581

695-
private func resolveSaveStateItem(id: String) -> FileProviderItem? {
582+
func resolveSaveStateItem(id: String) -> FileProviderItem? {
696583
let realm = RomFileProviderLibrary.realm
697584
guard let pvSS = realm.object(ofType: PVSaveState.self, forPrimaryKey: id),
698585
!pvSS.isInvalidated,
@@ -717,15 +604,15 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
717604
)
718605
}
719606

720-
private func resolveScreenshotGameFolder(md5: String) -> FileProviderItem? {
607+
func resolveScreenshotGameFolder(md5: String) -> FileProviderItem? {
721608
let realm = RomFileProviderLibrary.realm
722609
guard let pvGame = realm.object(ofType: PVGame.self, forPrimaryKey: md5),
723610
!pvGame.isInvalidated else { return nil }
724611
let parent = NSFileProviderItemIdentifier(RomFileProviderRootCategory.screenshots.rawIdentifier)
725612
return FileProviderItem(screenshotGameFolder: pvGame.asDomain(), parentItemIdentifier: parent)
726613
}
727614

728-
private func resolveScreenshotItem(gameMD5: String, index: Int) -> FileProviderItem? {
615+
func resolveScreenshotItem(gameMD5: String, index: Int) -> FileProviderItem? {
729616
let realm = RomFileProviderLibrary.realm
730617
guard let pvGame = realm.object(ofType: PVGame.self, forPrimaryKey: gameMD5),
731618
!pvGame.isInvalidated else { return nil }
@@ -738,17 +625,140 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
738625
return FileProviderItem(screenshotGameMD5: gameMD5, index: index, imageURL: imageURL, parentItemIdentifier: parent)
739626
}
740627

741-
private func resolvePublisherSystemFolder(raw: String) -> FileProviderItem? {
742-
let rest = String(raw.dropFirst(RomFileProviderVirtualPath.publisherSystemPrefix.count))
743-
guard let colon = rest.firstIndex(of: ":") else { return nil }
744-
let enc = String(rest[..<colon])
745-
let systemId = String(rest[rest.index(after: colon)...])
746-
guard let groupingKey = RomFileProviderVirtualPath.decodeSegment(enc) else { return nil }
628+
/// Resolves symlink or canonical `game:` raw value to uppercase MD5 primary key.
629+
func canonicalGameMD5(from raw: String) -> String? {
630+
if raw.hasPrefix(FileProviderItem.gameIdentifierPrefix) {
631+
return String(raw.dropFirst(FileProviderItem.gameIdentifierPrefix.count)).uppercased()
632+
}
633+
if raw.hasPrefix(RomFileProviderVirtualPath.symlinkPrefix) {
634+
return RomFileProviderVirtualPath.parseSymlinkMD5(from: raw)
635+
}
636+
return nil
637+
}
638+
}
639+
640+
// MARK: - Import & File Utilities
641+
642+
private extension FileProviderExtension {
643+
644+
func performImport(from sourceURL: URL, filename: String, systemID: String) throws -> FileProviderItem {
747645
let realm = RomFileProviderLibrary.realm
748-
guard let pvSystem = realm.object(ofType: PVSystem.self, forPrimaryKey: systemId),
749-
!pvSystem.isInvalidated else { return nil }
750-
let parent = NSFileProviderItemIdentifier(RomFileProviderVirtualPath.publisherFolderPrefix + enc)
751-
return FileProviderItem(publisherSystemFolderGroupingKey: groupingKey, system: pvSystem.asDomain(), parentItemIdentifier: parent)
646+
647+
guard let pvSystem = realm.object(ofType: PVSystem.self, forPrimaryKey: systemID),
648+
!pvSystem.isInvalidated else {
649+
throw NSFileProviderError(.noSuchItem)
650+
}
651+
652+
try Task.checkCancellation()
653+
guard let md5 = streamingMD5(for: sourceURL) else {
654+
ELOG("FileProvider: failed to compute MD5 for \(filename)")
655+
throw NSFileProviderError(.cannotSynchronize)
656+
}
657+
658+
if let existing = realm.object(ofType: PVGame.self, forPrimaryKey: md5),
659+
!existing.isInvalidated {
660+
if let existingFileURL = existing.file?.url,
661+
FileManager.default.fileExists(atPath: existingFileURL.path) {
662+
ILOG("FileProvider: ROM already exists (md5=\(md5)), skipping copy")
663+
return FileProviderItem(game: existing.asDomain(), romURL: existingFileURL)
664+
}
665+
}
666+
667+
let destDir = PVEmulatorConfiguration.romDirectory(forSystemIdentifier: systemID)
668+
try FileManager.default.createDirectory(at: destDir, withIntermediateDirectories: true, attributes: nil)
669+
670+
try Task.checkCancellation()
671+
672+
let destURL = uniqueDestinationURL(in: destDir, for: filename)
673+
try FileManager.default.copyItem(at: sourceURL, to: destURL)
674+
675+
try Task.checkCancellation()
676+
677+
if let existing = realm.object(ofType: PVGame.self, forPrimaryKey: md5),
678+
!existing.isInvalidated {
679+
ILOG("FileProvider: existing ROM record for md5=\(md5) has no valid local file; updating record with imported copy at \(destURL.lastPathComponent)")
680+
let newRomFile = PVFile(withURL: destURL)
681+
let newPartialPath = newRomFile.partialPath
682+
try realm.write {
683+
if let oldFile = existing.file {
684+
realm.delete(oldFile)
685+
}
686+
realm.add(newRomFile)
687+
existing.file = newRomFile
688+
existing.romPath = newPartialPath
689+
existing.isDownloaded = true
690+
}
691+
return FileProviderItem(game: existing.asDomain(), romURL: destURL)
692+
}
693+
694+
let romFile = PVFile(withURL: destURL)
695+
let actualFilename = destURL.lastPathComponent
696+
let game = PVGame()
697+
game.md5Hash = md5
698+
game.systemIdentifier = systemID
699+
game.system = pvSystem
700+
game.title = (actualFilename as NSString).deletingPathExtension
701+
game.requiresSync = true
702+
game.isDownloaded = true
703+
game.file = romFile
704+
game.romPath = romFile.partialPath
705+
706+
try realm.write {
707+
realm.add(romFile)
708+
realm.add(game)
709+
}
710+
711+
ILOG("FileProvider: imported \(filename) md5=\(md5) system=\(systemID)")
712+
return FileProviderItem(game: game.asDomain(), romURL: destURL)
713+
}
714+
715+
func sanitizedFilename(from rawFilename: String) -> String {
716+
var name = (rawFilename as NSString).lastPathComponent
717+
name = name
718+
.replacingOccurrences(of: "/", with: "-")
719+
.replacingOccurrences(of: ":", with: "-")
720+
let filteredScalars = name.unicodeScalars.filter { !CharacterSet.controlCharacters.contains($0) }
721+
name = String(String.UnicodeScalarView(filteredScalars))
722+
let components = name.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }
723+
name = components.joined(separator: " ")
724+
return name.isEmpty ? "Untitled" : name
725+
}
726+
727+
func uniqueDestinationURL(in directory: URL, for filename: String) -> URL {
728+
let safeFilename = sanitizedFilename(from: filename)
729+
let candidate = directory.appendingPathComponent(safeFilename)
730+
guard FileManager.default.fileExists(atPath: candidate.path) else {
731+
return candidate
732+
}
733+
let base = (safeFilename as NSString).deletingPathExtension
734+
let ext = (safeFilename as NSString).pathExtension
735+
var counter = 2
736+
while true {
737+
let name = ext.isEmpty ? "\(base)-\(counter)" : "\(base)-\(counter).\(ext)"
738+
let url = directory.appendingPathComponent(name)
739+
if !FileManager.default.fileExists(atPath: url.path) { return url }
740+
counter += 1
741+
}
742+
}
743+
744+
func streamingMD5(for url: URL) -> String? {
745+
let chunkSize = 1024 * 1024
746+
guard let stream = InputStream(url: url) else { return nil }
747+
stream.open()
748+
defer { stream.close() }
749+
750+
var hasher = Insecure.MD5()
751+
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: chunkSize)
752+
defer { buffer.deallocate() }
753+
754+
while stream.hasBytesAvailable {
755+
let n = stream.read(buffer, maxLength: chunkSize)
756+
if n < 0 { return nil }
757+
if n == 0 { break }
758+
hasher.update(bufferPointer: UnsafeRawBufferPointer(start: buffer, count: n))
759+
}
760+
761+
return hasher.finalize().map { String(format: "%02X", $0) }.joined()
752762
}
753763
}
754764

0 commit comments

Comments
 (0)