mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-02-24 19:31:06 +03:00
perf(backend): Rework sync behaviour (#2050)
This commit is contained in:
parent
e22ac51d6a
commit
542e5c7d00
10 changed files with 88 additions and 118 deletions
|
@ -1,5 +1,4 @@
|
|||
VITE_QBITTORRENT_TARGET=http://localhost:8080
|
||||
VITE_BACKEND_TARGET=http://localhost:8081
|
||||
|
||||
VITE_USE_MOCK_PROVIDER=false
|
||||
VITE_FAKE_TORRENTS_COUNT=5
|
||||
|
|
20
src/App.vue
20
src/App.vue
|
@ -9,7 +9,7 @@ import { backend } from '@/services/backend'
|
|||
import { useAddTorrentStore, useAppStore, useDialogStore, useLogStore, useMaindataStore, usePreferenceStore, useTorrentStore, useVueTorrentStore } from '@/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onBeforeMount, onMounted, watch, watchEffect } from 'vue'
|
||||
import { useI18nUtils } from '@/composables'
|
||||
import { useBackendSync, useI18nUtils } from '@/composables'
|
||||
import { toast } from 'vue3-toastify'
|
||||
|
||||
const { t } = useI18nUtils()
|
||||
|
@ -24,6 +24,10 @@ const preferencesStore = usePreferenceStore()
|
|||
const vuetorrentStore = useVueTorrentStore()
|
||||
const { language, uiTitleCustom, uiTitleType, useBitSpeed } = storeToRefs(vuetorrentStore)
|
||||
|
||||
const backendSync = useBackendSync(vuetorrentStore, 'vuetorrent_webuiSettings', {
|
||||
blacklist: ['uiTitleCustom']
|
||||
})
|
||||
|
||||
const checkAuthentication = async () => {
|
||||
const promise = appStore.fetchAuthStatus()
|
||||
const timer = setTimeout(() => toast.promise(promise, { pending: t('login.pending') }), 1000)
|
||||
|
@ -58,12 +62,6 @@ function addLaunchQueueConsumer() {
|
|||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
backend.init(vuetorrentStore.backendUrl)
|
||||
backend.ping().then(ok => {
|
||||
if (!backend.isAutoConfig && !ok) {
|
||||
toast.error(t('toast.backend_unreachable'), { delay: 1000, autoClose: 2500 })
|
||||
}
|
||||
})
|
||||
vuetorrentStore.updateTheme()
|
||||
vuetorrentStore.setLanguage(language.value)
|
||||
checkAuthentication()
|
||||
|
@ -84,8 +82,16 @@ watch(
|
|||
await preferencesStore.fetchPreferences()
|
||||
await logStore.cleanAndFetchLogs()
|
||||
addTorrentStore.initForm()
|
||||
|
||||
backend.ping().then(async ok => {
|
||||
if (ok) {
|
||||
await backendSync.loadState()
|
||||
await backendSync.registerWatcher()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
maindataStore.stopMaindataSync()
|
||||
await backendSync.cancelWatcher()
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -144,10 +144,6 @@ function openDateFormatHelp() {
|
|||
function openDurationFormatHelp() {
|
||||
openLink('https://day.js.org/docs/en/durations/format#list-of-all-available-formats')
|
||||
}
|
||||
|
||||
function openBackendHelp() {
|
||||
openLink('https://github.com/VueTorrent/vuetorrent-backend/wiki/Installation')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -292,16 +288,6 @@ function openBackendHelp() {
|
|||
append-inner-icon="mdi-help-circle"
|
||||
@click:appendInner="openDurationFormatHelp" />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="vueTorrentStore.backendUrl"
|
||||
:label="t('settings.vuetorrent.general.backendUrl')"
|
||||
:hint="t('settings.vuetorrent.general.backendUrlHint')"
|
||||
placeholder="https://YOUR-HOST:PORT/"
|
||||
append-inner-icon="mdi-help-circle"
|
||||
@click:appendInner="openBackendHelp" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list-item>
|
||||
|
||||
|
|
53
src/composables/BackendSync.ts
Normal file
53
src/composables/BackendSync.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { backend } from '@/services/backend'
|
||||
import { Store } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useBackendSync(store: Store, key: string, config: { blacklist?: string[], whitelist?: string[] } = {}) {
|
||||
let cancelWatcherCallback = ref<() => void>()
|
||||
|
||||
function keyMatchesFilter(k: string) {
|
||||
return config.whitelist?.includes(k) || !config.blacklist?.includes(k)
|
||||
}
|
||||
|
||||
async function loadState() {
|
||||
const data = await backend.get(key)
|
||||
if (data) {
|
||||
const newState = JSON.parse(data) as Record<string, any>
|
||||
const temp = {} as Record<string, any>
|
||||
Object.entries(newState).forEach(([k, v]) => {
|
||||
if (keyMatchesFilter(k)) {
|
||||
temp[k] = v
|
||||
}
|
||||
})
|
||||
store.$patch(temp)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveState() {
|
||||
const state = {} as Record<string, any>
|
||||
Object.entries(store.$state).forEach(([k, v]) => {
|
||||
if (keyMatchesFilter(k)) {
|
||||
state[k] = v
|
||||
}
|
||||
})
|
||||
|
||||
await backend.set(key, JSON.stringify(state))
|
||||
}
|
||||
|
||||
async function registerWatcher() {
|
||||
cancelWatcherCallback.value = store.$subscribe(() => {
|
||||
saveState()
|
||||
})
|
||||
}
|
||||
|
||||
async function cancelWatcher() {
|
||||
cancelWatcherCallback.value && cancelWatcherCallback.value()
|
||||
}
|
||||
|
||||
return {
|
||||
loadState,
|
||||
saveState,
|
||||
registerWatcher,
|
||||
cancelWatcher
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { useArrayPagination } from './ArrayPagination'
|
||||
import { useBackendSync } from './BackendSync'
|
||||
import { useDialog } from './Dialog'
|
||||
import { useI18nUtils } from './i18n'
|
||||
import { useSearchQuery } from './SearchQuery'
|
||||
import { useTorrentBuilder } from './TorrentBuilder'
|
||||
import { useTreeBuilder } from './TreeBuilder'
|
||||
|
||||
export { useArrayPagination, useDialog, useI18nUtils, useSearchQuery, useTorrentBuilder, useTreeBuilder }
|
||||
export { useArrayPagination, useBackendSync, useDialog, useI18nUtils, useSearchQuery, useTorrentBuilder, useTreeBuilder }
|
||||
|
|
|
@ -13,10 +13,9 @@ import VTorrentCardGrid from '@/components/Settings/VueTorrent/TorrentCard/Grid.
|
|||
import VTorrentCardList from '@/components/Settings/VueTorrent/TorrentCard/List.vue'
|
||||
import VTorrentCardTable from '@/components/Settings/VueTorrent/TorrentCard/Table.vue'
|
||||
import WebUI from '@/components/Settings/WebUI.vue'
|
||||
import { backend } from '@/services/backend'
|
||||
import { useDialogStore, usePreferenceStore, useVueTorrentStore } from '@/stores'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue'
|
||||
import { useI18nUtils } from '@/composables'
|
||||
import { useDialogStore, usePreferenceStore } from '@/stores'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watchEffect } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { toast } from 'vue3-toastify'
|
||||
|
||||
|
@ -24,7 +23,6 @@ const router = useRouter()
|
|||
const { t } = useI18nUtils()
|
||||
const dialogStore = useDialogStore()
|
||||
const preferenceStore = usePreferenceStore()
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
|
||||
const tabs = [
|
||||
{ text: t('settings.tabs.vuetorrent.title'), value: 'vuetorrent' },
|
||||
|
@ -56,10 +54,6 @@ const saveSettings = async () => {
|
|||
toast.success(t('settings.saveSuccess'))
|
||||
await preferenceStore.fetchPreferences()
|
||||
|
||||
const oldInit = backend.isInitialized && !backend.isAutoConfig
|
||||
backend.init(vuetorrentStore.backendUrl)
|
||||
const newInit = backend.isInitialized && !backend.isAutoConfig
|
||||
|
||||
if (!preferenceStore.preferences!.alternative_webui_enabled) {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations()
|
||||
|
@ -69,15 +63,6 @@ const saveSettings = async () => {
|
|||
}
|
||||
location.hash = ''
|
||||
location.reload()
|
||||
} else {
|
||||
if (!oldInit && newInit) {
|
||||
location.reload()
|
||||
} else {
|
||||
const ok = await backend.ping()
|
||||
if (!backend.isAutoConfig && !ok) {
|
||||
toast.error(t('toast.backend_unreachable'), { delay: 1000, autoClose: 2500 })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import { StorageLikeAsync } from '@vueuse/core'
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
|
||||
class BackendProvider {
|
||||
private axios: AxiosInstance
|
||||
private _isInitialized: boolean = false
|
||||
private _isAutoConfig: boolean = false
|
||||
private up: boolean = true
|
||||
private pingPromise: Promise<boolean> | null = null
|
||||
private up: boolean = false
|
||||
|
||||
constructor() {
|
||||
let baseURL = `${location.origin}${location.pathname}`
|
||||
if (!baseURL.endsWith('/')) baseURL += '/'
|
||||
baseURL += 'backend'
|
||||
|
||||
this.axios = axios.create({
|
||||
baseURL,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
put: { 'Content-Type': 'application/json' }
|
||||
|
@ -17,41 +18,12 @@ class BackendProvider {
|
|||
})
|
||||
}
|
||||
|
||||
get isInitialized() {
|
||||
return this._isInitialized
|
||||
}
|
||||
|
||||
get isAutoConfig() {
|
||||
return this._isAutoConfig
|
||||
}
|
||||
|
||||
init(baseURL?: string) {
|
||||
if (!baseURL) {
|
||||
baseURL = `${location.origin}${location.pathname}`
|
||||
if (baseURL.endsWith('/')) {
|
||||
baseURL = baseURL.slice(0, -1)
|
||||
}
|
||||
baseURL += '/backend'
|
||||
this._isAutoConfig = true
|
||||
} else {
|
||||
this._isAutoConfig = false
|
||||
}
|
||||
|
||||
this.axios.defaults.baseURL = baseURL
|
||||
this._isInitialized = !!baseURL
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the backend to check if it's up
|
||||
* @returns true if backend is up, false otherwise
|
||||
*/
|
||||
async ping(): Promise<boolean> {
|
||||
if (!this._isInitialized) return false
|
||||
if (this.pingPromise) {
|
||||
return this.pingPromise
|
||||
}
|
||||
|
||||
this.pingPromise = this.axios
|
||||
return await this.axios
|
||||
.get('/ping')
|
||||
.then(
|
||||
res => res.data === 'pong',
|
||||
|
@ -59,30 +31,16 @@ class BackendProvider {
|
|||
)
|
||||
.then(ok => {
|
||||
this.up = ok
|
||||
this.pingPromise = null
|
||||
|
||||
return ok
|
||||
})
|
||||
|
||||
return await this.pingPromise
|
||||
}
|
||||
|
||||
private async waitForPing() {
|
||||
if (this.pingPromise) {
|
||||
await this.pingPromise
|
||||
}
|
||||
}
|
||||
|
||||
shouldDiscardCalls() {
|
||||
return !this._isInitialized || !this.up
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all values
|
||||
*/
|
||||
async getAll(): Promise<Record<string, string>> {
|
||||
await this.waitForPing()
|
||||
if (this.shouldDiscardCalls()) return {}
|
||||
async getAll(): Promise<Record<string, string> | null> {
|
||||
if (!this.up) return null
|
||||
|
||||
return this.axios.get('/config').then(res => res.data)
|
||||
}
|
||||
|
@ -93,8 +51,7 @@ class BackendProvider {
|
|||
* @returns string or null if key doesn't exists
|
||||
*/
|
||||
async get(key: string): Promise<string | null> {
|
||||
await this.waitForPing()
|
||||
if (this.shouldDiscardCalls()) return null
|
||||
if (!this.up) return null
|
||||
|
||||
return this.axios.get(`/config/${key}`).then(
|
||||
res => res.data[key],
|
||||
|
@ -109,8 +66,7 @@ class BackendProvider {
|
|||
* @returns true if value was set, false otherwise
|
||||
*/
|
||||
async set(key: string, value: string): Promise<boolean> {
|
||||
await this.waitForPing()
|
||||
if (this.shouldDiscardCalls()) return false
|
||||
if (!this.up) return false
|
||||
|
||||
return this.axios.put(`/config/${key}`, { value }).then(
|
||||
() => true,
|
||||
|
@ -124,25 +80,17 @@ class BackendProvider {
|
|||
* @returns true if value was deleted, false otherwise
|
||||
*/
|
||||
async del(key: string): Promise<boolean> {
|
||||
await this.waitForPing()
|
||||
if (this.shouldDiscardCalls()) return false
|
||||
if (!this.up) return false
|
||||
|
||||
return this.axios.delete(`/config/${key}`).then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
}
|
||||
|
||||
async update() {
|
||||
return this.axios.get('/update')
|
||||
}
|
||||
}
|
||||
|
||||
export const backend = new BackendProvider()
|
||||
export const backendStorage: StorageLikeAsync = {
|
||||
async getItem(key) {
|
||||
return await backend.get(key)
|
||||
},
|
||||
async setItem(key, state) {
|
||||
await backend.set(key, state)
|
||||
},
|
||||
async removeItem(key) {
|
||||
await backend.del(key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
TitleOptions,
|
||||
TorrentProperty
|
||||
} from '@/constants/vuetorrent'
|
||||
import { backendStorage } from '@/services/backend'
|
||||
import { DarkLegacy, LightLegacy } from '@/themes'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
|
@ -22,8 +21,6 @@ import { useTheme } from 'vuetify'
|
|||
export const useVueTorrentStore = defineStore(
|
||||
'vuetorrent',
|
||||
() => {
|
||||
const backendUrl = ref('')
|
||||
|
||||
const language = ref('en')
|
||||
const theme = reactive({
|
||||
mode: ThemeMode.SYSTEM,
|
||||
|
@ -235,7 +232,6 @@ export const useVueTorrentStore = defineStore(
|
|||
}
|
||||
|
||||
return {
|
||||
backendUrl,
|
||||
theme,
|
||||
dateFormat,
|
||||
durationFormat,
|
||||
|
@ -291,7 +287,6 @@ export const useVueTorrentStore = defineStore(
|
|||
toggleDoneGridProperty,
|
||||
toggleTableProperty,
|
||||
$reset: () => {
|
||||
backendUrl.value = ''
|
||||
language.value = 'en'
|
||||
theme.mode = ThemeMode.SYSTEM
|
||||
theme.light = LightLegacy.id
|
||||
|
@ -338,8 +333,7 @@ export const useVueTorrentStore = defineStore(
|
|||
persistence: {
|
||||
enabled: true,
|
||||
storageItems: [
|
||||
{ storage: localStorage, key: 'webuiSettings' },
|
||||
{ storage: backendStorage, key: 'webuiSettings', excludePaths: ['backendUrl', 'uiTitleCustom'] }
|
||||
{ storage: localStorage, key: 'webuiSettings' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
|
@ -6,7 +6,6 @@ interface ImportMetaEnv extends BaseImportMetaEnv {
|
|||
readonly VITE_PACKAGE_VERSION: string
|
||||
|
||||
readonly VITE_QBITTORRENT_TARGET: string
|
||||
readonly VITE_QBITTORRENT_PORT: number
|
||||
|
||||
readonly VITE_USE_MOCK_PROVIDER: string
|
||||
readonly VITE_FAKE_TORRENTS_COUNT: number
|
||||
|
|
|
@ -10,7 +10,6 @@ import vuetify from 'vite-plugin-vuetify'
|
|||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
const qBittorrentTarget = env.VITE_QBITTORRENT_TARGET ?? 'http://localhost:8080'
|
||||
const backendTarget = env.VITE_BACKEND_TARGET ?? 'http://localhost:3000'
|
||||
|
||||
return {
|
||||
base: './',
|
||||
|
@ -65,7 +64,7 @@ export default defineConfig(({ mode }) => {
|
|||
'/backend': {
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
target: backendTarget
|
||||
target: qBittorrentTarget
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue