mirror of
https://github.com/cranci1/Sora.git
synced 2026-08-17 12:56:00 +00:00
Merge branch 'dev' into dev
This commit is contained in:
commit
68e8196c70
6 changed files with 112 additions and 39 deletions
|
|
@ -23,11 +23,13 @@ An iOS and macOS modular web scraping app, under the GPLv3.0 License.
|
||||||
|
|
||||||
- [x] iOS/iPadOS 15.0+ support
|
- [x] iOS/iPadOS 15.0+ support
|
||||||
- [x] macOS support 12.0+
|
- [x] macOS support 12.0+
|
||||||
|
- [x] Sync via iCloud data
|
||||||
- [x] JavaScript module support
|
- [x] JavaScript module support
|
||||||
- [x] Local Library
|
- [x] Tracking Services (AniList, Trakt)
|
||||||
|
- [x] Apple KeyChain support for auth Tokens
|
||||||
- [x] Streams support (Jellyfin/Plex like servers)
|
- [x] Streams support (Jellyfin/Plex like servers)
|
||||||
- [x] External Media players (VLC, infuse, Outplayer, nPlayer)
|
- [x] External Media players (VLC, infuse, Outplayer, nPlayer)
|
||||||
- [x] Tracking Services (AniList, Trakt)
|
- [x] Background playback and Picture-in-Picture (PiP) support
|
||||||
|
|
||||||
## Frequently Asked Questions
|
## Frequently Asked Questions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,17 @@
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||||
<array/>
|
<array>
|
||||||
|
<string>iCloud.me.cranci.sora.icloud</string>
|
||||||
|
</array>
|
||||||
<key>com.apple.developer.icloud-services</key>
|
<key>com.apple.developer.icloud-services</key>
|
||||||
<array>
|
<array>
|
||||||
<string>CloudDocuments</string>
|
<string>CloudDocuments</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
<key>com.apple.developer.ubiquity-container-identifiers</key>
|
||||||
<array/>
|
<array>
|
||||||
|
<string>iCloud.me.cranci.sora.icloud</string>
|
||||||
|
</array>
|
||||||
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
|
||||||
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
|
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ struct SoraApp: App {
|
||||||
if isAuthenticated {
|
if isAuthenticated {
|
||||||
Logger.shared.log("Trakt authentication is valid")
|
Logger.shared.log("Trakt authentication is valid")
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("Trakt authentication required", type: "Warning")
|
Logger.shared.log("Trakt authentication required", type: "Error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,8 +34,9 @@ struct SoraApp: App {
|
||||||
.accentColor(settings.accentColor)
|
.accentColor(settings.accentColor)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
settings.updateAppearance()
|
settings.updateAppearance()
|
||||||
if UserDefaults.standard.bool(forKey: "refreshModulesOnLaunch") {
|
iCloudSyncManager.shared.syncModulesFromiCloud()
|
||||||
Task {
|
Task {
|
||||||
|
if UserDefaults.standard.bool(forKey: "refreshModulesOnLaunch") {
|
||||||
await moduleManager.refreshModules()
|
await moduleManager.refreshModules()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,4 +98,4 @@ struct SoraApp: App {
|
||||||
Logger.shared.log("Unknown authentication service", type: "Error")
|
Logger.shared.log("Unknown authentication service", type: "Error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,5 @@ extension Notification.Name {
|
||||||
static let iCloudSyncDidComplete = Notification.Name("iCloudSyncDidComplete")
|
static let iCloudSyncDidComplete = Notification.Name("iCloudSyncDidComplete")
|
||||||
static let ContinueWatchingDidUpdate = Notification.Name("ContinueWatchingDidUpdate")
|
static let ContinueWatchingDidUpdate = Notification.Name("ContinueWatchingDidUpdate")
|
||||||
static let DownloadManagerStatusUpdate = Notification.Name("DownloadManagerStatusUpdate")
|
static let DownloadManagerStatusUpdate = Notification.Name("DownloadManagerStatusUpdate")
|
||||||
|
static let modulesSyncDidComplete = Notification.Name("modulesSyncDidComplete")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,21 @@ class ModuleManager: ObservableObject {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
loadModules()
|
loadModules()
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(handleModulesSyncCompleted), name: .modulesSyncDidComplete, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleModulesSyncCompleted() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.loadModules()
|
||||||
|
Task {
|
||||||
|
await self.checkJSModuleFiles()
|
||||||
|
}
|
||||||
|
Logger.shared.log("Reloaded modules after iCloud sync")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getDocumentsDirectory() -> URL {
|
private func getDocumentsDirectory() -> URL {
|
||||||
|
|
@ -31,22 +46,45 @@ class ModuleManager: ObservableObject {
|
||||||
modules = (try? JSONDecoder().decode([ScrapingModule].self, from: data)) ?? []
|
modules = (try? JSONDecoder().decode([ScrapingModule].self, from: data)) ?? []
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
for module in modules {
|
await checkJSModuleFiles()
|
||||||
let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath)
|
}
|
||||||
if (!fileManager.fileExists(atPath: localUrl.path)) {
|
}
|
||||||
do {
|
|
||||||
let scriptUrl = URL(string: module.metadata.scriptUrl)
|
func checkJSModuleFiles() async {
|
||||||
guard let scriptUrl = scriptUrl else { continue }
|
Logger.shared.log("Checking JS module files...", type: "Info")
|
||||||
let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl)
|
var missingCount = 0
|
||||||
guard let jsContent = String(data: scriptData, encoding: .utf8) else { continue }
|
|
||||||
try jsContent.write(to: localUrl, atomically: true, encoding: .utf8)
|
for module in modules {
|
||||||
Logger.shared.log("Recovered missing JS file for module: \(module.metadata.sourceName)")
|
let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath)
|
||||||
} catch {
|
if !fileManager.fileExists(atPath: localUrl.path) {
|
||||||
Logger.shared.log("Failed to recover JS file for module: \(module.metadata.sourceName) - \(error.localizedDescription)")
|
missingCount += 1
|
||||||
|
do {
|
||||||
|
guard let scriptUrl = URL(string: module.metadata.scriptUrl) else {
|
||||||
|
Logger.shared.log("Invalid script URL for module: \(module.metadata.sourceName)", type: "Error")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.shared.log("Downloading missing JS file for: \(module.metadata.sourceName)", type: "Info")
|
||||||
|
|
||||||
|
let (scriptData, _) = try await URLSession.custom.data(from: scriptUrl)
|
||||||
|
guard let jsContent = String(data: scriptData, encoding: .utf8) else {
|
||||||
|
Logger.shared.log("Invalid script encoding for module: \(module.metadata.sourceName)", type: "Error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
try jsContent.write(to: localUrl, atomically: true, encoding: .utf8)
|
||||||
|
Logger.shared.log("Successfully downloaded JS file for module: \(module.metadata.sourceName)")
|
||||||
|
} catch {
|
||||||
|
Logger.shared.log("Failed to download JS file for module: \(module.metadata.sourceName) - \(error.localizedDescription)", type: "Error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if missingCount > 0 {
|
||||||
|
Logger.shared.log("Downloaded \(missingCount) missing module JS files", type: "Info")
|
||||||
|
} else {
|
||||||
|
Logger.shared.log("All module JS files are present", type: "Info")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveModules() {
|
private func saveModules() {
|
||||||
|
|
|
||||||
|
|
@ -153,31 +153,58 @@ class iCloudSyncManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncModulesFromiCloud() {
|
func syncModulesFromiCloud() {
|
||||||
DispatchQueue.global(qos: .background).async {
|
guard let iCloudURL = self.ubiquityContainerURL else {
|
||||||
guard let iCloudURL = self.ubiquityContainerURL else { return }
|
Logger.shared.log("iCloud container not available", type: "Error")
|
||||||
let localModulesURL = self.getLocalModulesFileURL()
|
return
|
||||||
let iCloudModulesURL = iCloudURL.appendingPathComponent(self.modulesFileName)
|
}
|
||||||
do {
|
|
||||||
guard FileManager.default.fileExists(atPath: iCloudModulesURL.path) else { return }
|
let localModulesURL = self.getLocalModulesFileURL()
|
||||||
|
let iCloudModulesURL = iCloudURL.appendingPathComponent(self.modulesFileName)
|
||||||
|
|
||||||
|
do {
|
||||||
|
if !FileManager.default.fileExists(atPath: iCloudModulesURL.path) {
|
||||||
|
Logger.shared.log("No modules file found in iCloud", type: "Info")
|
||||||
|
|
||||||
let shouldCopy: Bool
|
|
||||||
if FileManager.default.fileExists(atPath: localModulesURL.path) {
|
if FileManager.default.fileExists(atPath: localModulesURL.path) {
|
||||||
let localData = try Data(contentsOf: localModulesURL)
|
Logger.shared.log("Copying local modules file to iCloud", type: "Info")
|
||||||
let iCloudData = try Data(contentsOf: iCloudModulesURL)
|
try FileManager.default.copyItem(at: localModulesURL, to: iCloudModulesURL)
|
||||||
shouldCopy = localData != iCloudData
|
|
||||||
} else {
|
} else {
|
||||||
shouldCopy = true
|
Logger.shared.log("Creating new empty modules file in iCloud", type: "Info")
|
||||||
}
|
let emptyModules: [ScrapingModule] = []
|
||||||
|
let emptyData = try JSONEncoder().encode(emptyModules)
|
||||||
if shouldCopy {
|
try emptyData.write(to: iCloudModulesURL)
|
||||||
if FileManager.default.fileExists(atPath: localModulesURL.path) {
|
|
||||||
try FileManager.default.removeItem(at: localModulesURL)
|
try emptyData.write(to: localModulesURL)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: .modulesSyncDidComplete, object: nil)
|
||||||
}
|
}
|
||||||
try FileManager.default.copyItem(at: iCloudModulesURL, to: localModulesURL)
|
|
||||||
}
|
}
|
||||||
} catch {
|
return
|
||||||
Logger.shared.log("iCloud modules fetch error: \(error)", type: "Error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue