mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-21 03:04:30 +02:00
Improved error handling
This commit is contained in:
parent
01678f2c91
commit
8c87b31e56
14 changed files with 220 additions and 141 deletions
|
@ -81,9 +81,9 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
NSLog("Failed to get local playback session")
|
NSLog("Failed to get local playback session")
|
||||||
return call.resolve([:])
|
return call.resolve([:])
|
||||||
}
|
}
|
||||||
playbackSession.save()
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
try playbackSession.save()
|
||||||
try self.startPlaybackSession(playbackSession, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
try self.startPlaybackSession(playbackSession, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||||
call.resolve(try playbackSession.asDictionary())
|
call.resolve(try playbackSession.asDictionary())
|
||||||
} catch(let exception) {
|
} catch(let exception) {
|
||||||
|
@ -93,8 +93,8 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
}
|
}
|
||||||
} else { // Playing from the server
|
} else { // Playing from the server
|
||||||
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId, forceTranscode: false) { session in
|
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId, forceTranscode: false) { session in
|
||||||
session.save()
|
|
||||||
do {
|
do {
|
||||||
|
try session.save()
|
||||||
try self.startPlaybackSession(session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
try self.startPlaybackSession(session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||||
call.resolve(try session.asDictionary())
|
call.resolve(try session.asDictionary())
|
||||||
} catch(let exception) {
|
} catch(let exception) {
|
||||||
|
@ -122,7 +122,7 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
@objc func setPlaybackSpeed(_ call: CAPPluginCall) {
|
@objc func setPlaybackSpeed(_ call: CAPPluginCall) {
|
||||||
let playbackRate = call.getFloat("value", 1.0)
|
let playbackRate = call.getFloat("value", 1.0)
|
||||||
let settings = PlayerSettings.main()
|
let settings = PlayerSettings.main()
|
||||||
settings.update {
|
try? settings.update {
|
||||||
settings.playbackRate = playbackRate
|
settings.playbackRate = playbackRate
|
||||||
}
|
}
|
||||||
PlayerHandler.setPlaybackSpeed(speed: settings.playbackRate)
|
PlayerHandler.setPlaybackSpeed(speed: settings.playbackRate)
|
||||||
|
@ -244,17 +244,15 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
|
|
||||||
// If direct playing then fallback to transcode
|
// If direct playing then fallback to transcode
|
||||||
ApiClient.startPlaybackSession(libraryItemId: libraryItemId, episodeId: episodeId, forceTranscode: true) { session in
|
ApiClient.startPlaybackSession(libraryItemId: libraryItemId, episodeId: episodeId, forceTranscode: true) { session in
|
||||||
session.save()
|
|
||||||
PlayerHandler.startPlayback(sessionId: session.id, playWhenReady: self.initialPlayWhenReady, playbackRate: PlayerSettings.main().playbackRate)
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
try session.save()
|
||||||
|
PlayerHandler.startPlayback(sessionId: session.id, playWhenReady: self.initialPlayWhenReady, playbackRate: PlayerSettings.main().playbackRate)
|
||||||
self.sendPlaybackSession(session: try session.asDictionary())
|
self.sendPlaybackSession(session: try session.asDictionary())
|
||||||
|
self.sendMetadata()
|
||||||
} catch(let exception) {
|
} catch(let exception) {
|
||||||
NSLog("failed to convert session to json")
|
NSLog("Failed to start transcoded session")
|
||||||
debugPrint(exception)
|
debugPrint(exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sendMetadata()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.notifyListeners("onPlaybackFailed", data: [
|
self.notifyListeners("onPlaybackFailed", data: [
|
||||||
|
|
|
@ -139,7 +139,7 @@ public class AbsDatabase: CAPPlugin {
|
||||||
call.reject("localMediaProgressId not specificed")
|
call.reject("localMediaProgressId not specificed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Database.shared.removeLocalMediaProgress(localMediaProgressId: localMediaProgressId)
|
try? Database.shared.removeLocalMediaProgress(localMediaProgressId: localMediaProgressId)
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,14 +171,14 @@ public class AbsDatabase: CAPPlugin {
|
||||||
return call.reject("localLibraryItemId or localMediaProgressId must be specified")
|
return call.reject("localLibraryItemId or localMediaProgressId must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
let localMediaProgress = try LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
||||||
guard let localMediaProgress = localMediaProgress else {
|
guard let localMediaProgress = localMediaProgress else {
|
||||||
call.reject("Local media progress not found or created")
|
call.reject("Local media progress not found or created")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog("syncServerMediaProgressWithLocalMediaProgress: Saving local media progress")
|
NSLog("syncServerMediaProgressWithLocalMediaProgress: Saving local media progress")
|
||||||
localMediaProgress.updateFromServerMediaProgress(serverMediaProgress)
|
try localMediaProgress.updateFromServerMediaProgress(serverMediaProgress)
|
||||||
|
|
||||||
call.resolve(try localMediaProgress.asDictionary())
|
call.resolve(try localMediaProgress.asDictionary())
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -195,14 +195,15 @@ public class AbsDatabase: CAPPlugin {
|
||||||
|
|
||||||
NSLog("updateLocalMediaProgressFinished \(localMediaProgressId ?? "Unknown") | Is Finished: \(isFinished)")
|
NSLog("updateLocalMediaProgressFinished \(localMediaProgressId ?? "Unknown") | Is Finished: \(isFinished)")
|
||||||
|
|
||||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
do {
|
||||||
|
let localMediaProgress = try LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
||||||
guard let localMediaProgress = localMediaProgress else {
|
guard let localMediaProgress = localMediaProgress else {
|
||||||
call.resolve(["error": "Library Item not found"])
|
call.resolve(["error": "Library Item not found"])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update finished status
|
// Update finished status
|
||||||
localMediaProgress.updateIsFinished(isFinished)
|
try localMediaProgress.updateIsFinished(isFinished)
|
||||||
|
|
||||||
// Build API response
|
// Build API response
|
||||||
let progressDictionary = try? localMediaProgress.asDictionary()
|
let progressDictionary = try? localMediaProgress.asDictionary()
|
||||||
|
@ -220,6 +221,11 @@ public class AbsDatabase: CAPPlugin {
|
||||||
} else {
|
} else {
|
||||||
call.resolve(response)
|
call.resolve(response)
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
call.resolve(["error": "Failed to mark as complete"])
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateDeviceSettings(_ call: CAPPluginCall) {
|
@objc func updateDeviceSettings(_ call: CAPPluginCall) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
||||||
|
|
||||||
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
|
||||||
handleDownloadTaskUpdate(downloadTask: downloadTask) { downloadItem, downloadItemPart in
|
handleDownloadTaskUpdate(downloadTask: downloadTask) { downloadItem, downloadItemPart in
|
||||||
let realm = try! Realm()
|
let realm = try Realm()
|
||||||
try realm.write {
|
try realm.write {
|
||||||
downloadItemPart.progress = 100
|
downloadItemPart.progress = 100
|
||||||
downloadItemPart.completed = true
|
downloadItemPart.completed = true
|
||||||
|
@ -139,7 +139,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
||||||
}
|
}
|
||||||
self.handleDownloadTaskCompleteFromDownloadItem(item)
|
self.handleDownloadTaskCompleteFromDownloadItem(item)
|
||||||
if let item = Database.shared.getDownloadItem(downloadItemId: item.id!) {
|
if let item = Database.shared.getDownloadItem(downloadItemId: item.id!) {
|
||||||
item.delete()
|
try? item.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
localLibraryItem = LocalLibraryItem(libraryItem, localUrl: localDirectory, server: Store.serverConfig!, files: files, coverPath: coverFile)
|
localLibraryItem = LocalLibraryItem(libraryItem, localUrl: localDirectory, server: Store.serverConfig!, files: files, coverPath: coverFile)
|
||||||
Database.shared.saveLocalLibraryItem(localLibraryItem: localLibraryItem!)
|
try? Database.shared.saveLocalLibraryItem(localLibraryItem: localLibraryItem!)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusNotification["localLibraryItem"] = try? localLibraryItem.asDictionary()
|
statusNotification["localLibraryItem"] = try? localLibraryItem.asDictionary()
|
||||||
|
@ -189,7 +189,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
||||||
if let progress = libraryItem.userMediaProgress {
|
if let progress = libraryItem.userMediaProgress {
|
||||||
let episode = downloadItem.media?.episodes.first(where: { $0.id == downloadItem.episodeId })
|
let episode = downloadItem.media?.episodes.first(where: { $0.id == downloadItem.episodeId })
|
||||||
let localMediaProgress = LocalMediaProgress(localLibraryItem: localLibraryItem!, episode: episode, progress: progress)
|
let localMediaProgress = LocalMediaProgress(localLibraryItem: localLibraryItem!, episode: episode, progress: progress)
|
||||||
Database.shared.saveLocalMediaProgress(localMediaProgress)
|
try? localMediaProgress.save()
|
||||||
statusNotification["localMediaProgress"] = try? localMediaProgress.asDictionary()
|
statusNotification["localMediaProgress"] = try? localMediaProgress.asDictionary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist in the database before status start coming in
|
// Persist in the database before status start coming in
|
||||||
Database.shared.saveDownloadItem(downloadItem)
|
try Database.shared.saveDownloadItem(downloadItem)
|
||||||
|
|
||||||
// Start all the downloads
|
// Start all the downloads
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class AbsFileSystem: CAPPlugin {
|
||||||
do {
|
do {
|
||||||
if let localLibraryItemId = localLibraryItemId, let item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) {
|
if let localLibraryItemId = localLibraryItemId, let item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) {
|
||||||
try FileManager.default.removeItem(at: item.contentDirectory!)
|
try FileManager.default.removeItem(at: item.contentDirectory!)
|
||||||
item.delete()
|
try item.delete()
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -89,7 +89,8 @@ public class AbsFileSystem: CAPPlugin {
|
||||||
|
|
||||||
var success = false
|
var success = false
|
||||||
if let localLibraryItemId = localLibraryItemId, let trackLocalFileId = trackLocalFileId, let item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) {
|
if let localLibraryItemId = localLibraryItemId, let trackLocalFileId = trackLocalFileId, let item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) {
|
||||||
item.update {
|
do {
|
||||||
|
try item.update {
|
||||||
do {
|
do {
|
||||||
if let fileIndex = item.localFiles.firstIndex(where: { $0.id == trackLocalFileId }) {
|
if let fileIndex = item.localFiles.firstIndex(where: { $0.id == trackLocalFileId }) {
|
||||||
try FileManager.default.removeItem(at: item.localFiles[fileIndex].contentPath)
|
try FileManager.default.removeItem(at: item.localFiles[fileIndex].contentPath)
|
||||||
|
@ -108,6 +109,10 @@ public class AbsFileSystem: CAPPlugin {
|
||||||
success = false
|
success = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
NSLog("Failed to delete \(error)")
|
||||||
|
success = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !success {
|
if !success {
|
||||||
|
|
|
@ -88,8 +88,8 @@ extension DownloadItem {
|
||||||
self.downloadItemParts.allSatisfy({ $0.failed == false })
|
self.downloadItemParts.allSatisfy({ $0.failed == false })
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete() {
|
func delete() throws {
|
||||||
try! self.realm?.write {
|
try self.realm?.write {
|
||||||
self.realm?.delete(self.downloadItemParts)
|
self.realm?.delete(self.downloadItemParts)
|
||||||
self.realm?.delete(self)
|
self.realm?.delete(self)
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,8 +198,8 @@ extension LocalLibraryItem {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete() {
|
func delete() throws {
|
||||||
try! self.realm?.write {
|
try self.realm?.write {
|
||||||
self.realm?.delete(self.localFiles)
|
self.realm?.delete(self.localFiles)
|
||||||
self.realm?.delete(self)
|
self.realm?.delete(self)
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,8 +119,8 @@ extension LocalMediaProgress {
|
||||||
self.finishedAt = progress.finishedAt
|
self.finishedAt = progress.finishedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsFinished(_ finished: Bool) {
|
func updateIsFinished(_ finished: Bool) throws {
|
||||||
try! self.realm?.write {
|
try self.realm?.write {
|
||||||
if self.isFinished != finished {
|
if self.isFinished != finished {
|
||||||
self.progress = finished ? 1.0 : 0.0
|
self.progress = finished ? 1.0 : 0.0
|
||||||
}
|
}
|
||||||
|
@ -135,8 +135,8 @@ extension LocalMediaProgress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFromPlaybackSession(_ playbackSession: PlaybackSession) {
|
func updateFromPlaybackSession(_ playbackSession: PlaybackSession) throws {
|
||||||
try! self.realm?.write {
|
try self.realm?.write {
|
||||||
self.currentTime = playbackSession.currentTime
|
self.currentTime = playbackSession.currentTime
|
||||||
self.progress = playbackSession.progress
|
self.progress = playbackSession.progress
|
||||||
self.lastUpdate = Date().timeIntervalSince1970 * 1000
|
self.lastUpdate = Date().timeIntervalSince1970 * 1000
|
||||||
|
@ -145,8 +145,8 @@ extension LocalMediaProgress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFromServerMediaProgress(_ serverMediaProgress: MediaProgress) {
|
func updateFromServerMediaProgress(_ serverMediaProgress: MediaProgress) throws {
|
||||||
try! self.realm?.write {
|
try self.realm?.write {
|
||||||
self.isFinished = serverMediaProgress.isFinished
|
self.isFinished = serverMediaProgress.isFinished
|
||||||
self.progress = serverMediaProgress.progress
|
self.progress = serverMediaProgress.progress
|
||||||
self.currentTime = serverMediaProgress.currentTime
|
self.currentTime = serverMediaProgress.currentTime
|
||||||
|
@ -157,9 +157,9 @@ extension LocalMediaProgress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func fetchOrCreateLocalMediaProgress(localMediaProgressId: String?, localLibraryItemId: String?, localEpisodeId: String?) -> LocalMediaProgress? {
|
static func fetchOrCreateLocalMediaProgress(localMediaProgressId: String?, localLibraryItemId: String?, localEpisodeId: String?) throws -> LocalMediaProgress? {
|
||||||
let realm = try! Realm()
|
let realm = try Realm()
|
||||||
return try! realm.write { () -> LocalMediaProgress? in
|
return try realm.write { () -> LocalMediaProgress? in
|
||||||
if let localMediaProgressId = localMediaProgressId {
|
if let localMediaProgressId = localMediaProgressId {
|
||||||
// Check if it existing in the database, if not, we need to create it
|
// Check if it existing in the database, if not, we need to create it
|
||||||
if let progress = Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId) {
|
if let progress = Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ class AudioTrack: EmbeddedObject, Codable {
|
||||||
contentUrl = try? values.decode(String.self, forKey: .contentUrl)
|
contentUrl = try? values.decode(String.self, forKey: .contentUrl)
|
||||||
mimeType = try values.decode(String.self, forKey: .mimeType)
|
mimeType = try values.decode(String.self, forKey: .mimeType)
|
||||||
metadata = try? values.decode(FileMetadata.self, forKey: .metadata)
|
metadata = try? values.decode(FileMetadata.self, forKey: .metadata)
|
||||||
localFileId = try! values.decodeIfPresent(String.self, forKey: .localFileId)
|
localFileId = try? values.decodeIfPresent(String.self, forKey: .localFileId)
|
||||||
serverIndex = try? values.decode(Int.self, forKey: .serverIndex)
|
serverIndex = try? values.decode(Int.self, forKey: .serverIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,7 @@ class AudioPlayer: NSObject {
|
||||||
if (self.currentTrackIndex != indexOfSeek) {
|
if (self.currentTrackIndex != indexOfSeek) {
|
||||||
self.currentTrackIndex = indexOfSeek
|
self.currentTrackIndex = indexOfSeek
|
||||||
|
|
||||||
playbackSession.update {
|
try? playbackSession.update {
|
||||||
playbackSession.currentTime = to
|
playbackSession.currentTime = to
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,16 +158,21 @@ class PlayerHandler {
|
||||||
// MARK: - Helper logic
|
// MARK: - Helper logic
|
||||||
|
|
||||||
private static func cleanupOldSessions(currentSessionId: String?) {
|
private static func cleanupOldSessions(currentSessionId: String?) {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
let oldSessions = realm.objects(PlaybackSession.self) .where({
|
let oldSessions = realm.objects(PlaybackSession.self) .where({
|
||||||
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
|
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
|
||||||
})
|
})
|
||||||
try! realm.write {
|
try realm.write {
|
||||||
for s in oldSessions {
|
for s in oldSessions {
|
||||||
if s.id != currentSessionId {
|
if s.id != currentSessionId {
|
||||||
s.isActiveSession = false
|
s.isActiveSession = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
debugPrint("Failed to cleanup sessions")
|
||||||
|
debugPrint(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,34 +21,49 @@ class PlayerProgress {
|
||||||
|
|
||||||
public func syncFromPlayer(currentTime: Double, includesPlayProgress: Bool, isStopping: Bool) async {
|
public func syncFromPlayer(currentTime: Double, includesPlayProgress: Bool, isStopping: Bool) async {
|
||||||
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromPlayer")
|
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromPlayer")
|
||||||
let session = updateLocalSessionFromPlayer(currentTime: currentTime, includesPlayProgress: includesPlayProgress)
|
do {
|
||||||
updateLocalMediaProgressFromLocalSession()
|
let session = try updateLocalSessionFromPlayer(currentTime: currentTime, includesPlayProgress: includesPlayProgress)
|
||||||
|
try updateLocalMediaProgressFromLocalSession()
|
||||||
if let session = session {
|
if let session = session {
|
||||||
await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
|
try await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
debugPrint("Failed to syncFromPlayer")
|
||||||
|
debugPrint(error)
|
||||||
}
|
}
|
||||||
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func syncToServer() async {
|
public func syncToServer() async {
|
||||||
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncToServer")
|
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncToServer")
|
||||||
await updateAllServerSessionFromLocalSession()
|
do {
|
||||||
|
try await updateAllServerSessionFromLocalSession()
|
||||||
|
} catch {
|
||||||
|
debugPrint("Failed to syncToServer")
|
||||||
|
debugPrint(error)
|
||||||
|
}
|
||||||
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func syncFromServer() async {
|
public func syncFromServer() async {
|
||||||
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromServer")
|
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromServer")
|
||||||
await updateLocalSessionFromServerMediaProgress()
|
do {
|
||||||
|
try await updateLocalSessionFromServerMediaProgress()
|
||||||
|
} catch {
|
||||||
|
debugPrint("Failed to syncFromServer")
|
||||||
|
debugPrint(error)
|
||||||
|
}
|
||||||
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - SYNC LOGIC
|
// MARK: - SYNC LOGIC
|
||||||
|
|
||||||
private func updateLocalSessionFromPlayer(currentTime: Double, includesPlayProgress: Bool) -> PlaybackSession? {
|
private func updateLocalSessionFromPlayer(currentTime: Double, includesPlayProgress: Bool) throws -> PlaybackSession? {
|
||||||
guard let session = PlayerHandler.getPlaybackSession() else { return nil }
|
guard let session = PlayerHandler.getPlaybackSession() else { return nil }
|
||||||
guard !currentTime.isNaN else { return nil } // Prevent bad data on player stop
|
guard !currentTime.isNaN else { return nil } // Prevent bad data on player stop
|
||||||
|
|
||||||
session.update {
|
try session.update {
|
||||||
session.realm?.refresh()
|
session.realm?.refresh()
|
||||||
|
|
||||||
let nowInSeconds = Date().timeIntervalSince1970
|
let nowInSeconds = Date().timeIntervalSince1970
|
||||||
|
@ -68,18 +83,18 @@ class PlayerProgress {
|
||||||
return session.freeze()
|
return session.freeze()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateLocalMediaProgressFromLocalSession() {
|
private func updateLocalMediaProgressFromLocalSession() throws {
|
||||||
guard let session = PlayerHandler.getPlaybackSession() else { return }
|
guard let session = PlayerHandler.getPlaybackSession() else { return }
|
||||||
guard session.isLocal else { return }
|
guard session.isLocal else { return }
|
||||||
|
|
||||||
let localMediaProgress = LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: session.localMediaProgressId, localLibraryItemId: session.localLibraryItem?.id, localEpisodeId: session.episodeId)
|
let localMediaProgress = try LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: session.localMediaProgressId, localLibraryItemId: session.localLibraryItem?.id, localEpisodeId: session.episodeId)
|
||||||
guard let localMediaProgress = localMediaProgress else {
|
guard let localMediaProgress = localMediaProgress else {
|
||||||
// Local media progress should have been created
|
// Local media progress should have been created
|
||||||
// If we're here, it means a library id is invalid
|
// If we're here, it means a library id is invalid
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localMediaProgress.updateFromPlaybackSession(session)
|
try localMediaProgress.updateFromPlaybackSession(session)
|
||||||
|
|
||||||
NSLog("Local progress saved to the database")
|
NSLog("Local progress saved to the database")
|
||||||
|
|
||||||
|
@ -87,25 +102,25 @@ class PlayerProgress {
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateAllServerSessionFromLocalSession() async {
|
private func updateAllServerSessionFromLocalSession() async throws {
|
||||||
await withTaskGroup(of: Void.self) { [self] group in
|
try await withThrowingTaskGroup(of: Void.self) { [self] group in
|
||||||
for session in try! await Realm().objects(PlaybackSession.self).where({ $0.serverConnectionConfigId == Store.serverConfig?.id }) {
|
for session in try await Realm().objects(PlaybackSession.self).where({ $0.serverConnectionConfigId == Store.serverConfig?.id }) {
|
||||||
let session = session.freeze()
|
let session = session.freeze()
|
||||||
group.addTask {
|
group.addTask {
|
||||||
await self.updateServerSessionFromLocalSession(session)
|
try await self.updateServerSessionFromLocalSession(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await group.waitForAll()
|
try await group.waitForAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateServerSessionFromLocalSession(_ session: PlaybackSession, rateLimitSync: Bool = false) async {
|
private func updateServerSessionFromLocalSession(_ session: PlaybackSession, rateLimitSync: Bool = false) async throws {
|
||||||
var safeToSync = true
|
var safeToSync = true
|
||||||
|
|
||||||
guard var session = session.thaw() else { return }
|
guard var session = session.thaw() else { return }
|
||||||
|
|
||||||
// We need to update and check the server time in a transaction for thread-safety
|
// We need to update and check the server time in a transaction for thread-safety
|
||||||
session.update {
|
try session.update {
|
||||||
session.realm?.refresh()
|
session.realm?.refresh()
|
||||||
|
|
||||||
let nowInMilliseconds = Date().timeIntervalSince1970 * 1000
|
let nowInMilliseconds = Date().timeIntervalSince1970 * 1000
|
||||||
|
@ -140,14 +155,14 @@ class PlayerProgress {
|
||||||
// Remove old sessions after they synced with the server
|
// Remove old sessions after they synced with the server
|
||||||
if success && !session.isActiveSession {
|
if success && !session.isActiveSession {
|
||||||
if let session = session.thaw() {
|
if let session = session.thaw() {
|
||||||
session.delete()
|
try session.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateLocalSessionFromServerMediaProgress() async {
|
private func updateLocalSessionFromServerMediaProgress() async throws {
|
||||||
NSLog("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
|
NSLog("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
|
||||||
guard let session = try! await Realm().objects(PlaybackSession.self).last(where: {
|
guard let session = try await Realm().objects(PlaybackSession.self).last(where: {
|
||||||
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
|
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
|
||||||
})?.freeze() else {
|
})?.freeze() else {
|
||||||
NSLog("updateLocalSessionFromServerMediaProgress: Failed to get session")
|
NSLog("updateLocalSessionFromServerMediaProgress: Failed to get session")
|
||||||
|
@ -177,7 +192,7 @@ class PlayerProgress {
|
||||||
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
||||||
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
NSLog("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
||||||
guard let session = session.thaw() else { return }
|
guard let session = session.thaw() else { return }
|
||||||
session.update {
|
try session.update {
|
||||||
session.currentTime = serverCurrentTime
|
session.currentTime = serverCurrentTime
|
||||||
session.updatedAt = serverLastUpdate
|
session.updatedAt = serverLastUpdate
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,12 @@ class ApiClient {
|
||||||
|
|
||||||
if let updates = response.localProgressUpdates {
|
if let updates = response.localProgressUpdates {
|
||||||
for update in updates {
|
for update in updates {
|
||||||
Database.shared.saveLocalMediaProgress(update)
|
do {
|
||||||
|
try update.save()
|
||||||
|
} catch {
|
||||||
|
debugPrint("Failed to update local media progress")
|
||||||
|
debugPrint(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,15 @@ import Foundation
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
|
|
||||||
extension Object {
|
extension Object {
|
||||||
func save() {
|
func save() throws {
|
||||||
let realm = try! Realm()
|
let realm = try Realm()
|
||||||
try! realm.write {
|
try realm.write {
|
||||||
realm.add(self, update: .modified)
|
realm.add(self, update: .modified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(handler: () -> Void) {
|
func update(handler: () -> Void) throws {
|
||||||
try! self.realm?.write {
|
try self.realm?.write {
|
||||||
handler()
|
handler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,12 +33,12 @@ extension EmbeddedObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol Deletable {
|
protocol Deletable {
|
||||||
func delete()
|
func delete() throws
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Deletable where Self: Object {
|
extension Deletable where Self: Object {
|
||||||
func delete() {
|
func delete() throws {
|
||||||
try! self.realm?.write {
|
try self.realm?.write {
|
||||||
self.realm?.delete(self)
|
self.realm?.delete(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,48 +112,83 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLocalLibraryItems(mediaType: MediaType? = nil) -> [LocalLibraryItem] {
|
public func getLocalLibraryItems(mediaType: MediaType? = nil) -> [LocalLibraryItem] {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return Array(realm.objects(LocalLibraryItem.self))
|
return Array(realm.objects(LocalLibraryItem.self))
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLocalLibraryItem(byServerLibraryItemId: String) -> LocalLibraryItem? {
|
public func getLocalLibraryItem(byServerLibraryItemId: String) -> LocalLibraryItem? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.objects(LocalLibraryItem.self).first(where: { $0.libraryItemId == byServerLibraryItemId })
|
return realm.objects(LocalLibraryItem.self).first(where: { $0.libraryItemId == byServerLibraryItemId })
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLocalLibraryItem(localLibraryItemId: String) -> LocalLibraryItem? {
|
public func getLocalLibraryItem(localLibraryItemId: String) -> LocalLibraryItem? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.object(ofType: LocalLibraryItem.self, forPrimaryKey: localLibraryItemId)
|
return realm.object(ofType: LocalLibraryItem.self, forPrimaryKey: localLibraryItemId)
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) {
|
public func saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) throws {
|
||||||
let realm = try! Realm()
|
let realm = try Realm()
|
||||||
try! realm.write { realm.add(localLibraryItem, update: .modified) }
|
try realm.write { realm.add(localLibraryItem, update: .modified) }
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLocalFile(localFileId: String) -> LocalFile? {
|
public func getLocalFile(localFileId: String) -> LocalFile? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.object(ofType: LocalFile.self, forPrimaryKey: localFileId)
|
return realm.object(ofType: LocalFile.self, forPrimaryKey: localFileId)
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getDownloadItem(downloadItemId: String) -> DownloadItem? {
|
public func getDownloadItem(downloadItemId: String) -> DownloadItem? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.object(ofType: DownloadItem.self, forPrimaryKey: downloadItemId)
|
return realm.object(ofType: DownloadItem.self, forPrimaryKey: downloadItemId)
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getDownloadItem(libraryItemId: String) -> DownloadItem? {
|
public func getDownloadItem(libraryItemId: String) -> DownloadItem? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.objects(DownloadItem.self).filter("libraryItemId == %@", libraryItemId).first
|
return realm.objects(DownloadItem.self).filter("libraryItemId == %@", libraryItemId).first
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getDownloadItem(downloadItemPartId: String) -> DownloadItem? {
|
public func getDownloadItem(downloadItemPartId: String) -> DownloadItem? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.objects(DownloadItem.self).filter("SUBQUERY(downloadItemParts, $part, $part.id == %@) .@count > 0", downloadItemPartId).first
|
return realm.objects(DownloadItem.self).filter("SUBQUERY(downloadItemParts, $part, $part.id == %@) .@count > 0", downloadItemPartId).first
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func saveDownloadItem(_ downloadItem: DownloadItem) {
|
public func saveDownloadItem(_ downloadItem: DownloadItem) throws {
|
||||||
let realm = try! Realm()
|
let realm = try Realm()
|
||||||
return try! realm.write { realm.add(downloadItem, update: .modified) }
|
return try realm.write { realm.add(downloadItem, update: .modified) }
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getDeviceSettings() -> DeviceSettings {
|
public func getDeviceSettings() -> DeviceSettings {
|
||||||
|
@ -162,31 +197,41 @@ class Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getAllLocalMediaProgress() -> [LocalMediaProgress] {
|
public func getAllLocalMediaProgress() -> [LocalMediaProgress] {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return Array(realm.objects(LocalMediaProgress.self))
|
return Array(realm.objects(LocalMediaProgress.self))
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
public func saveLocalMediaProgress(_ mediaProgress: LocalMediaProgress) {
|
|
||||||
let realm = try! Realm()
|
|
||||||
try! realm.write { realm.add(mediaProgress, update: .modified) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}"
|
// For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}"
|
||||||
public func getLocalMediaProgress(localMediaProgressId: String) -> LocalMediaProgress? {
|
public func getLocalMediaProgress(localMediaProgressId: String) -> LocalMediaProgress? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.object(ofType: LocalMediaProgress.self, forPrimaryKey: localMediaProgressId)
|
return realm.object(ofType: LocalMediaProgress.self, forPrimaryKey: localMediaProgressId)
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeLocalMediaProgress(localMediaProgressId: String) {
|
public func removeLocalMediaProgress(localMediaProgressId: String) throws {
|
||||||
let realm = try! Realm()
|
let realm = try Realm()
|
||||||
try! realm.write {
|
try realm.write {
|
||||||
let progress = realm.object(ofType: LocalMediaProgress.self, forPrimaryKey: localMediaProgressId)
|
let progress = realm.object(ofType: LocalMediaProgress.self, forPrimaryKey: localMediaProgressId)
|
||||||
realm.delete(progress!)
|
realm.delete(progress!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getPlaybackSession(id: String) -> PlaybackSession? {
|
public func getPlaybackSession(id: String) -> PlaybackSession? {
|
||||||
let realm = try! Realm()
|
do {
|
||||||
|
let realm = try Realm()
|
||||||
return realm.object(ofType: PlaybackSession.self, forPrimaryKey: id)
|
return realm.object(ofType: PlaybackSession.self, forPrimaryKey: id)
|
||||||
|
} catch {
|
||||||
|
debugPrint(error)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue