manage rank file update check

clubs
Raz 1 year ago
parent 40762f90b2
commit 5d7a81f7ef
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 15
      PadelClub/Data/MonthData.swift
  3. 2
      PadelClub/Data/PlayerRegistration.swift
  4. 2
      PadelClub/Data/TeamRegistration.swift
  5. 2
      PadelClub/Extensions/Date+Extensions.swift
  6. 37
      PadelClub/Extensions/URL+Extensions.swift
  7. 2
      PadelClub/Utils/FileImportManager.swift
  8. 43
      PadelClub/Utils/Network/NetworkManager.swift
  9. 2
      PadelClub/Utils/Network/NetworkManagerError.swift
  10. 16
      PadelClub/Utils/SourceFileManager.swift
  11. 2
      PadelClub/Views/Components/StepperView.swift
  12. 29
      PadelClub/Views/Navigation/MainView.swift
  13. 10
      PadelClub/Views/Navigation/Umpire/PadelClubView.swift

@ -1939,7 +1939,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -1989,7 +1989,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -19,7 +19,7 @@ final class MonthData : ModelObject, Storable {
private(set) var id: String = Store.randomId()
private(set) var monthKey: String
var creationDate: Date
private(set) var creationDate: Date
var maleUnrankedValue: Int? = nil
var femaleUnrankedValue: Int? = nil
var maleCount: Int? = nil
@ -32,6 +32,10 @@ final class MonthData : ModelObject, Storable {
self.creationDate = Date()
}
fileprivate func _updateCreationDate() {
self.creationDate = Date()
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: ._id)
@ -52,7 +56,10 @@ final class MonthData : ModelObject, Storable {
static func calculateCurrentUnrankedValues(fromDate: Date) async {
let fftImportingUncomplete = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == fromDate })?.fftImportingUncomplete()
let fileURL = SourceFileManager.shared.allFiles(true).first(where: { $0.dateFromPath == fromDate && $0.index == 0 })
print("calculateCurrentUnrankedValues", fromDate.monthYearFormatted, fileURL?.path())
let fftImportingUncomplete = fileURL?.fftImportingUncomplete()
let fftImportingMaleUnrankValue = fileURL?.fftImportingMaleUnrankValue()
let incompleteMode = fftImportingUncomplete != nil
@ -62,8 +69,8 @@ final class MonthData : ModelObject, Storable {
await MainActor.run {
let lastDataSource = URL.importDateFormatter.string(from: fromDate)
let currentMonthData : MonthData = DataStore.shared.monthData.first(where: { $0.monthKey == lastDataSource }) ?? MonthData(monthKey: lastDataSource)
currentMonthData.creationDate = Date()
currentMonthData.maleUnrankedValue = incompleteMode ? 60000 : lastDataSourceMaleUnranked?.0
currentMonthData._updateCreationDate()
currentMonthData.maleUnrankedValue = incompleteMode ? fftImportingMaleUnrankValue : lastDataSourceMaleUnranked?.0
currentMonthData.incompleteMode = incompleteMode
currentMonthData.maleCount = incompleteMode ? fftImportingUncomplete : lastDataSourceMaleUnranked?.1
currentMonthData.femaleUnrankedValue = lastDataSourceFemaleUnranked?.0

@ -281,7 +281,7 @@ final class PlayerRegistration: ModelObject, Storable {
}
func setComputedRank(in tournament: Tournament) {
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 100_000
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 70_000
switch tournament.tournamentCategory {
case .men:
computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)

@ -478,7 +478,7 @@ final class TeamRegistration: ModelObject, Storable {
}
func unrankValue(for malePlayer: Bool) -> Int {
return tournamentObject()?.unrankValue(for: malePlayer) ?? 100_000
return tournamentObject()?.unrankValue(for: malePlayer) ?? 70_000
}
func groupStageObject() -> GroupStage? {

@ -215,7 +215,7 @@ extension Date {
extension Date {
func isEarlierThan(_ date: Date) -> Bool {
self < date
Calendar.current.compare(self, to: date, toGranularity: .minute) == .orderedAscending
}
}

@ -51,6 +51,43 @@ extension URL {
}
extension URL {
func creationDate() -> Date? {
// Use FileManager to retrieve the file attributes
do {
let fileAttributes = try FileManager.default.attributesOfItem(atPath: self.path())
// Access the creationDate from the file attributes
if let creationDate = fileAttributes[.creationDate] as? Date {
print("File creationDate: \(creationDate)")
return creationDate
} else {
print("creationDate not found.")
}
} catch {
print("Error retrieving file attributes: \(error.localizedDescription)")
}
return nil
}
func fftImportingMaleUnrankValue() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
}
// Split the contents by newline characters
let lines = fileContents.components(separatedBy: .newlines)
if let line = lines.first(where: {
$0.hasPrefix("unrank-male-value:")
}) {
return Int(line.replacingOccurrences(of: "unrank-male-value:", with: ""))
}
return nil
}
func fftImportingUncomplete() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {

@ -147,7 +147,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 ? 100_000 : 10_000) }).prefix(significantPlayerCount)
let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 70_000 : 10_000) }).prefix(significantPlayerCount)
self.weight = pl.reduce(0,+) + missingPl.reduce(0,+)
} else {
self.weight = players.map { $0.computedRank }.reduce(0,+)

@ -18,6 +18,32 @@ class NetworkManager {
try? FileManager.default.removeItem(at: destinationFileUrl)
}
// func headerDataRankingData(lastDateString: String, fileName: String) async throws {
// let dateString = ["CLASSEMENT-PADEL", fileName, lastDateString].joined(separator: "-") + ".csv"
//
// let documentsUrl: URL = SourceFileManager.shared.rankingSourceDirectory
// let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)")
// let fileURL = URL(string: "https://xlr.alwaysdata.net/static/rankings/\(dateString)")
//
// var request = URLRequest(url:fileURL!)
// request.httpMethod = "HEAD"
// request.addValue("attachment;filename=\(dateString)", forHTTPHeaderField:"Content-Disposition")
// request.addValue("text/csv", forHTTPHeaderField: "Content-Type")
// let task = try await URLSession.shared.dataTask(with: request)
// if let urlResponse = task.1 as? HTTPURLResponse {
// print(urlResponse.allHeaderFields)
// }
// }
//
func formatDateForHTTPHeader(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0) // GMT timezone
return formatter.string(from: date)
}
func downloadRankingData(lastDateString: String, fileName: String) async throws {
let dateString = ["CLASSEMENT-PADEL", fileName, lastDateString].joined(separator: "-") + ".csv"
@ -26,15 +52,16 @@ class NetworkManager {
let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)")
let fileURL = URL(string: "https://xlr.alwaysdata.net/static/rankings/\(dateString)")
if FileManager.default.fileExists(atPath: destinationFileUrl.path()) {
return
}
var request = URLRequest(url:fileURL!)
request.addValue("attachment;filename=\(dateString)", forHTTPHeaderField:"Content-Disposition")
if FileManager.default.fileExists(atPath: destinationFileUrl.path()), let modificationDate = destinationFileUrl.creationDate() {
request.addValue(formatDateForHTTPHeader(modificationDate), forHTTPHeaderField: "If-Modified-Since")
}
request.addValue("text/csv", forHTTPHeaderField: "Content-Type")
let task = try await URLSession.shared.download(for: request)
if let urlResponse = task.1 as? HTTPURLResponse {
print(dateString, urlResponse.statusCode)
if urlResponse.statusCode == 200 {
//todo à voir si on en a besoin, permet de re-télécharger un csv si on détecte qu'il a été mis à jour
@ -47,12 +74,18 @@ class NetworkManager {
// }
// }
// }
try? FileManager.default.removeItem(at: destinationFileUrl)
try FileManager.default.copyItem(at: task.0, to: destinationFileUrl)
print("dl rank data ok", lastDateString, fileName)
} else if urlResponse.statusCode == 404 && fileName == "MESSIEURS" {
print("dl rank data failed", lastDateString, fileName)
print("dl rank data failedm fileNotYetAvailable", lastDateString, fileName)
throw NetworkManagerError.fileNotYetAvailable
} else if urlResponse.statusCode == 304 {
print("dl rank data failed, fileNotModified", lastDateString, fileName)
throw NetworkManagerError.fileNotModified
} else {
print("dl rank data failed, fileNotDownloaded", lastDateString, fileName, urlResponse.statusCode)
throw NetworkManagerError.fileNotDownloaded(urlResponse.statusCode)
}
}
}

@ -14,6 +14,8 @@ enum NetworkManagerError: LocalizedError {
case mailNotSent //no network no error
case messageFailed
case messageNotSent //no network no error
case fileNotModified
case fileNotDownloaded(Int)
var errorDescription: String? {
switch self {

@ -80,7 +80,8 @@ class SourceFileManager {
}
}
func fetchData(fromDate current: Date) async {
@discardableResult
func fetchData(fromDate current: Date) async -> Bool {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
@ -101,6 +102,7 @@ class SourceFileManager {
// await fetchData(fromDate: nextCurrent)
// }
// }
return true
} catch {
print("downloadRankingData", error)
@ -109,6 +111,8 @@ class SourceFileManager {
await fetchData(fromDate: previousDate)
}
}
return false
}
}
@ -180,7 +184,7 @@ class SourceFileManager {
url.pathExtension == "csv"
})
return (allFiles + (Bundle.main.urls(forResourcesWithExtension: "csv", subdirectory: nil) ?? [])).sorted(by: \.dateFromPath).reversed()
return (allFiles + (Bundle.main.urls(forResourcesWithExtension: "csv", subdirectory: nil) ?? [])).sorted { $0.dateFromPath == $1.dateFromPath ? $0.index < $1.index : $0.dateFromPath > $1.dateFromPath }
}
func allFiles(_ isManPlayer: Bool) -> [URL] {
@ -192,6 +196,14 @@ class SourceFileManager {
func allFilesSortedByDate(_ isManPlayer: Bool) -> [URL] {
return allFiles(isManPlayer)
}
static func isDateAfterUrlImportDate(date: Date, dateString: String) -> Bool {
guard let importDate = URL.importDateFormatter.date(from: dateString) else {
return false
}
return importDate.isEarlierThan(date)
}
}
enum SourceFile: String, CaseIterable {

@ -67,7 +67,7 @@ struct StepperView: View {
}
fileprivate func _plusIsDisabled() -> Bool {
count >= (maximum ?? 100_000)
count >= (maximum ?? 70_000)
}
fileprivate func _add() {

@ -231,26 +231,37 @@ struct MainView: View {
}
private func _checkingDataIntegrity() {
if lastDataSource == nil, importObserver.checkingFiles == false, importObserver.isImportingFile() == false {
guard importObserver.checkingFiles == false, importObserver.isImportingFile() == false else {
return
}
if lastDataSource == nil {
Task {
await self._checkSourceFileAvailability()
}
} else if let lastDataSource, lastDataSource != URL.importDateFormatter.string(from: Date()), importObserver.checkingFiles == false, importObserver.isImportingFile() == false {
} else if let lastDataSource, lastDataSource != URL.importDateFormatter.string(from: Date()) {
Task {
await self._checkSourceFileAvailability()
}
} else if let lastDataSource, lastDataSource == "08-2024" {
} else if let lastDataSource, let mostRecentDateImported = URL.importDateFormatter.date(from: lastDataSource), SourceFileManager.isDateAfterUrlImportDate(date:mostRecentDateImported, dateString: "07-2024") {
let monthData = dataStore.monthData.sorted(by: \.creationDate)
if let current = monthData.last, current.monthKey == "08-2024", current.incompleteMode == false {
Task {
await _calculateMonthData(dataSource: current.monthKey)
}
}
if monthData.first(where: { $0.monthKey == "07-2024" }) == nil, importObserver.isImportingFile() == false, importObserver.checkingFiles == false {
if monthData.first(where: { $0.monthKey == "07-2024" }) == nil {
Task {
await _checkSourceFileAvailability()
}
} else {
if let current = monthData.last {
Task {
let updated = await SourceFileManager.shared.fetchData(fromDate: mostRecentDateImported)
print("file updated", updated)
if updated {
await _startImporting(importingDate: mostRecentDateImported)
} else if current.incompleteMode == false {
await _calculateMonthData(dataSource: current.monthKey)
}
}
}
}
}
}

@ -39,7 +39,11 @@ 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. 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 60.000.")
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.")
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("Un classement souligné comme ci-dessous indiquera que l'information provient d'un mois précédent.")
@ -158,10 +162,12 @@ struct PadelClubView: View {
LabeledContent {
if let maleUnrankedValue = monthData.maleUnrankedValue {
Text(maleUnrankedValue.formatted())
} else {
Text(70_000.formatted())
}
} label: {
Text("Rang d'un non classé")
if monthData.incompleteMode {
if monthData.incompleteMode && monthData.maleUnrankedValue == nil {
Text("Messieurs (estimation car incomplet)")
} else {
Text("Messieurs")

Loading…
Cancel
Save