mirror of
https://github.com/cranci1/Sora.git
synced 2026-05-05 01:39:05 +00:00
test recode?
This commit is contained in:
parent
2805e3b705
commit
9b82caa1d0
3 changed files with 156 additions and 53 deletions
|
|
@ -14,7 +14,22 @@ struct SoraApp: App {
|
||||||
@StateObject private var librarykManager = LibraryManager()
|
@StateObject private var librarykManager = LibraryManager()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
_ = iCloudSyncManager.shared
|
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
|
TraktToken.checkAuthenticationStatus { isAuthenticated in
|
||||||
if isAuthenticated {
|
if isAuthenticated {
|
||||||
|
|
@ -34,7 +49,10 @@ struct SoraApp: App {
|
||||||
.accentColor(settings.accentColor)
|
.accentColor(settings.accentColor)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
settings.updateAppearance()
|
settings.updateAppearance()
|
||||||
|
|
||||||
|
iCloudSyncManager.shared.syncFromiCloud(retry: true)
|
||||||
iCloudSyncManager.shared.syncModulesFromiCloud()
|
iCloudSyncManager.shared.syncModulesFromiCloud()
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
if UserDefaults.standard.bool(forKey: "refreshModulesOnLaunch") {
|
if UserDefaults.standard.bool(forKey: "refreshModulesOnLaunch") {
|
||||||
await moduleManager.refreshModules()
|
await moduleManager.refreshModules()
|
||||||
|
|
|
||||||
|
|
@ -1089,7 +1089,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
|
||||||
skipOutroButton.trailingAnchor.constraint(equalTo: sliderHostingController!.view.trailingAnchor),
|
skipOutroButton.trailingAnchor.constraint(equalTo: sliderHostingController!.view.trailingAnchor),
|
||||||
skipOutroButton.bottomAnchor.constraint(equalTo: sliderHostingController!.view.topAnchor, constant: -5),
|
skipOutroButton.bottomAnchor.constraint(equalTo: sliderHostingController!.view.topAnchor, constant: -5),
|
||||||
skipOutroButton.heightAnchor.constraint(equalToConstant: 40),
|
skipOutroButton.heightAnchor.constraint(equalToConstant: 40),
|
||||||
skipOutroButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 104)
|
skipOutroButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 108)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,15 @@ import UIKit
|
||||||
class iCloudSyncManager {
|
class iCloudSyncManager {
|
||||||
static let shared = iCloudSyncManager()
|
static let shared = iCloudSyncManager()
|
||||||
|
|
||||||
private let syncQueue = DispatchQueue(label: "me.cranci.sora.icloud-sync", qos: .utility)
|
let syncQueue = DispatchQueue(label: "me.cranci.sora.icloud-sync", qos: .utility)
|
||||||
private let defaultsToSync: [String] = [
|
let retryAttempts = 3
|
||||||
|
let retryDelay: TimeInterval = 2.0
|
||||||
|
|
||||||
|
var isSyncing = false
|
||||||
|
var lastSyncAttempt: Date?
|
||||||
|
var syncErrors: Int = 0
|
||||||
|
|
||||||
|
let defaultsToSync: [String] = [
|
||||||
"externalPlayer",
|
"externalPlayer",
|
||||||
"alwaysLandscape",
|
"alwaysLandscape",
|
||||||
"rememberPlaySpeed",
|
"rememberPlaySpeed",
|
||||||
|
|
@ -35,31 +42,141 @@ class iCloudSyncManager {
|
||||||
"metadataProviders"
|
"metadataProviders"
|
||||||
]
|
]
|
||||||
|
|
||||||
private let modulesFileName = "modules.json"
|
var ubiquityContainerURL: URL? {
|
||||||
|
get {
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
var containerURL: URL?
|
||||||
|
|
||||||
private var ubiquityContainerURL: URL? {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
|
containerURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
|
||||||
|
semaphore.signal()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = semaphore.wait(timeout: .now() + 5.0)
|
||||||
|
return containerURL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
setupSync()
|
setupSync()
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(willEnterBackground), name: UIApplication.willResignActiveNotification, object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func 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
|
syncQueue.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
NSUbiquitousKeyValueStore.default.synchronize()
|
do {
|
||||||
self.syncFromiCloud()
|
try self.initializeICloudSync()
|
||||||
self.syncModulesFromiCloud()
|
} 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 {
|
DispatchQueue.main.async {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(self.iCloudDidChangeExternally), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default)
|
completion?(success)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(self.userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 value is [Any] || value is [String: Any] {
|
||||||
|
// Validate JSON serialization
|
||||||
|
_ = try JSONSerialization.data(withJSONObject: value)
|
||||||
|
}
|
||||||
|
container.set(value, forKey: key)
|
||||||
|
syncedKeys += 1
|
||||||
|
}
|
||||||
|
} 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) {
|
@objc private func iCloudDidChangeExternally(_ notification: NSNotification) {
|
||||||
|
|
@ -95,39 +212,7 @@ class iCloudSyncManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncToiCloud() {
|
func syncFromiCloud(retry: Bool = false) {
|
||||||
syncQueue.async { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
|
|
||||||
let container = NSUbiquitousKeyValueStore.default
|
|
||||||
let defaults = UserDefaults.standard
|
|
||||||
|
|
||||||
var syncedKeys = 0
|
|
||||||
let keysToSync = self.allKeysToSync()
|
|
||||||
|
|
||||||
for key in keysToSync {
|
|
||||||
guard let value = defaults.object(forKey: key) else { continue }
|
|
||||||
|
|
||||||
if !key.isEmpty && self.isValidValueType(value) {
|
|
||||||
autoreleasepool {
|
|
||||||
container.set(value, forKey: key)
|
|
||||||
syncedKeys += 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logger.shared.log("Skipping invalid key/value: \(key)", type: "Warning")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let success = container.synchronize()
|
|
||||||
if !success {
|
|
||||||
Logger.shared.log("Failed to synchronize with iCloud", type: "Error")
|
|
||||||
} else {
|
|
||||||
Logger.shared.log("Successfully synced \(syncedKeys) keys to iCloud", type: "Info")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func syncFromiCloud() {
|
|
||||||
syncQueue.async { [weak self] in
|
syncQueue.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
|
@ -230,7 +315,7 @@ class iCloudSyncManager {
|
||||||
guard let self = self, let iCloudURL = self.ubiquityContainerURL else { return }
|
guard let self = self, let iCloudURL = self.ubiquityContainerURL else { return }
|
||||||
|
|
||||||
let localModulesURL = self.getLocalModulesFileURL()
|
let localModulesURL = self.getLocalModulesFileURL()
|
||||||
let iCloudModulesURL = iCloudURL.appendingPathComponent(self.modulesFileName)
|
let iCloudModulesURL = iCloudURL.appendingPathComponent("modules.json")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
guard FileManager.default.fileExists(atPath: localModulesURL.path) else { return }
|
guard FileManager.default.fileExists(atPath: localModulesURL.path) else { return }
|
||||||
|
|
@ -256,7 +341,7 @@ class iCloudSyncManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let localModulesURL = self.getLocalModulesFileURL()
|
let localModulesURL = self.getLocalModulesFileURL()
|
||||||
let iCloudModulesURL = iCloudURL.appendingPathComponent(self.modulesFileName)
|
let iCloudModulesURL = iCloudURL.appendingPathComponent("modules.json")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if !FileManager.default.fileExists(atPath: iCloudModulesURL.path) {
|
if !FileManager.default.fileExists(atPath: iCloudModulesURL.path) {
|
||||||
|
|
@ -307,6 +392,6 @@ class iCloudSyncManager {
|
||||||
|
|
||||||
private func getLocalModulesFileURL() -> URL {
|
private func getLocalModulesFileURL() -> URL {
|
||||||
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
return docs.appendingPathComponent(modulesFileName)
|
return docs.appendingPathComponent("modules.json")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue