Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Folium-iOS/Actors/GamesManager.swift
2 views
//
//  GamesManager.swift
//  Folium-iOS
//
//  Created by Jarrod Norwell on 15/7/2025.
//

import CombinedCores
import Cytrus
import Foundation

public enum GamesManagerError : Error  {
    case generic(String)
}

actor GamesManager {
    let combinedCores: CombinedCores
    let skinManager: SkinManager = SkinManager()
    
    let cytrus: Cytrus
    
    init(combinedCores: CombinedCores, cytrus: Cytrus) {
        self.combinedCores = combinedCores
        self.cytrus = cytrus
    }
    
    func games() async -> (AnyRangeReplaceableCollection<Core>, [BaseGame], [Core : [AnyError]]) {
        func add(errors: inout [Core : [AnyError]], error: AnyError, to core: Core) {
            errors[core]?.append(error)
        }
        
        let cores: [Core] = Core.cores
        var coresWithGames: AnyRangeReplaceableCollection<Core> = []
        var errors: [Core : [AnyError]] = cores.reduce(into: [Core : [AnyError]](), { $0[$1] = [] })
        var games: AnyRangeReplaceableCollection<BaseGame> = []
        
        do {
            try skinManager.getSkins()
        } catch {
            add(errors: &errors, error: error, to: .generic)
        }
        
        guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return (coresWithGames, games.sorted(), errors)
        }
        
        /*
        for element in cytrus.installed() {
            do {
                let result = try CytrusGame.cytrusGame(from: element, with: skinManager, and: cytrus)
                if !result.game.title.trimmingCharacters(in: .whitespaces).isEmpty {
                    games.appendUnique(result.game)
                }
                if let error = result.error {
                    add(errors: &errors, error: error, to: .cytrus)
                }
            } catch {
                add(errors: &errors, error: error, to: .cytrus)
            }
        }
        
        for element in cytrus.system() {
            do {
                let result = try CytrusGame.cytrusGame(from: element, with: skinManager, and: cytrus)
                if !result.game.title.trimmingCharacters(in: .whitespaces).isEmpty {
                    games.appendUnique(result.game)
                }
                if let error = result.error {
                    add(errors: &errors, error: error, to: .cytrus)
                }
            } catch {
                add(errors: &errors, error: error, to: .cytrus)
            }
        }
         */
        
        await cores.asyncForEach { core in
            let romsDirectory = documentDirectory.appending(component: core.string).appending(component: "roms")
            if let enumerator = FileManager.default.enumerator(at: romsDirectory, includingPropertiesForKeys: [.isRegularFileKey]) {
                await enumerator.asyncForEach { element in
                    guard var cartridge: URL = element as? URL else {
                        return
                    }
                    
                    do {
                        let `extension`: String = cartridge.pathExtension.lowercased()
                        if let supportedFormat: SupportedFormat = SupportedFormat(rawValue: `extension`) {
                            switch core {
                            case .cherry:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    games.appendUnique(NewCherryGame(cartridge: cartridge, skinManager: skinManager))
                                }
                            case .cytrus:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    
                                    func renameAndMove() throws -> URL {
                                        let to: URL = cartridge.deletingPathExtension().appendingPathExtension("cci")
                                        try FileManager.default.moveItem(at: cartridge, to: to)
                                        return to
                                    }
                                    
                                    if supportedFormat == .`3ds` {
                                        cartridge = try renameAndMove()
                                    }
                                    
                                    let game: NewCytrusGame = NewCytrusGame(cartridge: cartridge, cytrusCore: cytrus, skinManager: skinManager)
                                    if let information: CytrusGameInformation = cytrus.information(cartridge) {
                                        var error: NSError?
                                        game.extras = NewCytrusGame.Extras(cytrus: cytrus, identifier: information.identifier, &error)
                                        game.icon = information.icon
                                        game.information = NewCytrusGame.Information(from: information)
                                        game.details.name = information.title
                                    }
                                    
                                    if !game.details.name.trimmingCharacters(in: .whitespaces).isEmpty {
                                        games.appendUnique(game)
                                    }
                                }
                            case .grape:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    
                                    let game: NewGrapeGame = NewGrapeGame(cartridge: cartridge,
                                                                          grapeCore: await combinedCores.grapeCore,
                                                                          skinManager: skinManager)
                                    game.icon = CGImage.grapeIcon(buffer: await combinedCores.grapeCore.icon(cartridge: cartridge).buffer)
                                    
                                    games.appendUnique(game)
                                }
                            case .guava:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    games.appendUnique(NewGuavaGame(cartridge: cartridge,
                                                                    guavaCore: await combinedCores.guavaCore,
                                                                    skinManager: skinManager))
                                }
                            case .kiwi:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    
                                    let game: NewKiwiGame = NewKiwiGame(cartridge: cartridge,
                                                                        kiwiCore: await combinedCores.kiwiCore,
                                                                        skinManager: skinManager)
                                    game.details.name = await combinedCores.kiwiCore.title(cartridge: cartridge)
                                    
                                    games.appendUnique(game)
                                }
                            case .mandarine:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    
                                    let game: NewMandarineGame = NewMandarineGame(cartridge: cartridge,
                                                                                  mandarineCore: await combinedCores.mandarineCore,
                                                                                  skinManager: skinManager)
                                    game.icon = await combinedCores.mandarineCore.icon(cartridge: cartridge)
                                    
                                    games.appendUnique(game)
                                }
                            case .mango:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    
                                    let game: NewMangoGame = NewMangoGame(cartridge: cartridge,
                                                                          mangoCore: await combinedCores.mangoCore,
                                                                          skinManager: skinManager)
                                    game.icon = await combinedCores.mangoCore.icon(cartridge: cartridge)
                                    
                                    games.appendUnique(game)
                                }
                            case .plum:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    games.appendUnique(NewPlumGame(cartridge: cartridge,
                                                                    skinManager: skinManager))
                                }
                            case .tomato:
                                if core.extensions.contains(supportedFormat) {
                                    coresWithGames.appendUnique(core)
                                    
                                    let game: NewTomatoGame = NewTomatoGame(cartridge: cartridge,
                                                                            tomatoCore: await combinedCores.tomatoCore,
                                                                            skinManager: skinManager)
                                    game.icon = await combinedCores.tomatoCore.icon(cartridge: cartridge)
                                    
                                    games.appendUnique(game)
                                }
                            default: break
                            }
                        }
                    } catch {
                        add(errors: &errors, error: error, to: core)
                    }
                }
            }
        }
        
        return (coresWithGames, games.sorted(), errors)
    }
}