mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-10 22:14:48 +02:00
Update:Syncing local sessions rewrite to support offline sessions #381
This commit is contained in:
parent
2f243787ce
commit
f215efdcd0
17 changed files with 207 additions and 225 deletions
|
@ -158,7 +158,7 @@ data class DeviceSettings(
|
||||||
data class DeviceData(
|
data class DeviceData(
|
||||||
var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
|
var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
|
||||||
var lastServerConnectionConfigId:String?,
|
var lastServerConnectionConfigId:String?,
|
||||||
var currentLocalPlaybackSession: PlaybackSession?, // Stored to open up where left off for local media
|
var currentLocalPlaybackSession: PlaybackSession?, // Stored to open up where left off for local media. TODO: Old
|
||||||
var deviceSettings: DeviceSettings?
|
var deviceSettings: DeviceSettings?
|
||||||
) {
|
) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
@ -33,6 +32,11 @@ class LocalMediaProgress(
|
||||||
if (localEpisodeId.isNullOrEmpty()) localLibraryItemId else "$localLibraryItemId-$localEpisodeId"
|
if (localEpisodeId.isNullOrEmpty()) localLibraryItemId else "$localLibraryItemId-$localEpisodeId"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun isMatch(mediaProgress:MediaProgress):Boolean {
|
||||||
|
if (episodeId != null) return libraryItemId == mediaProgress.libraryItemId && episodeId == mediaProgress.episodeId
|
||||||
|
return libraryItemId == mediaProgress.libraryItemId
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun updateIsFinished(finished:Boolean) {
|
fun updateIsFinished(finished:Boolean) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import android.os.Build
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.player.MediaProgressSyncData
|
import com.audiobookshelf.app.media.MediaProgressSyncData
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package com.audiobookshelf.app.device
|
package com.audiobookshelf.app.device
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.managers.DbManager
|
import com.audiobookshelf.app.managers.DbManager
|
||||||
|
@ -46,4 +49,22 @@ object DeviceManager {
|
||||||
if (id == null) return null
|
if (id == null) return null
|
||||||
return deviceData.serverConnectionConfigs.find { it.id == id }
|
return deviceData.serverConnectionConfigs.find { it.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkConnectivity(ctx:Context): Boolean {
|
||||||
|
val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||||
|
if (capabilities != null) {
|
||||||
|
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
||||||
|
Log.i("Internet", "NetworkCapabilities.TRANSPORT_CELLULAR")
|
||||||
|
return true
|
||||||
|
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||||
|
Log.i("Internet", "NetworkCapabilities.TRANSPORT_WIFI")
|
||||||
|
return true
|
||||||
|
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
|
||||||
|
Log.i("Internet", "NetworkCapabilities.TRANSPORT_ETHERNET")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,18 +237,26 @@ class DbManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveLocalPlaybackSession(playbackSession: PlaybackSession) {
|
|
||||||
Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession)
|
|
||||||
}
|
|
||||||
fun getLocalPlaybackSession(playbackSessionId:String): PlaybackSession? {
|
|
||||||
return Paper.book("localPlaybackSession").read(playbackSessionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun saveMediaItemHistory(mediaItemHistory: MediaItemHistory) {
|
fun saveMediaItemHistory(mediaItemHistory: MediaItemHistory) {
|
||||||
Paper.book("mediaItemHistory").write(mediaItemHistory.id,mediaItemHistory)
|
Paper.book("mediaItemHistory").write(mediaItemHistory.id,mediaItemHistory)
|
||||||
}
|
}
|
||||||
fun getMediaItemHistory(id:String): MediaItemHistory? {
|
fun getMediaItemHistory(id:String): MediaItemHistory? {
|
||||||
return Paper.book("mediaItemHistory").read(id)
|
return Paper.book("mediaItemHistory").read(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun savePlaybackSession(playbackSession: PlaybackSession) {
|
||||||
|
Paper.book("playbackSession").write(playbackSession.id,playbackSession)
|
||||||
|
}
|
||||||
|
fun removePlaybackSession(playbackSessionId:String) {
|
||||||
|
Paper.book("playbackSession").delete(playbackSessionId)
|
||||||
|
}
|
||||||
|
fun getPlaybackSessions():List<PlaybackSession> {
|
||||||
|
val sessions:MutableList<PlaybackSession> = mutableListOf()
|
||||||
|
Paper.book("playbackSession").allKeys.forEach { playbackSessionId ->
|
||||||
|
Paper.book("playbackSession").read<PlaybackSession>(playbackSessionId)?.let {
|
||||||
|
sessions.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sessions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.util.Log
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||||
import com.audiobookshelf.app.player.SyncResult
|
|
||||||
|
|
||||||
object MediaEventManager {
|
object MediaEventManager {
|
||||||
const val tag = "MediaEventManager"
|
const val tag = "MediaEventManager"
|
||||||
|
@ -103,8 +102,7 @@ object MediaEventManager {
|
||||||
libraryItemId,
|
libraryItemId,
|
||||||
episodeId,
|
episodeId,
|
||||||
isLocalOnly,
|
isLocalOnly,
|
||||||
playbackSession.
|
playbackSession.serverConnectionConfigId,
|
||||||
serverConnectionConfigId,
|
|
||||||
playbackSession.serverAddress,
|
playbackSession.serverAddress,
|
||||||
playbackSession.userId,
|
playbackSession.userId,
|
||||||
createdAt = System.currentTimeMillis(),
|
createdAt = System.currentTimeMillis(),
|
||||||
|
|
|
@ -66,7 +66,7 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
||||||
// and reset any server data already set
|
// and reset any server data already set
|
||||||
val serverConnConfig = if (DeviceManager.isConnectedToServer) DeviceManager.serverConnectionConfig else DeviceManager.deviceData.getLastServerConnectionConfig()
|
val serverConnConfig = if (DeviceManager.isConnectedToServer) DeviceManager.serverConnectionConfig else DeviceManager.deviceData.getLastServerConnectionConfig()
|
||||||
|
|
||||||
if (!DeviceManager.isConnectedToServer || !apiHandler.isOnline() || serverConnConfig == null || serverConnConfig.id !== serverConfigIdUsed) {
|
if (!DeviceManager.isConnectedToServer || !DeviceManager.checkConnectivity(ctx) || serverConnConfig == null || serverConnConfig.id !== serverConfigIdUsed) {
|
||||||
podcastEpisodeLibraryItemMap = mutableMapOf()
|
podcastEpisodeLibraryItemMap = mutableMapOf()
|
||||||
serverLibraryCategories = listOf()
|
serverLibraryCategories = listOf()
|
||||||
serverLibraries = listOf()
|
serverLibraries = listOf()
|
||||||
|
@ -217,7 +217,7 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
||||||
Log.d(tag, "checkSetValidServerConnectionConfig | $serverConfigIdUsed")
|
Log.d(tag, "checkSetValidServerConnectionConfig | $serverConfigIdUsed")
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
if (!apiHandler.isOnline()) {
|
if (!DeviceManager.checkConnectivity(ctx)) {
|
||||||
serverUserMediaProgress = mutableListOf()
|
serverUserMediaProgress = mutableListOf()
|
||||||
cb(false)
|
cb(false)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.audiobookshelf.app.player
|
package com.audiobookshelf.app.media
|
||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
@ -6,9 +6,8 @@ import android.util.Log
|
||||||
import com.audiobookshelf.app.data.LocalMediaProgress
|
import com.audiobookshelf.app.data.LocalMediaProgress
|
||||||
import com.audiobookshelf.app.data.MediaProgress
|
import com.audiobookshelf.app.data.MediaProgress
|
||||||
import com.audiobookshelf.app.data.PlaybackSession
|
import com.audiobookshelf.app.data.PlaybackSession
|
||||||
import com.audiobookshelf.app.data.Podcast
|
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.media.MediaEventManager
|
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
@ -199,8 +198,6 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||||
it.currentTime = mediaProgress.currentTime
|
it.currentTime = mediaProgress.currentTime
|
||||||
|
|
||||||
MediaEventManager.syncEvent(mediaProgress, "Received from server get media progress request while playback session open")
|
MediaEventManager.syncEvent(mediaProgress, "Received from server get media progress request while playback session open")
|
||||||
|
|
||||||
DeviceManager.dbManager.saveLocalPlaybackSession(it)
|
|
||||||
saveLocalProgress(it)
|
saveLocalProgress(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,16 +223,25 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||||
return cb(null)
|
return cb(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasNetworkConnection = DeviceManager.checkConnectivity(playerNotificationService)
|
||||||
|
|
||||||
|
// Save playback session to db (server linked sessions only)
|
||||||
|
// Sessions are removed once successfully synced with the server
|
||||||
|
currentPlaybackSession?.let {
|
||||||
|
if (!it.isLocalLibraryItemOnly) {
|
||||||
|
DeviceManager.dbManager.savePlaybackSession(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (currentIsLocal) {
|
if (currentIsLocal) {
|
||||||
// Save local progress sync
|
// Save local progress sync
|
||||||
currentPlaybackSession?.let {
|
currentPlaybackSession?.let {
|
||||||
DeviceManager.dbManager.saveLocalPlaybackSession(it)
|
|
||||||
saveLocalProgress(it)
|
saveLocalProgress(it)
|
||||||
lastSyncTime = System.currentTimeMillis()
|
lastSyncTime = System.currentTimeMillis()
|
||||||
|
|
||||||
// Local library item is linked to a server library item
|
// Local library item is linked to a server library item
|
||||||
// Send sync to server also if connected to this server and local item belongs to this server
|
// Send sync to server also if connected to this server and local item belongs to this server
|
||||||
if (shouldSyncServer && !it.libraryItemId.isNullOrEmpty() && it.serverConnectionConfigId != null && DeviceManager.serverConnectionConfig?.id == it.serverConnectionConfigId) {
|
if (hasNetworkConnection && shouldSyncServer && !it.libraryItemId.isNullOrEmpty() && it.serverConnectionConfigId != null && DeviceManager.serverConnectionConfig?.id == it.serverConnectionConfigId) {
|
||||||
apiHandler.sendLocalProgressSync(it) { syncSuccess, errorMsg ->
|
apiHandler.sendLocalProgressSync(it) { syncSuccess, errorMsg ->
|
||||||
Log.d(
|
Log.d(
|
||||||
tag,
|
tag,
|
||||||
|
@ -245,6 +251,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||||
if (syncSuccess) {
|
if (syncSuccess) {
|
||||||
failedSyncs = 0
|
failedSyncs = 0
|
||||||
playerNotificationService.alertSyncSuccess()
|
playerNotificationService.alertSyncSuccess()
|
||||||
|
DeviceManager.dbManager.removePlaybackSession(it.id) // Remove session from db
|
||||||
} else {
|
} else {
|
||||||
failedSyncs++
|
failedSyncs++
|
||||||
if (failedSyncs == 2) {
|
if (failedSyncs == 2) {
|
||||||
|
@ -260,7 +267,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||||
cb(SyncResult(false, null, null))
|
cb(SyncResult(false, null, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (shouldSyncServer) {
|
} else if (hasNetworkConnection && shouldSyncServer) {
|
||||||
Log.d(tag, "sync: currentSessionId=$currentSessionId")
|
Log.d(tag, "sync: currentSessionId=$currentSessionId")
|
||||||
apiHandler.sendProgressSync(currentSessionId, syncData) { syncSuccess, errorMsg ->
|
apiHandler.sendProgressSync(currentSessionId, syncData) { syncSuccess, errorMsg ->
|
||||||
if (syncSuccess) {
|
if (syncSuccess) {
|
||||||
|
@ -268,6 +275,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||||
failedSyncs = 0
|
failedSyncs = 0
|
||||||
playerNotificationService.alertSyncSuccess()
|
playerNotificationService.alertSyncSuccess()
|
||||||
lastSyncTime = System.currentTimeMillis()
|
lastSyncTime = System.currentTimeMillis()
|
||||||
|
DeviceManager.dbManager.removePlaybackSession(currentSessionId) // Remove session from db
|
||||||
} else {
|
} else {
|
||||||
failedSyncs++
|
failedSyncs++
|
||||||
if (failedSyncs == 2) {
|
if (failedSyncs == 2) {
|
||||||
|
@ -307,6 +315,7 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
currentPlaybackSession = null
|
currentPlaybackSession = null
|
||||||
currentLocalMediaProgress = null
|
currentLocalMediaProgress = null
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.audiobookshelf.app.models
|
||||||
|
|
||||||
|
import com.audiobookshelf.app.data.MediaProgress
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class User(
|
||||||
|
val id:String,
|
||||||
|
val username: String,
|
||||||
|
val mediaProgress:List<MediaProgress>
|
||||||
|
)
|
|
@ -34,6 +34,7 @@ import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.managers.DbManager
|
import com.audiobookshelf.app.managers.DbManager
|
||||||
import com.audiobookshelf.app.managers.SleepTimerManager
|
import com.audiobookshelf.app.managers.SleepTimerManager
|
||||||
import com.audiobookshelf.app.media.MediaManager
|
import com.audiobookshelf.app.media.MediaManager
|
||||||
|
import com.audiobookshelf.app.media.MediaProgressSyncer
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
import com.google.android.exoplayer2.*
|
import com.google.android.exoplayer2.*
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
|
@ -59,6 +60,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
var isStarted = false
|
var isStarted = false
|
||||||
var isClosed = false
|
var isClosed = false
|
||||||
var isUnmeteredNetwork = false
|
var isUnmeteredNetwork = false
|
||||||
|
var hasNetworkConnectivity = false // Not 100% reliable has internet
|
||||||
var isSwitchingPlayer = false // Used when switching between cast player and exoplayer
|
var isSwitchingPlayer = false // Used when switching between cast player and exoplayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,8 +195,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
// To listen for network change from metered to unmetered
|
// To listen for network change from metered to unmetered
|
||||||
val networkRequest = NetworkRequest.Builder()
|
val networkRequest = NetworkRequest.Builder()
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||||
.build()
|
.build()
|
||||||
val connectivityManager = getSystemService(ConnectivityManager::class.java) as ConnectivityManager
|
val connectivityManager = getSystemService(ConnectivityManager::class.java) as ConnectivityManager
|
||||||
connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
|
connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
|
||||||
|
@ -668,7 +670,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
if (currentPlaybackSession == null) return true
|
if (currentPlaybackSession == null) return true
|
||||||
|
|
||||||
mediaProgressSyncer.currentPlaybackSession?.let { playbackSession ->
|
mediaProgressSyncer.currentPlaybackSession?.let { playbackSession ->
|
||||||
if (!apiHandler.isOnline() || playbackSession.isLocalLibraryItemOnly) {
|
if (!DeviceManager.checkConnectivity(ctx) || playbackSession.isLocalLibraryItemOnly) {
|
||||||
return true // carry on
|
return true // carry on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1098,10 +1100,11 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
networkCapabilities: NetworkCapabilities
|
networkCapabilities: NetworkCapabilities
|
||||||
) {
|
) {
|
||||||
super.onCapabilitiesChanged(network, networkCapabilities)
|
super.onCapabilitiesChanged(network, networkCapabilities)
|
||||||
val unmetered = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
|
||||||
Log.i(tag, "Network capabilities changed is unmetered = $unmetered")
|
isUnmeteredNetwork = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||||
isUnmeteredNetwork = unmetered
|
hasNetworkConnectivity = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
clientEventEmitter?.onNetworkMeteredChanged(unmetered)
|
Log.i(tag, "Network capabilities changed. hasNetworkConnectivity=$hasNetworkConnectivity | isUnmeteredNetwork=$isUnmeteredNetwork")
|
||||||
|
clientEventEmitter?.onNetworkMeteredChanged(isUnmeteredNetwork)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,13 +216,25 @@ class AbsDatabase : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun syncLocalMediaProgressWithServer(call:PluginCall) {
|
fun syncLocalSessionsWithServer(call:PluginCall) {
|
||||||
if (DeviceManager.serverConnectionConfig == null) {
|
if (DeviceManager.serverConnectionConfig == null) {
|
||||||
Log.e(tag, "syncLocalMediaProgressWithServer not connected to server")
|
Log.e(tag, "syncLocalSessionsWithServer not connected to server")
|
||||||
return call.resolve()
|
return call.resolve()
|
||||||
}
|
}
|
||||||
apiHandler.syncMediaProgress {
|
|
||||||
call.resolve(JSObject(jacksonMapper.writeValueAsString(it)))
|
apiHandler.syncLocalMediaProgressForUser {
|
||||||
|
Log.d(tag, "Finished syncing local media progress for user")
|
||||||
|
val savedSessions = DeviceManager.dbManager.getPlaybackSessions().filter { it.serverConnectionConfigId == DeviceManager.serverConnectionConfigId }
|
||||||
|
|
||||||
|
if (savedSessions.isNotEmpty()) {
|
||||||
|
apiHandler.sendSyncLocalSessions(savedSessions) { success, errorMsg ->
|
||||||
|
if (!success) {
|
||||||
|
call.resolve(JSObject("{\"error\":\"$errorMsg\"}"))
|
||||||
|
} else {
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package com.audiobookshelf.app.server
|
package com.audiobookshelf.app.server
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.media.MediaEventManager
|
import com.audiobookshelf.app.media.MediaEventManager
|
||||||
import com.audiobookshelf.app.player.MediaProgressSyncData
|
import com.audiobookshelf.app.media.MediaProgressSyncData
|
||||||
|
import com.audiobookshelf.app.media.SyncResult
|
||||||
|
import com.audiobookshelf.app.models.User
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
@ -28,14 +28,14 @@ class ApiHandler(var ctx:Context) {
|
||||||
|
|
||||||
private var defaultClient = OkHttpClient()
|
private var defaultClient = OkHttpClient()
|
||||||
private var pingClient = OkHttpClient.Builder().callTimeout(3, TimeUnit.SECONDS).build()
|
private var pingClient = OkHttpClient.Builder().callTimeout(3, TimeUnit.SECONDS).build()
|
||||||
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
data class LocalMediaProgressSyncPayload(val localMediaProgress:List<LocalMediaProgress>)
|
data class LocalSessionsSyncRequestPayload(val sessions:List<PlaybackSession>)
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class MediaProgressSyncResponsePayload(val numServerProgressUpdates:Int, val localProgressUpdates:List<LocalMediaProgress>, val serverProgressUpdates:List<MediaProgress>)
|
data class LocalSessionSyncResult(val id:String, val success:Boolean, val progressSynced:Boolean?, val error:String?)
|
||||||
data class LocalMediaProgressSyncResultsPayload(var numLocalMediaProgressForServer:Int, var numServerProgressUpdates:Int, var numLocalProgressUpdates:Int, var serverProgressUpdates:List<MediaProgress>)
|
data class LocalSessionsSyncResponsePayload(val results:List<LocalSessionSyncResult>)
|
||||||
|
|
||||||
fun getRequest(endpoint:String, httpClient:OkHttpClient?, config:ServerConnectionConfig?, cb: (JSObject) -> Unit) {
|
private fun getRequest(endpoint:String, httpClient:OkHttpClient?, config:ServerConnectionConfig?, cb: (JSObject) -> Unit) {
|
||||||
val address = config?.address ?: DeviceManager.serverAddress
|
val address = config?.address ?: DeviceManager.serverAddress
|
||||||
val token = config?.token ?: DeviceManager.token
|
val token = config?.token ?: DeviceManager.token
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
makeRequest(request, null, cb)
|
makeRequest(request, null, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
private fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
||||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
val requestBody = payload.toString().toRequestBody(mediaType)
|
val requestBody = payload.toString().toRequestBody(mediaType)
|
||||||
val request = Request.Builder().patch(requestBody)
|
val request = Request.Builder().patch(requestBody)
|
||||||
|
@ -67,31 +67,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
makeRequest(request, null, cb)
|
makeRequest(request, null, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isOnline(): Boolean {
|
private fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) {
|
||||||
val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
||||||
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
|
||||||
if (capabilities != null) {
|
|
||||||
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
|
||||||
Log.i("Internet", "NetworkCapabilities.TRANSPORT_CELLULAR")
|
|
||||||
return true
|
|
||||||
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
|
||||||
Log.i("Internet", "NetworkCapabilities.TRANSPORT_WIFI")
|
|
||||||
return true
|
|
||||||
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
|
|
||||||
Log.i("Internet", "NetworkCapabilities.TRANSPORT_ETHERNET")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isUsingCellularData(): Boolean {
|
|
||||||
val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
||||||
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
|
||||||
return capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun makeRequest(request:Request, httpClient:OkHttpClient?, cb: (JSObject) -> Unit) {
|
|
||||||
val client = httpClient ?: defaultClient
|
val client = httpClient ?: defaultClient
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).enqueue(object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
@ -137,6 +113,18 @@ class ApiHandler(var ctx:Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCurrentUser(cb: (User?) -> Unit) {
|
||||||
|
getRequest("/api/me", null, null) {
|
||||||
|
if (it.has("error")) {
|
||||||
|
Log.e(tag, it.getString("error") ?: "getCurrentUser Failed")
|
||||||
|
cb(null)
|
||||||
|
} else {
|
||||||
|
val user = jacksonMapper.readValue<User>(it.toString())
|
||||||
|
cb(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getLibraries(cb: (List<Library>) -> Unit) {
|
fun getLibraries(cb: (List<Library>) -> Unit) {
|
||||||
val mapper = jacksonMapper
|
val mapper = jacksonMapper
|
||||||
getRequest("/api/libraries", null,null) {
|
getRequest("/api/libraries", null,null) {
|
||||||
|
@ -253,59 +241,6 @@ class ApiHandler(var ctx:Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun syncMediaProgress(cb: (LocalMediaProgressSyncResultsPayload) -> Unit) {
|
|
||||||
if (!isOnline()) {
|
|
||||||
Log.d(tag, "Error not online")
|
|
||||||
cb(LocalMediaProgressSyncResultsPayload(0,0,0, mutableListOf()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all local media progress connected to items on the current connected server
|
|
||||||
val localMediaProgress = DeviceManager.dbManager.getAllLocalMediaProgress().filter {
|
|
||||||
it.serverConnectionConfigId == DeviceManager.serverConnectionConfig?.id
|
|
||||||
}
|
|
||||||
|
|
||||||
val localSyncResultsPayload = LocalMediaProgressSyncResultsPayload(localMediaProgress.size,0, 0, mutableListOf())
|
|
||||||
|
|
||||||
if (localMediaProgress.isNotEmpty()) {
|
|
||||||
Log.d(tag, "Sending sync local progress request with ${localMediaProgress.size} progress items")
|
|
||||||
val payload = JSObject(jacksonMapper.writeValueAsString(LocalMediaProgressSyncPayload(localMediaProgress)))
|
|
||||||
postRequest("/api/me/sync-local-progress", payload, null) {
|
|
||||||
Log.d(tag, "Media Progress Sync payload $payload - response ${it}")
|
|
||||||
|
|
||||||
if (it.toString() == "{}") {
|
|
||||||
Log.e(tag, "Progress sync received empty object")
|
|
||||||
} else if (it.has("error")) {
|
|
||||||
Log.e(tag, it.getString("error") ?: "Progress sync error")
|
|
||||||
} else {
|
|
||||||
val progressSyncResponsePayload = jacksonMapper.readValue<MediaProgressSyncResponsePayload>(it.toString())
|
|
||||||
|
|
||||||
localSyncResultsPayload.numLocalProgressUpdates = progressSyncResponsePayload.localProgressUpdates.size
|
|
||||||
localSyncResultsPayload.serverProgressUpdates = progressSyncResponsePayload.serverProgressUpdates
|
|
||||||
localSyncResultsPayload.numServerProgressUpdates = progressSyncResponsePayload.numServerProgressUpdates
|
|
||||||
Log.d(tag, "Media Progress Sync | Local Updates: $localSyncResultsPayload")
|
|
||||||
if (progressSyncResponsePayload.localProgressUpdates.isNotEmpty()) {
|
|
||||||
// Update all local media progress
|
|
||||||
progressSyncResponsePayload.localProgressUpdates.forEach { localMediaProgress ->
|
|
||||||
MediaEventManager.syncEvent(localMediaProgress, "Local progress updated. Received from server sync local API request")
|
|
||||||
|
|
||||||
DeviceManager.dbManager.saveLocalMediaProgress(localMediaProgress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressSyncResponsePayload.serverProgressUpdates.forEach { localMediaProgress ->
|
|
||||||
MediaEventManager.syncEvent(localMediaProgress, "Server progress updated. Received from server sync local API request")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(localSyncResultsPayload)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.d(tag, "No local media progress to sync")
|
|
||||||
cb(localSyncResultsPayload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateMediaProgress(libraryItemId:String,episodeId:String?,updatePayload:JSObject, cb: () -> Unit) {
|
fun updateMediaProgress(libraryItemId:String,episodeId:String?,updatePayload:JSObject, cb: () -> Unit) {
|
||||||
Log.d(tag, "updateMediaProgress $libraryItemId $episodeId $updatePayload")
|
Log.d(tag, "updateMediaProgress $libraryItemId $episodeId $updatePayload")
|
||||||
val endpoint = if(episodeId.isNullOrEmpty()) "/api/me/progress/$libraryItemId" else "/api/me/progress/$libraryItemId/$episodeId"
|
val endpoint = if(episodeId.isNullOrEmpty()) "/api/me/progress/$libraryItemId" else "/api/me/progress/$libraryItemId/$episodeId"
|
||||||
|
@ -377,4 +312,55 @@ class ApiHandler(var ctx:Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sendSyncLocalSessions(playbackSessions:List<PlaybackSession>, cb: (Boolean, String?) -> Unit) {
|
||||||
|
val payload = JSObject(jacksonMapper.writeValueAsString(LocalSessionsSyncRequestPayload(playbackSessions)))
|
||||||
|
|
||||||
|
postRequest("/api/session/local-all", payload, null) {
|
||||||
|
if (!it.getString("error").isNullOrEmpty()) {
|
||||||
|
cb(false, it.getString("error"))
|
||||||
|
} else {
|
||||||
|
val response = jacksonMapper.readValue<LocalSessionsSyncResponsePayload>(it.toString())
|
||||||
|
response.results.forEach { localSessionSyncResult ->
|
||||||
|
playbackSessions.find { ps -> ps.id == localSessionSyncResult.id }?.let { session ->
|
||||||
|
if (localSessionSyncResult.progressSynced == true) {
|
||||||
|
val syncResult = SyncResult(true, true, "Progress synced on server")
|
||||||
|
MediaEventManager.saveEvent(session, syncResult)
|
||||||
|
DeviceManager.dbManager.removePlaybackSession(session.id)
|
||||||
|
Log.i(tag, "Successfully synced session ${session.displayTitle} with server")
|
||||||
|
} else if (!localSessionSyncResult.success) {
|
||||||
|
Log.e(tag, "Failed to sync session ${session.displayTitle} with server. Error: ${localSessionSyncResult.error}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(true, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun syncLocalMediaProgressForUser(cb: () -> Unit) {
|
||||||
|
// Get all local media progress for this server
|
||||||
|
val allLocalMediaProgress = DeviceManager.dbManager.getAllLocalMediaProgress().filter { it.serverConnectionConfigId == DeviceManager.serverConnectionConfigId }
|
||||||
|
if (allLocalMediaProgress.isEmpty()) {
|
||||||
|
Log.d(tag, "No local media progress to sync")
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUser { _user ->
|
||||||
|
_user?.let { user->
|
||||||
|
// Compare server user progress with local progress
|
||||||
|
user.mediaProgress.forEach { mediaProgress ->
|
||||||
|
// Get matching local media progress
|
||||||
|
allLocalMediaProgress.find { it.isMatch(mediaProgress) }?.let { localMediaProgress ->
|
||||||
|
if (mediaProgress.lastUpdate > localMediaProgress.lastUpdate) {
|
||||||
|
Log.d(tag, "Server progress for media item id=\"${mediaProgress.mediaItemId}\" is more recent then local. Updating local current time ${localMediaProgress.currentTime} to ${mediaProgress.currentTime}")
|
||||||
|
localMediaProgress.updateFromServerMediaProgress(mediaProgress)
|
||||||
|
MediaEventManager.syncEvent(mediaProgress, "Sync on server connection")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,10 +169,23 @@ export default {
|
||||||
this.$eventBus.$emit('library-changed')
|
this.$eventBus.$emit('library-changed')
|
||||||
this.inittingLibraries = false
|
this.inittingLibraries = false
|
||||||
},
|
},
|
||||||
|
async syncLocalSessions() {
|
||||||
|
if (!this.user) {
|
||||||
|
console.log('[default] No need to sync local sessions - not connected to server')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[default] Calling syncLocalSessions')
|
||||||
|
const response = await this.$db.syncLocalSessionsWithServer()
|
||||||
|
if (response && response.error) {
|
||||||
|
console.error('[default] Failed to sync local sessions', response.error)
|
||||||
|
} else {
|
||||||
|
console.log('[default] Successfully synced local sessions')
|
||||||
|
}
|
||||||
|
},
|
||||||
async syncLocalMediaProgress() {
|
async syncLocalMediaProgress() {
|
||||||
if (!this.user) {
|
if (!this.user) {
|
||||||
console.log('[default] No need to sync local media progress - not connected to server')
|
console.log('[default] No need to sync local media progress - not connected to server')
|
||||||
this.$store.commit('setLastLocalMediaSyncResults', null)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,15 +193,10 @@ export default {
|
||||||
const response = await this.$db.syncLocalMediaProgressWithServer()
|
const response = await this.$db.syncLocalMediaProgressWithServer()
|
||||||
if (!response) {
|
if (!response) {
|
||||||
if (this.$platform != 'web') this.$toast.error('Failed to sync local media with server')
|
if (this.$platform != 'web') this.$toast.error('Failed to sync local media with server')
|
||||||
this.$store.commit('setLastLocalMediaSyncResults', null)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { numLocalMediaProgressForServer, numServerProgressUpdates, numLocalProgressUpdates, serverProgressUpdates } = response
|
const { numLocalMediaProgressForServer, numServerProgressUpdates, numLocalProgressUpdates, serverProgressUpdates } = response
|
||||||
if (numLocalMediaProgressForServer > 0) {
|
if (numLocalMediaProgressForServer > 0) {
|
||||||
response.syncedAt = Date.now()
|
|
||||||
response.serverConfigName = this.$store.getters['user/getServerConfigName']
|
|
||||||
this.$store.commit('setLastLocalMediaSyncResults', response)
|
|
||||||
|
|
||||||
if (serverProgressUpdates && serverProgressUpdates.length) {
|
if (serverProgressUpdates && serverProgressUpdates.length) {
|
||||||
serverProgressUpdates.forEach((progress) => {
|
serverProgressUpdates.forEach((progress) => {
|
||||||
console.log(`[default] Server progress was updated ${progress.id}`)
|
console.log(`[default] Server progress was updated ${progress.id}`)
|
||||||
|
@ -203,7 +211,6 @@ export default {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('[default] syncLocalMediaProgress No local media progress to sync')
|
console.log('[default] syncLocalMediaProgress No local media progress to sync')
|
||||||
this.$store.commit('setLastLocalMediaSyncResults', null)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userUpdated(user) {
|
userUpdated(user) {
|
||||||
|
@ -295,7 +302,12 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[default] finished connection attempt or already connected ${!!this.user}`)
|
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()
|
await this.syncLocalMediaProgress()
|
||||||
|
} else {
|
||||||
|
await this.syncLocalSessions()
|
||||||
|
}
|
||||||
|
|
||||||
this.loadSavedSettings()
|
this.loadSavedSettings()
|
||||||
this.hasMounted = true
|
this.hasMounted = true
|
||||||
|
|
|
@ -1,33 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full py-6">
|
<div class="w-full h-full py-6">
|
||||||
<div v-if="localLibraryItemsOnCurrentServer.length" class="flex items-center justify-between mb-4 pb-2 px-2 border-b border-white border-opacity-10">
|
|
||||||
<p class="text-sm text-gray-100">{{ localLibraryItemsOnCurrentServer.length }} local items on this server</p>
|
|
||||||
<ui-btn small :loading="syncing" @click="syncLocalMedia">Sync</ui-btn>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="lastLocalMediaSyncResults" class="px-2 mb-4">
|
|
||||||
<div class="w-full pl-2 pr-2 py-2 bg-black bg-opacity-25 rounded-lg relative">
|
|
||||||
<div class="flex items-center mb-1">
|
|
||||||
<span class="material-icons text-success text-xl">sync</span>
|
|
||||||
<p class="text-sm text-gray-300 pl-2">Local media progress synced with server</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between mb-1.5">
|
|
||||||
<p class="text-xs text-gray-400 font-semibold">{{ syncedServerConfigName }}</p>
|
|
||||||
<p class="text-xs text-gray-400 italic">{{ $dateDistanceFromNow(syncedAt) }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!numLocalProgressUpdates && !numServerProgressUpdates">
|
|
||||||
<p class="text-sm text-gray-300">Local media progress was up-to-date with server ({{ numLocalMediaSynced }} item{{ numLocalMediaSynced == 1 ? '' : 's' }})</p>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<p v-if="numServerProgressUpdates" class="text-sm text-gray-300">- {{ numServerProgressUpdates }} local media item{{ numServerProgressUpdates === 1 ? '' : 's' }} progress was updated on the server (local more recent).</p>
|
|
||||||
<p v-else class="text-sm text-gray-300">- No local media progress had to be synced on the server.</p>
|
|
||||||
<p v-if="numLocalProgressUpdates" class="text-sm text-gray-300">- {{ numLocalProgressUpdates }} local media item{{ numLocalProgressUpdates === 1 ? '' : 's' }} progress was updated to match the server (server more recent).</p>
|
|
||||||
<p v-else class="text-sm text-gray-300">- No server progress had to be synced with local media progress.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 class="text-base font-semibold px-2 mb-2">Local Folders</h1>
|
<h1 class="text-base font-semibold px-2 mb-2">Local Folders</h1>
|
||||||
|
|
||||||
<div v-if="!isIos" class="w-full max-w-full px-2 py-2">
|
<div v-if="!isIos" class="w-full max-w-full px-2 py-2">
|
||||||
|
@ -78,67 +50,9 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
isIos() {
|
isIos() {
|
||||||
return this.$platform === 'ios'
|
return this.$platform === 'ios'
|
||||||
},
|
|
||||||
lastLocalMediaSyncResults() {
|
|
||||||
return this.$store.state.lastLocalMediaSyncResults
|
|
||||||
},
|
|
||||||
serverConnectionConfigId() {
|
|
||||||
return this.$store.getters['user/getServerConnectionConfigId']
|
|
||||||
},
|
|
||||||
localLibraryItemsOnCurrentServer() {
|
|
||||||
return this.localLibraryItems.filter((lli) => {
|
|
||||||
return lli.serverConnectionConfigId === this.serverConnectionConfigId
|
|
||||||
})
|
|
||||||
},
|
|
||||||
numLocalMediaSynced() {
|
|
||||||
if (!this.lastLocalMediaSyncResults) return 0
|
|
||||||
return this.lastLocalMediaSyncResults.numLocalMediaProgressForServer || 0
|
|
||||||
},
|
|
||||||
syncedAt() {
|
|
||||||
if (!this.lastLocalMediaSyncResults) return 0
|
|
||||||
return this.lastLocalMediaSyncResults.syncedAt || 0
|
|
||||||
},
|
|
||||||
syncedServerConfigName() {
|
|
||||||
if (!this.lastLocalMediaSyncResults) return ''
|
|
||||||
return this.lastLocalMediaSyncResults.serverConfigName
|
|
||||||
},
|
|
||||||
numLocalProgressUpdates() {
|
|
||||||
if (!this.lastLocalMediaSyncResults) return 0
|
|
||||||
return this.lastLocalMediaSyncResults.numLocalProgressUpdates || 0
|
|
||||||
},
|
|
||||||
numServerProgressUpdates() {
|
|
||||||
if (!this.lastLocalMediaSyncResults) return 0
|
|
||||||
return this.lastLocalMediaSyncResults.numServerProgressUpdates || 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async syncLocalMedia() {
|
|
||||||
console.log('[localMedia] Calling syncLocalMediaProgress')
|
|
||||||
this.syncing = true
|
|
||||||
const response = await this.$db.syncLocalMediaProgressWithServer()
|
|
||||||
if (!response) {
|
|
||||||
if (this.$platform != 'web') this.$toast.error('Failed to sync local media with server')
|
|
||||||
this.$store.commit('setLastLocalMediaSyncResults', null)
|
|
||||||
this.syncing = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { numLocalMediaProgressForServer, numServerProgressUpdates, numLocalProgressUpdates } = response
|
|
||||||
if (numLocalMediaProgressForServer > 0) {
|
|
||||||
response.syncedAt = Date.now()
|
|
||||||
response.serverConfigName = this.$store.getters['user/getServerConfigName']
|
|
||||||
this.$store.commit('setLastLocalMediaSyncResults', response)
|
|
||||||
|
|
||||||
if (numServerProgressUpdates > 0 || numLocalProgressUpdates > 0) {
|
|
||||||
console.log(`[localMedia] ${numServerProgressUpdates} Server progress updates | ${numLocalProgressUpdates} Local progress updates`)
|
|
||||||
} else {
|
|
||||||
console.log('[localMedia] syncLocalMediaProgress No updates were necessary')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('[localMedia] syncLocalMediaProgress No local media progress to sync')
|
|
||||||
this.$store.commit('setLastLocalMediaSyncResults', null)
|
|
||||||
}
|
|
||||||
this.syncing = false
|
|
||||||
},
|
|
||||||
async selectFolder() {
|
async selectFolder() {
|
||||||
if (!this.newFolderMediaType) {
|
if (!this.newFolderMediaType) {
|
||||||
return this.$toast.error('Must select a media type')
|
return this.$toast.error('Must select a media type')
|
||||||
|
|
|
@ -198,6 +198,10 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncLocalSessionsWithServer() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
async syncServerMediaProgressWithLocalMediaProgress(payload) {
|
async syncServerMediaProgressWithLocalMediaProgress(payload) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,10 @@ class DbService {
|
||||||
return AbsDatabase.syncLocalMediaProgressWithServer()
|
return AbsDatabase.syncLocalMediaProgressWithServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncLocalSessionsWithServer() {
|
||||||
|
return AbsDatabase.syncLocalSessionsWithServer()
|
||||||
|
}
|
||||||
|
|
||||||
syncServerMediaProgressWithLocalMediaProgress(payload) {
|
syncServerMediaProgressWithLocalMediaProgress(payload) {
|
||||||
return AbsDatabase.syncServerMediaProgressWithLocalMediaProgress(payload)
|
return AbsDatabase.syncServerMediaProgressWithLocalMediaProgress(payload)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,7 @@ export const state = () => ({
|
||||||
showSideDrawer: false,
|
showSideDrawer: false,
|
||||||
isNetworkListenerInit: false,
|
isNetworkListenerInit: false,
|
||||||
serverSettings: null,
|
serverSettings: null,
|
||||||
lastBookshelfScrollData: {},
|
lastBookshelfScrollData: {}
|
||||||
lastLocalMediaSyncResults: null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
|
@ -147,8 +146,5 @@ export const mutations = {
|
||||||
setServerSettings(state, val) {
|
setServerSettings(state, val) {
|
||||||
state.serverSettings = val
|
state.serverSettings = val
|
||||||
this.$localStore.setServerSettings(state.serverSettings)
|
this.$localStore.setServerSettings(state.serverSettings)
|
||||||
},
|
|
||||||
setLastLocalMediaSyncResults(state, val) {
|
|
||||||
state.lastLocalMediaSyncResults = val
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue