Update:iOS using new sync local sessions endpoint

- remove local session sync function call on starting playback
- Add User model and getCurrentUser api function
This commit is contained in:
advplyr 2023-11-12 13:19:36 -06:00
parent ff5a1bb09f
commit 36be91962c
12 changed files with 128 additions and 97 deletions

View file

@ -28,6 +28,7 @@
4D66B956282EE951008272D4 /* AbsFileSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D66B955282EE951008272D4 /* AbsFileSystem.m */; };
4D66B958282EEA14008272D4 /* AbsFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D66B957282EEA14008272D4 /* AbsFileSystem.swift */; };
4D91EEC62A40F28D004807ED /* EBookFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D91EEC52A40F28D004807ED /* EBookFile.swift */; };
4DABC04F2B0139CA000F6264 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DABC04E2B0139CA000F6264 /* User.swift */; };
4DF74912287105C600AC7814 /* DeviceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DF74911287105C600AC7814 /* DeviceSettings.swift */; };
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
@ -99,6 +100,7 @@
4D66B957282EEA14008272D4 /* AbsFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsFileSystem.swift; sourceTree = "<group>"; };
4D8D412C26E187E400BA5F0D /* App-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "App-Bridging-Header.h"; sourceTree = "<group>"; };
4D91EEC52A40F28D004807ED /* EBookFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EBookFile.swift; sourceTree = "<group>"; };
4DABC04E2B0139CA000F6264 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
4DF74911287105C600AC7814 /* DeviceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceSettings.swift; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* Audiobookshelf.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Audiobookshelf.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -302,6 +304,7 @@
E9D5505B28AC1C6200C746DD /* LibraryFile.swift */,
E9D5505D28AC1C8500C746DD /* MediaProgress.swift */,
4D91EEC52A40F28D004807ED /* EBookFile.swift */,
4DABC04E2B0139CA000F6264 /* User.swift */,
);
path = server;
sourceTree = "<group>";
@ -542,6 +545,7 @@
E9D5505828AC1C1A00C746DD /* Library.swift in Sources */,
3AD4FCEB280443DD006DB301 /* Database.swift in Sources */,
3AD4FCE528043E50006DB301 /* AbsDatabase.swift in Sources */,
4DABC04F2B0139CA000F6264 /* User.swift in Sources */,
4D66B952282EE822008272D4 /* AbsDownloader.m in Sources */,
E9D5506828AC1DC300C746DD /* LocalPodcastEpisode.swift in Sources */,
E9D5505228AC1B5D00C746DD /* Chapter.swift in Sources */,

View file

