From d52f811ac5281e9370313035e5a49ea381086505 Mon Sep 17 00:00:00 2001 From: Francesco <100066266+cranci1@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:08:50 +0200 Subject: [PATCH] removed mf iCloud --- README.md | 1 - Sora/Sora.entitlements | 14 - Sora/SoraApp.swift | 21 - .../iCloudSyncManager/iCloudSyncManager.swift | 441 ------------------ Sulfur.xcodeproj/project.pbxproj | 12 - 5 files changed, 489 deletions(-) delete mode 100644 Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift diff --git a/README.md b/README.md index ea07baf..b2eb745 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ An iOS and macOS modular web scraping app, under the GPLv3.0 License. - [x] iOS/iPadOS 15.0+ support - [x] macOS support 12.0+ -- [x] Sync via iCloud data - [x] JavaScript module support - [x] Tracking Services (AniList, Trakt) - [x] Apple KeyChain support for auth Tokens diff --git a/Sora/Sora.entitlements b/Sora/Sora.entitlements index d90dbc3..ee95ab7 100644 --- a/Sora/Sora.entitlements +++ b/Sora/Sora.entitlements @@ -2,20 +2,6 @@ - com.apple.developer.icloud-container-identifiers - - iCloud.me.cranci.sora.icloud - - com.apple.developer.icloud-services - - CloudDocuments - - com.apple.developer.ubiquity-container-identifiers - - iCloud.me.cranci.sora.icloud - - com.apple.developer.ubiquity-kvstore-identifier - $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.app-sandbox com.apple.security.network.client diff --git a/Sora/SoraApp.swift b/Sora/SoraApp.swift index 1bf97b9..4f34d9a 100644 --- a/Sora/SoraApp.swift +++ b/Sora/SoraApp.swift @@ -14,23 +14,6 @@ struct SoraApp: App { @StateObject private var librarykManager = LibraryManager() init() { - setupManagers() - } - - private func setupManagers() { - _ = settings - _ = moduleManager - _ = librarykManager - - let cloudManager = iCloudSyncManager.shared - cloudManager.syncQueue.async { - do { - try cloudManager.initializeICloudSync() - } catch { - Logger.shared.log("Failed to initialize iCloud sync: \(error.localizedDescription)", type: "Error") - } - } - TraktToken.checkAuthenticationStatus { isAuthenticated in if isAuthenticated { Logger.shared.log("Trakt authentication is valid") @@ -49,10 +32,6 @@ struct SoraApp: App { .accentColor(settings.accentColor) .onAppear { settings.updateAppearance() - - iCloudSyncManager.shared.syncFromiCloud(retry: true) - iCloudSyncManager.shared.syncModulesFromiCloud() - Task { if UserDefaults.standard.bool(forKey: "refreshModulesOnLaunch") { await moduleManager.refreshModules() diff --git a/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift b/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift deleted file mode 100644 index 2303aa8..0000000 --- a/Sora/Utils/iCloudSyncManager/iCloudSyncManager.swift +++ /dev/null @@ -1,441 +0,0 @@ -// -// iCloudSyncManager.swift -// Sulfur -// -// Created by Francesco on 17/04/25. -// - -import UIKit - -class iCloudSyncManager { - static let shared = iCloudSyncManager() - - let syncQueue = DispatchQueue(label: "me.cranci.sora.icloud-sync", qos: .utility) - let retryAttempts = 3 - let retryDelay: TimeInterval = 2.0 - - var isSyncing = false - var lastSyncAttempt: Date? - var syncErrors: Int = 0 - - let defaultsToSync: [String] = [ - "externalPlayer", - "alwaysLandscape", - "rememberPlaySpeed", - "holdSpeedPlayer", - "skipIncrement", - "skipIncrementHold", - "holdForPauseEnabled", - "skip85Visible", - "doubleTapSeekEnabled", - "selectedModuleId", - "mediaColumnsPortrait", - "mediaColumnsLandscape", - "sendPushUpdates", - "sendTraktUpdates", - "bookmarkedItems", - "continueWatchingItems", - "analyticsEnabled", - "refreshModulesOnLaunch", - "fetchEpisodeMetadata", - "multiThreads", - "metadataProviders" - ] - - var ubiquityContainerURL: URL? { - get { - let semaphore = DispatchSemaphore(value: 0) - var containerURL: URL? - - DispatchQueue.global(qos: .userInitiated).async { - containerURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") - semaphore.signal() - } - - _ = semaphore.wait(timeout: .now() + 5.0) - return containerURL - } - } - - private init() { - setupSync() - } - - private func setupSync() { - guard FileManager.default.ubiquityIdentityToken != nil else { - Logger.shared.log("iCloud is not available", type: "Error") - return - } - - syncQueue.async { [weak self] in - guard let self = self else { return } - - do { - try self.initializeICloudSync() - } catch { - Logger.shared.log("Failed to initialize iCloud sync: \(error.localizedDescription)", type: "Error") - } - } - - setupNotifications() - } - - private func setupNotifications() { - NotificationCenter.default.addObserver(self, selector: #selector(willEnterBackground), name: UIApplication.willResignActiveNotification, object: nil) - - NotificationCenter.default.addObserver(self, selector: #selector(iCloudDidChangeExternally), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default ) - - NotificationCenter.default.addObserver( self, selector: #selector(userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil) - } - - func initializeICloudSync() throws { - guard !isSyncing else { return } - isSyncing = true - - defer { isSyncing = false } - guard NSUbiquitousKeyValueStore.default.synchronize() else { - throw NSError(domain: "iCloudSync", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to initialize iCloud store"]) - } - - syncFromiCloud(retry: true) - syncModulesFromiCloud() - } - - func syncToiCloud(completion: ((Bool) -> Void)? = nil) { - guard !isSyncing else { - completion?(false) - return - } - - syncQueue.async { [weak self] in - guard let self = self else { - completion?(false) - return - } - - self.isSyncing = true - var success = false - - defer { - self.isSyncing = false - DispatchQueue.main.async { - completion?(success) - } - } - - let container = NSUbiquitousKeyValueStore.default - let defaults = UserDefaults.standard - - do { - try self.performSync(from: defaults, to: container) - success = container.synchronize() - - if success { - self.syncErrors = 0 - Logger.shared.log("Successfully synced to iCloud", type: "Info") - } else { - self.syncErrors += 1 - throw NSError( - domain: "iCloudSync", - code: -1, - userInfo: [NSLocalizedDescriptionKey: "Failed to synchronize with iCloud"] - ) - } - } catch { - Logger.shared.log("Sync to iCloud failed: \(error.localizedDescription)", type: "Error") - - if self.syncErrors < self.retryAttempts { - let delay = TimeInterval(pow(2.0, Double(self.syncErrors))) * self.retryDelay - DispatchQueue.global().asyncAfter(deadline: .now() + delay) { - self.syncToiCloud(completion: completion) - } - } - } - } - } - - private func performSync(from defaults: UserDefaults, to container: NSUbiquitousKeyValueStore) throws { - var syncedKeys = 0 - let keysToSync = allKeysToSync() - - for key in keysToSync { - guard let value = defaults.object(forKey: key) else { continue } - - do { - if self.isValidValueType(value) { - if let arrayValue = value as? [Any] { - if !isValidPropertyListArray(arrayValue) { - Logger.shared.log("Skipping key \(key): contains invalid array elements", type: "Warning") - continue - } - _ = try JSONSerialization.data(withJSONObject: arrayValue) - } else if let dictValue = value as? [String: Any] { - if !isValidPropertyListDictionary(dictValue) { - Logger.shared.log("Skipping key \(key): contains invalid dictionary elements", type: "Warning") - continue - } - _ = try JSONSerialization.data(withJSONObject: dictValue) - } - - do { - container.set(value, forKey: key) - syncedKeys += 1 - } catch { - Logger.shared.log("Failed to store key \(key) in iCloud: \(error.localizedDescription)", type: "Error") - continue - } - } - } catch { - Logger.shared.log("Failed to sync key \(key): \(error.localizedDescription)", type: "Warning") - continue - } - } - - Logger.shared.log("Synced \(syncedKeys) keys", type: "Info") - } - - @objc private func iCloudDidChangeExternally(_ notification: NSNotification) { - guard let iCloud = notification.object as? NSUbiquitousKeyValueStore, - let changedKeys = notification.userInfo?[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String] else { - Logger.shared.log("Invalid iCloud notification data", type: "Error") - return - } - - syncQueue.async { [weak self] in - guard let self = self else { return } - - let defaults = UserDefaults.standard - for key in changedKeys { - if let value = iCloud.object(forKey: key), self.isValidValueType(value) { - defaults.set(value, forKey: key) - } else { - defaults.removeObject(forKey: key) - } - } - - defaults.synchronize() - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .iCloudSyncDidComplete, object: nil) - } - } - } - - @objc private func userDefaultsDidChange(_ notification: Notification) { - syncQueue.async { [weak self] in - self?.syncToiCloud() - } - } - - func syncFromiCloud(retry: Bool = false) { - syncQueue.async { [weak self] in - guard let self = self else { return } - - let iCloud = NSUbiquitousKeyValueStore.default - let defaults = UserDefaults.standard - - var syncedKeys = 0 - var failedKeys = 0 - - let keysToSync = self.allKeysToSync() - - for key in keysToSync { - autoreleasepool { - if let value = iCloud.object(forKey: key) { - do { - if !key.isEmpty && self.isValidValueType(value) { - if JSONSerialization.isValidJSONObject(value) { - _ = try JSONSerialization.data(withJSONObject: value) - defaults.set(value, forKey: key) - syncedKeys += 1 - } else { - Logger.shared.log("Invalid JSON value for key: \(key)", type: "Warning") - defaults.removeObject(forKey: key) - failedKeys += 1 - } - } else { - Logger.shared.log("Invalid value type for key: \(key)", type: "Warning") - defaults.removeObject(forKey: key) - failedKeys += 1 - } - } catch { - Logger.shared.log("JSON serialization failed for key: \(key) - \(error.localizedDescription)", type: "Error") - defaults.removeObject(forKey: key) - failedKeys += 1 - } - } - } - } - - let success = defaults.synchronize() - - DispatchQueue.main.async { [weak self] in - guard self != nil else { return } - - if !success || failedKeys > 0 { - let error = NSError(domain: "iCloudSync", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Sync partially failed", "syncedKeys": syncedKeys, "failedKeys": failedKeys] - ) - NotificationCenter.default.post(name: .iCloudSyncDidFail, object: error) - Logger.shared.log("Sync completed with errors: \(syncedKeys) succeeded, \(failedKeys) failed", type: "Warning") - } else { - NotificationCenter.default.post(name: .iCloudSyncDidComplete, object: ["syncedKeys": syncedKeys]) - Logger.shared.log("Successfully synced \(syncedKeys) keys from iCloud", type: "Info") - } - } - } - } - - private func isValidValueType(_ value: Any) -> Bool { - return value is String || - value is Bool || - value is Int || - value is Float || - value is Double || - value is Data || - value is Date || - value is [Any] || - value is [String: Any] - } - - @objc private func willEnterBackground() { - syncQueue.async { [weak self] in - self?.syncToiCloud() - self?.syncModulesToiCloud() - } - } - - private func allProgressKeys() -> [String] { - let allKeys = UserDefaults.standard.dictionaryRepresentation().keys - let progressPrefixes = ["lastPlayedTime_", "totalTime_"] - return allKeys.filter { key in - progressPrefixes.contains { prefix in key.hasPrefix(prefix) } - } - } - - private func allKeysToSync() -> [String] { - var keys = Set(defaultsToSync + allProgressKeys()) - let userDefaults = UserDefaults.standard - let all = userDefaults.dictionaryRepresentation() - for (key, value) in all { - if key.hasPrefix("Apple") || key.hasPrefix("_") { continue } - if value is Int || value is Double || value is Bool || value is String { - keys.insert(key) - } - } - return Array(keys) - } - - func syncModulesToiCloud() { - DispatchQueue.global(qos: .background).async { [weak self] in - guard let self = self, let iCloudURL = self.ubiquityContainerURL else { return } - - let localModulesURL = self.getLocalModulesFileURL() - let iCloudModulesURL = iCloudURL.appendingPathComponent("modules.json") - - do { - guard FileManager.default.fileExists(atPath: localModulesURL.path) else { return } - - let localData = try Data(contentsOf: localModulesURL) - let _ = try JSONSerialization.jsonObject(with: localData, options: []) - - if FileManager.default.fileExists(atPath: iCloudModulesURL.path) { - try FileManager.default.removeItem(at: iCloudModulesURL) - } - try FileManager.default.copyItem(at: localModulesURL, to: iCloudModulesURL) - - } catch { - Logger.shared.log("iCloud modules sync error: \(error)", type: "Error") - } - } - } - - func syncModulesFromiCloud() { - guard let iCloudURL = self.ubiquityContainerURL else { - Logger.shared.log("iCloud container not available", type: "Error") - return - } - - let localModulesURL = self.getLocalModulesFileURL() - let iCloudModulesURL = iCloudURL.appendingPathComponent("modules.json") - - do { - if !FileManager.default.fileExists(atPath: iCloudModulesURL.path) { - Logger.shared.log("No modules file found in iCloud", type: "Info") - - if FileManager.default.fileExists(atPath: localModulesURL.path) { - Logger.shared.log("Copying local modules file to iCloud", type: "Info") - try FileManager.default.copyItem(at: localModulesURL, to: iCloudModulesURL) - } else { - Logger.shared.log("Creating new empty modules file in iCloud", type: "Info") - let emptyModules: [ScrapingModule] = [] - let emptyData = try JSONEncoder().encode(emptyModules) - try emptyData.write(to: iCloudModulesURL) - - try emptyData.write(to: localModulesURL) - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .modulesSyncDidComplete, object: nil) - } - } - return - } - - let shouldCopy: Bool - if FileManager.default.fileExists(atPath: localModulesURL.path) { - let localData = try Data(contentsOf: localModulesURL) - let iCloudData = try Data(contentsOf: iCloudModulesURL) - shouldCopy = localData != iCloudData - } else { - shouldCopy = true - } - - if shouldCopy { - Logger.shared.log("Syncing modules from iCloud", type: "Info") - if FileManager.default.fileExists(atPath: localModulesURL.path) { - try FileManager.default.removeItem(at: localModulesURL) - } - try FileManager.default.copyItem(at: iCloudModulesURL, to: localModulesURL) - - DispatchQueue.main.async { - NotificationCenter.default.post(name: .modulesSyncDidComplete, object: nil) - } - } - } catch { - Logger.shared.log("iCloud modules sync error: \(error)", type: "Error") - } - } - - private func getLocalModulesFileURL() -> URL { - let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] - return docs.appendingPathComponent("modules.json") - } - - private func isValidPropertyListArray(_ array: [Any]) -> Bool { - for item in array { - if !isValidPropertyListType(item) { - return false - } - } - return true - } - - private func isValidPropertyListDictionary(_ dict: [String: Any]) -> Bool { - for (_, value) in dict { - if !isValidPropertyListType(value) { - return false - } - } - return true - } - - private func isValidPropertyListType(_ value: Any) -> Bool { - if value is String || value is Bool || value is Int || value is Float || value is Double || value is Data || value is Date { - return true - } else if let array = value as? [Any] { - return isValidPropertyListArray(array) - } else if let dict = value as? [String: Any] { - return isValidPropertyListDictionary(dict) - } - return false - } -} diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index c4617a0..3d72d16 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ 133F55BB2D33B55100E08EEA /* LibraryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133F55BA2D33B55100E08EEA /* LibraryManager.swift */; }; 1359ED142D76F49900C13034 /* finTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1359ED132D76F49900C13034 /* finTopView.swift */; }; 135CCBE22D4D1138008B9C0E /* SettingsViewPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */; }; - 136BBE7E2DB102D600906B5E /* iCloudSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136BBE7D2DB102D600906B5E /* iCloudSyncManager.swift */; }; 136BBE802DB1038000906B5E /* Notification+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 136BBE7F2DB1038000906B5E /* Notification+Name.swift */; }; 138AA1B82D2D66FD0021F9DF /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */; }; 138AA1B92D2D66FD0021F9DF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */; }; @@ -96,7 +95,6 @@ 133F55BA2D33B55100E08EEA /* LibraryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryManager.swift; sourceTree = ""; }; 1359ED132D76F49900C13034 /* finTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = finTopView.swift; sourceTree = ""; }; 135CCBE12D4D1138008B9C0E /* SettingsViewPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewPlayer.swift; sourceTree = ""; }; - 136BBE7D2DB102D600906B5E /* iCloudSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iCloudSyncManager.swift; sourceTree = ""; }; 136BBE7F2DB1038000906B5E /* Notification+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Name.swift"; sourceTree = ""; }; 138AA1B62D2D66FD0021F9DF /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; }; 138AA1B72D2D66FD0021F9DF /* CircularProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = ""; }; @@ -257,7 +255,6 @@ 133D7C852D2BE2640075467E /* Utils */ = { isa = PBXGroup; children = ( - 136BBE7C2DB102BE00906B5E /* iCloudSyncManager */, 13C0E5E82D5F85DD00E7F619 /* ContinueWatching */, 13103E8C2D58E037000F0673 /* SkeletonCells */, 13DC0C442D302C6A00D0F966 /* MediaPlayer */, @@ -316,14 +313,6 @@ path = LibraryView; sourceTree = ""; }; - 136BBE7C2DB102BE00906B5E /* iCloudSyncManager */ = { - isa = PBXGroup; - children = ( - 136BBE7D2DB102D600906B5E /* iCloudSyncManager.swift */, - ); - path = iCloudSyncManager; - sourceTree = ""; - }; 1384DCDF2D89BE870094797A /* Helpers */ = { isa = PBXGroup; children = ( @@ -538,7 +527,6 @@ 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 132AF1252D9995F900A0140B /* JSController-Search.swift in Sources */, 13CBEFDA2D5F7D1200D011EE /* String.swift in Sources */, - 136BBE7E2DB102D600906B5E /* iCloudSyncManager.swift in Sources */, 1E26E9E72DA9577900B9DC02 /* VolumeSlider.swift in Sources */, 136BBE802DB1038000906B5E /* Notification+Name.swift in Sources */, 13DB46902D900A38008CBC03 /* URL.swift in Sources */,