diff --git a/MacTorn-1.2.2.zip b/MacTorn-1.2.2.zip new file mode 100644 index 0000000..f8a6089 Binary files /dev/null and b/MacTorn-1.2.2.zip differ diff --git a/MacTorn/MacTorn/Info.plist b/MacTorn/MacTorn/Info.plist index 58a8280..2369fc4 100644 --- a/MacTorn/MacTorn/Info.plist +++ b/MacTorn/MacTorn/Info.plist @@ -17,8 +17,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.2.1 + 1.2.2 CFBundleVersion - 1.2.1 + 1.2.2 diff --git a/MacTorn/MacTorn/ViewModels/AppState.swift b/MacTorn/MacTorn/ViewModels/AppState.swift index bb111a0..def912f 100644 --- a/MacTorn/MacTorn/ViewModels/AppState.swift +++ b/MacTorn/MacTorn/ViewModels/AppState.swift @@ -120,30 +120,27 @@ class AppState: ObservableObject { private func fetchItemPrice(itemId: Int) async { guard !apiKey.isEmpty, let url = TornAPI.marketURL(itemId: itemId, apiKey: apiKey) else { return } - - // Debug - // print("Fetching price for item \(itemId): \(url.absoluteString)") - + + logger.info("Fetching price for item \(itemId)") + do { - let (data, response) = try await URLSession.shared.data(from: url) - + var request = URLRequest(url: url) + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + let (data, response) = try await URLSession.shared.data(for: request) + if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { - // print("HTTP Error: \(httpResponse.statusCode)") + logger.error("Item \(itemId) HTTP Error: \(httpResponse.statusCode)") await updateItemError(itemId: itemId, error: "HTTP \(httpResponse.statusCode)") return } - // Debug JSON - // if let str = String(data: data, encoding: .utf8) { - // print("Market JSON: \(str)") - // } - if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { - + // Check if API returned error if let error = json["error"] as? [String: Any], let errorText = error["error"] as? String { - await updateItemError(itemId: itemId, error: errorText) - return + logger.warning("Item \(itemId) API error: \(errorText)") + await updateItemError(itemId: itemId, error: errorText) + return } var allListings: [(price: Int, amount: Int)] = [] @@ -176,7 +173,7 @@ class AppState: ObservableObject { } let sortedListings = allListings.sorted { $0.price < $1.price } - // print("Found \(sortedListings.count) listings for item \(itemId). Lowest: \(sortedListings.first?.price ?? 0)") + logger.debug("Item \(itemId): found \(sortedListings.count) listings, lowest: \(sortedListings.first?.price ?? 0)") await MainActor.run { if let index = watchlistItems.firstIndex(where: { $0.id == itemId }) { @@ -200,7 +197,7 @@ class AppState: ObservableObject { } } } catch { - // print("Price fetch error: \(error)") + logger.error("Item \(itemId) price fetch error: \(error.localizedDescription)") await updateItemError(itemId: itemId, error: "Network Error") } } @@ -250,7 +247,7 @@ class AppState: ObservableObject { isLoading = true errorMsg = nil - logger.info("Starting data fetch...") + logger.info("Starting data fetch from: \(url.absoluteString.prefix(80))...") Task { let startTime = Date() @@ -267,7 +264,11 @@ class AppState: ObservableObject { } do { - let (data, response) = try await URLSession.shared.data(from: url) + // Create request with no-cache policy to ensure fresh data + var request = URLRequest(url: url) + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + + let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw APIError.invalidResponse @@ -277,6 +278,11 @@ class AppState: ObservableObject { switch httpResponse.statusCode { case 200: + // Log raw JSON for debugging (first 500 chars) + if let jsonString = String(data: data, encoding: .utf8) { + logger.info("Raw API response: \(jsonString.prefix(500))") + } + // Check for Torn API error in response (API returns 200 even on errors) if let tornError = checkForTornAPIError(data: data) { await MainActor.run { @@ -331,9 +337,9 @@ class AppState: ObservableObject { // Move parsing logic here and mark as non-isolated or detached private func parseDataInBackground(data: Data) async throws { // Run CPU-heavy parsing detached from MainActor - let result = await Task.detached(priority: .userInitiated) { () -> (TornResponse?, MoneyData?, BattleStats?, [AttackResult]?, [PropertyInfo]?) in + let result = await Task.detached(priority: .userInitiated) { () -> (TornResponse?, MoneyData?, BattleStats?, [AttackResult]?, [PropertyInfo]?, String?) in guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { - return (nil, nil, nil, nil, nil) + return (nil, nil, nil, nil, nil, "Failed to parse response") } // Attempt to decode TornResponse first @@ -391,43 +397,76 @@ class AppState: ObservableObject { ) } } - return (decodedTornResponse, moneyData, battleStats, attacksList, propertiesList) + // If we couldn't decode TornResponse, report error but continue with extended data + let parseError: String? = (decodedTornResponse == nil) ? "Failed to decode user data" : nil + + return (decodedTornResponse, moneyData, battleStats, attacksList, propertiesList, parseError) }.value - + await MainActor.run { + // Check for parse errors + if let parseError = result.5, result.0 == nil { + self.errorMsg = parseError + logger.error("Parse error: \(parseError)") + self.lastUpdated = Date() // Still update timestamp on error + return + } + if let decoded = result.0 { + logger.info("Parsed data - Name: \(decoded.name ?? "nil"), Life: \(decoded.life?.current ?? -1)/\(decoded.life?.maximum ?? -1)") + logger.info("Status: \(decoded.status?.description ?? "nil"), State: \(decoded.status?.state ?? "nil")") + if let events = decoded.events { + logger.info("Events count: \(events.count)") + } + self.checkNotifications(newData: decoded) self.data = decoded - + self.previousBars = decoded.bars self.previousCooldowns = decoded.cooldowns self.previousTravel = decoded.travel self.previousChain = decoded.chain self.previousStatus = decoded.status + } else { + logger.warning("TornResponse decoded as nil but no parse error reported") } - + if let m = result.1 { self.moneyData = m } if let b = result.2 { self.battleStats = b } if let a = result.3 { self.recentAttacks = a } if let p = result.4 { self.propertiesData = p } - + self.lastUpdated = Date() - self.isLoading = false self.errorMsg = nil + + // Force UI update by triggering objectWillChange + self.objectWillChange.send() + logger.info("UI update triggered, lastUpdated: \(self.lastUpdated?.description ?? "nil")") } } // MARK: - Fetch Faction Data private func fetchFactionData() async { guard let url = TornAPI.factionURL(for: apiKey) else { return } - + do { - let (data, _) = try await URLSession.shared.data(from: url) + var request = URLRequest(url: url) + request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData + let (data, _) = try await URLSession.shared.data(for: request) + + // Check for Torn API error + if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let error = json["error"] as? [String: Any], + let errorMessage = error["error"] as? String { + logger.warning("Faction API error: \(errorMessage)") + return + } + if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { let name = json["name"] as? String ?? "" let factionId = json["ID"] as? Int ?? 0 let respect = json["respect"] as? Int ?? 0 - + var chain = FactionChain() if let chainDict = json["chain"] as? [String: Any] { chain = FactionChain( @@ -437,10 +476,12 @@ class AppState: ObservableObject { cooldown: chainDict["cooldown"] as? Int ?? 0 ) } - + self.factionData = FactionData(name: name, factionId: factionId, respect: respect, chain: chain) + logger.info("Faction data fetched: \(name)") } } catch { + logger.warning("Faction fetch error (optional): \(error.localizedDescription)") // Faction data is optional, ignore errors } }