@ -21,8 +21,8 @@ CAP_PLUGIN(AbsDatabase, "AbsDatabase",
CAP_PLUGIN_METHOD(getLocalLibraryItemsInFolder, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getAllLocalMediaProgress, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(removeLocalMediaProgress, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(syncLocalMediaProgressWithServer, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(syncServerMediaProgressWithLocalMediaProgress, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(syncLocalSessionsWithServer, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(updateLocalMediaProgressFinished, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(updateDeviceSettings, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(updateLocalEbookProgress, CAPPluginReturnPromise);

View file

@ -146,17 +146,16 @@ public class AbsDatabase: CAPPlugin {
call.resolve()
}
@objc func syncLocalMediaProgressWithServer(_ call: CAPPluginCall) {
@objc func syncLocalSessionsWithServer(_ call: CAPPluginCall) {
logger.log("syncLocalSessionsWithServer: Starting")
guard Store.serverConfig != nil else {
call.reject("syncLocalMediaProgressWithServer not connected to server")
return
}
ApiClient.syncMediaProgress { results in
do {
call.resolve(try results.asDictionary())
} catch {
call.reject("Failed to report synced media progress")
call.reject("syncLocalSessionsWithServer not connected to server")
return call.resolve()
}
Task {
await ApiClient.syncLocalSessionsWithServer()
call.resolve()
}
}

View file

@ -90,4 +90,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 7a8fc177ef0646dd60a1ee8aa387964975fcc1e3
COCOAPODS: 1.12.0
COCOAPODS: 1.12.1

View file

@ -0,0 +1,41 @@
//
// User.swift
// Audiobookshelf
//
// Created by advplyr on 11/12/23.
//
import Foundation
import RealmSwift
class User: EmbeddedObject, Codable {
@Persisted var id: String = ""
@Persisted var username: String = ""
@Persisted var mediaProgress = List<MediaProgress>()
private enum CodingKeys : String, CodingKey {
case id, username, mediaProgress
}
override init() {
super.init()
}
required init(from decoder: Decoder) throws {
super.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
username = try values.decode(String.self, forKey: .username)
if let progresses = try? values.decode([MediaProgress].self, forKey: .mediaProgress) {
mediaProgress.append(objectsIn: progresses)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(username, forKey: .username)
try container.encode(Array(mediaProgress), forKey: .mediaProgress)
}
}

View file

@ -19,7 +19,6 @@ class PlayerHandler {
// Cleanup and sync old sessions
cleanupOldSessions(currentSessionId: sessionId)
Task { await PlayerProgress.shared.syncToServer() }
// Set now playing info
NowPlayingInfo.shared.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))

View file

@ -36,17 +36,6 @@ class PlayerProgress {
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
public func syncToServer() async {
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncToServer")
do {
try await updateAllServerSessionFromLocalSession()
} catch {
logger.error("Failed to syncToServer")
logger.error(error)
}
await UIApplication.shared.endBackgroundTask(backgroundToken)
}
public func syncFromServer() async {
let backgroundToken = await UIApplication.shared.beginBackgroundTask(withName: "ABS:syncFromServer")
do {

View file

@ -189,34 +189,62 @@ class ApiClient {
return await postResource(endpoint: "api/session/local", parameters: session)
}
public static func syncMediaProgress(callback: @escaping (_ results: LocalMediaProgressSyncResultsPayload) -> Void) {
public static func reportAllLocalPlaybackSessions(_ sessions: [PlaybackSession]) async -> Bool {
return await postResource(endpoint: "api/session/local-all", parameters: LocalPlaybackSessionSyncAllPayload(sessions: sessions))
}
public static func syncLocalSessionsWithServer() async {
do {
// Sync server progress with local media progress
let localMediaProgressList = Database.shared.getAllLocalMediaProgress().filter {
$0.serverConnectionConfigId == Store.serverConfig?.id
}.map { $0.freeze() }
logger.log("syncLocalSessionsWithServer: Found \(localMediaProgressList.count) local media progress for server")
if ( !localMediaProgressList.isEmpty ) {
let payload = LocalMediaProgressSyncPayload(localMediaProgress: localMediaProgressList)
logger.log("Sending sync local progress request with \(localMediaProgressList.count) progress items")
postResource(endpoint: "api/me/sync-local-progress", parameters: payload, decodable: MediaProgressSyncResponsePayload.self) { response in
let resultsPayload = LocalMediaProgressSyncResultsPayload(numLocalMediaProgressForServer: localMediaProgressList.count, numServerProgressUpdates: response.numServerProgressUpdates, numLocalProgressUpdates: response.localProgressUpdates?.count)
logger.log("Media Progress Sync | \(String(describing: try? resultsPayload.asDictionary()))")
if let updates = response.localProgressUpdates {
for update in updates {
do {
try update.save()
} catch {
debugPrint("Failed to update local media progress")
debugPrint(error)
}
}
}
callback(resultsPayload)
}
if (localMediaProgressList.isEmpty) {
logger.log("syncLocalSessionsWithServer: No local progress to sync")
} else {
logger.log("No local media progress to sync")
callback(LocalMediaProgressSyncResultsPayload(numLocalMediaProgressForServer: 0, numServerProgressUpdates: 0, numLocalProgressUpdates: 0))
let currentUser = await ApiClient.getCurrentUser()
guard let currentUser = currentUser else {
logger.log("syncLocalSessionsWithServer: No User")
return
}
try currentUser.mediaProgress.forEach { mediaProgress in
let localMediaProgress = localMediaProgressList.first { lmp in
if (lmp.episodeId != nil) {
return lmp.episodeId == mediaProgress.episodeId
} else {
return lmp.libraryItemId == mediaProgress.libraryItemId
}
}
if (localMediaProgress != nil && mediaProgress.lastUpdate > localMediaProgress!.lastUpdate) {
logger.log("syncLocalSessionsWithServer: Updating local media progress \(localMediaProgress!.id) with server media progress")
try localMediaProgress?.updateFromServerMediaProgress(mediaProgress)
} else if (localMediaProgress != nil) {
logger.log("syncLocalSessionsWithServer: Local progress for \(localMediaProgress!.id) is more recent then server progress")
}
}
}
// Send saved playback sessions to server and remove them from db
let playbackSessions = Database.shared.getAllPlaybackSessions().filter {
$0.serverConnectionConfigId == Store.serverConfig?.id
}.map { $0.freeze() }
logger.log("syncLocalSessionsWithServer: Found \(playbackSessions.count) playback sessions for server")
if (!playbackSessions.isEmpty) {
let success = await ApiClient.reportAllLocalPlaybackSessions(playbackSessions)
if (success) {
// Remove sessions from db
try playbackSessions.forEach { session in
if let session = session.thaw() {
try session.delete()
}
}
}
}
} catch {
debugPrint(error)
return
}
}
@ -234,6 +262,11 @@ class ApiClient {
return await getResource(endpoint: endpoint, decodable: MediaProgress.self)
}
public static func getCurrentUser() async -> User? {
logger.log("getCurrentUser")
return await getResource(endpoint: "api/me", decodable: User.self)
}
public static func getLibraryItemWithProgress(libraryItemId:String, episodeId:String?, callback: @escaping (_ param: LibraryItem?) -> Void) {
var endpoint = "api/items/\(libraryItemId)?expanded=1&include=progress"
if episodeId != nil {
@ -287,6 +320,10 @@ struct LocalMediaProgressSyncResultsPayload: Codable {
var numLocalProgressUpdates: Int?
}
struct LocalPlaybackSessionSyncAllPayload: Codable {
var sessions: [PlaybackSession]
}
struct Connectivity {
static private let sharedInstance = NetworkReachabilityManager()!
static var isConnectedToInternet:Bool {

View file

@ -241,6 +241,16 @@ class Database {
}
}
public func getAllPlaybackSessions() -> [PlaybackSession] {
do {
let realm = try Realm()
return Array(realm.objects(PlaybackSession.self))
} catch {
debugPrint(error)
return []
}
}
public func getPlaybackSession(id: String) -> PlaybackSession? {
do {
let realm = try Realm()

View file

@ -44,12 +44,7 @@ export default {
if (timeSinceDisconnect > 5000) {
console.log('Time since disconnect was', timeSinceDisconnect, 'sync with server')
setTimeout(() => {
if (this.$platform === 'ios') {
// TODO: Update ios to not use this
this.syncLocalMediaProgress()
} else {
this.syncLocalSessions()
}
}, 4000)
}
}
@ -215,36 +210,6 @@ export default {
await this.$store.dispatch('globals/loadLocalMediaProgress')
}
},
async syncLocalMediaProgress() {
if (!this.user) {
console.log('[default] No need to sync local media progress - not connected to server')
return
}
console.log('[default] Calling syncLocalMediaProgress')
const response = await this.$db.syncLocalMediaProgressWithServer()
if (!response) {
if (this.$platform != 'web') this.$toast.error('Failed to sync local media with server')
return
}
const { numLocalMediaProgressForServer, numServerProgressUpdates, numLocalProgressUpdates, serverProgressUpdates } = response
if (numLocalMediaProgressForServer > 0) {
if (serverProgressUpdates && serverProgressUpdates.length) {
serverProgressUpdates.forEach((progress) => {
console.log(`[default] Server progress was updated ${progress.id}`)
this.$store.commit('user/updateUserMediaProgress', progress)
})
}
if (numServerProgressUpdates > 0 || numLocalProgressUpdates > 0) {
console.log(`[default] ${numServerProgressUpdates} Server progress updates | ${numLocalProgressUpdates} Local progress updates`)
} else {
console.log('[default] syncLocalMediaProgress No updates were necessary')
}
} else {
console.log('[default] syncLocalMediaProgress No local media progress to sync')
}
},
userUpdated(user) {
// console.log('[default] userUpdated:', JSON.stringify(user))
if (this.user && this.user.id == user.id) {
@ -362,12 +327,7 @@ export default {
}
console.log(`[default] finished connection attempt or already connected ${!!this.user}`)
if (this.$platform === 'ios') {
// TODO: Update ios to not use this
await this.syncLocalMediaProgress()
} else {
await this.syncLocalSessions()
}
this.loadSavedSettings()
this.hasMounted = true

View file

@ -194,10 +194,6 @@ class AbsDatabaseWeb extends WebPlugin {
return null
}
async syncLocalMediaProgressWithServer() {
return null
}
async syncLocalSessionsWithServer() {
return null
}

View file

@ -70,10 +70,6 @@ class DbService {
return AbsDatabase.removeLocalMediaProgress({ localMediaProgressId })
}
syncLocalMediaProgressWithServer() {
return AbsDatabase.syncLocalMediaProgressWithServer()
}
syncLocalSessionsWithServer() {
return AbsDatabase.syncLocalSessionsWithServer()
}