mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-15 16:34:43 +02:00
Remove old code for downloads, user progress, sql, server config. Add web plugin for DbManager
This commit is contained in:
parent
9fd3dc6978
commit
4b834cb5c1
25 changed files with 106 additions and 2901 deletions
304
Server.js
304
Server.js
|
@ -1,304 +0,0 @@
|
||||||
import { io } from 'socket.io-client'
|
|
||||||
import { Storage } from '@capacitor/storage'
|
|
||||||
import EventEmitter from 'events'
|
|
||||||
|
|
||||||
class Server extends EventEmitter {
|
|
||||||
constructor(store, $axios) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
this.store = store
|
|
||||||
this.$axios = $axios
|
|
||||||
|
|
||||||
this.url = null
|
|
||||||
this.socket = null
|
|
||||||
|
|
||||||
this.user = null
|
|
||||||
this.connected = false
|
|
||||||
this.initialized = false
|
|
||||||
|
|
||||||
this.stream = null
|
|
||||||
|
|
||||||
this.isConnectingSocket = false
|
|
||||||
}
|
|
||||||
|
|
||||||
get token() {
|
|
||||||
return this.user ? this.user.token : null
|
|
||||||
}
|
|
||||||
|
|
||||||
getAxiosConfig() {
|
|
||||||
return { headers: { Authorization: `Bearer ${this.token}` } }
|
|
||||||
}
|
|
||||||
|
|
||||||
getServerUrl(url) {
|
|
||||||
if (!url) return null
|
|
||||||
try {
|
|
||||||
var urlObject = new URL(url)
|
|
||||||
return `${urlObject.protocol}//${urlObject.hostname}:${urlObject.port}`
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Invalid URL', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setUser(user) {
|
|
||||||
this.user = user
|
|
||||||
this.store.commit('user/setUser', user)
|
|
||||||
if (user) {
|
|
||||||
// this.store.commit('user/setSettings', user.settings)
|
|
||||||
Storage.set({ key: 'token', value: user.token })
|
|
||||||
} else {
|
|
||||||
Storage.remove({ key: 'token' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setServerUrl(url) {
|
|
||||||
this.url = url
|
|
||||||
this.store.commit('setServerUrl', url)
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
Storage.set({ key: 'serverUrl', value: url })
|
|
||||||
} else {
|
|
||||||
Storage.remove({ key: 'serverUrl' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect(url, token) {
|
|
||||||
if (this.connected) {
|
|
||||||
console.warn('[SOCKET] Connection already established for ' + this.url)
|
|
||||||
return { success: true }
|
|
||||||
}
|
|
||||||
if (!url) {
|
|
||||||
console.error('Invalid url to connect')
|
|
||||||
return {
|
|
||||||
error: 'Invalid URL'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverUrl = this.getServerUrl(url)
|
|
||||||
var res = await this.ping(serverUrl)
|
|
||||||
|
|
||||||
if (!res || !res.success) {
|
|
||||||
return {
|
|
||||||
error: res ? res.error : 'Unknown Error'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var authRes = await this.authorize(serverUrl, token)
|
|
||||||
if (!authRes || authRes.error) {
|
|
||||||
return {
|
|
||||||
error: authRes ? authRes.error : 'Authorization Error'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setServerUrl(serverUrl)
|
|
||||||
|
|
||||||
this.setUser(authRes.user)
|
|
||||||
this.connectSocket()
|
|
||||||
|
|
||||||
return { success: true }
|
|
||||||
}
|
|
||||||
|
|
||||||
async check(url) {
|
|
||||||
var serverUrl = this.getServerUrl(url)
|
|
||||||
if (!serverUrl) {
|
|
||||||
return {
|
|
||||||
error: 'Invalid server url'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var res = await this.ping(serverUrl)
|
|
||||||
if (!res || res.error) {
|
|
||||||
return {
|
|
||||||
error: res ? res.error : 'Ping Failed'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
serverUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async login(url, username, password) {
|
|
||||||
var serverUrl = this.getServerUrl(url)
|
|
||||||
var authUrl = serverUrl + '/login'
|
|
||||||
return this.$axios.post(authUrl, { username, password }).then((res) => {
|
|
||||||
if (!res.data || !res.data.user) {
|
|
||||||
console.error(res.data.error)
|
|
||||||
return {
|
|
||||||
error: res.data.error || 'Unknown Error'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setServerUrl(serverUrl)
|
|
||||||
this.setUser(res.data.user)
|
|
||||||
this.connectSocket()
|
|
||||||
return {
|
|
||||||
user: res.data.user
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('[Server] Server auth failed', error)
|
|
||||||
var errorMsg = null
|
|
||||||
if (error.response) {
|
|
||||||
errorMsg = error.response.data || 'Unknown Error'
|
|
||||||
} else if (error.request) {
|
|
||||||
errorMsg = 'Server did not respond'
|
|
||||||
} else {
|
|
||||||
errorMsg = 'Failed to send request'
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
error: errorMsg
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
logout() {
|
|
||||||
this.setUser(null)
|
|
||||||
this.stream = null
|
|
||||||
if (this.socket) {
|
|
||||||
this.socket.disconnect()
|
|
||||||
}
|
|
||||||
this.emit('logout')
|
|
||||||
}
|
|
||||||
|
|
||||||
authorize(serverUrl, token) {
|
|
||||||
var authUrl = serverUrl + '/api/authorize'
|
|
||||||
return this.$axios.post(authUrl, null, { headers: { Authorization: `Bearer ${token}` } }).then((res) => {
|
|
||||||
return res.data
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('[Server] Server auth failed', error)
|
|
||||||
var errorMsg = null
|
|
||||||
if (error.response) {
|
|
||||||
errorMsg = error.response.data || 'Unknown Error'
|
|
||||||
} else if (error.request) {
|
|
||||||
errorMsg = 'Server did not respond'
|
|
||||||
} else {
|
|
||||||
errorMsg = 'Failed to send request'
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
error: errorMsg
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ping(url) {
|
|
||||||
var pingUrl = url + '/ping'
|
|
||||||
console.log('[Server] Check server', pingUrl)
|
|
||||||
return this.$axios.get(pingUrl, { timeout: 1000 }).then((res) => {
|
|
||||||
return res.data
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('Server check failed', error)
|
|
||||||
var errorMsg = null
|
|
||||||
if (error.response) {
|
|
||||||
errorMsg = error.response.data || 'Unknown Error'
|
|
||||||
} else if (error.request) {
|
|
||||||
errorMsg = 'Server did not respond'
|
|
||||||
} else {
|
|
||||||
errorMsg = 'Failed to send request'
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: errorMsg
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
connectSocket() {
|
|
||||||
if (this.socket && !this.connected) {
|
|
||||||
this.socket.connect()
|
|
||||||
console.log('[SOCKET] Submitting connect')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.connected || this.socket) {
|
|
||||||
if (this.socket) console.error('[SOCKET] Socket already established', this.url)
|
|
||||||
else console.error('[SOCKET] Already connected to socket', this.url)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[SOCKET] Connect Socket', this.url)
|
|
||||||
|
|
||||||
const socketOptions = {
|
|
||||||
transports: ['websocket'],
|
|
||||||
upgrade: false,
|
|
||||||
// reconnectionAttempts: 3
|
|
||||||
}
|
|
||||||
this.socket = io(this.url, socketOptions)
|
|
||||||
this.socket.on('connect', () => {
|
|
||||||
console.log('[SOCKET] Socket Connected ' + this.socket.id)
|
|
||||||
|
|
||||||
// Authenticate socket with token
|
|
||||||
this.socket.emit('auth', this.token)
|
|
||||||
this.connected = true
|
|
||||||
this.emit('connected', true)
|
|
||||||
this.store.commit('setSocketConnected', true)
|
|
||||||
})
|
|
||||||
this.socket.on('disconnect', (reason) => {
|
|
||||||
console.log('[SOCKET] Socket Disconnected: ' + reason)
|
|
||||||
this.connected = false
|
|
||||||
this.emit('connected', false)
|
|
||||||
this.emit('initialized', false)
|
|
||||||
this.initialized = false
|
|
||||||
this.store.commit('setSocketConnected', false)
|
|
||||||
|
|
||||||
// this.socket.removeAllListeners()
|
|
||||||
// if (this.socket.io && this.socket.io.removeAllListeners) {
|
|
||||||
// console.log(`[SOCKET] Removing ALL IO listeners`)
|
|
||||||
// this.socket.io.removeAllListeners()
|
|
||||||
// }
|
|
||||||
})
|
|
||||||
this.socket.on('init', (data) => {
|
|
||||||
console.log('[SOCKET] Initial socket data received', data)
|
|
||||||
if (data.stream) {
|
|
||||||
this.stream = data.stream
|
|
||||||
this.store.commit('setStreamAudiobook', data.stream.audiobook)
|
|
||||||
this.emit('initialStream', data.stream)
|
|
||||||
}
|
|
||||||
if (data.serverSettings) {
|
|
||||||
this.store.commit('setServerSettings', data.serverSettings)
|
|
||||||
}
|
|
||||||
this.initialized = true
|
|
||||||
this.emit('initialized', true)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.on('user_updated', (user) => {
|
|
||||||
if (this.user && user.id === this.user.id) {
|
|
||||||
this.setUser(user)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.on('current_user_audiobook_update', (payload) => {
|
|
||||||
this.emit('currentUserAudiobookUpdate', payload)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.on('show_error_toast', (payload) => {
|
|
||||||
this.emit('show_error_toast', payload)
|
|
||||||
})
|
|
||||||
this.socket.on('show_success_toast', (payload) => {
|
|
||||||
this.emit('show_success_toast', payload)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.onAny((evt, args) => {
|
|
||||||
console.log(`[SOCKET] ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.on('connect_error', (err) => {
|
|
||||||
console.error('[SOCKET] connection failed', err)
|
|
||||||
this.emit('socketConnectionFailed', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.io.on("reconnect_attempt", (attempt) => {
|
|
||||||
console.log(`[SOCKET] Reconnect Attempt ${this.socket.id}: ${attempt}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.io.on("reconnect_error", (err) => {
|
|
||||||
console.log(`[SOCKET] Reconnect Error ${this.socket.id}: ${err}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.io.on("reconnect_failed", () => {
|
|
||||||
console.log(`[SOCKET] Reconnect Failed ${this.socket.id}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.socket.io.on("reconnect", () => {
|
|
||||||
console.log(`[SOCKET] Reconnect Success ${this.socket.id}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Server
|
|
|
@ -9,14 +9,12 @@ android {
|
||||||
|
|
||||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':capacitor-community-sqlite')
|
|
||||||
implementation project(':capacitor-app')
|
implementation project(':capacitor-app')
|
||||||
implementation project(':capacitor-dialog')
|
implementation project(':capacitor-dialog')
|
||||||
implementation project(':capacitor-network')
|
implementation project(':capacitor-network')
|
||||||
implementation project(':capacitor-status-bar')
|
implementation project(':capacitor-status-bar')
|
||||||
implementation project(':capacitor-storage')
|
implementation project(':capacitor-storage')
|
||||||
implementation project(':robingenz-capacitor-app-update')
|
implementation project(':robingenz-capacitor-app-update')
|
||||||
implementation project(':capacitor-data-storage-sqlite')
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
[
|
[
|
||||||
{
|
|
||||||
"pkg": "@capacitor-community/sqlite",
|
|
||||||
"classpath": "com.getcapacitor.community.database.sqlite.CapacitorSQLitePlugin"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"pkg": "@capacitor/app",
|
"pkg": "@capacitor/app",
|
||||||
"classpath": "com.capacitorjs.plugins.app.AppPlugin"
|
"classpath": "com.capacitorjs.plugins.app.AppPlugin"
|
||||||
|
@ -26,9 +22,5 @@
|
||||||
{
|
{
|
||||||
"pkg": "@robingenz/capacitor-app-update",
|
"pkg": "@robingenz/capacitor-app-update",
|
||||||
"classpath": "dev.robingenz.capacitor.appupdate.AppUpdatePlugin"
|
"classpath": "dev.robingenz.capacitor.appupdate.AppUpdatePlugin"
|
||||||
},
|
|
||||||
{
|
|
||||||
"pkg": "capacitor-data-storage-sqlite",
|
|
||||||
"classpath": "com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlitePlugin"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,9 +8,7 @@ import android.os.Looper
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.getcapacitor.JSArray
|
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlite
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -191,29 +189,6 @@ class AudiobookManager {
|
||||||
hasLoaded = true
|
hasLoaded = true
|
||||||
|
|
||||||
localMediaManager.loadLocalAudio()
|
localMediaManager.loadLocalAudio()
|
||||||
|
|
||||||
// Load downloads from sql db
|
|
||||||
var db = CapacitorDataStorageSqlite(ctx)
|
|
||||||
db.openStore("storage", "downloads", false, "no-encryption", 1)
|
|
||||||
var keyvalues = db.keysvalues()
|
|
||||||
keyvalues.forEach {
|
|
||||||
Log.d(tag, "keyvalue ${it.getString("key")} | ${it.getString("value")}")
|
|
||||||
|
|
||||||
var dlobj = JSObject(it.getString("value"))
|
|
||||||
if (dlobj.has("audiobook")) {
|
|
||||||
var abobj = dlobj.getJSObject("audiobook")!!
|
|
||||||
abobj.put("isDownloaded", true)
|
|
||||||
abobj.put("contentUrl", dlobj.getString("contentUrl", "").toString())
|
|
||||||
abobj.put("filename", dlobj.getString("filename", "").toString())
|
|
||||||
abobj.put("folderUrl", dlobj.getString("folderUrl", "").toString())
|
|
||||||
abobj.put("downloadFolderUrl", dlobj.getString("downloadFolderUrl", "").toString())
|
|
||||||
abobj.put("localCoverUrl", dlobj.getString("coverUrl", "").toString())
|
|
||||||
abobj.put("localCover", dlobj.getString("cover", "").toString())
|
|
||||||
|
|
||||||
var audiobook = Audiobook(abobj, serverUrl, token)
|
|
||||||
audiobooks.add(audiobook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openStream(audiobook:Audiobook, streamListener:OnStreamData) {
|
fun openStream(audiobook:Audiobook, streamListener:OnStreamData) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ class DbManager : Plugin() {
|
||||||
val tag = "DbManager"
|
val tag = "DbManager"
|
||||||
|
|
||||||
fun getDeviceData(): DeviceData {
|
fun getDeviceData(): DeviceData {
|
||||||
return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null)
|
return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null, null)
|
||||||
}
|
}
|
||||||
fun saveDeviceData(deviceData:DeviceData) {
|
fun saveDeviceData(deviceData:DeviceData) {
|
||||||
Paper.book("device").write("data", deviceData)
|
Paper.book("device").write("data", deviceData)
|
||||||
|
|
|
@ -15,7 +15,8 @@ data class ServerConnectionConfig(
|
||||||
|
|
||||||
data class DeviceData(
|
data class DeviceData(
|
||||||
var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
|
var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
|
||||||
var lastServerConnectionConfigId:String?
|
var lastServerConnectionConfigId:String?,
|
||||||
|
var localLibraryItemIdPlaying:String?
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
include ':capacitor-android'
|
include ':capacitor-android'
|
||||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
|
|
||||||
include ':capacitor-community-sqlite'
|
|
||||||
project(':capacitor-community-sqlite').projectDir = new File('../node_modules/@capacitor-community/sqlite/android')
|
|
||||||
|
|
||||||
include ':capacitor-app'
|
include ':capacitor-app'
|
||||||
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
|
@ -22,6 +19,3 @@ project(':capacitor-storage').projectDir = new File('../node_modules/@capacitor/
|
||||||
|
|
||||||
include ':robingenz-capacitor-app-update'
|
include ':robingenz-capacitor-app-update'
|
||||||
project(':robingenz-capacitor-app-update').projectDir = new File('../node_modules/@robingenz/capacitor-app-update/android')
|
project(':robingenz-capacitor-app-update').projectDir = new File('../node_modules/@robingenz/capacitor-app-update/android')
|
||||||
|
|
||||||
include ':capacitor-data-storage-sqlite'
|
|
||||||
project(':capacitor-data-storage-sqlite').projectDir = new File('../node_modules/capacitor-data-storage-sqlite/android')
|
|
||||||
|
|
|
@ -59,15 +59,9 @@ export default {
|
||||||
entityName() {
|
entityName() {
|
||||||
return this.page
|
return this.page
|
||||||
},
|
},
|
||||||
bookshelfView() {
|
|
||||||
return this.$store.state.bookshelfView
|
|
||||||
},
|
|
||||||
hasFilter() {
|
hasFilter() {
|
||||||
return this.filterBy !== 'all'
|
return this.filterBy !== 'all'
|
||||||
},
|
},
|
||||||
isListView() {
|
|
||||||
return this.bookshelfView === 'list'
|
|
||||||
},
|
|
||||||
books() {
|
books() {
|
||||||
return this.$store.getters['downloads/getAudiobooks']
|
return this.$store.getters['downloads/getAudiobooks']
|
||||||
},
|
},
|
||||||
|
@ -121,13 +115,7 @@ export default {
|
||||||
return this.$store.getters['downloads/getDownloads']
|
return this.$store.getters['downloads/getDownloads']
|
||||||
},
|
},
|
||||||
downloadedBooks() {
|
downloadedBooks() {
|
||||||
return this.downloads.map((dl) => {
|
return []
|
||||||
var download = { ...dl }
|
|
||||||
var ab = { ...download.audiobook }
|
|
||||||
delete download.audiobook
|
|
||||||
ab.download = download
|
|
||||||
return ab
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
<p v-show="selectedSeriesName" class="ml-2 font-book pt-1">{{ selectedSeriesName }} ({{ totalEntities }})</p>
|
<p v-show="selectedSeriesName" class="ml-2 font-book pt-1">{{ selectedSeriesName }} ({{ totalEntities }})</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<template v-if="page === 'library'">
|
<template v-if="page === 'library'">
|
||||||
<!-- <span class="material-icons px-2" @click="changeView">{{ viewIcon }}</span> -->
|
|
||||||
<div class="relative flex items-center px-2">
|
<div class="relative flex items-center px-2">
|
||||||
<span class="material-icons" @click="showFilterModal = true">filter_alt</span>
|
<span class="material-icons" @click="showFilterModal = true">filter_alt</span>
|
||||||
<div v-show="hasFilters" class="absolute top-0 right-2 w-2 h-2 rounded-full bg-success border border-green-300 shadow-sm z-10 pointer-events-none" />
|
<div v-show="hasFilters" class="absolute top-0 right-2 w-2 h-2 rounded-full bg-success border border-green-300 shadow-sm z-10 pointer-events-none" />
|
||||||
|
@ -31,7 +30,6 @@ export default {
|
||||||
showSortModal: false,
|
showSortModal: false,
|
||||||
showFilterModal: false,
|
showFilterModal: false,
|
||||||
settings: {},
|
settings: {},
|
||||||
isListView: false,
|
|
||||||
totalEntities: 0
|
totalEntities: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -60,19 +58,9 @@ export default {
|
||||||
return this.$decode(this.$route.params.id)
|
return this.$decode(this.$route.params.id)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
|
||||||
viewIcon() {
|
|
||||||
return this.isListView ? 'grid_view' : 'view_stream'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
changeView() {
|
|
||||||
this.isListView = !this.isListView
|
|
||||||
|
|
||||||
var bookshelfView = this.isListView ? 'list' : 'grid'
|
|
||||||
this.$localStore.setBookshelfView(bookshelfView)
|
|
||||||
this.$store.commit('setBookshelfView', bookshelfView)
|
|
||||||
},
|
|
||||||
updateOrder() {
|
updateOrder() {
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
},
|
},
|
||||||
|
@ -85,11 +73,7 @@ export default {
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
this.settings = { ...this.$store.state.user.settings }
|
this.settings = { ...this.$store.state.user.settings }
|
||||||
|
|
||||||
var bookshelfView = await this.$localStore.getBookshelfView()
|
|
||||||
this.isListView = bookshelfView === 'list'
|
|
||||||
this.bookshelfReady = true
|
this.bookshelfReady = true
|
||||||
this.$store.commit('setBookshelfView', bookshelfView)
|
|
||||||
},
|
},
|
||||||
settingsUpdated(settings) {
|
settingsUpdated(settings) {
|
||||||
for (const key in settings) {
|
for (const key in settings) {
|
||||||
|
|
|
@ -9,14 +9,12 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||||
def capacitor_pods
|
def capacitor_pods
|
||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCommunitySqlite', :path => '..\..\node_modules\@capacitor-community\sqlite'
|
|
||||||
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app'
|
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app'
|
||||||
pod 'CapacitorDialog', :path => '..\..\node_modules\@capacitor\dialog'
|
pod 'CapacitorDialog', :path => '..\..\node_modules\@capacitor\dialog'
|
||||||
pod 'CapacitorNetwork', :path => '..\..\node_modules\@capacitor\network'
|
pod 'CapacitorNetwork', :path => '..\..\node_modules\@capacitor\network'
|
||||||
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar'
|
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar'
|
||||||
pod 'CapacitorStorage', :path => '..\..\node_modules\@capacitor\storage'
|
pod 'CapacitorStorage', :path => '..\..\node_modules\@capacitor\storage'
|
||||||
pod 'RobingenzCapacitorAppUpdate', :path => '..\..\node_modules\@robingenz\capacitor-app-update'
|
pod 'RobingenzCapacitorAppUpdate', :path => '..\..\node_modules\@robingenz\capacitor-app-update'
|
||||||
pod 'CapacitorDataStorageSqlite', :path => '..\..\node_modules\capacitor-data-storage-sqlite'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'App' do
|
target 'App' do
|
||||||
|
|
|
@ -51,14 +51,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
currentUserAudiobookUpdate({ id, data }) {
|
|
||||||
if (data) {
|
|
||||||
console.log(`Current User Audiobook Updated ${id} ${JSON.stringify(data)}`)
|
|
||||||
this.$sqlStore.setUserAudiobookData(data)
|
|
||||||
} else {
|
|
||||||
this.$sqlStore.removeUserAudiobookData(id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initialStream(stream) {
|
initialStream(stream) {
|
||||||
if (this.$refs.streamContainer && this.$refs.streamContainer.audioPlayerReady) {
|
if (this.$refs.streamContainer && this.$refs.streamContainer.audioPlayerReady) {
|
||||||
this.$refs.streamContainer.streamOpen(stream)
|
this.$refs.streamContainer.streamOpen(stream)
|
||||||
|
@ -94,94 +86,6 @@ export default {
|
||||||
}, 5000)
|
}, 5000)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDownloadProgress(data) {
|
|
||||||
var progress = data.progress
|
|
||||||
var audiobookId = data.audiobookId
|
|
||||||
|
|
||||||
var downloadObj = this.$store.getters['downloads/getDownload'](audiobookId)
|
|
||||||
if (downloadObj) {
|
|
||||||
this.$toast.update(downloadObj.toastId, { content: `${progress}% Downloading ${downloadObj.audiobook.book.title}` })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDownloadFailed(data) {
|
|
||||||
if (!data.audiobookId) {
|
|
||||||
console.error('Download failed invalid audiobook id', data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var downloadObj = this.$store.getters['downloads/getDownload'](data.audiobookId)
|
|
||||||
if (!downloadObj) {
|
|
||||||
console.error('Failed to find download for audiobook', data.audiobookId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var message = data.error || 'Unknown Error'
|
|
||||||
this.$toast.update(downloadObj.toastId, { content: `Failed. ${message}.`, options: { timeout: 5000, type: 'error' } }, true)
|
|
||||||
this.$store.commit('downloads/removeDownload', downloadObj)
|
|
||||||
},
|
|
||||||
onDownloadComplete(data) {
|
|
||||||
if (!data.audiobookId) {
|
|
||||||
console.error('Download compelte invalid audiobook id', data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var downloadId = data.downloadId
|
|
||||||
var contentUrl = data.contentUrl
|
|
||||||
var folderUrl = data.folderUrl
|
|
||||||
var folderName = data.folderName
|
|
||||||
var storageId = data.storageId
|
|
||||||
var storageType = data.storageType
|
|
||||||
var simplePath = data.simplePath
|
|
||||||
var filename = data.filename
|
|
||||||
var audiobookId = data.audiobookId
|
|
||||||
var size = data.size || 0
|
|
||||||
var isCover = !!data.isCover
|
|
||||||
|
|
||||||
console.log(`Download complete "${contentUrl}" | ${filename} | DlId: ${downloadId} | Is Cover? ${isCover}`)
|
|
||||||
var downloadObj = this.$store.getters['downloads/getDownload'](audiobookId)
|
|
||||||
if (!downloadObj) {
|
|
||||||
console.error('Failed to find download for audiobook', audiobookId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCover) {
|
|
||||||
// Notify server to remove prepared download
|
|
||||||
if (this.$server.socket) {
|
|
||||||
this.$server.socket.emit('remove_download', audiobookId)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$toast.update(downloadObj.toastId, { content: `Success! ${downloadObj.audiobook.book.title} downloaded.`, options: { timeout: 5000, type: 'success' } }, true)
|
|
||||||
|
|
||||||
delete downloadObj.isDownloading
|
|
||||||
delete downloadObj.isPreparing
|
|
||||||
downloadObj.contentUrl = contentUrl
|
|
||||||
downloadObj.simplePath = simplePath
|
|
||||||
downloadObj.folderUrl = folderUrl
|
|
||||||
downloadObj.folderName = folderName
|
|
||||||
downloadObj.storageType = storageType
|
|
||||||
downloadObj.storageId = storageId
|
|
||||||
downloadObj.basePath = data.basePath || null
|
|
||||||
downloadObj.size = size
|
|
||||||
this.$store.commit('downloads/addUpdateDownload', downloadObj)
|
|
||||||
} else {
|
|
||||||
downloadObj.coverUrl = contentUrl
|
|
||||||
downloadObj.cover = Capacitor.convertFileSrc(contentUrl)
|
|
||||||
downloadObj.coverSize = size
|
|
||||||
downloadObj.coverBasePath = data.basePath || null
|
|
||||||
console.log('Updating download with cover', downloadObj.cover)
|
|
||||||
this.$store.commit('downloads/addUpdateDownload', downloadObj)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async checkLoadCurrent() {
|
|
||||||
var currentObj = await this.$localStore.getCurrent()
|
|
||||||
if (!currentObj) return
|
|
||||||
|
|
||||||
console.log('Has Current playing', currentObj.audiobookId)
|
|
||||||
var download = this.$store.getters['downloads/getDownload'](currentObj.audiobookId)
|
|
||||||
if (download) {
|
|
||||||
this.$store.commit('setPlayingDownload', download)
|
|
||||||
} else {
|
|
||||||
console.warn('Download not available for previous current playing', currentObj.audiobookId)
|
|
||||||
this.$localStore.setCurrent(null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async searchFolder(downloadFolder) {
|
async searchFolder(downloadFolder) {
|
||||||
try {
|
try {
|
||||||
var response = await StorageManager.searchFolder({ folderUrl: downloadFolder.uri })
|
var response = await StorageManager.searchFolder({ folderUrl: downloadFolder.uri })
|
||||||
|
@ -233,11 +137,6 @@ export default {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// // Match media scanned folders with books from server
|
|
||||||
// if (this.isSocketConnected) {
|
|
||||||
// await this.$store.dispatch('downloads/linkOrphanDownloads')
|
|
||||||
// }
|
|
||||||
// },
|
// },
|
||||||
onItemDownloadUpdate(data) {
|
onItemDownloadUpdate(data) {
|
||||||
console.log('ON ITEM DOWNLOAD UPDATE', JSON.stringify(data))
|
console.log('ON ITEM DOWNLOAD UPDATE', JSON.stringify(data))
|
||||||
|
@ -253,25 +152,6 @@ export default {
|
||||||
AudioDownloader.addListener('onItemDownloadComplete', (data) => {
|
AudioDownloader.addListener('onItemDownloadComplete', (data) => {
|
||||||
this.onItemDownloadComplete(data)
|
this.onItemDownloadComplete(data)
|
||||||
})
|
})
|
||||||
// AudioDownloader.addListener('onDownloadFailed', (data) => {
|
|
||||||
// this.onDownloadFailed(data)
|
|
||||||
// })
|
|
||||||
// AudioDownloader.addListener('onDownloadProgress', (data) => {
|
|
||||||
// this.onDownloadProgress(data)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// var downloads = await this.$store.dispatch('downloads/loadFromStorage')
|
|
||||||
// var downloadFolder = await this.$localStore.getDownloadFolder()
|
|
||||||
// this.$eventBus.$emit('downloads-loaded')
|
|
||||||
|
|
||||||
// var checkPermission = await StorageManager.checkStoragePermission()
|
|
||||||
// console.log('Storage Permission is' + checkPermission.value)
|
|
||||||
// if (!checkPermission.value) {
|
|
||||||
// console.log('Will require permissions')
|
|
||||||
// } else {
|
|
||||||
// console.log('Has Storage Permission')
|
|
||||||
// this.$store.commit('setHasStoragePermission', true)
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
async loadSavedSettings() {
|
async loadSavedSettings() {
|
||||||
var userSavedServerSettings = await this.$localStore.getServerSettings()
|
var userSavedServerSettings = await this.$localStore.getServerSettings()
|
||||||
|
@ -283,9 +163,6 @@ export default {
|
||||||
if (userSavedSettings) {
|
if (userSavedSettings) {
|
||||||
this.$store.commit('user/setSettings', userSavedSettings)
|
this.$store.commit('user/setSettings', userSavedSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Loading offline user audiobook data')
|
|
||||||
await this.$store.dispatch('user/loadOfflineUserAudiobookData')
|
|
||||||
},
|
},
|
||||||
async attemptConnection() {
|
async attemptConnection() {
|
||||||
if (!this.networkConnected) {
|
if (!this.networkConnected) {
|
||||||
|
@ -354,7 +231,6 @@ export default {
|
||||||
// this.$server.on('connected', this.connected)
|
// this.$server.on('connected', this.connected)
|
||||||
// this.$server.on('connectionFailed', this.socketConnectionFailed)
|
// this.$server.on('connectionFailed', this.socketConnectionFailed)
|
||||||
// this.$server.on('initialStream', this.initialStream)
|
// this.$server.on('initialStream', this.initialStream)
|
||||||
// this.$server.on('currentUserAudiobookUpdate', this.currentUserAudiobookUpdate)
|
|
||||||
// this.$server.on('show_error_toast', this.showErrorToast)
|
// this.$server.on('show_error_toast', this.showErrorToast)
|
||||||
// this.$server.on('show_success_toast', this.showSuccessToast)
|
// this.$server.on('show_success_toast', this.showSuccessToast)
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,6 @@ export default {
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
'@/plugins/server.js',
|
'@/plugins/server.js',
|
||||||
'@/plugins/sqlStore.js',
|
|
||||||
'@/plugins/db.js',
|
'@/plugins/db.js',
|
||||||
'@/plugins/localStore.js',
|
'@/plugins/localStore.js',
|
||||||
'@/plugins/init.client.js',
|
'@/plugins/init.client.js',
|
||||||
|
|
1102
package-lock.json
generated
1102
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,6 @@
|
||||||
"icons-android": "cordova-res android --skip-config --copy"
|
"icons-android": "cordova-res android --skip-config --copy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor-community/sqlite": "^3.2.0",
|
|
||||||
"@capacitor/android": "^3.2.2",
|
"@capacitor/android": "^3.2.2",
|
||||||
"@capacitor/app": "^1.0.7",
|
"@capacitor/app": "^1.0.7",
|
||||||
"@capacitor/cli": "^3.1.2",
|
"@capacitor/cli": "^3.1.2",
|
||||||
|
@ -22,11 +21,9 @@
|
||||||
"@capacitor/storage": "^1.1.0",
|
"@capacitor/storage": "^1.1.0",
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
"@robingenz/capacitor-app-update": "^1.0.0",
|
"@robingenz/capacitor-app-update": "^1.0.0",
|
||||||
"capacitor-data-storage-sqlite": "^3.2.0",
|
|
||||||
"core-js": "^3.15.1",
|
"core-js": "^3.15.1",
|
||||||
"date-fns": "^2.25.0",
|
"date-fns": "^2.25.0",
|
||||||
"epubjs": "^0.3.88",
|
"epubjs": "^0.3.88",
|
||||||
"hls.js": "^1.0.9",
|
|
||||||
"libarchive.js": "^1.3.0",
|
"libarchive.js": "^1.3.0",
|
||||||
"nuxt": "^2.15.7",
|
"nuxt": "^2.15.7",
|
||||||
"socket.io-client": "^4.1.3",
|
"socket.io-client": "^4.1.3",
|
||||||
|
|
|
@ -39,15 +39,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
books() {
|
|
||||||
return this.$store.getters['downloads/getDownloads'].map((dl) => {
|
|
||||||
var download = { ...dl }
|
|
||||||
var ab = { ...download.audiobook }
|
|
||||||
delete download.audiobook
|
|
||||||
ab.download = download
|
|
||||||
return ab
|
|
||||||
})
|
|
||||||
},
|
|
||||||
user() {
|
user() {
|
||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
},
|
},
|
||||||
|
@ -57,69 +48,6 @@ export default {
|
||||||
currentLibraryName() {
|
currentLibraryName() {
|
||||||
return this.$store.getters['libraries/getCurrentLibraryName']
|
return this.$store.getters['libraries/getCurrentLibraryName']
|
||||||
},
|
},
|
||||||
booksWithUserAbData() {
|
|
||||||
var books = this.books.map((b) => {
|
|
||||||
var userAbData = this.$store.getters['user/getUserAudiobookData'](b.id)
|
|
||||||
return { ...b, userAbData }
|
|
||||||
})
|
|
||||||
return books
|
|
||||||
},
|
|
||||||
booksCurrentlyReading() {
|
|
||||||
var books = this.booksWithUserAbData
|
|
||||||
.map((b) => ({ ...b }))
|
|
||||||
.filter((b) => b.userAbData && !b.userAbData.isRead && b.userAbData.progress > 0)
|
|
||||||
.sort((a, b) => {
|
|
||||||
return b.userAbData.lastUpdate - a.userAbData.lastUpdate
|
|
||||||
})
|
|
||||||
return books
|
|
||||||
},
|
|
||||||
booksRecentlyAdded() {
|
|
||||||
var books = this.books
|
|
||||||
.map((b) => {
|
|
||||||
return { ...b }
|
|
||||||
})
|
|
||||||
.sort((a, b) => b.addedAt - a.addedAt)
|
|
||||||
return books.slice(0, 10)
|
|
||||||
},
|
|
||||||
booksRead() {
|
|
||||||
var books = this.booksWithUserAbData
|
|
||||||
.filter((b) => b.userAbData && b.userAbData.isRead)
|
|
||||||
.sort((a, b) => {
|
|
||||||
return b.userAbData.lastUpdate - a.userAbData.lastUpdate
|
|
||||||
})
|
|
||||||
return books.slice(0, 10)
|
|
||||||
},
|
|
||||||
downloadOnlyShelves() {
|
|
||||||
var shelves = []
|
|
||||||
|
|
||||||
if (this.booksCurrentlyReading.length) {
|
|
||||||
shelves.push({
|
|
||||||
id: 'recent',
|
|
||||||
label: 'Continue Reading',
|
|
||||||
type: 'books',
|
|
||||||
entities: this.booksCurrentlyReading
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.booksRecentlyAdded.length) {
|
|
||||||
shelves.push({
|
|
||||||
id: 'added',
|
|
||||||
label: 'Recently Added',
|
|
||||||
type: 'books',
|
|
||||||
entities: this.booksRecentlyAdded
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.booksRead.length) {
|
|
||||||
shelves.push({
|
|
||||||
id: 'read',
|
|
||||||
label: 'Read Again',
|
|
||||||
type: 'books',
|
|
||||||
entities: this.booksRead
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return shelves
|
|
||||||
},
|
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
}
|
}
|
||||||
|
@ -195,11 +123,6 @@ export default {
|
||||||
await this.fetchCategories()
|
await this.fetchCategories()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// downloadsLoaded() {
|
|
||||||
// if (!this.isSocketConnected) {
|
|
||||||
// this.shelves = this.downloadOnlyShelves
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
audiobookAdded(audiobook) {
|
audiobookAdded(audiobook) {
|
||||||
console.log('Audiobook added', audiobook)
|
console.log('Audiobook added', audiobook)
|
||||||
// TODO: Check if audiobook would be on this shelf
|
// TODO: Check if audiobook would be on this shelf
|
||||||
|
|
|
@ -1,291 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full h-full py-6">
|
|
||||||
<h1 class="text-2xl px-4">Downloads</h1>
|
|
||||||
|
|
||||||
<div v-if="!isIos" class="w-full px-2 py-2">
|
|
||||||
<template v-for="folder in localFolders">
|
|
||||||
<div :key="folder.id" class="flex items-center p-2">
|
|
||||||
<div class="flex-grow">
|
|
||||||
<p>{{ folder.id }}|{{ folder.name }}|{{ folder.contentUrl }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-40">
|
|
||||||
<ui-btn @click="searchFolder(folder.id)">Scan</ui-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-if="!localFolders.length" class="flex justify-center">
|
|
||||||
<p class="text-center">No Media Folders</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!isIos" class="w-full px-2 py-2" :class="hasStoragePermission ? '' : 'text-error'">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="material-icons" @click="changeDownloadFolderClick">{{ hasStoragePermission ? 'folder' : 'error' }}</span>
|
|
||||||
<p v-if="hasStoragePermission" class="text-sm px-4" @click="changeDownloadFolderClick">{{ downloadFolderSimplePath || 'No Download Folder Selected' }}</p>
|
|
||||||
<p v-else class="text-sm px-4" @click="changeDownloadFolderClick">No Storage Permissions. Click here</p>
|
|
||||||
</div>
|
|
||||||
<!-- <p v-if="hasStoragePermission" class="text-xs text-gray-400 break-all max-w-full">{{ downloadFolderUri }}</p> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!isIos" class="w-full h-10 relative">
|
|
||||||
<div class="absolute top-px left-0 z-10 w-full h-full flex">
|
|
||||||
<div class="flex-grow h-full bg-primary rounded-t-md mr-px" @click="showingDownloads = true">
|
|
||||||
<div class="flex items-center justify-center rounded-t-md border-t border-l border-r border-white border-opacity-20 h-full" :class="showingDownloads ? 'text-gray-100' : 'border-b bg-black bg-opacity-20 text-gray-400'">
|
|
||||||
<p>Downloads</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow h-full bg-primary rounded-t-md ml-px" @click="showingDownloads = false">
|
|
||||||
<div class="flex items-center justify-center h-full rounded-t-md border-t border-l border-r border-white border-opacity-20" :class="!showingDownloads ? 'text-gray-100' : 'border-b bg-black bg-opacity-20 text-gray-400'">
|
|
||||||
<p>Files</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!isIos" class="list-content-body relative w-full overflow-x-hidden overflow-y-auto bg-primary">
|
|
||||||
<template v-if="showingDownloads">
|
|
||||||
<div v-if="!totalDownloads" class="flex items-center justify-center h-40">
|
|
||||||
<p>No Downloads</p>
|
|
||||||
</div>
|
|
||||||
<ul v-else class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
|
||||||
<li v-for="download in downloadsDownloading" :key="download.id" class="text-gray-400 select-none relative px-4 py-5 border-b border-white border-opacity-10 bg-black bg-opacity-10">
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<div class="w-3/4">
|
|
||||||
<span class="text-xs">({{ downloadingProgress[download.id] || 0 }}%) {{ download.isPreparing ? 'Preparing' : 'Downloading' }}...</span>
|
|
||||||
<p class="font-normal truncate text-sm">{{ download.audiobook.book.title }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow" />
|
|
||||||
|
|
||||||
<div class="shadow-sm text-white flex items-center justify-center rounded-full animate-spin">
|
|
||||||
<span class="material-icons">refresh</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li v-for="download in downloadsReady" :key="download.id" class="text-gray-50 select-none relative pr-4 pl-2 py-5 border-b border-white border-opacity-10" @click="jumpToAudiobook(download)">
|
|
||||||
<modals-downloads-download-item :download="download" @play="playDownload" @delete="clickDeleteDownload" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="w-full h-full">
|
|
||||||
<div class="w-full flex justify-around py-4 px-2">
|
|
||||||
<!-- <ui-btn small @click="searchFolder">Re-Scan</ui-btn> -->
|
|
||||||
<!-- <ui-btn small @click="changeDownloadFolderClick">Change Folder</ui-btn> -->
|
|
||||||
<ui-btn small color="error" @click="resetFolder">Reset</ui-btn>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Temp testing new folder scan results -->
|
|
||||||
<div v-for="mediaItem in localMediaItems" :key="mediaItem.contentUrl" class="flex py-2">
|
|
||||||
<div class="w-12 h-12 bg-primary">
|
|
||||||
<img v-if="mediaItem.coverPathSrc" :src="mediaItem.coverPathSrc" class="w-full h-full object-contain" />
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow px-2">
|
|
||||||
<p>{{ mediaItem.name }}</p>
|
|
||||||
<p>{{ mediaItem.audioTracks.length }} Tracks</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p v-if="isScanning" class="text-center my-8">Scanning Folder..</p>
|
|
||||||
<p v-else-if="!mediaScanResults" class="text-center my-8">No Files Found</p>
|
|
||||||
<div v-else>
|
|
||||||
<div v-for="mediaFolder in mediaScanResults.folders" :key="mediaFolder.uri" class="w-full px-2 py-2">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="material-icons text-base text-white text-opacity-50">folder</span>
|
|
||||||
<p class="ml-1 py-0.5">{{ mediaFolder.name }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-for="mediaFile in mediaFolder.files" :key="mediaFile.uri" class="ml-3 flex items-center">
|
|
||||||
<span class="material-icons text-base text-white text-opacity-50">{{ mediaFile.isAudio ? 'music_note' : 'image' }}</span>
|
|
||||||
<p class="ml-1 py-0.5">{{ mediaFile.name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-for="mediaFile in mediaScanResults.files" :key="mediaFile.uri" class="w-full px-2 py-2">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="material-icons text-base text-white text-opacity-50">{{ mediaFile.isAudio ? 'music_note' : 'image' }}</span>
|
|
||||||
<p class="ml-1 py-0.5">{{ mediaFile.name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { Capacitor } from '@capacitor/core'
|
|
||||||
import { Dialog } from '@capacitor/dialog'
|
|
||||||
import AudioDownloader from '@/plugins/audio-downloader'
|
|
||||||
import StorageManager from '@/plugins/storage-manager'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
downloadingProgress: {},
|
|
||||||
totalSize: 0,
|
|
||||||
showingDownloads: true,
|
|
||||||
isScanning: false,
|
|
||||||
localMediaItems: [],
|
|
||||||
localFolders: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isIos() {
|
|
||||||
return this.$platform === 'ios'
|
|
||||||
},
|
|
||||||
isSocketConnected() {
|
|
||||||
return this.$store.state.socketConnected
|
|
||||||
},
|
|
||||||
hasStoragePermission() {
|
|
||||||
return this.$store.state.hasStoragePermission
|
|
||||||
},
|
|
||||||
downloadFolder() {
|
|
||||||
return this.$store.state.downloadFolder
|
|
||||||
},
|
|
||||||
downloadFolderSimplePath() {
|
|
||||||
return this.downloadFolder ? this.downloadFolder.simplePath : null
|
|
||||||
},
|
|
||||||
downloadFolderUri() {
|
|
||||||
return this.downloadFolder ? this.downloadFolder.uri : null
|
|
||||||
},
|
|
||||||
totalDownloads() {
|
|
||||||
return this.downloadsReady.length + this.downloadsDownloading.length
|
|
||||||
},
|
|
||||||
downloadsDownloading() {
|
|
||||||
return this.downloads.filter((d) => d.isDownloading || d.isPreparing)
|
|
||||||
},
|
|
||||||
downloadsReady() {
|
|
||||||
return this.downloads.filter((d) => !d.isDownloading && !d.isPreparing)
|
|
||||||
},
|
|
||||||
downloads() {
|
|
||||||
return this.$store.state.downloads.downloads
|
|
||||||
},
|
|
||||||
mediaScanResults() {
|
|
||||||
return this.$store.state.downloads.mediaScanResults
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async changeDownloadFolderClick() {
|
|
||||||
if (!this.hasStoragePermission) {
|
|
||||||
StorageManager.requestStoragePermission()
|
|
||||||
} else {
|
|
||||||
var folderObj = await StorageManager.selectFolder({ mediaType: 'book' })
|
|
||||||
if (folderObj.error) {
|
|
||||||
return this.$toast.error(`Error: ${folderObj.error || 'Unknown Error'}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexOfExisting = this.localFolders.findIndex((lf) => lf.id == folderObj.id)
|
|
||||||
if (indexOfExisting >= 0) {
|
|
||||||
this.localFolders.splice(indexOfExisting, 1, folderObj)
|
|
||||||
} else {
|
|
||||||
this.localFolders.push(folderObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
var permissionsGood = await StorageManager.checkFolderPermissions({ folderUrl: folderObj.contentUrl })
|
|
||||||
|
|
||||||
if (!permissionsGood) {
|
|
||||||
this.$toast.error('Folder permissions failed')
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
this.$toast.success('Folder permission success')
|
|
||||||
}
|
|
||||||
|
|
||||||
// await this.$localStore.setDownloadFolder(folderObj)
|
|
||||||
await this.searchFolder(folderObj.id)
|
|
||||||
|
|
||||||
if (this.isSocketConnected) {
|
|
||||||
this.$store.dispatch('downloads/linkOrphanDownloads')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async searchFolder(folderId) {
|
|
||||||
this.isScanning = true
|
|
||||||
var response = await StorageManager.searchFolder({ folderId })
|
|
||||||
|
|
||||||
if (response && response.localMediaItems) {
|
|
||||||
this.localMediaItems = response.localMediaItems.map((mi) => {
|
|
||||||
if (mi.coverPath) {
|
|
||||||
mi.coverPathSrc = Capacitor.convertFileSrc(mi.coverPath)
|
|
||||||
}
|
|
||||||
return mi
|
|
||||||
})
|
|
||||||
console.log('Set Local Media Items', this.localMediaItems.length)
|
|
||||||
} else {
|
|
||||||
console.log('No Local media items found')
|
|
||||||
}
|
|
||||||
// var searchResults = response
|
|
||||||
// searchResults.folders = JSON.parse(searchResults.folders)
|
|
||||||
// searchResults.files = JSON.parse(searchResults.files)
|
|
||||||
|
|
||||||
// if (searchResults.folders.length) {
|
|
||||||
// console.log('Search results folders length', searchResults.folders.length)
|
|
||||||
|
|
||||||
// searchResults.folders = searchResults.folders.map((sr) => {
|
|
||||||
// if (sr.files) {
|
|
||||||
// sr.files = JSON.parse(sr.files)
|
|
||||||
// }
|
|
||||||
// return sr
|
|
||||||
// })
|
|
||||||
// this.$store.commit('downloads/setMediaScanResults', searchResults)
|
|
||||||
// } else {
|
|
||||||
// this.$toast.warning('No audio or image files found')
|
|
||||||
// }
|
|
||||||
this.isScanning = false
|
|
||||||
},
|
|
||||||
async resetFolder() {
|
|
||||||
await this.$localStore.setDownloadFolder(null)
|
|
||||||
this.$store.commit('downloads/setMediaScanResults', {})
|
|
||||||
this.$toast.info('Unlinked Folder')
|
|
||||||
},
|
|
||||||
jumpToAudiobook(download) {
|
|
||||||
this.show = false
|
|
||||||
this.$router.push(`/audiobook/${download.id}`)
|
|
||||||
},
|
|
||||||
async clickDeleteDownload(download) {
|
|
||||||
const { value } = await Dialog.confirm({
|
|
||||||
title: 'Confirm',
|
|
||||||
message: 'Delete this download?'
|
|
||||||
})
|
|
||||||
if (value) {
|
|
||||||
this.deleteDownload(download)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
playDownload(download) {
|
|
||||||
this.$store.commit('setPlayOnLoad', true)
|
|
||||||
this.$store.commit('setPlayingDownload', download)
|
|
||||||
this.show = false
|
|
||||||
},
|
|
||||||
async deleteDownload(download) {
|
|
||||||
console.log('Delete download', download.filename)
|
|
||||||
|
|
||||||
if (this.$store.state.playingDownload && this.$store.state.playingDownload.id === download.id) {
|
|
||||||
console.warn('Deleting download when currently playing download - terminate play')
|
|
||||||
if (this.$refs.streamContainer) {
|
|
||||||
this.$refs.streamContainer.cancelStream()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (download.contentUrl) {
|
|
||||||
await StorageManager.delete(download)
|
|
||||||
}
|
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
|
||||||
},
|
|
||||||
onDownloadProgress(data) {
|
|
||||||
var progress = data.progress
|
|
||||||
var audiobookId = data.audiobookId
|
|
||||||
|
|
||||||
var downloadObj = this.$store.getters['downloads/getDownload'](audiobookId)
|
|
||||||
if (downloadObj) {
|
|
||||||
this.$set(this.downloadingProgress, audiobookId, progress)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async init() {
|
|
||||||
this.localFolders = (await this.$db.getLocalFolders()) || []
|
|
||||||
AudioDownloader.addListener('onDownloadProgress', this.onDownloadProgress)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
AudioDownloader.removeListener('onDownloadProgress', this.onDownloadProgress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -66,7 +66,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Path from 'path'
|
|
||||||
import { Dialog } from '@capacitor/dialog'
|
import { Dialog } from '@capacitor/dialog'
|
||||||
import AudioDownloader from '@/plugins/audio-downloader'
|
import AudioDownloader from '@/plugins/audio-downloader'
|
||||||
import StorageManager from '@/plugins/storage-manager'
|
import StorageManager from '@/plugins/storage-manager'
|
||||||
|
@ -197,19 +196,10 @@ export default {
|
||||||
if (!this.ebookFile) return null
|
if (!this.ebookFile) return null
|
||||||
return this.ebookFile.ebookFormat
|
return this.ebookFile.ebookFormat
|
||||||
},
|
},
|
||||||
// isDownloadPreparing() {
|
|
||||||
// return this.downloadObj ? this.downloadObj.isPreparing : false
|
|
||||||
// },
|
|
||||||
isDownloadPlayable() {
|
isDownloadPlayable() {
|
||||||
return false
|
return false
|
||||||
// return this.downloadObj && !this.isDownloading && !this.isDownloadPreparing
|
// return this.downloadObj && !this.isDownloading && !this.isDownloadPreparing
|
||||||
},
|
},
|
||||||
// downloadedCover() {
|
|
||||||
// return this.downloadObj ? this.downloadObj.cover : null
|
|
||||||
// },
|
|
||||||
// downloadObj() {
|
|
||||||
// return this.$store.getters['downloads/getDownload'](this.libraryItemId)
|
|
||||||
// },
|
|
||||||
hasStoragePermission() {
|
hasStoragePermission() {
|
||||||
return this.$store.state.hasStoragePermission
|
return this.$store.state.hasStoragePermission
|
||||||
}
|
}
|
||||||
|
@ -220,25 +210,8 @@ export default {
|
||||||
},
|
},
|
||||||
playClick() {
|
playClick() {
|
||||||
this.$eventBus.$emit('play-item', this.libraryItem.id)
|
this.$eventBus.$emit('play-item', this.libraryItem.id)
|
||||||
|
|
||||||
// this.$store.commit('setPlayOnLoad', true)
|
|
||||||
// if (!this.isDownloadPlayable) {
|
|
||||||
// Stream
|
|
||||||
// console.log('[PLAYCLICK] Set Playing STREAM ' + this.title)
|
|
||||||
// this.$store.commit('setStreamAudiobook', this.libraryItem)
|
|
||||||
// this.$server.socket.emit('open_stream', this.libraryItem.id)
|
|
||||||
// } else {
|
|
||||||
// Local
|
|
||||||
// console.log('[PLAYCLICK] Set Playing Local Download ' + this.title)
|
|
||||||
// this.$store.commit('setPlayingDownload', this.downloadObj)
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
async clearProgressClick() {
|
async clearProgressClick() {
|
||||||
// if (!this.$server.connected) {
|
|
||||||
// this.$toast.info('Clear downloaded book progress not yet implemented')
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
const { value } = await Dialog.confirm({
|
const { value } = await Dialog.confirm({
|
||||||
title: 'Confirm',
|
title: 'Confirm',
|
||||||
message: 'Are you sure you want to reset your progress?'
|
message: 'Are you sure you want to reset your progress?'
|
||||||
|
@ -327,33 +300,9 @@ export default {
|
||||||
if (downloadRes.error) {
|
if (downloadRes.error) {
|
||||||
var errorMsg = downloadRes.error || 'Unknown error'
|
var errorMsg = downloadRes.error || 'Unknown error'
|
||||||
console.error('Download error', errorMsg)
|
console.error('Download error', errorMsg)
|
||||||
this.$toast.update(download.toastId, { content: `Error: ${errorMsg}`, options: { timeout: 5000, type: 'error' } })
|
this.$toast.error(errorMsg)
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async changeDownloadFolderClick() {
|
|
||||||
if (!this.hasStoragePermission) {
|
|
||||||
console.log('Requesting Storage Permission')
|
|
||||||
await StorageManager.requestStoragePermission()
|
|
||||||
} else {
|
|
||||||
var folderObj = await StorageManager.selectFolder()
|
|
||||||
if (folderObj.error) {
|
|
||||||
return this.$toast.error(`Error: ${folderObj.error || 'Unknown Error'}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var permissionsGood = await StorageManager.checkFolderPermissions({ folderUrl: folderObj.uri })
|
|
||||||
console.log('Storage Permission check folder ' + permissionsGood)
|
|
||||||
|
|
||||||
if (!permissionsGood) {
|
|
||||||
this.$toast.error('Folder permissions failed')
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
this.$toast.success('Folder permission success')
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.$localStore.setDownloadFolder(folderObj)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// async prepareDownload() {
|
// async prepareDownload() {
|
||||||
// var audiobook = this.libraryItem
|
// var audiobook = this.libraryItem
|
||||||
// if (!audiobook) {
|
// if (!audiobook) {
|
||||||
|
@ -451,34 +400,34 @@ export default {
|
||||||
// this.$store.commit('downloads/removeDownload', download)
|
// this.$store.commit('downloads/removeDownload', download)
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
downloadReady(prepareDownload) {
|
// downloadReady(prepareDownload) {
|
||||||
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
// var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
||||||
if (download) {
|
// if (download) {
|
||||||
var fileext = prepareDownload.ext
|
// var fileext = prepareDownload.ext
|
||||||
var url = `${this.$store.state.serverUrl}/downloads/${prepareDownload.id}/${prepareDownload.filename}?token=${this.userToken}`
|
// var url = `${this.$store.state.serverUrl}/downloads/${prepareDownload.id}/${prepareDownload.filename}?token=${this.userToken}`
|
||||||
this.startDownload(url, fileext, download)
|
// this.startDownload(url, fileext, download)
|
||||||
} else {
|
// } else {
|
||||||
console.error('Prepare download killed but download not found', prepareDownload)
|
// console.error('Prepare download killed but download not found', prepareDownload)
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
downloadKilled(prepareDownload) {
|
// downloadKilled(prepareDownload) {
|
||||||
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
// var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
||||||
if (download) {
|
// if (download) {
|
||||||
this.$toast.update(download.toastId, { content: `Prepare download killed for "${download.audiobook.book.title}"`, options: { timeout: 5000, type: 'error' } })
|
// this.$toast.update(download.toastId, { content: `Prepare download killed for "${download.audiobook.book.title}"`, options: { timeout: 5000, type: 'error' } })
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
// this.$store.commit('downloads/removeDownload', download)
|
||||||
} else {
|
// } else {
|
||||||
console.error('Prepare download killed but download not found', prepareDownload)
|
// console.error('Prepare download killed but download not found', prepareDownload)
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
downloadFailed(prepareDownload) {
|
// downloadFailed(prepareDownload) {
|
||||||
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
// var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
||||||
if (download) {
|
// if (download) {
|
||||||
this.$toast.update(download.toastId, { content: `Prepare download failed for "${download.audiobook.book.title}"`, options: { timeout: 5000, type: 'error' } })
|
// this.$toast.update(download.toastId, { content: `Prepare download failed for "${download.audiobook.book.title}"`, options: { timeout: 5000, type: 'error' } })
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
// this.$store.commit('downloads/removeDownload', download)
|
||||||
} else {
|
// } else {
|
||||||
console.error('Prepare download failed but download not found', prepareDownload)
|
// console.error('Prepare download failed but download not found', prepareDownload)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// if (!this.$server.socket) {
|
// if (!this.$server.socket) {
|
||||||
|
|
63
plugins/capacitor/DbManager.js
Normal file
63
plugins/capacitor/DbManager.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { registerPlugin, Capacitor, WebPlugin } from '@capacitor/core';
|
||||||
|
|
||||||
|
class DbWeb extends WebPlugin {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDeviceData_WV() {
|
||||||
|
var dd = localStorage.getItem('device')
|
||||||
|
if (dd) {
|
||||||
|
return JSON.parse(dd)
|
||||||
|
}
|
||||||
|
const deviceData = {
|
||||||
|
serverConnectionConfigs: [],
|
||||||
|
lastServerConnectionConfigId: null,
|
||||||
|
localLibraryItemIdPlaying: null
|
||||||
|
}
|
||||||
|
return deviceData
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCurrentServerConnectionConfig_WV(serverConnectionConfig) {
|
||||||
|
var deviceData = await this.getDeviceData_WV()
|
||||||
|
|
||||||
|
var ssc = deviceData.serverConnectionConfigs.find(_ssc => _ssc.id == serverConnectionConfig.id)
|
||||||
|
if (ssc) {
|
||||||
|
deviceData.lastServerConnectionConfigId = ssc.id
|
||||||
|
ssc.name = `${ssc.address} (${serverConnectionConfig.username})`
|
||||||
|
ssc.token = serverConnectionConfig.token
|
||||||
|
ssc.username = serverConnectionConfig.username
|
||||||
|
localStorage.setItem('device', JSON.stringify(deviceData))
|
||||||
|
} else {
|
||||||
|
ssc = {
|
||||||
|
id: encodeURIComponent(Buffer.from(`${serverConnectionConfig.address}@${serverConnectionConfig.username}`).toString('base64')),
|
||||||
|
index: deviceData.serverConnectionConfigs.length,
|
||||||
|
name: `${serverConnectionConfig.address} (${serverConnectionConfig.username})`,
|
||||||
|
username: serverConnectionConfig.username,
|
||||||
|
address: serverConnectionConfig.address,
|
||||||
|
token: serverConnectionConfig.token
|
||||||
|
}
|
||||||
|
deviceData.serverConnectionConfigs.push(ssc)
|
||||||
|
deviceData.lastServerConnectionConfigId = ssc.id
|
||||||
|
localStorage.setItem('device', JSON.stringify(deviceData))
|
||||||
|
}
|
||||||
|
return ssc
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeServerConnectionConfig_WV(serverConnectionConfigCallObject) {
|
||||||
|
var serverConnectionConfigId = serverConnectionConfigCallObject.serverConnectionConfigId
|
||||||
|
var deviceData = await this.getDeviceData_WV()
|
||||||
|
deviceData.serverConnectionConfigs = deviceData.serverConnectionConfigs.filter(ssc => ssc.id == serverConnectionConfigId)
|
||||||
|
localStorage.setItem('device', JSON.stringify(deviceData))
|
||||||
|
}
|
||||||
|
|
||||||
|
logout_WV() {
|
||||||
|
// Nothing to do on web
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DbManager = registerPlugin('DbManager', {
|
||||||
|
web: () => new DbWeb()
|
||||||
|
})
|
||||||
|
|
||||||
|
export { DbManager }
|
|
@ -1,7 +1,7 @@
|
||||||
import { registerPlugin, Capacitor } from '@capacitor/core';
|
import { Capacitor } from '@capacitor/core';
|
||||||
|
import { DbManager } from './capacitor/DbManager'
|
||||||
|
|
||||||
const isWeb = Capacitor.getPlatform() == 'web'
|
const isWeb = Capacitor.getPlatform() == 'web'
|
||||||
const DbManager = registerPlugin('DbManager')
|
|
||||||
|
|
||||||
class DbService {
|
class DbService {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
@ -27,7 +27,6 @@ class DbService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceData() {
|
getDeviceData() {
|
||||||
if (isWeb) return {}
|
|
||||||
return DbManager.getDeviceData_WV().then((data) => {
|
return DbManager.getDeviceData_WV().then((data) => {
|
||||||
console.log('Loaded device data', JSON.stringify(data))
|
console.log('Loaded device data', JSON.stringify(data))
|
||||||
return data
|
return data
|
||||||
|
@ -35,7 +34,6 @@ class DbService {
|
||||||
}
|
}
|
||||||
|
|
||||||
setServerConnectionConfig(serverConnectionConfig) {
|
setServerConnectionConfig(serverConnectionConfig) {
|
||||||
if (isWeb) return null
|
|
||||||
return DbManager.setCurrentServerConnectionConfig_WV(serverConnectionConfig).then((data) => {
|
return DbManager.setCurrentServerConnectionConfig_WV(serverConnectionConfig).then((data) => {
|
||||||
console.log('Set server connection config', JSON.stringify(data))
|
console.log('Set server connection config', JSON.stringify(data))
|
||||||
return data
|
return data
|
||||||
|
@ -43,7 +41,6 @@ class DbService {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeServerConnectionConfig(serverConnectionConfigId) {
|
removeServerConnectionConfig(serverConnectionConfigId) {
|
||||||
if (isWeb) return null
|
|
||||||
return DbManager.removeServerConnectionConfig_WV({ serverConnectionConfigId }).then((data) => {
|
return DbManager.removeServerConnectionConfig_WV({ serverConnectionConfigId }).then((data) => {
|
||||||
console.log('Removed server connection config', serverConnectionConfigId)
|
console.log('Removed server connection config', serverConnectionConfigId)
|
||||||
return true
|
return true
|
||||||
|
@ -51,7 +48,6 @@ class DbService {
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
if (isWeb) return null
|
|
||||||
return DbManager.logout_WV()
|
return DbManager.logout_WV()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,39 +3,6 @@ import { Storage } from '@capacitor/storage'
|
||||||
class LocalStorage {
|
class LocalStorage {
|
||||||
constructor(vuexStore) {
|
constructor(vuexStore) {
|
||||||
this.vuexStore = vuexStore
|
this.vuexStore = vuexStore
|
||||||
|
|
||||||
this.userAudiobooksLoaded = false
|
|
||||||
this.downloadFolder = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDownloadFolder(folderObj) {
|
|
||||||
try {
|
|
||||||
if (folderObj) {
|
|
||||||
await Storage.set({ key: 'downloadFolder', value: JSON.stringify(folderObj) })
|
|
||||||
this.downloadFolder = folderObj
|
|
||||||
this.vuexStore.commit('setDownloadFolder', { ...this.downloadFolder })
|
|
||||||
} else {
|
|
||||||
await Storage.remove({ key: 'downloadFolder' })
|
|
||||||
this.downloadFolder = null
|
|
||||||
this.vuexStore.commit('setDownloadFolder', null)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to set download folder', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDownloadFolder() {
|
|
||||||
try {
|
|
||||||
var _value = (await Storage.get({ key: 'downloadFolder' }) || {}).value || null
|
|
||||||
if (!_value) return null
|
|
||||||
this.downloadFolder = JSON.parse(_value)
|
|
||||||
this.vuexStore.commit('setDownloadFolder', { ...this.downloadFolder })
|
|
||||||
return this.downloadFolder
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to get download folder', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserSettings(settings) {
|
async setUserSettings(settings) {
|
||||||
|
@ -75,46 +42,6 @@ class LocalStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCurrent(current) {
|
|
||||||
try {
|
|
||||||
if (current) {
|
|
||||||
await Storage.set({ key: 'current', value: JSON.stringify(current) })
|
|
||||||
} else {
|
|
||||||
await Storage.remove({ key: 'current' })
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to set current', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCurrent() {
|
|
||||||
try {
|
|
||||||
var currentObj = await Storage.get({ key: 'current' }) || {}
|
|
||||||
return currentObj.value ? JSON.parse(currentObj.value) : null
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to get current', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setBookshelfView(view) {
|
|
||||||
try {
|
|
||||||
await Storage.set({ key: 'bookshelfView', value: view })
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to set bookshelf view', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getBookshelfView() {
|
|
||||||
try {
|
|
||||||
var view = await Storage.get({ key: 'bookshelfView' }) || {}
|
|
||||||
return view.value || null
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to get bookshelf view', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setUseChapterTrack(useChapterTrack) {
|
async setUseChapterTrack(useChapterTrack) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' })
|
await Storage.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' })
|
||||||
|
|
|
@ -37,6 +37,11 @@ class ServerSocket extends EventEmitter {
|
||||||
this.socket.on('connect', this.onConnect.bind(this))
|
this.socket.on('connect', this.onConnect.bind(this))
|
||||||
this.socket.on('disconnect', this.onDisconnect.bind(this))
|
this.socket.on('disconnect', this.onDisconnect.bind(this))
|
||||||
this.socket.on('init', this.onInit.bind(this))
|
this.socket.on('init', this.onInit.bind(this))
|
||||||
|
|
||||||
|
this.socket.onAny((evt, args) => {
|
||||||
|
console.log(`[SOCKET] ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnect() {
|
onConnect() {
|
||||||
|
|
|
@ -1,555 +0,0 @@
|
||||||
import { Capacitor } from '@capacitor/core';
|
|
||||||
import { CapacitorDataStorageSqlite } from 'capacitor-data-storage-sqlite';
|
|
||||||
|
|
||||||
class StoreService {
|
|
||||||
store
|
|
||||||
platform
|
|
||||||
isOpen = false
|
|
||||||
|
|
||||||
constructor(vuexStore) {
|
|
||||||
this.vuexStore = vuexStore
|
|
||||||
this.currentTable = null
|
|
||||||
|
|
||||||
this.lockWaitQueue = []
|
|
||||||
this.isLocked = false
|
|
||||||
this.lockedFor = null
|
|
||||||
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin Initialization
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this.platform = Capacitor.getPlatform()
|
|
||||||
this.store = CapacitorDataStorageSqlite
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a Store
|
|
||||||
* @param _dbName string optional
|
|
||||||
* @param _table string optional
|
|
||||||
* @param _encrypted boolean optional
|
|
||||||
* @param _mode string optional
|
|
||||||
*/
|
|
||||||
async openStore(_dbName, _table, _encrypted, _mode) {
|
|
||||||
if (this.store != null) {
|
|
||||||
const database = _dbName ? _dbName : "storage"
|
|
||||||
const table = _table ? _table : "storage_table"
|
|
||||||
const encrypted = _encrypted ? _encrypted : false
|
|
||||||
const mode = _mode ? _mode : "no-encryption"
|
|
||||||
|
|
||||||
this.isOpen = false
|
|
||||||
try {
|
|
||||||
await this.store.openStore({ database, table, encrypted, mode })
|
|
||||||
// return Promise.resolve()
|
|
||||||
this.currentTable = table
|
|
||||||
this.isOpen = true
|
|
||||||
return true
|
|
||||||
} catch (err) {
|
|
||||||
// return Promise.reject(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// return Promise.reject(new Error("openStore: Store not opened"))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close a store
|
|
||||||
* @param dbName
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async closeStore(dbName) {
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
await this.store.closeStore({ database: dbName })
|
|
||||||
return Promise.resolve()
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("close: Store not opened"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a store is opened
|
|
||||||
* @param dbName
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async isStoreOpen(dbName) {
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
const ret = await this.store.isStoreOpen({ database: dbName })
|
|
||||||
return Promise.resolve(ret)
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("isStoreOpen: Store not opened"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Check if a store already exists
|
|
||||||
* @param dbName
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async isStoreExists(dbName) {
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
const ret = await this.store.isStoreExists({ database: dbName })
|
|
||||||
return Promise.resolve(ret)
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("isStoreExists: Store not opened"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create/Set a Table
|
|
||||||
* @param table string
|
|
||||||
*/
|
|
||||||
async setTable(table) {
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
await this.store.setTable({ table })
|
|
||||||
this.currentTable = table
|
|
||||||
return Promise.resolve()
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("setTable: Store not opened"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set of Key
|
|
||||||
* @param key string
|
|
||||||
* @param value string
|
|
||||||
*/
|
|
||||||
async setItem(key, value) {
|
|
||||||
if (this.store != null) {
|
|
||||||
if (key.length > 0) {
|
|
||||||
try {
|
|
||||||
await this.store.set({ key, value });
|
|
||||||
return Promise.resolve();
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("setItem: Must give a key"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("setItem: Store not opened"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get the Value for a given Key
|
|
||||||
* @param key string
|
|
||||||
*/
|
|
||||||
async getItem(key) {
|
|
||||||
if (this.store != null) {
|
|
||||||
if (key.length > 0) {
|
|
||||||
try {
|
|
||||||
const { value } = await this.store.get({ key });
|
|
||||||
console.log("in getItem value ", value)
|
|
||||||
return Promise.resolve(value);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`in getItem key: ${key} err: ${JSON.stringify(err)}`)
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("getItem: Must give a key"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("getItem: Store not opened"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
async isKey(key) {
|
|
||||||
if (this.store != null) {
|
|
||||||
if (key.length > 0) {
|
|
||||||
try {
|
|
||||||
const { result } = await this.store.iskey({ key });
|
|
||||||
return Promise.resolve(result);
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("isKey: Must give a key"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("isKey: Store not opened"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllKeysValues() {
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
const { keysvalues } = await this.store.keysvalues();
|
|
||||||
return Promise.resolve(keysvalues);
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("getAllKeysValues: Store not opened"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async removeItem(key) {
|
|
||||||
if (this.store != null) {
|
|
||||||
if (key.length > 0) {
|
|
||||||
try {
|
|
||||||
await this.store.remove({ key });
|
|
||||||
return Promise.resolve();
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("removeItem: Must give a key"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("removeItem: Store not opened"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async clear() {
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
await this.store.clear()
|
|
||||||
return true
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[SqlStore] Failed to clear table', err.message)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('[SqlStore] Clear: Store not opened')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async deleteStore(_dbName) {
|
|
||||||
const database = _dbName ? _dbName : "storage"
|
|
||||||
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
await this.store.deleteStore({ database })
|
|
||||||
return Promise.resolve();
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err.message)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("deleteStore: Store not opened"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async isTable(table) {
|
|
||||||
if (this.store != null) {
|
|
||||||
if (table.length > 0) {
|
|
||||||
try {
|
|
||||||
const { result } = await this.store.isTable({ table });
|
|
||||||
return Promise.resolve(result);
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("isTable: Must give a table"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("isTable: Store not opened"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async getAllTables() {
|
|
||||||
if (this.store != null) {
|
|
||||||
try {
|
|
||||||
const { tables } = await this.store.tables();
|
|
||||||
return Promise.resolve(tables);
|
|
||||||
} catch (err) {
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Promise.reject(new Error("getAllTables: Store not opened"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getLockId(prefix) {
|
|
||||||
return prefix + '-' + Math.floor(Math.random() * 100000000).toString(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForLock(id, count = 0) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!this.lockWaitQueue.includes(id)) {
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
if (count > 200) {
|
|
||||||
console.error('[SqlStore] Lock was never released', id)
|
|
||||||
resolve(false)
|
|
||||||
} else {
|
|
||||||
resolve(this.waitForLock(id, ++count))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setLock(prefix) {
|
|
||||||
this.lockedFor = prefix
|
|
||||||
this.isLocked = true
|
|
||||||
console.log('[SqlStore] Locked for', this.lockedFor)
|
|
||||||
}
|
|
||||||
|
|
||||||
initWaitLock(prefix) {
|
|
||||||
var lockId = this.getLockId(prefix)
|
|
||||||
this.lockWaitQueue.push(lockId)
|
|
||||||
console.log('[SqlStore] Waiting for lock', lockId, 'In queue', this.lockWaitQueue.length)
|
|
||||||
return this.waitForLock(lockId)
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseLock() {
|
|
||||||
console.log('[SqlStore] Releasing lock', this.lockedFor)
|
|
||||||
if (!this.lockWaitQueue.length) {
|
|
||||||
console.log('[SqlStore] Release Lock no queue')
|
|
||||||
this.isLocked = false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log('[SqlStore] Release Lock Queue:', this.lockWaitQueue.length)
|
|
||||||
var task = this.lockWaitQueue.shift()
|
|
||||||
console.log('[SqlStore] Released lock next task', task)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ensureTable(tablename) {
|
|
||||||
if (!this.isOpen) {
|
|
||||||
var success = await this.openStore('storage', tablename)
|
|
||||||
if (!success) {
|
|
||||||
console.error('Store failed to open')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this.setTable(tablename)
|
|
||||||
console.log('[SqlStore] Set Table ' + this.currentTable)
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set table', error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDownload(download) {
|
|
||||||
if (!download) return false
|
|
||||||
|
|
||||||
if (this.isLocked) {
|
|
||||||
await this.initWaitLock('setdl')
|
|
||||||
} else {
|
|
||||||
this.setLock('setdl')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.ensureTable('downloads'))) {
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!download.id) {
|
|
||||||
console.error(`[SqlStore] set download invalid download ${download ? JSON.stringify(download) : 'null'}`)
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var success = false
|
|
||||||
try {
|
|
||||||
await this.setItem(download.id, JSON.stringify(download))
|
|
||||||
console.log(`[STORE] Set Download ${download.id}`)
|
|
||||||
success = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set download in store', error)
|
|
||||||
}
|
|
||||||
this.releaseLock()
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeDownload(id) {
|
|
||||||
if (!id) return false
|
|
||||||
|
|
||||||
if (this.isLocked) {
|
|
||||||
await this.initWaitLock('remdl')
|
|
||||||
} else {
|
|
||||||
this.setLock('remdl')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.ensureTable('downloads'))) {
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var success = false
|
|
||||||
try {
|
|
||||||
await this.removeItem(id)
|
|
||||||
console.log(`[STORE] Removed download ${id}`)
|
|
||||||
success = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to remove download in store', error)
|
|
||||||
}
|
|
||||||
this.releaseLock()
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllDownloads() {
|
|
||||||
if (this.isLocked) {
|
|
||||||
await this.initWaitLock('alldl')
|
|
||||||
} else {
|
|
||||||
this.setLock('alldl')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.ensureTable('downloads'))) {
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var keysvalues = await this.getAllKeysValues()
|
|
||||||
var downloads = []
|
|
||||||
|
|
||||||
for (let i = 0; i < keysvalues.length; i++) {
|
|
||||||
try {
|
|
||||||
var download = JSON.parse(keysvalues[i].value)
|
|
||||||
if (!download.id) {
|
|
||||||
console.error('[SqlStore] Removing invalid download', JSON.stringify(download))
|
|
||||||
await this.removeItem(keysvalues[i].key)
|
|
||||||
} else {
|
|
||||||
downloads.push(download)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to parse download', error)
|
|
||||||
await this.removeItem(keysvalues[i].key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.releaseLock()
|
|
||||||
return downloads
|
|
||||||
}
|
|
||||||
|
|
||||||
async setUserAudiobookData(userAudiobookData) {
|
|
||||||
if (this.isLocked) {
|
|
||||||
await this.initWaitLock('setuad')
|
|
||||||
} else {
|
|
||||||
this.setLock('setuad')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var success = false
|
|
||||||
try {
|
|
||||||
await this.setItem(userAudiobookData.audiobookId, JSON.stringify(userAudiobookData))
|
|
||||||
this.vuexStore.commit('user/setUserAudiobookData', userAudiobookData)
|
|
||||||
|
|
||||||
console.log(`[STORE] Set UserAudiobookData ${userAudiobookData.audiobookId}`)
|
|
||||||
success = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to set UserAudiobookData in store', error)
|
|
||||||
}
|
|
||||||
this.releaseLock()
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeUserAudiobookData(audiobookId) {
|
|
||||||
if (this.isLocked) {
|
|
||||||
await this.initWaitLock('remuad')
|
|
||||||
} else {
|
|
||||||
this.setLock('remuad')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var success = false
|
|
||||||
try {
|
|
||||||
await this.removeItem(audiobookId)
|
|
||||||
this.vuexStore.commit('user/removeUserAudiobookData', audiobookId)
|
|
||||||
|
|
||||||
console.log(`[STORE] Removed userAudiobookData ${id}`)
|
|
||||||
success = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to remove userAudiobookData in store', error)
|
|
||||||
}
|
|
||||||
this.releaseLock()
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllUserAudiobookData() {
|
|
||||||
if (this.isLocked) {
|
|
||||||
await this.initWaitLock('alluad')
|
|
||||||
} else {
|
|
||||||
this.setLock('alluad')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var keysvalues = await this.getAllKeysValues()
|
|
||||||
var data = []
|
|
||||||
|
|
||||||
for (let i = 0; i < keysvalues.length; i++) {
|
|
||||||
try {
|
|
||||||
var abdata = JSON.parse(keysvalues[i].value)
|
|
||||||
if (!abdata.audiobookId) {
|
|
||||||
console.error('[SqlStore] Removing invalid user audiobook data')
|
|
||||||
await this.removeItem(keysvalues[i].key)
|
|
||||||
} else {
|
|
||||||
data.push(abdata)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to parse userAudiobookData', error)
|
|
||||||
await this.removeItem(keysvalues[i].key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[SqlStore] All UAD finished')
|
|
||||||
this.releaseLock()
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
async setAllUserAudiobookData(userAbData) {
|
|
||||||
if (this.isLocked) {
|
|
||||||
await this.initWaitLock('setuad')
|
|
||||||
} else {
|
|
||||||
this.setLock('setuad')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await this.ensureTable('userAudiobookData'))) {
|
|
||||||
this.releaseLock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[SqlStore] Setting all user audiobook data ' + userAbData.length)
|
|
||||||
|
|
||||||
var success = await this.clear()
|
|
||||||
if (!success) {
|
|
||||||
console.error('[SqlStore] Did not clear old user ab data, overwriting')
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < userAbData.length; i++) {
|
|
||||||
try {
|
|
||||||
var abdata = userAbData[i]
|
|
||||||
await this.setItem(abdata.audiobookId, JSON.stringify(abdata))
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[SqlStore] Failed to set userAudiobookData', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.vuexStore.commit('user/setAllUserAudiobookData', userAbData)
|
|
||||||
this.releaseLock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ({ app, store }, inject) => {
|
|
||||||
inject('sqlStore', new StoreService(store))
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
import { Capacitor } from '@capacitor/core'
|
|
||||||
|
|
||||||
export const state = () => ({
|
|
||||||
downloads: [],
|
|
||||||
showModal: false,
|
|
||||||
mediaScanResults: {},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getters = {
|
|
||||||
getDownload: (state) => id => {
|
|
||||||
return state.downloads.find(d => d.id === id)
|
|
||||||
},
|
|
||||||
getDownloads: state => {
|
|
||||||
return state.downloads
|
|
||||||
},
|
|
||||||
getDownloadIfReady: (state) => id => {
|
|
||||||
var download = state.downloads.find(d => d.id === id)
|
|
||||||
return !!download && !download.isDownloading && !download.isPreparing ? download : null
|
|
||||||
},
|
|
||||||
getAudiobooks: (state) => {
|
|
||||||
return state.downloads.map(dl => dl.audiobook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const actions = {
|
|
||||||
async loadFromStorage({ commit, state }) {
|
|
||||||
var downloads = await this.$sqlStore.getAllDownloads()
|
|
||||||
console.log('Load downloads from storage', downloads.length)
|
|
||||||
downloads.forEach(ab => {
|
|
||||||
if (ab.isDownloading || ab.isPreparing) {
|
|
||||||
ab.isIncomplete = true
|
|
||||||
}
|
|
||||||
ab.isDownloading = false
|
|
||||||
ab.isPreparing = false
|
|
||||||
commit('setDownload', ab)
|
|
||||||
})
|
|
||||||
return state.downloads
|
|
||||||
},
|
|
||||||
async linkOrphanDownloads({ state, commit, rootState }) {
|
|
||||||
if (!state.mediaScanResults || !state.mediaScanResults.folders) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < state.mediaScanResults.folders.length; i++) {
|
|
||||||
var folder = state.mediaScanResults.folders[i]
|
|
||||||
if (!folder.files || !folder.files.length) return
|
|
||||||
|
|
||||||
var download = state.downloads.find(dl => dl.folderName === folder.name)
|
|
||||||
if (!download) {
|
|
||||||
console.log('Link orphan downloads searching for ' + folder.name)
|
|
||||||
var results = await this.$axios.$get(`/api/libraries/${rootState.libraries.currentLibraryId}/search?q=${folder.name}`)
|
|
||||||
var matchingAb = null
|
|
||||||
if (results && results.audiobooks) {
|
|
||||||
console.log('Link orphan downloads audiobooks ' + results.audiobooks.length)
|
|
||||||
matchingAb = results.audiobooks.find(ab => {
|
|
||||||
return ab.audiobook.book.title === folder.name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingAb) {
|
|
||||||
matchingAb = matchingAb.audiobook
|
|
||||||
// Found matching download for ab
|
|
||||||
var audioFile = folder.files.find(f => f.isAudio)
|
|
||||||
if (!audioFile) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var coverImg = folder.files.find(f => !f.isAudio)
|
|
||||||
const downloadObj = {
|
|
||||||
id: matchingAb.id,
|
|
||||||
audiobook: { ...matchingAb },
|
|
||||||
contentUrl: audioFile.uri,
|
|
||||||
simplePath: audioFile.simplePath,
|
|
||||||
folderUrl: folder.uri,
|
|
||||||
folderName: folder.name,
|
|
||||||
storageType: '',
|
|
||||||
storageId: '',
|
|
||||||
basePath: '',
|
|
||||||
size: audioFile.size,
|
|
||||||
coverUrl: coverImg ? coverImg.uri : null,
|
|
||||||
cover: coverImg ? Capacitor.convertFileSrc(coverImg.uri) : null,
|
|
||||||
coverSize: coverImg ? coverImg.size : 0,
|
|
||||||
coverBasePath: ''
|
|
||||||
}
|
|
||||||
console.log('Link orphan downloads book found ' + matchingAb.book.title)
|
|
||||||
commit('addUpdateDownload', downloadObj)
|
|
||||||
} else {
|
|
||||||
console.log('Link orphan downloads book not found ' + folder.name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('Link orphan downloads folder already has dl ' + folder.name, JSON.stringify(download))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const mutations = {
|
|
||||||
setShowModal(state, val) {
|
|
||||||
state.showModal = val
|
|
||||||
},
|
|
||||||
setDownload(state, download) {
|
|
||||||
if (!download || !download.id) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var index = state.downloads.findIndex(d => d.id === download.id)
|
|
||||||
if (index >= 0) {
|
|
||||||
state.downloads.splice(index, 1, download)
|
|
||||||
} else {
|
|
||||||
state.downloads.push(download)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addUpdateDownload(state, download) {
|
|
||||||
if (!download || !download.id) {
|
|
||||||
console.error('Orphan invalid download ' + download.id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var index = state.downloads.findIndex(d => d.id === download.id)
|
|
||||||
if (index >= 0) {
|
|
||||||
state.downloads.splice(index, 1, download)
|
|
||||||
} else {
|
|
||||||
state.downloads.push(download)
|
|
||||||
}
|
|
||||||
this.$sqlStore.setDownload(download)
|
|
||||||
},
|
|
||||||
removeDownload(state, download) {
|
|
||||||
state.downloads = state.downloads.filter(d => d.id !== download.id)
|
|
||||||
this.$sqlStore.removeDownload(download.id)
|
|
||||||
},
|
|
||||||
setMediaScanResults(state, val) {
|
|
||||||
state.mediaScanResults = val
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,10 +15,7 @@ export const state = () => ({
|
||||||
hasStoragePermission: false,
|
hasStoragePermission: false,
|
||||||
selectedBook: null,
|
selectedBook: null,
|
||||||
showReader: false,
|
showReader: false,
|
||||||
downloadFolder: null,
|
|
||||||
|
|
||||||
showSideDrawer: false,
|
showSideDrawer: false,
|
||||||
bookshelfView: 'grid',
|
|
||||||
isNetworkListenerInit: false,
|
isNetworkListenerInit: false,
|
||||||
serverSettings: null
|
serverSettings: null
|
||||||
})
|
})
|
||||||
|
@ -120,15 +117,9 @@ export const mutations = {
|
||||||
setShowReader(state, val) {
|
setShowReader(state, val) {
|
||||||
state.showReader = val
|
state.showReader = val
|
||||||
},
|
},
|
||||||
setDownloadFolder(state, val) {
|
|
||||||
state.downloadFolder = val
|
|
||||||
},
|
|
||||||
setShowSideDrawer(state, val) {
|
setShowSideDrawer(state, val) {
|
||||||
state.showSideDrawer = val
|
state.showSideDrawer = val
|
||||||
},
|
},
|
||||||
setBookshelfView(state, val) {
|
|
||||||
state.bookshelfView = val
|
|
||||||
},
|
|
||||||
setServerSettings(state, val) {
|
setServerSettings(state, val) {
|
||||||
state.serverSettings = val
|
state.serverSettings = val
|
||||||
this.$localStore.setServerSettings(state.serverSettings)
|
this.$localStore.setServerSettings(state.serverSettings)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
user: null,
|
user: null,
|
||||||
serverConnectionConfig: null,
|
serverConnectionConfig: null,
|
||||||
userAudiobookData: [],
|
|
||||||
settings: {
|
settings: {
|
||||||
mobileOrderBy: 'addedAt',
|
mobileOrderBy: 'addedAt',
|
||||||
mobileOrderDesc: true,
|
mobileOrderDesc: true,
|
||||||
|
@ -12,8 +11,7 @@ export const state = () => ({
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
bookshelfCoverSize: 120
|
bookshelfCoverSize: 120
|
||||||
},
|
},
|
||||||
settingsListeners: [],
|
settingsListeners: []
|
||||||
userAudiobooksListeners: []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
|
@ -32,17 +30,8 @@ export const getters = {
|
||||||
if (!state.user.bookmarks) return []
|
if (!state.user.bookmarks) return []
|
||||||
return state.user.bookmarks.filter(bm => bm.libraryItemId === libraryItemId)
|
return state.user.bookmarks.filter(bm => bm.libraryItemId === libraryItemId)
|
||||||
},
|
},
|
||||||
getUserAudiobookData: (state, getters) => (audiobookId) => {
|
|
||||||
return getters.getUserAudiobook(audiobookId)
|
|
||||||
},
|
|
||||||
getUserAudiobook: (state, getters) => (audiobookId) => {
|
|
||||||
return state.userAudiobookData.find(uabd => uabd.audiobookId === audiobookId)
|
|
||||||
},
|
|
||||||
getUserSetting: (state) => (key) => {
|
getUserSetting: (state) => (key) => {
|
||||||
return state.settings ? state.settings[key] || null : null
|
return state.settings ? state.settings[key] || null : null
|
||||||
},
|
|
||||||
getFilterOrderKey: (state) => {
|
|
||||||
return Object.values(state.settings).join('-')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,41 +56,6 @@ export const actions = {
|
||||||
console.log('Update settings without server')
|
console.log('Update settings without server')
|
||||||
commit('setSettings', payload)
|
commit('setSettings', payload)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async loadOfflineUserAudiobookData({ state, commit }) {
|
|
||||||
var localUserAudiobookData = await this.$sqlStore.getAllUserAudiobookData() || []
|
|
||||||
if (localUserAudiobookData.length) {
|
|
||||||
console.log('loadOfflineUserAudiobookData found', localUserAudiobookData.length, 'user audiobook data')
|
|
||||||
commit('setAllUserAudiobookData', localUserAudiobookData)
|
|
||||||
} else {
|
|
||||||
console.log('loadOfflineUserAudiobookData No user audiobook data')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async syncUserAudiobookData({ state, commit }) {
|
|
||||||
if (!state.user) {
|
|
||||||
console.error('Sync user audiobook data invalid no user')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var localUserAudiobookData = await this.$sqlStore.getAllUserAudiobookData() || []
|
|
||||||
this.$axios.$post(`/api/syncUserAudiobookData`, { data: localUserAudiobookData }).then(async (abData) => {
|
|
||||||
console.log('Synced user audiobook data', abData)
|
|
||||||
await this.$sqlStore.setAllUserAudiobookData(abData)
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Failed to sync user ab data', error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async updateUserAudiobookData({ state, commit }, uabdUpdate) {
|
|
||||||
var userAbData = state.userAudiobookData.find(uab => uab.audiobookId === uabdUpdate.audiobookId)
|
|
||||||
if (!userAbData) {
|
|
||||||
uabdUpdate.startedAt = Date.now()
|
|
||||||
this.$sqlStore.setUserAudiobookData(uabdUpdate)
|
|
||||||
} else {
|
|
||||||
var mergedUabData = { ...userAbData }
|
|
||||||
for (const key in uabdUpdate) {
|
|
||||||
mergedUabData[key] = uabdUpdate[key]
|
|
||||||
}
|
|
||||||
this.$sqlStore.setUserAudiobookData(mergedUabData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,20 +70,6 @@ export const mutations = {
|
||||||
setServerConnectionConfig(state, serverConnectionConfig) {
|
setServerConnectionConfig(state, serverConnectionConfig) {
|
||||||
state.serverConnectionConfig = serverConnectionConfig
|
state.serverConnectionConfig = serverConnectionConfig
|
||||||
},
|
},
|
||||||
setUserAudiobookData(state, abdata) {
|
|
||||||
var index = state.userAudiobookData.findIndex(uab => uab.audiobookId === abdata.audiobookId)
|
|
||||||
if (index >= 0) {
|
|
||||||
state.userAudiobookData.splice(index, 1, abdata)
|
|
||||||
} else {
|
|
||||||
state.userAudiobookData.push(abdata)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeUserAudiobookData(state, audiobookId) {
|
|
||||||
state.userAudiobookData = state.userAudiobookData.filter(uab => uab.audiobookId !== audiobookId)
|
|
||||||
},
|
|
||||||
setAllUserAudiobookData(state, allAbData) {
|
|
||||||
state.userAudiobookData = allAbData
|
|
||||||
},
|
|
||||||
setSettings(state, settings) {
|
setSettings(state, settings) {
|
||||||
if (!settings) return
|
if (!settings) return
|
||||||
|
|
||||||
|
@ -157,13 +97,5 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
removeSettingsListener(state, listenerId) {
|
removeSettingsListener(state, listenerId) {
|
||||||
state.settingsListeners = state.settingsListeners.filter(l => l.id !== listenerId)
|
state.settingsListeners = state.settingsListeners.filter(l => l.id !== listenerId)
|
||||||
},
|
|
||||||
addUserAudiobookListener(state, listener) {
|
|
||||||
var index = state.userAudiobooksListeners.findIndex(l => l.id === listener.id)
|
|
||||||
if (index >= 0) state.userAudiobooksListeners.splice(index, 1, listener)
|
|
||||||
else state.userAudiobooksListeners.push(listener)
|
|
||||||
},
|
|
||||||
removeUserAudiobookListener(state, listenerId) {
|
|
||||||
state.userAudiobooksListeners = state.userAudiobooksListeners.filter(l => l.id !== listenerId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue