AppVersionManager.swift 4.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. //
  2. // AppVersionManager.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2024-05-11.
  6. // Copyright © 2024 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. class AppVersionManager {
  10. private let githubService = GitHubService()
  11. func checkForNewVersion(completion: @escaping (String?, Bool, Bool) -> Void) {
  12. let currentVersion = version()
  13. let now = Date()
  14. // Retrieve cache
  15. let lastChecked = UserDefaults.standard.object(forKey: "latestVersionChecked") as? Date ?? Date.distantPast
  16. let cachedLatestVersion = UserDefaults.standard.string(forKey: "latestVersion")
  17. let isBlacklistedCached = UserDefaults.standard.bool(forKey: "isCurrentVersionBlacklisted")
  18. // Check if the cache is still valid
  19. if now.timeIntervalSince(lastChecked) < 24 * 3600, let latestVersion = cachedLatestVersion {
  20. let isNewer = isVersion(latestVersion, newerThan: currentVersion)
  21. completion(latestVersion, isNewer, isBlacklistedCached)
  22. return
  23. }
  24. // Fetch new data if cache is outdated
  25. githubService.fetchData(for: .versionConfig) { versionData in
  26. self.githubService.fetchData(for: .blacklistedVersions) { blacklistData in
  27. DispatchQueue.main.async {
  28. let fetchedVersion = versionData.flatMap { String(data: $0, encoding: .utf8) }
  29. .flatMap { self.parseVersionFromConfig(contents: $0) }
  30. let isNewer = fetchedVersion.map { self.isVersion($0, newerThan: currentVersion) } ?? false
  31. let isBlacklisted = (try? blacklistData.flatMap { try JSONDecoder().decode(Blacklist.self, from: $0) })
  32. .map { $0.blacklistedVersions.map { $0.version }.contains(currentVersion) } ?? false
  33. // Update cache with new data
  34. UserDefaults.standard.set(fetchedVersion, forKey: "latestVersion")
  35. UserDefaults.standard.set(Date(), forKey: "latestVersionChecked")
  36. UserDefaults.standard.set(isBlacklisted, forKey: "isCurrentVersionBlacklisted")
  37. // Call completion with new data
  38. completion(fetchedVersion, isNewer, isBlacklisted)
  39. }
  40. }
  41. }
  42. }
  43. private func parseVersionFromConfig(contents: String) -> String? {
  44. let lines = contents.split(separator: "\n")
  45. for line in lines {
  46. if line.contains("LOOP_FOLLOW_MARKETING_VERSION") {
  47. let components = line.split(separator: "=").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
  48. if components.count > 1 {
  49. return components[1]
  50. }
  51. }
  52. }
  53. return nil
  54. }
  55. private func isVersion(_ fetchedVersion: String, newerThan currentVersion: String) -> Bool {
  56. let fetchedVersionComponents = fetchedVersion.split(separator: ".").map { Int($0) ?? 0 }
  57. let currentVersionComponents = currentVersion.split(separator: ".").map { Int($0) ?? 0 }
  58. let maxCount = max(fetchedVersionComponents.count, currentVersionComponents.count)
  59. for i in 0..<maxCount {
  60. let fetched = i < fetchedVersionComponents.count ? fetchedVersionComponents[i] : 0
  61. let current = i < currentVersionComponents.count ? currentVersionComponents[i] : 0
  62. if fetched > current {
  63. return true
  64. } else if fetched < current {
  65. return false
  66. }
  67. }
  68. return false
  69. }
  70. func version() -> String {
  71. if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
  72. return version
  73. }
  74. return "Unknown"
  75. }
  76. struct Blacklist: Decodable {
  77. let blacklistedVersions: [VersionEntry]
  78. }
  79. struct VersionEntry: Decodable {
  80. let version: String
  81. }
  82. }