@@ -92,7 +92,69 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
9292 request: NSFileProviderRequest ,
9393 completionHandler: @escaping ( URL ? , NSFileProviderItem ? , Error ? ) -> Void
9494 ) -> Progress {
95- guard let md5 = canonicalGameMD5 ( from: itemIdentifier. rawValue) else {
95+ let raw = itemIdentifier. rawValue
96+
97+ // Save state file
98+ if let ssID = RomFileProviderVirtualPath . parseSaveStateID ( from: raw) {
99+ let realm = RomFileProviderLibrary . realm
100+ guard let pvSS = realm. object ( ofType: PVSaveState . self, forPrimaryKey: ssID) ,
101+ !pvSS. isInvalidated,
102+ let pvGame = pvSS. game, !pvGame. isInvalidated,
103+ let pvFile = pvSS. file, let fileURL = pvFile. url,
104+ FileManager . default. fileExists ( atPath: fileURL. path) else {
105+ completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem) )
106+ return Progress ( )
107+ }
108+ let md5 = pvGame. md5Hash
109+ let parent = NSFileProviderItemIdentifier ( RomFileProviderVirtualPath . saveStateGameFolderIdentifier ( gameMD5: md5) )
110+ let item = FileProviderItem (
111+ saveStateID: ssID,
112+ game: pvGame. asDomain ( ) ,
113+ date: pvSS. date,
114+ isAutosave: pvSS. isAutosave,
115+ userDescription: pvSS. userDescription,
116+ fileURL: fileURL,
117+ parentItemIdentifier: parent
118+ )
119+ ILOG ( " FileProvider: serving save state \( fileURL. lastPathComponent) for \( pvGame. title) " )
120+ completionHandler ( fileURL, item, nil )
121+ return Progress ( )
122+ }
123+
124+ // Screenshot file
125+ if let parsed = RomFileProviderVirtualPath . parseScreenshotID ( from: raw) {
126+ let realm = RomFileProviderLibrary . realm
127+ guard let pvGame = realm. object ( ofType: PVGame . self, forPrimaryKey: parsed. gameMD5) ,
128+ !pvGame. isInvalidated else {
129+ completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem) )
130+ return Progress ( )
131+ }
132+ let shots = Array ( pvGame. screenShots)
133+ guard parsed. index < shots. count else {
134+ completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem) )
135+ return Progress ( )
136+ }
137+ let pvImageFile = shots [ parsed. index]
138+ guard !pvImageFile. isInvalidated,
139+ let imageURL = pvImageFile. url,
140+ FileManager . default. fileExists ( atPath: imageURL. path) else {
141+ completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem) )
142+ return Progress ( )
143+ }
144+ let parent = NSFileProviderItemIdentifier ( RomFileProviderVirtualPath . screenshotGameFolderIdentifier ( gameMD5: parsed. gameMD5) )
145+ let item = FileProviderItem (
146+ screenshotGameMD5: parsed. gameMD5,
147+ index: parsed. index,
148+ imageURL: imageURL,
149+ parentItemIdentifier: parent
150+ )
151+ ILOG ( " FileProvider: serving screenshot \( imageURL. lastPathComponent) for \( pvGame. title) " )
152+ completionHandler ( imageURL, item, nil )
153+ return Progress ( )
154+ }
155+
156+ // ROM file
157+ guard let md5 = canonicalGameMD5 ( from: raw) else {
96158 completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem) )
97159 return Progress ( )
98160 }
@@ -173,7 +235,16 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
173235 request: NSFileProviderRequest ,
174236 completionHandler: @escaping ( NSFileProviderItem ? , NSFileProviderItemFields , Bool , Error ? ) -> Void
175237 ) -> Progress {
176- guard let md5 = canonicalGameMD5 ( from: item. itemIdentifier. rawValue) else {
238+ let raw = item. itemIdentifier. rawValue
239+
240+ // Save states and screenshots are read-only (no renames or content edits)
241+ if RomFileProviderVirtualPath . parseSaveStateID ( from: raw) != nil
242+ || RomFileProviderVirtualPath . parseScreenshotID ( from: raw) != nil {
243+ completionHandler ( nil , [ ] , false , CocoaError ( . featureUnsupported) )
244+ return Progress ( )
245+ }
246+
247+ guard let md5 = canonicalGameMD5 ( from: raw) else {
177248 completionHandler ( nil , [ ] , false , CocoaError ( . featureUnsupported) )
178249 return Progress ( )
179250 }
@@ -245,7 +316,73 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
245316 request: NSFileProviderRequest ,
246317 completionHandler: @escaping ( Error ? ) -> Void
247318 ) -> Progress {
248- guard let md5 = canonicalGameMD5 ( from: identifier. rawValue) else {
319+ let raw = identifier. rawValue
320+
321+ // Delete save state
322+ if let ssID = RomFileProviderVirtualPath . parseSaveStateID ( from: raw) {
323+ do {
324+ let realm = RomFileProviderLibrary . realm
325+ guard let pvSS = realm. object ( ofType: PVSaveState . self, forPrimaryKey: ssID) ,
326+ !pvSS. isInvalidated else {
327+ completionHandler ( nil )
328+ return Progress ( )
329+ }
330+ if let pvFile = pvSS. file, let fileURL = pvFile. url,
331+ FileManager . default. fileExists ( atPath: fileURL. path) {
332+ try FileManager . default. removeItem ( at: fileURL)
333+ ILOG ( " FileProvider: deleted save state file \( fileURL. lastPathComponent) " )
334+ }
335+ let imageToDelete = pvSS. image
336+ try realm. write {
337+ if let img = imageToDelete { realm. delete ( img) }
338+ if let f = pvSS. file { realm. delete ( f) }
339+ realm. delete ( pvSS)
340+ }
341+ completionHandler ( nil )
342+ } catch {
343+ ELOG ( " FileProvider: deleteItem (save state) error — \( error) " )
344+ completionHandler ( error)
345+ }
346+ return Progress ( )
347+ }
348+
349+ // Delete screenshot
350+ if let parsed = RomFileProviderVirtualPath . parseScreenshotID ( from: raw) {
351+ do {
352+ let realm = RomFileProviderLibrary . realm
353+ guard let pvGame = realm. object ( ofType: PVGame . self, forPrimaryKey: parsed. gameMD5) ,
354+ !pvGame. isInvalidated else {
355+ completionHandler ( nil )
356+ return Progress ( )
357+ }
358+ let shots = Array ( pvGame. screenShots)
359+ guard parsed. index < shots. count else {
360+ completionHandler ( nil )
361+ return Progress ( )
362+ }
363+ let pvImageFile = shots [ parsed. index]
364+ guard !pvImageFile. isInvalidated else {
365+ completionHandler ( nil )
366+ return Progress ( )
367+ }
368+ if let imageURL = pvImageFile. url,
369+ FileManager . default. fileExists ( atPath: imageURL. path) {
370+ try FileManager . default. removeItem ( at: imageURL)
371+ ILOG ( " FileProvider: deleted screenshot \( imageURL. lastPathComponent) " )
372+ }
373+ try realm. write {
374+ realm. delete ( pvImageFile)
375+ }
376+ completionHandler ( nil )
377+ } catch {
378+ ELOG ( " FileProvider: deleteItem (screenshot) error — \( error) " )
379+ completionHandler ( error)
380+ }
381+ return Progress ( )
382+ }
383+
384+ // Delete ROM game
385+ guard let md5 = canonicalGameMD5 ( from: raw) else {
249386 completionHandler ( CocoaError ( . featureUnsupported) )
250387 return Progress ( )
251388 }
@@ -486,6 +623,22 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
486623 return FileProviderItem ( ratingFolderKey: key, title: title, parentItemIdentifier: parent)
487624 }
488625
626+ if let md5 = RomFileProviderVirtualPath . parseSaveStateGameMD5 ( from: raw) {
627+ return resolveSaveStateGameFolder ( md5: md5)
628+ }
629+
630+ if let ssID = RomFileProviderVirtualPath . parseSaveStateID ( from: raw) {
631+ return resolveSaveStateItem ( id: ssID)
632+ }
633+
634+ if let md5 = RomFileProviderVirtualPath . parseScreenshotGameMD5 ( from: raw) {
635+ return resolveScreenshotGameFolder ( md5: md5)
636+ }
637+
638+ if let parsed = RomFileProviderVirtualPath . parseScreenshotID ( from: raw) {
639+ return resolveScreenshotItem ( gameMD5: parsed. gameMD5, index: parsed. index)
640+ }
641+
489642 return nil
490643 }
491644
@@ -531,6 +684,60 @@ final class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension {
531684 return FileProviderItem ( publisherFolderGroupingKey: groupingKey, title: title, parentItemIdentifier: parent)
532685 }
533686
687+ private func resolveSaveStateGameFolder( md5: String ) -> FileProviderItem ? {
688+ let realm = RomFileProviderLibrary . realm
689+ guard let pvGame = realm. object ( ofType: PVGame . self, forPrimaryKey: md5) ,
690+ !pvGame. isInvalidated else { return nil }
691+ let parent = NSFileProviderItemIdentifier ( RomFileProviderRootCategory . saveStates. rawIdentifier)
692+ return FileProviderItem ( saveStateGameFolder: pvGame. asDomain ( ) , parentItemIdentifier: parent)
693+ }
694+
695+ private func resolveSaveStateItem( id: String ) -> FileProviderItem ? {
696+ let realm = RomFileProviderLibrary . realm
697+ guard let pvSS = realm. object ( ofType: PVSaveState . self, forPrimaryKey: id) ,
698+ !pvSS. isInvalidated,
699+ let pvGame = pvSS. game, !pvGame. isInvalidated else { return nil }
700+ let fileURL : URL ?
701+ if let pvFile = pvSS. file, let url = pvFile. url,
702+ FileManager . default. fileExists ( atPath: url. path) {
703+ fileURL = url
704+ } else {
705+ fileURL = nil
706+ }
707+ let md5 = pvGame. md5Hash
708+ let parent = NSFileProviderItemIdentifier ( RomFileProviderVirtualPath . saveStateGameFolderIdentifier ( gameMD5: md5) )
709+ return FileProviderItem (
710+ saveStateID: id,
711+ game: pvGame. asDomain ( ) ,
712+ date: pvSS. date,
713+ isAutosave: pvSS. isAutosave,
714+ userDescription: pvSS. userDescription,
715+ fileURL: fileURL,
716+ parentItemIdentifier: parent
717+ )
718+ }
719+
720+ private func resolveScreenshotGameFolder( md5: String ) -> FileProviderItem ? {
721+ let realm = RomFileProviderLibrary . realm
722+ guard let pvGame = realm. object ( ofType: PVGame . self, forPrimaryKey: md5) ,
723+ !pvGame. isInvalidated else { return nil }
724+ let parent = NSFileProviderItemIdentifier ( RomFileProviderRootCategory . screenshots. rawIdentifier)
725+ return FileProviderItem ( screenshotGameFolder: pvGame. asDomain ( ) , parentItemIdentifier: parent)
726+ }
727+
728+ private func resolveScreenshotItem( gameMD5: String , index: Int ) -> FileProviderItem ? {
729+ let realm = RomFileProviderLibrary . realm
730+ guard let pvGame = realm. object ( ofType: PVGame . self, forPrimaryKey: gameMD5) ,
731+ !pvGame. isInvalidated else { return nil }
732+ let shots = Array ( pvGame. screenShots)
733+ guard index < shots. count else { return nil }
734+ let pvImageFile = shots [ index]
735+ guard !pvImageFile. isInvalidated else { return nil }
736+ let imageURL = pvImageFile. url. flatMap { FileManager . default. fileExists ( atPath: $0. path) ? $0 : nil }
737+ let parent = NSFileProviderItemIdentifier ( RomFileProviderVirtualPath . screenshotGameFolderIdentifier ( gameMD5: gameMD5) )
738+ return FileProviderItem ( screenshotGameMD5: gameMD5, index: index, imageURL: imageURL, parentItemIdentifier: parent)
739+ }
740+
534741 private func resolvePublisherSystemFolder( raw: String ) -> FileProviderItem ? {
535742 let rest = String ( raw. dropFirst ( RomFileProviderVirtualPath . publisherSystemPrefix. count) )
536743 guard let colon = rest. firstIndex ( of: " : " ) else { return nil }
0 commit comments