diff --git a/PadelClub/Data/Federal/FederalPlayer.swift b/PadelClub/Data/Federal/FederalPlayer.swift index a8b2780..d5a6784 100644 --- a/PadelClub/Data/Federal/FederalPlayer.swift +++ b/PadelClub/Data/Federal/FederalPlayer.swift @@ -197,8 +197,6 @@ class FederalPlayer: Decodable { } lastPlayerFetch.predicate = predicate let count = try? context.count(for: lastPlayerFetch) - - print("count", count) do { if let lr = try context.fetch(lastPlayerFetch).first?.rank { let fetch = ImportedPlayer.fetchRequest() @@ -207,8 +205,9 @@ class FederalPlayer: Decodable { rankPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [rankPredicate, NSPredicate(format: "importDate == %@", mostRecentDateAvailable as CVarArg)]) } fetch.predicate = rankPredicate - + print(fetch.predicate) let lastPlayersCount = try context.count(for: fetch) + print(Int(lr), Int(lastPlayersCount) - 1, count) return (Int(lr) + Int(lastPlayersCount) - 1, count) } } catch { diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index bd93be7..3d17346 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -44,7 +44,7 @@ final class PlayerRegistration: ModelObject, Storable { func localizedSourceLabel() -> String { switch source { - case .frenchFederation, .onlineRegistration: + case .frenchFederation: return "base fédérale" case .beachPadel: return "beach-padel" @@ -262,82 +262,87 @@ final class PlayerRegistration: ModelObject, Storable { } func updateRank(from sources: [CSVParser], lastRank: Int) async throws { -#if DEBUG_TIME //DEBUGING TIME -let start = Date() -defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) -} -#endif + #if DEBUG_TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + #endif if let dataFound = try await history(from: sources) { rank = dataFound.rankValue?.toInt() points = dataFound.points tournamentPlayed = dataFound.tournamentCountValue?.toInt() + } else if let dataFound = try await historyFromName(from: sources) { + rank = dataFound.rankValue?.toInt() + points = dataFound.points + tournamentPlayed = dataFound.tournamentCountValue?.toInt() } else { rank = lastRank } } - + func history(from sources: [CSVParser]) async throws -> Line? { -#if DEBUG_TIME //DEBUGING TIME -let start = Date() -defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) -} -#endif + #if DEBUG_TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func history()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + #endif guard let license = licenceId?.strippedLicense else { - return try await historyFromName(from: sources) + return nil // Do NOT call historyFromName here, let updateRank handle it } + let filteredSources = sources.filter { $0.maleData == isMalePlayer() } + return await withTaskGroup(of: Line?.self) { group in - for source in sources.filter({ $0.maleData == isMalePlayer() }) { + for source in filteredSources { group.addTask { guard !Task.isCancelled else { print("Cancelled"); return nil } - - return try? await source.first(where: { line in - line.rawValue.contains(";\(license);") - }) + return try? await source.first { $0.rawValue.contains(";\(license);") } } } - + if let first = await group.first(where: { $0 != nil }) { group.cancelAll() return first - } else { - return nil } + return nil } } - + func historyFromName(from sources: [CSVParser]) async throws -> Line? { -#if DEBUG_TIME //DEBUGING TIME -let start = Date() -defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) -} -#endif + #if DEBUG_TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func historyFromName()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + #endif + + let filteredSources = sources.filter { $0.maleData == isMalePlayer() } + let normalizedLastName = lastName.canonicalVersionWithPunctuation + let normalizedFirstName = firstName.canonicalVersionWithPunctuation return await withTaskGroup(of: Line?.self) { group in - for source in sources.filter({ $0.maleData == isMalePlayer() }) { - group.addTask { [lastName, firstName] in + for source in filteredSources { + group.addTask { guard !Task.isCancelled else { print("Cancelled"); return nil } - - return try? await source.first(where: { line in - line.rawValue.canonicalVersionWithPunctuation.contains(";\(lastName.canonicalVersionWithPunctuation);\(firstName.canonicalVersionWithPunctuation);") - }) + return try? await source.first { + let lineValue = $0.rawValue.canonicalVersionWithPunctuation + return lineValue.contains(";\(normalizedLastName);\(normalizedFirstName);") + } } } - + if let first = await group.first(where: { $0 != nil }) { group.cancelAll() return first - } else { - return nil } + return nil } } @@ -347,7 +352,7 @@ defer { return } - let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 70_000 + let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 90_000 switch tournament.tournamentCategory { case .men: computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0) @@ -473,7 +478,6 @@ defer { enum PlayerDataSource: Int, Codable { case frenchFederation = 0 case beachPadel = 1 - case onlineRegistration = 2 } enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 5a17734..457b6e7 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -551,7 +551,7 @@ final class TeamRegistration: ModelObject, Storable { } func unrankValue(for malePlayer: Bool) -> Int { - return tournamentObject()?.unrankValue(for: malePlayer) ?? 70_000 + return tournamentObject()?.unrankValue(for: malePlayer) ?? 90_000 } func groupStageObject() -> GroupStage? { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 7daa905..18d9245 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1511,43 +1511,53 @@ defer { func updateRank(to newDate: Date?) async throws { -#if DEBUG_TIME //DEBUGING TIME -let start = Date() -defer { - let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) - print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) -} -#endif + #if DEBUG_TIME + let start = Date() + defer { + let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) + print("func updateRank()", id, duration.formatted(.units(allowed: [.seconds, .milliseconds]))) + } + #endif guard let newDate else { return } rankSourceDate = newDate + + // Fetch current month data only once + let monthData = currentMonthData() - if currentMonthData() == nil { - let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate) - let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) - await MainActor.run { - let formatted: String = URL.importDateFormatter.string(from: newDate) - let monthData: MonthData = MonthData(monthKey: formatted) - monthData.maleUnrankedValue = lastRankMan - monthData.femaleUnrankedValue = lastRankWoman - do { - try DataStore.shared.monthData.addOrUpdate(instance: monthData) - } catch { - Logger.error(error) - } + if monthData == nil { + async let lastRankWoman = SourceFileManager.shared.getUnrankValue(forMale: false, rankSourceDate: rankSourceDate) + async let lastRankMan = SourceFileManager.shared.getUnrankValue(forMale: true, rankSourceDate: rankSourceDate) + + let formatted = URL.importDateFormatter.string(from: newDate) + let newMonthData = MonthData(monthKey: formatted) + + newMonthData.maleUnrankedValue = await lastRankMan + newMonthData.femaleUnrankedValue = await lastRankWoman + + do { + try DataStore.shared.monthData.addOrUpdate(instance: newMonthData) + } catch { + Logger.error(error) } } - - let lastRankMan = currentMonthData()?.maleUnrankedValue - let lastRankWoman = currentMonthData()?.femaleUnrankedValue + + let lastRankMan = monthData?.maleUnrankedValue ?? 0 + let lastRankWoman = monthData?.femaleUnrankedValue ?? 0 + + // Fetch only the required files let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } + guard !dataURLs.isEmpty else { return } // Early return if no files found + let sources = dataURLs.map { CSVParser(url: $0) } let players = unsortedPlayers() try await players.concurrentForEach { player in - try await player.updateRank(from: sources, lastRank: (player.sex == .female ? lastRankWoman : lastRankMan) ?? 0) + let lastRank = (player.sex == .female) ? lastRankWoman : lastRankMan + try await player.updateRank(from: sources, lastRank: lastRank) } } + func missingUnrankedValue() -> Bool { return maleUnrankedValue == nil || femaleUnrankedValue == nil } diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index f67228b..f1c83a5 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -28,9 +28,6 @@ class ImportObserver { func currentlyImportingLabel() -> String { guard let currentImportDate else { return "import en cours" } - if URL.importDateFormatter.string(from: currentImportDate) == "07-2024" { - return "consolidation des données" - } return "import " + currentImportDate.monthYearFormatted } @@ -44,32 +41,38 @@ class ImportObserver { class FileImportManager { static let shared = FileImportManager() - func updatePlayers(isMale: Bool, players: inout [FederalPlayer]) { let replacements: [(Character, Character)] = [("Á", "ç"), ("‡", "à"), ("Ù", "ô"), ("Ë", "è"), ("Ó", "î"), ("Î", "ë"), ("…", "É"), ("Ô", "ï"), ("È", "é"), ("«", "Ç"), ("»", "È")] - var playersLeft = players - SourceFileManager.shared.allFilesSortedByDate(isMale).forEach({ url in - if playersLeft.isEmpty == false { - let federalPlayers = readCSV(inputFile: url) - let replacementsCharacters = url.dateFromPath.monthYearFormatted != "04-2024" ? [] : replacements + var playersLeft = Dictionary(uniqueKeysWithValues: players.map { ($0.license, $0) }) + + SourceFileManager.shared.allFilesSortedByDate(isMale).forEach { url in + if playersLeft.isEmpty { return } + + let federalPlayers = readCSV(inputFile: url) + let replacementsCharacters = url.dateFromPath.monthYearFormatted != "04-2024" ? [] : replacements + + let federalPlayersDict = Dictionary(uniqueKeysWithValues: federalPlayers.map { ($0.license, $0) }) + + for (license, importedPlayer) in playersLeft { + guard let federalPlayer = federalPlayersDict[license] else { continue } - playersLeft.forEach { importedPlayer in - if let federalPlayer = federalPlayers.first(where: { $0.license == importedPlayer.license }) { - var lastName = federalPlayer.lastName - lastName.replace(characters: replacementsCharacters) - var firstName = federalPlayer.firstName - firstName.replace(characters: replacementsCharacters) - importedPlayer.lastName = lastName.trimmed.uppercased() - importedPlayer.firstName = firstName.trimmed.capitalized - } - } + var lastName = federalPlayer.lastName + var firstName = federalPlayer.firstName + + lastName.replace(characters: replacementsCharacters) + firstName.replace(characters: replacementsCharacters) + + importedPlayer.lastName = lastName.trimmed.uppercased() + importedPlayer.firstName = firstName.trimmed.capitalized + + playersLeft.removeValue(forKey: license) // Remove processed player } - }) + } - players = playersLeft + players = Array(playersLeft.values) } - + func foundInWomenData(license: String?) -> Bool { guard let license = license?.strippedLicense else { return false @@ -148,7 +151,7 @@ class FileImportManager { } let significantPlayerCount = 2 let pl = players.prefix(significantPlayerCount).map { $0.computedRank } - let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 70_000 : 10_000) }).prefix(significantPlayerCount) + let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 90_000 : 10_000) }).prefix(significantPlayerCount) self.weight = pl.reduce(0,+) + missingPl.reduce(0,+) } else { self.weight = players.map { $0.computedRank }.reduce(0,+) diff --git a/PadelClub/Utils/SourceFileManager.swift b/PadelClub/Utils/SourceFileManager.swift index 80c330c..41fef12 100644 --- a/PadelClub/Utils/SourceFileManager.swift +++ b/PadelClub/Utils/SourceFileManager.swift @@ -16,6 +16,7 @@ class SourceFileManager { } let rankingSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "rankings") + let anonymousSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "anonymous") func createDirectoryIfNeeded() { let fileManager = FileManager.default @@ -193,6 +194,13 @@ class SourceFileManager { } } + func anonymousFiles() -> [URL] { + let allJSONFiles = try! FileManager.default.contentsOfDirectory(at: anonymousSourceDirectory, includingPropertiesForKeys: nil).filter({ url in + url.pathExtension == "csv" + }) + return allJSONFiles + } + func jsonFiles() -> [URL] { let allJSONFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in url.pathExtension == "json" diff --git a/PadelClub/Views/Components/StepperView.swift b/PadelClub/Views/Components/StepperView.swift index aadca52..217fce5 100644 --- a/PadelClub/Views/Components/StepperView.swift +++ b/PadelClub/Views/Components/StepperView.swift @@ -88,7 +88,7 @@ struct StepperView: View { } fileprivate func _plusIsDisabled() -> Bool { - count >= (maximum ?? 70_000) + count >= (maximum ?? 90_000) } fileprivate func _add() { diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index 62f9ceb..afd626c 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -185,11 +185,6 @@ struct MainView: View { importObserver.checkingFilesAttempt += 1 importObserver.checkingFiles = false - if lastDataSource == nil || (dataStore.monthData.first(where: { $0.monthKey == "07-2024" }) == nil) { -// await _downloadPreviousDate() - await _importMandatoryData() - } - if let mostRecentDateAvailable = SourceFileManager.shared.mostRecentDateAvailable, mostRecentDateAvailable > SourceFileManager.shared.lastDataSourceDate() ?? .distantPast { print("importing \(mostRecentDateAvailable)") @@ -222,17 +217,6 @@ struct MainView: View { await SourceFileManager.shared.getAllFiles(initialDate: "05-2024") } - private func _importMandatoryData() async { - let mandatoryKey = "07-2024" - if dataStore.monthData.first(where: { $0.monthKey == mandatoryKey }) == nil, let importingDate = URL.importDateFormatter.date(from: mandatoryKey) { - print("importing mandatory july data") - dataStore.appSettings.lastDataSource = mandatoryKey - dataStore.appSettingsStorage.write() - await SourceFileManager.shared.getAllFiles(initialDate: "07-2024") - await _calculateMonthData(dataSource: mandatoryKey) - } - } - private func _checkingDataIntegrity() { guard importObserver.checkingFiles == false, importObserver.isImportingFile() == false else { return @@ -245,27 +229,21 @@ struct MainView: View { Task { await self._checkSourceFileAvailability() } - } else if let lastDataSource, let mostRecentDateImported = URL.importDateFormatter.date(from: lastDataSource), SourceFileManager.isDateAfterUrlImportDate(date:mostRecentDateImported, dateString: "07-2024") { + } else if let lastDataSource, let mostRecentDateImported = URL.importDateFormatter.date(from: lastDataSource) { let monthData = dataStore.monthData.sorted(by: \.creationDate) - if monthData.first(where: { $0.monthKey == "07-2024" }) == nil { + if let current = monthData.last { Task { - await _checkSourceFileAvailability() - } - } else { - if let current = monthData.last { - Task { - let updated = await SourceFileManager.shared.fetchData(fromDate: mostRecentDateImported) - let fileURL = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == mostRecentDateImported && $0.index == 0 }) - print("file updated", updated) - if let updated, updated == 1 { - await _startImporting(importingDate: mostRecentDateImported) - } else if current.dataModelIdentifier != PersistenceController.getModelVersion() && current.fileModelIdentifier != fileURL?.fileModelIdentifier() { - await _startImporting(importingDate: mostRecentDateImported) - } else if updated == 0 { - await _calculateMonthData(dataSource: current.monthKey) - } + let updated = await SourceFileManager.shared.fetchData(fromDate: mostRecentDateImported) + let fileURL = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == mostRecentDateImported && $0.index == 0 }) + print("file updated", updated) + if let updated, updated == 1 { + await _startImporting(importingDate: mostRecentDateImported) + } else if current.dataModelIdentifier != PersistenceController.getModelVersion() && current.fileModelIdentifier != fileURL?.fileModelIdentifier() { + await _startImporting(importingDate: mostRecentDateImported) + } else if updated == 0 { + await _calculateMonthData(dataSource: current.monthKey) } } } diff --git a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift index 464a52d..5263260 100644 --- a/PadelClub/Views/Navigation/Umpire/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Umpire/PadelClubView.swift @@ -40,10 +40,10 @@ struct PadelClubView: View { if let currentMonth = monthData.first, currentMonth.incompleteMode { Section { - Text("Attention, depuis Août 2024, les données fédérales publiques des joueurs (messieurs) récupérables sont incomplètes car limité au 40.000 premiers joueurs.") + Text("Attention, depuis Août 2024, les données fédérales publiques des joueurs (messieurs) récupérables sont incomplètes car limité au 80.000 premiers joueurs.") if currentMonth.maleUnrankedValue == nil { - Text("Le rang d'un joueur non-classé n'est donc pas calculable pour le moment, Padel Club utilisera une valeur par défaut de de 70.000.") + Text("Le rang d'un joueur non-classé n'est donc pas calculable pour le moment, Padel Club utilisera une valeur par défaut de de 90.000.") } Text("Un classement souligné comme ci-dessous indiquera que l'information provient d'un mois précédent.") @@ -61,32 +61,22 @@ struct PadelClubView: View { ["36435", "BRUL…", "Romain", "France", "2993139", "15,00", "Non", "2", "NOUVELLE AQUITAINE", "59 33 0447", "SAINT LOUBES TC"] */ + + Section { + RowButtonView("Retry Anonymous") { + await _retryAnonymous() + } + } + + Section { + RowButtonView("Write anonymous") { + _writeAnonymous() + } + } + Section { RowButtonView("Exporter en csv") { - for fileURL in SourceFileManager.shared.jsonFiles() { - let decoder = JSONDecoder() - decoder.userInfo[.maleData] = fileURL.manData - - do { - let data = try Data(contentsOf: fileURL) - let players = try decoder.decode([FederalPlayer].self, from: data) - var anonymousPlayers = players.filter { $0.firstName.isEmpty && $0.lastName.isEmpty } - let okPlayers = players.filter { $0.firstName.isEmpty == false && $0.lastName.isEmpty == false } - - print("before anonymousPlayers.count", anonymousPlayers.count) - FileImportManager.shared.updatePlayers(isMale: fileURL.manData, players: &anonymousPlayers) - print("after local anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }.count) - - await fetchPlayersDataSequentially(for: &anonymousPlayers) - - print("after beach anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty } - .count) - SourceFileManager.shared.exportToCSV(players: okPlayers + anonymousPlayers, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) - SourceFileManager.shared.exportToCSV("anonymes", players: anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) - } catch { - Logger.error(error) - } - } + await _exportCsv() } } #endif @@ -169,7 +159,7 @@ struct PadelClubView: View { if let maleUnrankedValue = monthData.maleUnrankedValue { Text(maleUnrankedValue.formatted()) } else { - Text(70_000.formatted()) + Text(90_000.formatted()) } } label: { Text("Rang d'un non classé") @@ -187,6 +177,11 @@ struct PadelClubView: View { Text("Rang d'une non classée") Text("Dames") } + #if DEBUG + RowButtonView("recalc") { + await _calculateLastRank(dataSource: monthData.monthKey) + } + #endif } header: { HStack { Text(monthData.monthKey) @@ -242,15 +237,110 @@ struct PadelClubView: View { await SourceFileManager.shared.getAllFiles(initialDate: "08-2022") self.uuid = UUID() } + +#if DEBUG + private func _calculateMonthData(dataSource: String?) async { + if let dataSource, let mostRecentDate = URL.importDateFormatter.date(from: dataSource) { + await MonthData.calculateCurrentUnrankedValues(fromDate: mostRecentDate) + } + } + + private func _calculateLastRank(dataSource: String) async { + await _calculateMonthData(dataSource: dataSource) + } + + private func _writeAnonymous() { + for fileURL in SourceFileManager.shared.anonymousFiles() { + let lastDateString = URL.importDateFormatter.string(from: fileURL.dateFromPath) + let sourceType = fileURL.manData ? SourceFile.messieurs : SourceFile.dames + let dateString = ["CLASSEMENT-PADEL", sourceType.rawValue, lastDateString].filter({ $0.isEmpty == false }).joined(separator: "-") + "." + "csv" + + let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)! + let destinationFileUrl = documentsUrl.appendingPathComponent("rankings").appendingPathComponent("\(dateString)") + + updateCSVFile(sourceCSVURL: destinationFileUrl, updatedCSVURL: fileURL) + } + } + + private func _retryAnonymous() async { + for fileURL in SourceFileManager.shared.anonymousFiles() { + let players = FileImportManager.shared.readCSV(inputFile: fileURL) + var anonymousPlayers = players + print("before anonymousPlayers.count", anonymousPlayers.count) + await fetchPlayersDataSequentially(for: &anonymousPlayers) + print("after beach anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty } + .count) + SourceFileManager.shared.exportToCSV("anonymes", players: anonymousPlayers, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) + } + } + + private func _exportCsv() async { + for fileURL in SourceFileManager.shared.jsonFiles() { + let decoder = JSONDecoder() + decoder.userInfo[.maleData] = fileURL.manData + + do { + let data = try Data(contentsOf: fileURL) + let players = try decoder.decode([FederalPlayer].self, from: data) + var anonymousPlayers = players.filter { $0.firstName.isEmpty && $0.lastName.isEmpty } + let okPlayers = players.filter { $0.firstName.isEmpty == false && $0.lastName.isEmpty == false } + + print("before anonymousPlayers.count", anonymousPlayers.count) + FileImportManager.shared.updatePlayers(isMale: fileURL.manData, players: &anonymousPlayers) + print("after local anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }.count) + + await fetchPlayersDataSequentially(for: &anonymousPlayers) + + print("after beach anonymousPlayers.count", anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty } + .count) + SourceFileManager.shared.exportToCSV(players: okPlayers + anonymousPlayers, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) + SourceFileManager.shared.exportToCSV("anonymes", players: anonymousPlayers.filter { $0.firstName.isEmpty && $0.lastName.isEmpty }, sourceFileType: fileURL.manData ? .messieurs : .dames, date: fileURL.dateFromPath) + } catch { + Logger.error(error) + } + } + } +#endif } -//#Preview { -// PadelClubView() -//} +func updateCSVFile(sourceCSVURL: URL, updatedCSVURL: URL) { + do { + let sourceCSVContent = try String(contentsOf: sourceCSVURL, encoding: .utf8) + var sourceCSVLines = sourceCSVContent.components(separatedBy: "\n") + let delimiter = ";" + + let updatedCSVContent = try String(contentsOf: updatedCSVURL, encoding: .utf8) + let updatedCSVLines = updatedCSVContent.components(separatedBy: "\n") + + // Create a dictionary of updated player data by licenseId + var updatedPlayerDict: [String: String] = [:] + for line in updatedCSVLines { + let components = line.components(separatedBy: delimiter) + if let licenseId = components.dropFirst(5).first { + updatedPlayerDict[licenseId] = line + } + } + + // Update the source CSV lines if licenseId matches + for (index, line) in sourceCSVLines.enumerated() { + let components = line.components(separatedBy: delimiter) + if let licenseId = components.dropFirst(5).first, let updatedLine = updatedPlayerDict[licenseId] { + sourceCSVLines[index] = updatedLine + } + } + + // Write back to the file + let finalCSVContent = sourceCSVLines.joined(separator: "\n") + try finalCSVContent.write(to: sourceCSVURL, atomically: true, encoding: .utf8) + print("CSV file updated successfully.") + } catch { + print("Error updating CSV file: \(error)") + } +} // Function to fetch data for a single license ID -func fetchPlayerData(for licenseID: String) async throws -> [Player]? { - guard let url = URL(string: "https://beach-padel.app.fft.fr/beachja/rechercheJoueur/licencies?idHomologation=82469282&numeroLicence=\(licenseID)") else { +func fetchPlayerData(for licenseID: String, idHomologation: String, sessionId: String) async throws -> [Player]? { + guard let url = URL(string: "https://beach-padel.app.fft.fr/beachja/rechercheJoueur/licencies?idHomologation=\(idHomologation)&numeroLicence=\(licenseID)") else { throw URLError(.badURL) } @@ -268,7 +358,7 @@ func fetchPlayerData(for licenseID: String) async throws -> [Player]? { request.setValue("XMLHttpRequest", forHTTPHeaderField: "X-Requested-With") // Add cookies if needed (example cookie header value shown, replace with valid cookies) - request.setValue("JSESSIONID=48C272263C9454774F0DA95F491C3765; AWSALB=nNF/fDwCSmsO9PQD5jkXNUuoMuAzziHTeIkno1uRkNDkKfaOT7VVbh0KOdvGZ5afMw3epLw0p9J+4Ih6cpwqW+XdcLUrr9kJhpQEgP1oeLPRsi/4Yn9uCLCRgPKI; AWSALBCORS=nNF/fDwCSmsO9PQD5jkXNUuoMuAzziHTeIkno1uRkNDkKfaOT7VVbh0KOdvGZ5afMw3epLw0p9J+4Ih6cpwqW+XdcLUrr9kJhpQEgP1oeLPRsi/4Yn9uCLCRgPKI; datadome=3T1lKPP7j_r9MhBRIq_5sBwcCuhI0lfYgQ414DuY7BdYm3jpvHECT05w6Ohl0xMvGVJi3XayxoRsnsKvPti_TIZ90B~boSu2LYs2lm_OssxFSoDGEHTFOf4HTjVkM6i8; TCID=125221542552726081269; TCSESSION=125221542558139099625; TCPID=125221535336434794787; incap_ses_2224_2712217=T+1ySNxxGx/yVRcIdDzdHrUlomcAAAAAAWv/NX2ushG21NP0K9l10g==; nlbi_2712217=Wd9XXxrrXQSWKZBPb9lUTgAAAADRjV4zuALYepgab2n0ra/7; xtan=-; xtant=1; xtvrn=$548419$; visid_incap_2712217=PSfJngzoSuiowsuXXhvOu5K+7mUAAAAAQUIPAAAAAAAleL9ldvN/FC1VykkU9ret; SessionStatId=10.91.140.42.1662124965429001", forHTTPHeaderField: "Cookie") + request.setValue(sessionId, forHTTPHeaderField: "Cookie") let (data, _) = try await URLSession.shared.data(for: request) let decoder = JSONDecoder() @@ -288,9 +378,21 @@ func fetchPlayerData(for licenseID: String) async throws -> [Player]? { // Function to fetch data for multiple license IDs using TaskGroup func fetchPlayersDataSequentially(for licenseIDs: inout [FederalPlayer]) async { + var idHomologation: String = "82469282" + if let _idHomologation = PListReader.readString(plist: "local", key: "idHomologation") { + idHomologation = _idHomologation + } + + var sessionId: String = "" + + if let _sessionId = PListReader.readString(plist: "local", key: "JSESSIONID") { + sessionId = _sessionId + } + + for licenseID in licenseIDs.filter({ $0.firstName.isEmpty && $0.lastName.isEmpty }) { do { - if let playerData = try await fetchPlayerData(for: licenseID.license)?.first { + if let playerData = try await fetchPlayerData(for: licenseID.license, idHomologation: idHomologation, sessionId: sessionId)?.first { licenseID.lastName = playerData.nom licenseID.firstName = playerData.prenom }