Improved error handling

This commit is contained in:
ronaldheft 2022-08-25 15:42:37 -04:00
parent 01678f2c91
commit 8c87b31e56
14 changed files with 220 additions and 141 deletions

View file

@ -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: [

View file

@ -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) {

View file

@ -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 {

View file

@ -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 {

View file

@ -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)
} }

View file

@ -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)
} }

View file

@ -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) {

View file

@ -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)
} }

View file

@ -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
} }

View file

@ -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)
}
} }
} }

View file

@ -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
} }

View file

@ -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)
}
} }
} }

View file

@ -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)
} }
} }

View file

@ -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
}
} }
} }