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)
}
}