mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-04 18:15:01 +02:00
Merge pull request #1546 from advplyr/abslogger
Logs page with AbsLogger plugin
This commit is contained in:
commit
3bb5ce5924
25 changed files with 426 additions and 208 deletions
|
@ -32,8 +32,8 @@ object DeviceManager {
|
|||
var deviceData: DeviceData = dbManager.getDeviceData()
|
||||
var serverConnectionConfig: ServerConnectionConfig? = null
|
||||
|
||||
val serverConnectionConfigId
|
||||
get() = serverConnectionConfig?.id ?: ""
|
||||
val serverConnectionConfigId get() = serverConnectionConfig?.id ?: ""
|
||||
val serverConnectionConfigName get() = serverConnectionConfig?.name ?: ""
|
||||
val serverAddress
|
||||
get() = serverConnectionConfig?.address ?: ""
|
||||
val serverUserId
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.util.Log
|
|||
import com.audiobookshelf.app.data.*
|
||||
import com.audiobookshelf.app.models.DownloadItem
|
||||
import com.audiobookshelf.app.plugins.AbsLog
|
||||
import com.audiobookshelf.app.plugins.AbsLogger
|
||||
import io.paperdb.Paper
|
||||
import java.io.File
|
||||
|
||||
|
@ -299,6 +300,24 @@ class DbManager {
|
|||
logs.add(it)
|
||||
}
|
||||
}
|
||||
return logs
|
||||
return logs.sortedBy { it.timestamp }
|
||||
}
|
||||
fun removeAllLogs() {
|
||||
Paper.book("log").destroy()
|
||||
}
|
||||
fun cleanLogs() {
|
||||
val numberOfHoursToKeep = 48
|
||||
val keepLogCutoff = System.currentTimeMillis() - (3600000 * numberOfHoursToKeep)
|
||||
val allLogs = getAllLogs()
|
||||
var logsRemoved = 0
|
||||
allLogs.forEach {
|
||||
if (it.timestamp < keepLogCutoff) {
|
||||
Paper.book("log").delete(it.id)
|
||||
logsRemoved++
|
||||
}
|
||||
}
|
||||
if (logsRemoved > 0) {
|
||||
AbsLogger.info("DbManager", "cleanLogs: Removed $logsRemoved logs older than $numberOfHoursToKeep hours")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import com.audiobookshelf.app.data.MediaProgress
|
|||
import com.audiobookshelf.app.data.PlaybackSession
|
||||
import com.audiobookshelf.app.device.DeviceManager
|
||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||
import com.audiobookshelf.app.plugins.AbsLogger
|
||||
import com.audiobookshelf.app.server.ApiHandler
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
|
@ -208,6 +209,7 @@ class MediaProgressSyncer(
|
|||
MediaEventManager.seekEvent(currentPlaybackSession!!, null)
|
||||
}
|
||||
|
||||
// Currently unused
|
||||
fun syncFromServerProgress(mediaProgress: MediaProgress) {
|
||||
currentPlaybackSession?.let {
|
||||
it.updatedAt = mediaProgress.lastUpdate
|
||||
|
@ -260,44 +262,46 @@ class MediaProgressSyncer(
|
|||
tag,
|
||||
"Sync local device current serverConnectionConfigId=${DeviceManager.serverConnectionConfig?.id}"
|
||||
)
|
||||
AbsLogger.info("MediaProgressSyncer", "sync: Saved local progress (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: ${it.id})")
|
||||
|
||||
// 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
|
||||
val isConnectedToSameServer = it.serverConnectionConfigId != null && DeviceManager.serverConnectionConfig?.id == it.serverConnectionConfigId
|
||||
if (hasNetworkConnection &&
|
||||
shouldSyncServer &&
|
||||
!it.libraryItemId.isNullOrEmpty() &&
|
||||
it.serverConnectionConfigId != null &&
|
||||
DeviceManager.serverConnectionConfig?.id == it.serverConnectionConfigId
|
||||
isConnectedToSameServer
|
||||
) {
|
||||
apiHandler.sendLocalProgressSync(it) { syncSuccess, errorMsg ->
|
||||
if (syncSuccess) {
|
||||
failedSyncs = 0
|
||||
playerNotificationService.alertSyncSuccess()
|
||||
DeviceManager.dbManager.removePlaybackSession(it.id) // Remove session from db
|
||||
AbsLogger.info("MediaProgressSyncer", "sync: Successfully synced local progress (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: ${it.id})")
|
||||
} else {
|
||||
failedSyncs++
|
||||
if (failedSyncs == 2) {
|
||||
playerNotificationService.alertSyncFailing() // Show alert in client
|
||||
failedSyncs = 0
|
||||
}
|
||||
Log.e(
|
||||
tag,
|
||||
"Local Progress sync failed ($failedSyncs) to send to server $currentDisplayTitle for time $currentTime with session id=${it.id}"
|
||||
)
|
||||
AbsLogger.error("MediaProgressSyncer", "sync: Local progress sync failed (count: $failedSyncs) (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: ${it.id}) (${DeviceManager.serverConnectionConfigName})")
|
||||
}
|
||||
|
||||
cb(SyncResult(true, syncSuccess, errorMsg))
|
||||
}
|
||||
} else {
|
||||
AbsLogger.info("MediaProgressSyncer", "sync: Not sending local progress to server (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: ${it.id}) (hasNetworkConnection: $hasNetworkConnection) (isConnectedToSameServer: $isConnectedToSameServer)")
|
||||
cb(SyncResult(false, null, null))
|
||||
}
|
||||
}
|
||||
} else if (hasNetworkConnection && shouldSyncServer) {
|
||||
Log.d(tag, "sync: currentSessionId=$currentSessionId")
|
||||
AbsLogger.info("MediaProgressSyncer", "sync: Sending progress sync to server (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: ${currentSessionId}) (${DeviceManager.serverConnectionConfigName})")
|
||||
|
||||
apiHandler.sendProgressSync(currentSessionId, syncData) { syncSuccess, errorMsg ->
|
||||
if (syncSuccess) {
|
||||
Log.d(tag, "Progress sync data sent to server $currentDisplayTitle for time $currentTime")
|
||||
AbsLogger.info("MediaProgressSyncer", "sync: Successfully synced progress (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: ${currentSessionId}) (${DeviceManager.serverConnectionConfigName})")
|
||||
|
||||
failedSyncs = 0
|
||||
playerNotificationService.alertSyncSuccess()
|
||||
lastSyncTime = System.currentTimeMillis()
|
||||
|
@ -308,14 +312,12 @@ class MediaProgressSyncer(
|
|||
playerNotificationService.alertSyncFailing() // Show alert in client
|
||||
failedSyncs = 0
|
||||
}
|
||||
Log.e(
|
||||
tag,
|
||||
"Progress sync failed ($failedSyncs) to send to server $currentDisplayTitle for time $currentTime with session id=${currentSessionId}"
|
||||
)
|
||||
AbsLogger.error("MediaProgressSyncer", "sync: Progress sync failed (count: $failedSyncs) (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: $currentSessionId) (${DeviceManager.serverConnectionConfigName})")
|
||||
}
|
||||
cb(SyncResult(true, syncSuccess, errorMsg))
|
||||
}
|
||||
} else {
|
||||
AbsLogger.info("MediaProgressSyncer", "sync: Not sending progress to server (title: \"$currentDisplayTitle\") (currentTime: $currentTime) (session id: $currentSessionId) (${DeviceManager.serverConnectionConfigName}) (hasNetworkConnection: $hasNetworkConnection)")
|
||||
cb(SyncResult(false, null, null))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import com.audiobookshelf.app.media.MediaManager
|
|||
import com.audiobookshelf.app.media.MediaProgressSyncer
|
||||
import com.audiobookshelf.app.media.getUriToAbsIconDrawable
|
||||
import com.audiobookshelf.app.media.getUriToDrawable
|
||||
import com.audiobookshelf.app.plugins.AbsLogger
|
||||
import com.audiobookshelf.app.server.ApiHandler
|
||||
import com.google.android.exoplayer2.*
|
||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||
|
@ -452,7 +453,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
playbackSession
|
||||
) // Save playback session to use when app is closed
|
||||
|
||||
Log.d(tag, "Set CurrentPlaybackSession MediaPlayer ${currentPlaybackSession?.mediaPlayer}")
|
||||
AbsLogger.info("PlayerNotificationService", "preparePlayer: Started playback session for item ${currentPlaybackSession?.mediaItemId}. MediaPlayer ${currentPlaybackSession?.mediaPlayer}")
|
||||
// Notify client
|
||||
clientEventEmitter?.onPlaybackSession(playbackSession)
|
||||
|
||||
|
@ -469,7 +470,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
val mediaSource: MediaSource
|
||||
|
||||
if (playbackSession.isLocal) {
|
||||
Log.d(tag, "Playing Local Item")
|
||||
AbsLogger.info("PlayerNotificationService", "preparePlayer: Playing local item ${currentPlaybackSession?.mediaItemId}.")
|
||||
val dataSourceFactory = DefaultDataSource.Factory(ctx)
|
||||
|
||||
val extractorsFactory = DefaultExtractorsFactory()
|
||||
|
@ -483,7 +484,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(mediaItems[0])
|
||||
} else if (!playbackSession.isHLS) {
|
||||
Log.d(tag, "Direct Playing Item")
|
||||
AbsLogger.info("PlayerNotificationService", "preparePlayer: Direct playing item ${currentPlaybackSession?.mediaItemId}.")
|
||||
val dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||
|
||||
val extractorsFactory = DefaultExtractorsFactory()
|
||||
|
@ -498,7 +499,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||
ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(mediaItems[0])
|
||||
} else {
|
||||
Log.d(tag, "Playing HLS Item")
|
||||
AbsLogger.info("PlayerNotificationService", "preparePlayer: Playing HLS stream of item ${currentPlaybackSession?.mediaItemId}.")
|
||||
val dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||
dataSourceFactory.setUserAgent(channelId)
|
||||
dataSourceFactory.setDefaultRequestProperties(
|
||||
|
|
|
@ -180,7 +180,8 @@ class AbsAudioPlayer : Plugin() {
|
|||
val playWhenReady = call.getBoolean("playWhenReady") == true
|
||||
val playbackRate = call.getFloat("playbackRate",1f) ?: 1f
|
||||
val startTimeOverride = call.getDouble("startTime")
|
||||
Log.d(tag, "prepareLibraryItem lid=$libraryItemId, startTimeOverride=$startTimeOverride, playbackRate=$playbackRate")
|
||||
|
||||
AbsLogger.info("AbsAudioPlayer", "prepareLibraryItem: lid=$libraryItemId, startTimeOverride=$startTimeOverride, playbackRate=$playbackRate")
|
||||
|
||||
if (libraryItemId.isEmpty()) {
|
||||
Log.e(tag, "Invalid call to play library item no library item id")
|
||||
|
|
|
@ -36,6 +36,7 @@ class AbsDatabase : Plugin() {
|
|||
|
||||
DeviceManager.dbManager.cleanLocalMediaProgress()
|
||||
DeviceManager.dbManager.cleanLocalLibraryItems()
|
||||
DeviceManager.dbManager.cleanLogs()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
|
@ -219,15 +220,12 @@ class AbsDatabase : Plugin() {
|
|||
|
||||
@PluginMethod
|
||||
fun syncLocalSessionsWithServer(call:PluginCall) {
|
||||
AbsLogger.info("[AbsDatabase] syncLocalSessionsWithServer")
|
||||
if (DeviceManager.serverConnectionConfig == null) {
|
||||
Log.e(tag, "syncLocalSessionsWithServer not connected to server")
|
||||
AbsLogger.error("AbsDatabase", "syncLocalSessionsWithServer: not connected to server")
|
||||
return call.resolve()
|
||||
}
|
||||
|
||||
apiHandler.syncLocalMediaProgressForUser {
|
||||
Log.d(tag, "Finished syncing local media progress for user")
|
||||
AbsLogger.info("[AbsDatabase] Finished syncing local media progress for user")
|
||||
val savedSessions = DeviceManager.dbManager.getPlaybackSessions().filter { it.serverConnectionConfigId == DeviceManager.serverConnectionConfigId }
|
||||
|
||||
if (savedSessions.isNotEmpty()) {
|
||||
|
@ -235,6 +233,7 @@ class AbsDatabase : Plugin() {
|
|||
if (!success) {
|
||||
call.resolve(JSObject("{\"error\":\"$errorMsg\"}"))
|
||||
} else {
|
||||
AbsLogger.info("AbsDatabase", "syncLocalSessionsWithServer: Finished sending local playback sessions to server. Removing ${savedSessions.size} saved sessions.")
|
||||
// Remove all local sessions
|
||||
savedSessions.forEach {
|
||||
DeviceManager.dbManager.removePlaybackSession(it.id)
|
||||
|
@ -243,6 +242,7 @@ class AbsDatabase : Plugin() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
AbsLogger.info("AbsDatabase", "syncLocalSessionsWithServer: No saved local playback sessions to send to server.")
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.UUID
|
|||
|
||||
data class AbsLog(
|
||||
var id:String,
|
||||
var tag:String,
|
||||
var level:String,
|
||||
var message:String,
|
||||
var timestamp:Long
|
||||
|
@ -25,31 +26,43 @@ class AbsLogger : Plugin() {
|
|||
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||
|
||||
override fun load() {
|
||||
onLogEmitter = { log:AbsLog ->
|
||||
notifyListeners("onLog", JSObject(jacksonMapper.writeValueAsString(log)))
|
||||
}
|
||||
Log.i("AbsLogger", "Initialize AbsLogger plugin")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun info(message:String) {
|
||||
Log.i("AbsLogger", message)
|
||||
DeviceManager.dbManager.saveLog(AbsLog(id = UUID.randomUUID().toString(), level = "info", message, timestamp = System.currentTimeMillis()))
|
||||
lateinit var onLogEmitter:(log:AbsLog) -> Unit
|
||||
|
||||
fun log(level:String, tag:String, message:String) {
|
||||
val absLog = AbsLog(id = UUID.randomUUID().toString(), tag, level, message, timestamp = System.currentTimeMillis())
|
||||
DeviceManager.dbManager.saveLog(absLog)
|
||||
onLogEmitter(absLog)
|
||||
}
|
||||
fun error(message:String) {
|
||||
fun info(tag:String, message:String) {
|
||||
Log.i("AbsLogger", message)
|
||||
log("info", tag, message)
|
||||
}
|
||||
fun error(tag:String, message:String) {
|
||||
Log.e("AbsLogger", message)
|
||||
DeviceManager.dbManager.saveLog(AbsLog(id = UUID.randomUUID().toString(), level = "error", message, timestamp = System.currentTimeMillis()))
|
||||
log("error", tag, message)
|
||||
}
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun info(call: PluginCall) {
|
||||
val msg = call.getString("message") ?: return call.reject("No message")
|
||||
info(msg)
|
||||
val tag = call.getString("tag") ?: ""
|
||||
info(tag, msg)
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun error(call: PluginCall) {
|
||||
val msg = call.getString("message") ?: return call.reject("No message")
|
||||
error(msg)
|
||||
val tag = call.getString("tag") ?: ""
|
||||
error(tag, msg)
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
|
@ -58,4 +71,10 @@ class AbsLogger : Plugin() {
|
|||
val absLogs = DeviceManager.dbManager.getAllLogs()
|
||||
call.resolve(JSObject(jacksonMapper.writeValueAsString(AbsLogList(absLogs))))
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun clearLogs(call: PluginCall) {
|
||||
DeviceManager.dbManager.removeAllLogs()
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import com.audiobookshelf.app.media.MediaProgressSyncData
|
|||
import com.audiobookshelf.app.media.SyncResult
|
||||
import com.audiobookshelf.app.models.User
|
||||
import com.audiobookshelf.app.BuildConfig
|
||||
import com.audiobookshelf.app.plugins.AbsLogger
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
|
@ -468,22 +469,27 @@ class ApiHandler(var ctx:Context) {
|
|||
val deviceInfo = DeviceInfo(deviceId, Build.MANUFACTURER, Build.MODEL, Build.VERSION.SDK_INT, BuildConfig.VERSION_NAME)
|
||||
|
||||
val payload = JSObject(jacksonMapper.writeValueAsString(LocalSessionsSyncRequestPayload(playbackSessions, deviceInfo)))
|
||||
Log.d(tag, "Sending ${playbackSessions.size} saved local playback sessions to server")
|
||||
AbsLogger.info("ApiHandler", "sendSyncLocalSessions: Sending ${playbackSessions.size} saved local playback sessions to server (${DeviceManager.serverConnectionConfigName})")
|
||||
|
||||
postRequest("/api/session/local-all", payload, null) {
|
||||
if (!it.getString("error").isNullOrEmpty()) {
|
||||
Log.e(tag, "Failed to sync local sessions")
|
||||
AbsLogger.error("ApiHandler", "sendSyncLocalSessions: Failed to sync local sessions. (${it.getString("error")})")
|
||||
cb(false, it.getString("error"))
|
||||
} else {
|
||||
val response = jacksonMapper.readValue<LocalSessionsSyncResponsePayload>(it.toString())
|
||||
response.results.forEach { localSessionSyncResult ->
|
||||
Log.d(tag, "Synced session result ${localSessionSyncResult.id}|${localSessionSyncResult.progressSynced}|${localSessionSyncResult.success}")
|
||||
|
||||
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)
|
||||
Log.i(tag, "Successfully synced session ${session.displayTitle} with server")
|
||||
|
||||
AbsLogger.info("ApiHandler", "sendSyncLocalSessions: Synced session \"${session.displayTitle}\" with server, server progress was updated for item ${session.mediaItemId}")
|
||||
} else if (!localSessionSyncResult.success) {
|
||||
Log.e(tag, "Failed to sync session ${session.displayTitle} with server. Error: ${localSessionSyncResult.error}")
|
||||
AbsLogger.error("ApiHandler", "sendSyncLocalSessions: Failed to sync session \"${session.displayTitle}\" with server. Error: ${localSessionSyncResult.error}")
|
||||
} else {
|
||||
AbsLogger.info("ApiHandler", "sendSyncLocalSessions: Synced session \"${session.displayTitle}\" with server. Server progress was up-to-date for item ${session.mediaItemId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -493,37 +499,72 @@ class ApiHandler(var ctx:Context) {
|
|||
}
|
||||
|
||||
fun syncLocalMediaProgressForUser(cb: () -> Unit) {
|
||||
AbsLogger.info("ApiHandler", "[ApiHandler] syncLocalMediaProgressForUser: Server connection ${DeviceManager.serverConnectionConfigName}")
|
||||
|
||||
// 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")
|
||||
AbsLogger.info("ApiHandler", "[ApiHandler] syncLocalMediaProgressForUser: No local media progress to sync")
|
||||
return cb()
|
||||
}
|
||||
|
||||
getCurrentUser { _user ->
|
||||
_user?.let { user->
|
||||
AbsLogger.info("ApiHandler", "syncLocalMediaProgressForUser: Found ${allLocalMediaProgress.size} local media progress")
|
||||
|
||||
getCurrentUser { user ->
|
||||
if (user == null) {
|
||||
AbsLogger.error("ApiHandler", "syncLocalMediaProgressForUser: Failed to load user from server (${DeviceManager.serverConnectionConfigName})")
|
||||
} else {
|
||||
var numLocalMediaProgressUptToDate = 0
|
||||
var numLocalMediaProgressUpdated = 0
|
||||
|
||||
// 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}")
|
||||
val updateLogs = mutableListOf<String>()
|
||||
if (mediaProgress.progress != localMediaProgress.progress) {
|
||||
updateLogs.add("Updated progress from ${localMediaProgress.progress} to ${mediaProgress.progress}")
|
||||
}
|
||||
if (mediaProgress.currentTime != localMediaProgress.currentTime) {
|
||||
updateLogs.add("Updated currentTime from ${localMediaProgress.currentTime} to ${mediaProgress.currentTime}")
|
||||
}
|
||||
if (mediaProgress.isFinished != localMediaProgress.isFinished) {
|
||||
updateLogs.add("Updated isFinished from ${localMediaProgress.isFinished} to ${mediaProgress.isFinished}")
|
||||
}
|
||||
if (mediaProgress.ebookProgress != localMediaProgress.ebookProgress) {
|
||||
updateLogs.add("Updated ebookProgress from ${localMediaProgress.isFinished} to ${mediaProgress.isFinished}")
|
||||
}
|
||||
if (updateLogs.isNotEmpty()) {
|
||||
AbsLogger.info("ApiHandler", "syncLocalMediaProgressForUser: Server progress for item \"${mediaProgress.mediaItemId}\" is more recent than local (server lastUpdate=${mediaProgress.lastUpdate}, local lastUpdate=${localMediaProgress.lastUpdate}). ${updateLogs.joinToString()}")
|
||||
}
|
||||
|
||||
localMediaProgress.updateFromServerMediaProgress(mediaProgress)
|
||||
MediaEventManager.syncEvent(mediaProgress, "Sync on server connection")
|
||||
|
||||
// Only report sync if progress changed
|
||||
if (updateLogs.isNotEmpty()) {
|
||||
MediaEventManager.syncEvent(mediaProgress, "Sync on server connection")
|
||||
}
|
||||
DeviceManager.dbManager.saveLocalMediaProgress(localMediaProgress)
|
||||
numLocalMediaProgressUpdated++
|
||||
} else if (localMediaProgress.lastUpdate > mediaProgress.lastUpdate && localMediaProgress.ebookLocation != null && localMediaProgress.ebookLocation != mediaProgress.ebookLocation) {
|
||||
// Patch ebook progress to server
|
||||
AbsLogger.info("ApiHandler", "syncLocalMediaProgressForUser: Local progress for ebook item \"${mediaProgress.mediaItemId}\" is more recent than server progress. Local progress last updated ${localMediaProgress.lastUpdate}, server progress last updated ${mediaProgress.lastUpdate}. Sending server request to update ebook progress from ${mediaProgress.ebookProgress} to ${localMediaProgress.ebookProgress}")
|
||||
val endpoint = "/api/me/progress/${localMediaProgress.libraryItemId}"
|
||||
val updatePayload = JSObject()
|
||||
updatePayload.put("ebookLocation", localMediaProgress.ebookLocation)
|
||||
updatePayload.put("ebookProgress", localMediaProgress.ebookProgress)
|
||||
updatePayload.put("lastUpdate", localMediaProgress.lastUpdate)
|
||||
patchRequest(endpoint,updatePayload) {
|
||||
Log.d(tag, "syncLocalMediaProgressForUser patched ebook progress")
|
||||
AbsLogger.info("ApiHandler", "syncLocalMediaProgressForUser: Successfully updated server ebook progress for item item \"${mediaProgress.mediaItemId}\"")
|
||||
}
|
||||
} else {
|
||||
numLocalMediaProgressUptToDate++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbsLogger.info("ApiHandler", "syncLocalMediaProgressForUser: Finishing syncing local media progress with server. $numLocalMediaProgressUptToDate up-to-date, $numLocalMediaProgressUpdated updated")
|
||||
}
|
||||
cb()
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { AbsAudioPlayer } from '@/plugins/capacitor'
|
||||
import { AbsAudioPlayer, AbsLogger } from '@/plugins/capacitor'
|
||||
import { Dialog } from '@capacitor/dialog'
|
||||
import CellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
|
||||
|
||||
|
@ -190,6 +190,7 @@ export default {
|
|||
})
|
||||
},
|
||||
async playLibraryItem(payload) {
|
||||
await AbsLogger.info({ tag: 'AudioPlayerContainer', message: `playLibraryItem: Received play request for library item ${payload.libraryItemId} ${payload.episodeId ? `episode ${payload.episodeId}` : ''}` })
|
||||
const libraryItemId = payload.libraryItemId
|
||||
const episodeId = payload.episodeId
|
||||
const startTime = payload.startTime
|
||||
|
|
|
@ -15,20 +15,17 @@
|
|||
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */; };
|
||||
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABF618E2804325C0070250E /* PlayerHandler.swift */; };
|
||||
3AD4FCE528043E50006DB301 /* AbsDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE428043E50006DB301 /* AbsDatabase.swift */; };
|
||||
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE628043E72006DB301 /* AbsDatabase.m */; };
|
||||
3AD4FCE928043FD7006DB301 /* ServerConnectionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */; };
|
||||
3AD4FCEB280443DD006DB301 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCEA280443DD006DB301 /* Database.swift */; };
|
||||
3AD4FCED28044E6C006DB301 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AD4FCEC28044E6C006DB301 /* Store.swift */; };
|
||||
3AF1970C2806E2590096F747 /* ApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF1970B2806E2590096F747 /* ApiClient.swift */; };
|
||||
3AF1970E2806E3CA0096F747 /* AbsAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF1970D2806E3CA0096F747 /* AbsAudioPlayer.swift */; };
|
||||
3AF197102806E3DC0096F747 /* AbsAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AF1970F2806E3DC0096F747 /* AbsAudioPlayer.m */; };
|
||||
3AFCB5E827EA240D00ECCC05 /* NowPlayingInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFCB5E727EA240D00ECCC05 /* NowPlayingInfo.swift */; };
|
||||
4D66B952282EE822008272D4 /* AbsDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D66B951282EE822008272D4 /* AbsDownloader.m */; };
|
||||
4D66B954282EE87C008272D4 /* AbsDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D66B953282EE87C008272D4 /* AbsDownloader.swift */; };
|
||||
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 */; };
|
||||
4DF6C7172DB58ABF004059F1 /* AbsLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DF6C7162DB58ABF004059F1 /* AbsLogger.swift */; };
|
||||
4DF74912287105C600AC7814 /* DeviceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DF74911287105C600AC7814 /* DeviceSettings.swift */; };
|
||||
4DFE2DA32D345C390000B204 /* MyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DFE2DA22D345C390000B204 /* MyViewController.swift */; };
|
||||
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
|
||||
|
@ -90,21 +87,18 @@
|
|||
3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSession.swift; sourceTree = "<group>"; };
|
||||
3ABF618E2804325C0070250E /* PlayerHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHandler.swift; sourceTree = "<group>"; };
|
||||
3AD4FCE428043E50006DB301 /* AbsDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsDatabase.swift; sourceTree = "<group>"; };
|
||||
3AD4FCE628043E72006DB301 /* AbsDatabase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsDatabase.m; sourceTree = "<group>"; };
|
||||
3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConnectionConfig.swift; sourceTree = "<group>"; };
|
||||
3AD4FCEA280443DD006DB301 /* Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
|
||||
3AD4FCEC28044E6C006DB301 /* Store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = "<group>"; };
|
||||
3AF1970B2806E2590096F747 /* ApiClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiClient.swift; sourceTree = "<group>"; };
|
||||
3AF1970D2806E3CA0096F747 /* AbsAudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsAudioPlayer.swift; sourceTree = "<group>"; };
|
||||
3AF1970F2806E3DC0096F747 /* AbsAudioPlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsAudioPlayer.m; sourceTree = "<group>"; };
|
||||
3AFCB5E727EA240D00ECCC05 /* NowPlayingInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingInfo.swift; sourceTree = "<group>"; };
|
||||
4D66B951282EE822008272D4 /* AbsDownloader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsDownloader.m; sourceTree = "<group>"; };
|
||||
4D66B953282EE87C008272D4 /* AbsDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsDownloader.swift; sourceTree = "<group>"; };
|
||||
4D66B955282EE951008272D4 /* AbsFileSystem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AbsFileSystem.m; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
4DF6C7162DB58ABF004059F1 /* AbsLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbsLogger.swift; sourceTree = "<group>"; };
|
||||
4DF74911287105C600AC7814 /* DeviceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceSettings.swift; sourceTree = "<group>"; };
|
||||
4DFE2DA22D345C390000B204 /* MyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyViewController.swift; sourceTree = "<group>"; };
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||
|
@ -217,13 +211,10 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
3AD4FCE428043E50006DB301 /* AbsDatabase.swift */,
|
||||
3AD4FCE628043E72006DB301 /* AbsDatabase.m */,
|
||||
3AF1970D2806E3CA0096F747 /* AbsAudioPlayer.swift */,
|
||||
3AF1970F2806E3DC0096F747 /* AbsAudioPlayer.m */,
|
||||
4D66B951282EE822008272D4 /* AbsDownloader.m */,
|
||||
4D66B953282EE87C008272D4 /* AbsDownloader.swift */,
|
||||
4D66B955282EE951008272D4 /* AbsFileSystem.m */,
|
||||
4D66B957282EEA14008272D4 /* AbsFileSystem.swift */,
|
||||
4DF6C7162DB58ABF004059F1 /* AbsLogger.swift */,
|
||||
);
|
||||
path = plugins;
|
||||
sourceTree = "<group>";
|
||||
|
@ -531,7 +522,6 @@
|
|||
files = (
|
||||
E9D5507328AC218300C746DD /* DaoExtensions.swift in Sources */,
|
||||
E9D5506228AC1CC900C746DD /* PlayerState.swift in Sources */,
|
||||
3AD4FCE728043E72006DB301 /* AbsDatabase.m in Sources */,
|
||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||
EACB38122BCCA1330060DA4A /* AudioPlayerRateManager.swift in Sources */,
|
||||
E9FA07E328C82848005520B0 /* Logger.swift in Sources */,
|
||||
|
@ -545,6 +535,7 @@
|
|||
4D66B958282EEA14008272D4 /* AbsFileSystem.swift in Sources */,
|
||||
E9D5504C28AC1AE000C746DD /* PodcastEpisode.swift in Sources */,
|
||||
E9D5506A28AC1DF100C746DD /* LocalFile.swift in Sources */,
|
||||
4DF6C7172DB58ABF004059F1 /* AbsLogger.swift in Sources */,
|
||||
3AF1970E2806E3CA0096F747 /* AbsAudioPlayer.swift in Sources */,
|
||||
E9D5506F28AC1E8E00C746DD /* DownloadItem.swift in Sources */,
|
||||
3AD4FCE928043FD7006DB301 /* ServerConnectionConfig.swift in Sources */,
|
||||
|
@ -553,7 +544,6 @@
|
|||
3A200C1527D64D7E00CBF02E /* AudioPlayer.swift in Sources */,
|
||||
4DFE2DA32D345C390000B204 /* MyViewController.swift in Sources */,
|
||||
E9D5507128AC1EC700C746DD /* DownloadItemPart.swift in Sources */,
|
||||
4D66B956282EE951008272D4 /* AbsFileSystem.m in Sources */,
|
||||
EACB38142BCCA1410060DA4A /* LegacyAudioPlayerRateManager.swift in Sources */,
|
||||
3AFCB5E827EA240D00ECCC05 /* NowPlayingInfo.swift in Sources */,
|
||||
3AB34053280829BF0039308B /* Extensions.swift in Sources */,
|
||||
|
@ -561,7 +551,6 @@
|
|||
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 */,
|
||||
EACB38162BCCA1500060DA4A /* DefaultedAudioPlayerRateManager.swift in Sources */,
|
||||
E9D5505228AC1B5D00C746DD /* Chapter.swift in Sources */,
|
||||
|
@ -574,7 +563,6 @@
|
|||
E9D5505C28AC1C6200C746DD /* LibraryFile.swift in Sources */,
|
||||
4DF74912287105C600AC7814 /* DeviceSettings.swift in Sources */,
|
||||
E9D5504A28AC1AA600C746DD /* Metadata.swift in Sources */,
|
||||
3AF197102806E3DC0096F747 /* AbsAudioPlayer.m in Sources */,
|
||||
E9D5507528AEF93100C746DD /* PlayerSettings.swift in Sources */,
|
||||
E9D5505028AC1B3E00C746DD /* Author.swift in Sources */,
|
||||
3AF1970C2806E2590096F747 /* ApiClient.swift in Sources */,
|
||||
|
|
|
@ -21,6 +21,7 @@ class MyViewController: CAPBridgeViewController {
|
|||
bridge?.registerPluginInstance(AbsAudioPlayer())
|
||||
bridge?.registerPluginInstance(AbsDownloader())
|
||||
bridge?.registerPluginInstance(AbsFileSystem())
|
||||
bridge?.registerPluginInstance(AbsLogger())
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
//
|
||||
// AbsAudioPlayer.m
|
||||
// App
|
||||
//
|
||||
// Created by Rasmus Krämer on 13.04.22.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Capacitor/Capacitor.h>
|
||||
|
||||
CAP_PLUGIN(AbsAudioPlayer, "AbsAudioPlayer",
|
||||
CAP_PLUGIN_METHOD(onReady, CAPPluginReturnNone);
|
||||
|
||||
CAP_PLUGIN_METHOD(prepareLibraryItem, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(closePlayback, CAPPluginReturnPromise);
|
||||
|
||||
CAP_PLUGIN_METHOD(setPlaybackSpeed, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(setChapterTrack, CAPPluginReturnPromise);
|
||||
|
||||
CAP_PLUGIN_METHOD(playPlayer, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(pausePlayer, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(playPause, CAPPluginReturnPromise);
|
||||
|
||||
CAP_PLUGIN_METHOD(seek, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(seekForward, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(seekBackward, CAPPluginReturnPromise);
|
||||
|
||||
CAP_PLUGIN_METHOD(getCurrentTime, CAPPluginReturnPromise);
|
||||
|
||||
CAP_PLUGIN_METHOD(cancelSleepTimer, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(decreaseSleepTime, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(increaseSleepTime, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getSleepTimerTime, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(setSleepTimer, CAPPluginReturnPromise);
|
||||
)
|
|
@ -11,7 +11,29 @@ import RealmSwift
|
|||
import Network
|
||||
|
||||
@objc(AbsAudioPlayer)
|
||||
public class AbsAudioPlayer: CAPPlugin {
|
||||
public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
public var identifier = "AbsAudioPlayerPlugin"
|
||||
public var jsName = "AbsAudioPlayer"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "onReady", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "prepareLibraryItem", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "closePlayback", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "setPlaybackSpeed", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "setChapterTrack", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "playPlayer", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "pausePlayer", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "playPause", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "seek", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "seekForward", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "seekBackward", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getCurrentTime", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "cancelSleepTimer", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "decreaseSleepTime", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "increaseSleepTime", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getSleepTimerTime", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "setSleepTimer", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsAudioPlayer")
|
||||
|
||||
private var initialPlayWhenReady = false
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
//
|
||||
// AbsDatabase.m
|
||||
// App
|
||||
//
|
||||
// Created by Rasmus Krämer on 11.04.22.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Capacitor/Capacitor.h>
|
||||
|
||||
CAP_PLUGIN(AbsDatabase, "AbsDatabase",
|
||||
CAP_PLUGIN_METHOD(setCurrentServerConnectionConfig, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(removeServerConnectionConfig, CAPPluginReturnPromise);
|
||||
|
||||
CAP_PLUGIN_METHOD(logout, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getDeviceData, CAPPluginReturnPromise);
|
||||
|
||||
CAP_PLUGIN_METHOD(getLocalLibraryItems, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getLocalLibraryItem, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getLocalLibraryItemByLId, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getLocalLibraryItemsInFolder, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(getAllLocalMediaProgress, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(removeLocalMediaProgress, 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);
|
||||
)
|
|
@ -27,7 +27,27 @@ extension String {
|
|||
}
|
||||
|
||||
@objc(AbsDatabase)
|
||||
public class AbsDatabase: CAPPlugin {
|
||||
public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
public var identifier = "AbsDatabasePlugin"
|
||||
public var jsName = "AbsDatabase"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "setCurrentServerConnectionConfig", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "removeServerConnectionConfig", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "logout", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getDeviceData", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getLocalLibraryItems", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getLocalLibraryItem", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getLocalLibraryItemByLId", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getLocalLibraryItemsInFolder", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getAllLocalMediaProgress", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "removeLocalMediaProgress", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "syncServerMediaProgressWithLocalMediaProgress", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "syncLocalSessionsWithServer", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "updateLocalMediaProgressFinished", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "updateDeviceSettings", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "updateLocalEbookProgress", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsDatabase")
|
||||
|
||||
@objc func setCurrentServerConnectionConfig(_ call: CAPPluginCall) {
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// AbsDownloader.m
|
||||
// App
|
||||
//
|
||||
// Created by advplyr on 5/13/22.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Capacitor/Capacitor.h>
|
||||
|
||||
CAP_PLUGIN(AbsDownloader, "AbsDownloader",
|
||||
CAP_PLUGIN_METHOD(downloadLibraryItem, CAPPluginReturnPromise);
|
||||
)
|
|
@ -10,7 +10,12 @@ import Capacitor
|
|||
import RealmSwift
|
||||
|
||||
@objc(AbsDownloader)
|
||||
public class AbsDownloader: CAPPlugin, URLSessionDownloadDelegate {
|
||||
public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDelegate {
|
||||
public var identifier = "AbsDownloaderPlugin"
|
||||
public var jsName = "AbsDownloader"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "downloadLibraryItem", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
static private let downloadsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// AbsFileSystem.m
|
||||
// App
|
||||
//
|
||||
// Created by advplyr on 5/13/22.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Capacitor/Capacitor.h>
|
||||
|
||||
CAP_PLUGIN(AbsFileSystem, "AbsFileSystem",
|
||||
CAP_PLUGIN_METHOD(selectFolder, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(checkFolderPermission, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(scanFolder, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(removeFolder, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(removeLocalLibraryItem, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(scanLocalLibraryItem, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(deleteItem, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(deleteTrackFromItem, CAPPluginReturnPromise);
|
||||
)
|
|
@ -9,7 +9,20 @@ import Foundation
|
|||
import Capacitor
|
||||
|
||||
@objc(AbsFileSystem)
|
||||
public class AbsFileSystem: CAPPlugin {
|
||||
public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
public var identifier = "AbsFileSystemPlugin"
|
||||
public var jsName = "AbsFileSystem"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "selectFolder", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "checkFolderPermission", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "scanFolder", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "removeFolder", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "removeLocalLibraryItem", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "scanLocalLibraryItem", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "deleteItem", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "deleteTrackFromItem", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsFileSystem")
|
||||
|
||||
@objc func selectFolder(_ call: CAPPluginCall) {
|
||||
|
|
47
ios/App/App/plugins/AbsLogger.swift
Normal file
47
ios/App/App/plugins/AbsLogger.swift
Normal file
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// AbsLogger.swift
|
||||
// Audiobookshelf
|
||||
//
|
||||
// Created by advplyr on 4/20/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Capacitor
|
||||
|
||||
@objc(AbsLogger)
|
||||
public class AbsLogger: CAPPlugin, CAPBridgedPlugin {
|
||||
public var identifier = "AbsLoggerPlugin"
|
||||
public var jsName = "AbsLogger"
|
||||
public let pluginMethods: [CAPPluginMethod] = [
|
||||
CAPPluginMethod(name: "info", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "error", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "getAllLogs", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "clearLogs", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsLogger")
|
||||
|
||||
@objc func info(_ call: CAPPluginCall) {
|
||||
let message = call.getString("message") ?? ""
|
||||
let tag = call.getString("tag") ?? ""
|
||||
|
||||
logger.log("[\(tag)] \(message)")
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@objc func error(_ call: CAPPluginCall) {
|
||||
let message = call.getString("message") ?? ""
|
||||
let tag = call.getString("tag") ?? ""
|
||||
|
||||
logger.error("[\(tag)] \(message)")
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@objc func getAllLogs(_ call: CAPPluginCall) {
|
||||
call.unimplemented("Not implemented on iOS")
|
||||
}
|
||||
|
||||
@objc func clearLogs(_ call: CAPPluginCall) {
|
||||
call.unimplemented("Not implemented on iOS")
|
||||
}
|
||||
}
|
|
@ -118,7 +118,7 @@ export default {
|
|||
console.warn('[default] attemptConnection')
|
||||
if (!this.networkConnected) {
|
||||
console.warn('[default] No network connection')
|
||||
AbsLogger.info({ message: '[default] attemptConnection: No network connection' })
|
||||
AbsLogger.info({ tag: 'default', message: 'attemptConnection: No network connection' })
|
||||
return
|
||||
}
|
||||
if (this.attemptingConnection) {
|
||||
|
@ -139,14 +139,14 @@ export default {
|
|||
if (!serverConfig) {
|
||||
// No last server config set
|
||||
this.attemptingConnection = false
|
||||
AbsLogger.info({ message: `[default] attemptConnection: No last server config set` })
|
||||
AbsLogger.info({ tag: 'default', message: 'attemptConnection: No last server config set' })
|
||||
return
|
||||
}
|
||||
|
||||
AbsLogger.info({ message: `[default] attemptConnection: Got server config, attempt authorize (${serverConfig.name})` })
|
||||
AbsLogger.info({ tag: 'default', message: `attemptConnection: Got server config, attempt authorize (${serverConfig.name})` })
|
||||
|
||||
const authRes = await this.postRequest(`${serverConfig.address}/api/authorize`, null, { Authorization: `Bearer ${serverConfig.token}` }, 6000).catch((error) => {
|
||||
AbsLogger.error({ message: `[default] attemptConnection: Server auth failed (${serverConfig.name})` })
|
||||
AbsLogger.error({ tag: 'default', message: `attemptConnection: Server auth failed (${serverConfig.name})` })
|
||||
return false
|
||||
})
|
||||
if (!authRes) {
|
||||
|
@ -172,7 +172,7 @@ export default {
|
|||
|
||||
this.$socket.connect(serverConnectionConfig.address, serverConnectionConfig.token)
|
||||
|
||||
AbsLogger.info({ message: `[default] attemptConnection: Successful connection to last saved server config (${serverConnectionConfig.name})` })
|
||||
AbsLogger.info({ tag: 'default', message: `attemptConnection: Successful connection to last saved server config (${serverConnectionConfig.name})` })
|
||||
await this.initLibraries()
|
||||
this.attemptingConnection = false
|
||||
},
|
||||
|
@ -193,7 +193,7 @@ export default {
|
|||
this.inittingLibraries = true
|
||||
await this.$store.dispatch('libraries/load')
|
||||
|
||||
AbsLogger.info({ message: `[default] initLibraries loading library ${this.currentLibraryName}` })
|
||||
AbsLogger.info({ tag: 'default', message: `initLibraries loading library ${this.currentLibraryName}` })
|
||||
await this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
||||
this.$eventBus.$emit('library-changed')
|
||||
this.inittingLibraries = false
|
||||
|
@ -204,7 +204,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
AbsLogger.info({ message: '[default] Calling syncLocalSessions' })
|
||||
AbsLogger.info({ tag: 'default', message: 'Calling syncLocalSessions' })
|
||||
const response = await this.$db.syncLocalSessionsWithServer(isFirstSync)
|
||||
if (response?.error) {
|
||||
console.error('[default] Failed to sync local sessions', response.error)
|
||||
|
@ -221,12 +221,12 @@ export default {
|
|||
},
|
||||
async userMediaProgressUpdated(payload) {
|
||||
const prog = payload.data // MediaProgress
|
||||
console.log(`[default] userMediaProgressUpdate checking for local media progress ${payload.id}`)
|
||||
await AbsLogger.info({ tag: 'default', message: `userMediaProgressUpdate: Received updated media progress for current user from socket event. Media item id ${payload.id}` })
|
||||
|
||||
// Check if this media item is currently open in the player, paused, and this progress update is coming from a different session
|
||||
const isMediaOpenInPlayer = this.$store.getters['getIsMediaStreaming'](prog.libraryItemId, prog.episodeId)
|
||||
if (isMediaOpenInPlayer && this.$store.getters['getCurrentPlaybackSessionId'] !== payload.sessionId && !this.$store.state.playerIsPlaying) {
|
||||
console.log('[default] userMediaProgressUpdated for current open media item', payload.data.currentTime)
|
||||
await AbsLogger.info({ tag: 'default', message: `userMediaProgressUpdate: Item is currently open in player, paused and this progress update is coming from a different session. Updating playback time to ${payload.data.currentTime}` })
|
||||
this.$eventBus.$emit('playback-time-update', payload.data.currentTime)
|
||||
}
|
||||
|
||||
|
@ -237,12 +237,12 @@ export default {
|
|||
// Progress update is more recent then local progress
|
||||
if (localProg && localProg.lastUpdate < prog.lastUpdate) {
|
||||
if (localProg.currentTime == prog.currentTime && localProg.isFinished == prog.isFinished) {
|
||||
console.log('[default] syncing progress server lastUpdate > local lastUpdate but currentTime and isFinished is equal')
|
||||
await AbsLogger.info({ tag: 'default', message: `userMediaProgressUpdate: server lastUpdate is more recent but progress is up-to-date (libraryItemId: ${prog.libraryItemId}${prog.episodeId ? ` episodeId: ${prog.episodeId}` : ''})` })
|
||||
return
|
||||
}
|
||||
|
||||
// Server progress is more up-to-date
|
||||
console.log(`[default] syncing progress from server with local item for "${prog.libraryItemId}" ${prog.episodeId ? `episode ${prog.episodeId}` : ''} | server lastUpdate=${prog.lastUpdate} > local lastUpdate=${localProg.lastUpdate}`)
|
||||
await AbsLogger.info({ tag: 'default', message: `userMediaProgressUpdate: syncing progress from server with local item for "${prog.libraryItemId}" ${prog.episodeId ? `episode ${prog.episodeId}` : ''} | server lastUpdate=${prog.lastUpdate} > local lastUpdate=${localProg.lastUpdate}` })
|
||||
const payload = {
|
||||
localMediaProgressId: localProg.id,
|
||||
mediaProgress: prog
|
||||
|
@ -280,7 +280,7 @@ export default {
|
|||
}
|
||||
|
||||
if (newLocalMediaProgress?.id) {
|
||||
console.log(`[default] local media progress updated for ${newLocalMediaProgress.id}`)
|
||||
await AbsLogger.info({ tag: 'default', message: `userMediaProgressUpdate: local media progress updated for ${newLocalMediaProgress.id}` })
|
||||
this.$store.commit('globals/updateLocalMediaProgress', newLocalMediaProgress)
|
||||
}
|
||||
},
|
||||
|
@ -317,7 +317,7 @@ export default {
|
|||
this.$socket.on('user_media_progress_updated', this.userMediaProgressUpdated)
|
||||
|
||||
if (this.$store.state.isFirstLoad) {
|
||||
AbsLogger.info({ message: `[default] mounted: first load` })
|
||||
AbsLogger.info({ tag: 'default', message: `mounted: initializing first load (${this.$platform} v${this.$config.version})` })
|
||||
this.$store.commit('setIsFirstLoad', false)
|
||||
|
||||
this.loadSavedSettings()
|
||||
|
@ -330,19 +330,18 @@ export default {
|
|||
await this.$store.dispatch('setupNetworkListener')
|
||||
|
||||
if (this.$store.state.user.serverConnectionConfig) {
|
||||
AbsLogger.info({ message: `[default] Server connected, init libraries (ServerConfigName: ${this.$store.getters['user/getServerConfigName']})` })
|
||||
AbsLogger.info({ tag: 'default', message: `mounted: Server connected, init libraries (${this.$store.getters['user/getServerConfigName']})` })
|
||||
await this.initLibraries()
|
||||
} else {
|
||||
AbsLogger.info({ message: `[default] Server not connected, attempt connection` })
|
||||
AbsLogger.info({ tag: 'default', message: `mounted: Server not connected, attempt connection` })
|
||||
await this.attemptConnection()
|
||||
}
|
||||
|
||||
console.log(`[default] finished connection attempt or already connected ${!!this.user}`)
|
||||
await this.syncLocalSessions(true)
|
||||
|
||||
this.hasMounted = true
|
||||
|
||||
console.log('[default] fully initialized')
|
||||
AbsLogger.info({ tag: 'default', message: 'mounted: fully initialized' })
|
||||
this.$eventBus.$emit('abs-ui-ready')
|
||||
}
|
||||
},
|
||||
|
|
163
pages/logs.vue
163
pages/logs.vue
|
@ -1,60 +1,175 @@
|
|||
<template>
|
||||
<div class="w-full h-full p-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="w-full h-full py-4">
|
||||
<div class="flex items-center mb-2 space-x-2 px-4">
|
||||
<p class="text-lg font-bold">{{ $strings.ButtonLogs }}</p>
|
||||
<ui-icon-btn outlined borderless icon="content_copy" @click="copyToClipboard" />
|
||||
<ui-icon-btn outlined borderless :icon="isCopied ? 'check' : 'content_copy'" @click="copyToClipboard" />
|
||||
<ui-icon-btn outlined borderless icon="share" @click="shareLogs" />
|
||||
<div class="flex-grow"></div>
|
||||
<ui-icon-btn outlined borderless icon="more_vert" @click="showDialog = true" />
|
||||
</div>
|
||||
|
||||
<div class="w-full h-[calc(100%-40px)] overflow-y-auto relative" ref="logContainer">
|
||||
<div v-if="!logs.length && !isLoading" class="flex items-center justify-center h-32 p-4">
|
||||
<p class="text-gray-400">{{ $strings.MessageNoLogs }}</p>
|
||||
</div>
|
||||
<div v-if="hasScrolled" class="sticky top-0 left-0 w-full h-10 bg-gradient-to-t from-transparent to-bg z-10 pointer-events-none"></div>
|
||||
|
||||
<div v-for="log in logs" :key="log.id" class="py-1">
|
||||
<div v-for="(log, index) in logs" :key="log.id" class="py-2 px-4" :class="{ 'bg-white/5': index % 2 === 0 }">
|
||||
<div class="flex items-center space-x-4 mb-1">
|
||||
<div class="text-xs uppercase font-bold" :class="{ 'text-error': log.level === 'error', 'text-blue-600': log.level === 'info' }">{{ log.level }}</div>
|
||||
<div class="text-xs text-gray-400">{{ new Date(log.timestamp).toLocaleString() }}</div>
|
||||
<div class="text-xs uppercase font-bold" :class="{ 'text-error': log.level === 'error', 'text-blue-500': log.level === 'info' }">{{ log.level }}</div>
|
||||
<div class="text-xs text-gray-400">{{ formatEpochToDatetimeString(log.timestamp) }}</div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="text-xs text-gray-400">{{ log.tag }}</div>
|
||||
</div>
|
||||
<div class="text-xs">{{ log.message }}</div>
|
||||
<div class="text-xs">{{ maskServerAddress ? log.maskedMessage : log.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<modals-dialog v-model="showDialog" :items="dialogItems" @action="dialogAction" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AbsLogger } from '@/plugins/capacitor'
|
||||
import { FileSharer } from '@webnativellc/capacitor-filesharer'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
logs: [],
|
||||
hasScrolled: false
|
||||
isLoading: true,
|
||||
isCopied: false,
|
||||
hasScrolled: false,
|
||||
maskServerAddress: true,
|
||||
showDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogItems() {
|
||||
return [
|
||||
{
|
||||
text: this.maskServerAddress ? this.$strings.ButtonUnmaskServerAddress : this.$strings.ButtonMaskServerAddress,
|
||||
value: 'toggle-mask-server-address',
|
||||
icon: this.maskServerAddress ? 'remove_moderator' : 'shield'
|
||||
},
|
||||
{
|
||||
text: this.$strings.ButtonClearLogs,
|
||||
value: 'clear-logs',
|
||||
icon: 'delete'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
copyToClipboard() {
|
||||
this.$copyToClipboard(
|
||||
this.logs
|
||||
.map((log) => {
|
||||
return `${log.timestamp} [${log.level}] ${log.message}`
|
||||
})
|
||||
.join('\n')
|
||||
)
|
||||
async dialogAction(action) {
|
||||
await this.$hapticsImpact()
|
||||
|
||||
if (action === 'clear-logs') {
|
||||
await AbsLogger.clearLogs()
|
||||
this.logs = []
|
||||
} else if (action === 'toggle-mask-server-address') {
|
||||
this.maskServerAddress = !this.maskServerAddress
|
||||
}
|
||||
this.showDialog = false
|
||||
},
|
||||
toggleMaskServerAddress() {
|
||||
this.maskServerAddress = !this.maskServerAddress
|
||||
},
|
||||
async copyToClipboard() {
|
||||
await this.$hapticsImpact()
|
||||
this.$copyToClipboard(this.getLogsString()).then(() => {
|
||||
this.isCopied = true
|
||||
setTimeout(() => {
|
||||
this.isCopied = false
|
||||
}, 2000)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Formats an epoch timestamp to YYYY-MM-DD HH:mm:ss.SSS
|
||||
* Use 24 hour time format
|
||||
* @param {number} epoch
|
||||
* @returns {string}
|
||||
*/
|
||||
formatEpochToDatetimeString(epoch) {
|
||||
return new Date(epoch)
|
||||
.toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
fractionalSecondDigits: 3,
|
||||
hour12: false
|
||||
})
|
||||
.replace(',', '')
|
||||
},
|
||||
getLogsString() {
|
||||
return this.logs
|
||||
.map((log) => {
|
||||
const logMessage = this.maskServerAddress ? log.maskedMessage : log.message
|
||||
return `${this.formatEpochToDatetimeString(log.timestamp)} [${log.level.toUpperCase()}] ${logMessage}`
|
||||
})
|
||||
.join('\n')
|
||||
},
|
||||
async shareLogs() {
|
||||
await this.$hapticsImpact()
|
||||
// Share .txt file with logs
|
||||
const base64Data = Buffer.from(this.getLogsString()).toString('base64')
|
||||
|
||||
FileSharer.share({
|
||||
filename: `abs_logs_${this.$platform}_${this.$config.version}.txt`,
|
||||
contentType: 'text/plain',
|
||||
base64Data
|
||||
}).catch((error) => {
|
||||
if (error.message !== 'USER_CANCELLED') {
|
||||
console.error('Failed to share', error.message)
|
||||
this.$toast.error('Failed to share: ' + error.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
scrollToBottom() {
|
||||
this.$refs.logContainer.scrollTop = this.$refs.logContainer.scrollHeight
|
||||
this.hasScrolled = this.$refs.logContainer.scrollTop > 0
|
||||
},
|
||||
maskLogMessage(message) {
|
||||
return message.replace(/(https?:\/\/)\S+/g, '$1[SERVER_ADDRESS]')
|
||||
},
|
||||
loadLogs() {
|
||||
AbsLogger.getAllLogs().then((logData) => {
|
||||
const logs = logData.value || []
|
||||
this.logs = logs
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom()
|
||||
this.isLoading = true
|
||||
AbsLogger.getAllLogs()
|
||||
.then((logData) => {
|
||||
const logs = logData.value || []
|
||||
this.logs = logs.map((log) => {
|
||||
log.maskedMessage = this.maskLogMessage(log.message)
|
||||
return log
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom()
|
||||
})
|
||||
this.isLoading = false
|
||||
})
|
||||
.catch((error) => {
|
||||
this.isLoading = false
|
||||
console.error('Failed to load logs', error)
|
||||
this.$toast.error('Failed to load logs: ' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
AbsLogger.addListener('onLog', (log) => {
|
||||
log.maskedMessage = this.maskLogMessage(log.message)
|
||||
this.logs.push(log)
|
||||
this.logs.sort((a, b) => a.timestamp - b.timestamp)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom()
|
||||
})
|
||||
})
|
||||
this.loadLogs()
|
||||
},
|
||||
beforeDestroy() {
|
||||
AbsLogger.removeAllListeners()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { registerPlugin, WebPlugin } from '@capacitor/core'
|
||||
import { AbsLogger } from '@/plugins/capacitor'
|
||||
import { nanoid } from 'nanoid'
|
||||
const { PlayerState } = require('../constants')
|
||||
|
||||
|
@ -88,6 +89,9 @@ class AbsAudioPlayerWeb extends WebPlugin {
|
|||
return
|
||||
}
|
||||
|
||||
// For testing onLog events in web while on the logs page
|
||||
AbsLogger.info({ tag: 'AbsAudioPlayer', message: 'playPause' })
|
||||
|
||||
if (this.player.paused) this.player.play()
|
||||
else this.player.pause()
|
||||
return {
|
||||
|
|
|
@ -7,31 +7,44 @@ class AbsLoggerWeb extends WebPlugin {
|
|||
this.logs = []
|
||||
}
|
||||
|
||||
saveLog(level, message) {
|
||||
this.logs.push({
|
||||
saveLog(level, tag, message) {
|
||||
const log = {
|
||||
id: Math.random().toString(36).substring(2, 15),
|
||||
tag: tag,
|
||||
timestamp: Date.now(),
|
||||
level: level,
|
||||
message: message
|
||||
})
|
||||
}
|
||||
this.logs.push(log)
|
||||
this.notifyListeners('onLog', log)
|
||||
}
|
||||
|
||||
// PluginMethod
|
||||
async info(data) {
|
||||
if (data?.message) {
|
||||
this.saveLog('info', data.message)
|
||||
console.log('AbsLogger: info', data.message)
|
||||
this.saveLog('info', data.tag || '', data.message)
|
||||
console.log('AbsLogger: info', `[${data.tag || ''}]:`, data.message)
|
||||
}
|
||||
}
|
||||
|
||||
// PluginMethod
|
||||
async error(data) {
|
||||
if (data?.message) {
|
||||
this.saveLog('error', data.message)
|
||||
console.error('AbsLogger: error', data.message)
|
||||
this.saveLog('error', data.tag || '', data.message)
|
||||
console.error('AbsLogger: error', `[${data.tag || ''}]:`, data.message)
|
||||
}
|
||||
}
|
||||
|
||||
// PluginMethod
|
||||
async getAllLogs() {
|
||||
return this.logs
|
||||
return {
|
||||
value: this.logs
|
||||
}
|
||||
}
|
||||
|
||||
// PluginMethod
|
||||
async clearLogs() {
|
||||
this.logs = []
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"ButtonCancel": "Cancel",
|
||||
"ButtonCancelTimer": "Cancel Timer",
|
||||
"ButtonClearFilter": "Clear Filter",
|
||||
"ButtonClearLogs": "Clear Logs",
|
||||
"ButtonCloseFeed": "Close Feed",
|
||||
"ButtonCollections": "Collections",
|
||||
"ButtonConnect": "Connect",
|
||||
|
@ -28,6 +29,7 @@
|
|||
"ButtonLocalMedia": "Local Media",
|
||||
"ButtonLogs": "Logs",
|
||||
"ButtonManageLocalFiles": "Manage Local Files",
|
||||
"ButtonMaskServerAddress": "Mask server address",
|
||||
"ButtonNewFolder": "New Folder",
|
||||
"ButtonNextEpisode": "Next Episode",
|
||||
"ButtonOk": "Ok",
|
||||
|
@ -51,6 +53,7 @@
|
|||
"ButtonStream": "Stream",
|
||||
"ButtonSubmit": "Submit",
|
||||
"ButtonSwitchServerUser": "Switch Server/User",
|
||||
"ButtonUnmaskServerAddress": "Unmask server address",
|
||||
"ButtonUserStats": "User Stats",
|
||||
"ButtonYes": "Yes",
|
||||
"HeaderAccount": "Account",
|
||||
|
@ -311,6 +314,7 @@
|
|||
"MessageNoItems": "No Items",
|
||||
"MessageNoItemsFound": "No items found",
|
||||
"MessageNoListeningSessions": "No Listening Sessions",
|
||||
"MessageNoLogs": "No logs",
|
||||
"MessageNoMediaFolders": "No Media Folders",
|
||||
"MessageNoNetworkConnection": "No network connection",
|
||||
"MessageNoPodcastsFound": "No podcasts found",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue