Merge pull request #1401 from chme/web-partial-scan

Web UI - Add support for updating a single library source
This commit is contained in:
Christian Meffert 2022-01-22 12:35:03 +01:00 committed by GitHub
commit 3caa6f5b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 227 additions and 118 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -114,6 +114,7 @@ DATETAG : 'time_added'
ENUMTAG : 'data_kind'
| 'media_kind'
| 'scan_kind'
;
GROUPTAG : 'track_count'
@ -205,6 +206,8 @@ ENUMVAL : 'music'
| 'url'
| 'spotify'
| 'pipe'
| 'files'
| 'rss'
;
ORDERBY : 'order by'

View File

@ -304,6 +304,21 @@ expression returns [ pANTLR3_STRING result, pANTLR3_STRING orderby, pANTLR3_STRI
sprintf(str, "f.data_kind = \%d", DATA_KIND_PIPE);
}
}
else if (strcmp((char *)tag, "scan_kind") == 0)
{
if (strcmp((char *)val, "files") == 0)
{
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_FILES);
}
else if (strcmp((char *)val, "spotify") == 0)
{
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_SPOTIFY);
}
else if (strcmp((char *)val, "rss") == 0)
{
sprintf(str, "f.scan_kind = \%d", SCAN_KIND_RSS);
}
}
$result = $ENUMTAG.text->factory->newRaw($ENUMTAG.text->factory);
$result->append8($result, str);

View File

@ -9,11 +9,10 @@
"version": "1.2.0",
"license": "GPL-2.0",
"dependencies": {
"axios": "^0.24.0",
"axios": "^0.25.0",
"bulma": "^0.9.3",
"bulma-switch": "^2.0.0",
"bulma-switch": "^2.0.4",
"core-js": "^3.15.2",
"follow-redirects": "^1.14.7",
"mdi": "^2.2.43",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
@ -4387,11 +4386,11 @@
"dev": true
},
"node_modules/axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
"dependencies": {
"follow-redirects": "^1.14.4"
"follow-redirects": "^1.14.7"
}
},
"node_modules/babel-eslint": {
@ -4946,9 +4945,9 @@
"integrity": "sha512-0d7GNW1PY4ud8TWxdNcP6Cc8Bu7MxcntD/RRLGWuiw/s0a9P+XlH/6QoOIrmbj6o8WWJzJYhytiu9nFjTszk1g=="
},
"node_modules/bulma-switch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.0.tgz",
"integrity": "sha512-myD38zeUfjmdduq+pXabhJEe3x2hQP48l/OI+Y0fO3HdDynZUY/VJygucvEAJKRjr4HxD5DnEm4yx+oDOBXpAA=="
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.4.tgz",
"integrity": "sha512-kMu4H0Pr0VjvfsnT6viRDCgptUq0Rvy7y7PX6q+IHg1xUynsjszPjhAdal5ysAlCG5HNO+5YXxeiu92qYGQolw=="
},
"node_modules/bytes": {
"version": "3.1.0",
@ -23600,11 +23599,11 @@
"dev": true
},
"axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
"requires": {
"follow-redirects": "^1.14.4"
"follow-redirects": "^1.14.7"
}
},
"babel-eslint": {
@ -24082,9 +24081,9 @@
"integrity": "sha512-0d7GNW1PY4ud8TWxdNcP6Cc8Bu7MxcntD/RRLGWuiw/s0a9P+XlH/6QoOIrmbj6o8WWJzJYhytiu9nFjTszk1g=="
},
"bulma-switch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.0.tgz",
"integrity": "sha512-myD38zeUfjmdduq+pXabhJEe3x2hQP48l/OI+Y0fO3HdDynZUY/VJygucvEAJKRjr4HxD5DnEm4yx+oDOBXpAA=="
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.4.tgz",
"integrity": "sha512-kMu4H0Pr0VjvfsnT6viRDCgptUq0Rvy7y7PX6q+IHg1xUynsjszPjhAdal5ysAlCG5HNO+5YXxeiu92qYGQolw=="
},
"bytes": {
"version": "3.1.0",

View File

@ -11,11 +11,10 @@
"dev": "vue-cli-service serve"
},
"dependencies": {
"axios": "^0.24.0",
"axios": "^0.25.0",
"bulma": "^0.9.3",
"bulma-switch": "^2.0.0",
"bulma-switch": "^2.0.4",
"core-js": "^3.15.2",
"follow-redirects": "^1.14.7",
"mdi": "^2.2.43",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",

View File

@ -7,6 +7,9 @@
<router-view v-show="true" />
</transition>
<modal-dialog-remote-pairing :show="pairing_active" @close="pairing_active = false" />
<modal-dialog-update
:show="show_update_dialog"
@close="show_update_dialog = false" />
<notifications v-show="!show_burger_menu" />
<navbar-bottom />
<div class="fd-overlay-fullscreen" v-show="show_burger_menu || show_player_menu"
@ -19,6 +22,7 @@ import NavbarTop from '@/components/NavbarTop'
import NavbarBottom from '@/components/NavbarBottom'
import Notifications from '@/components/Notifications'
import ModalDialogRemotePairing from '@/components/ModalDialogRemotePairing'
import ModalDialogUpdate from '@/components/ModalDialogUpdate'
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
import ReconnectingWebSocket from 'reconnectingwebsocket'
@ -26,7 +30,7 @@ import moment from 'moment'
export default {
name: 'App',
components: { NavbarTop, NavbarBottom, Notifications, ModalDialogRemotePairing },
components: { NavbarTop, NavbarBottom, Notifications, ModalDialogRemotePairing, ModalDialogUpdate },
template: '<App/>',
data () {
@ -53,6 +57,14 @@ export default {
set (value) {
this.$store.commit(types.SHOW_PLAYER_MENU, value)
}
},
show_update_dialog: {
get () {
return this.$store.state.show_update_dialog
},
set (value) {
this.$store.commit(types.SHOW_UPDATE_DIALOG, value)
}
}
},
@ -181,6 +193,9 @@ export default {
webapi.library_count('media_kind is podcast').then(({ data }) => {
this.$store.commit(types.UPDATE_LIBRARY_PODCASTS_COUNT, data)
})
webapi.library_count('scan_kind is rss').then(({ data }) => {
this.$store.commit(types.UPDATE_LIBRARY_RSS_COUNT, data)
})
},
update_outputs: function () {

View File

@ -0,0 +1,95 @@
<template>
<modal-dialog
:show="show"
title="Update library"
:ok_action="library.updating ? '' : 'Rescan'"
close_action="Close"
@ok="update_library"
@close="close()">
<template slot="modal-content">
<div v-if="!library.updating">
<p class="mb-3">Scan for new, deleted and modified files</p>
<div class="field" v-if="spotify_enabled || rss.tracks > 0">
<div class="control">
<div class="select is-small">
<select v-model="update_dialog_scan_kind">
<option value="">Update everything</option>
<option value="files">Only update local library</option>
<option value="spotify" v-if="spotify_enabled">Only update Spotify</option>
<option value="rss" v-if="rss.tracks > 0">Only update RSS feeds</option>
</select>
</div>
</div>
</div>
<div class="field">
<label class="checkbox is-size-7 is-small">
<input type="checkbox" v-model="rescan_metadata">
Rescan metadata for unmodified files
</label>
</div>
</div>
<div v-else>
<p class="mb-3">Library update in progress ...</p>
</div>
</template>
</modal-dialog>
</template>
<script>
import ModalDialog from '@/components/ModalDialog'
import * as types from '@/store/mutation_types'
import webapi from '@/webapi'
export default {
name: 'ModalDialogUpdate',
components: { ModalDialog },
props: ['show'],
data () {
return {
rescan_metadata: false
}
},
computed: {
library () {
return this.$store.state.library
},
rss () {
return this.$store.state.rss_count
},
spotify_enabled () {
return this.$store.state.spotify.webapi_token_valid
},
update_dialog_scan_kind: {
get () {
return this.$store.state.update_dialog_scan_kind
},
set (value) {
this.$store.commit(types.UPDATE_DIALOG_SCAN_KIND, value)
}
}
},
methods: {
update_library () {
if (this.rescan_metadata) {
webapi.library_rescan(this.update_dialog_scan_kind)
} else {
webapi.library_update(this.update_dialog_scan_kind)
}
},
close () {
this.update_dialog_scan_kind = ''
this.$emit('close')
}
}
}
</script>
<style>
</style>

View File

@ -61,7 +61,7 @@
<hr class="fd-navbar-divider">
<navbar-item-link to="/settings/webinterface">Settings</navbar-item-link>
<a class="navbar-item" @click.stop.prevent="show_update_library = true; show_settings_menu = false; show_burger_menu = false">
<a class="navbar-item" @click.stop.prevent="show_update_dialog = true; show_settings_menu = false; show_burger_menu = false">
Update Library
</a>
<navbar-item-link to="/about">About</navbar-item-link>
@ -72,29 +72,6 @@
</div>
</div>
<modal-dialog
:show="show_update_library"
title="Update library"
:ok_action="library.updating ? '' : 'Rescan'"
close_action="Close"
@ok="update_library"
@close="show_update_library = false">
<template slot="modal-content">
<div v-if="!library.updating">
<p class="mb-3">Scan for new, deleted and modified files</p>
<div class="field">
<label class="checkbox is-size-7 is-small">
<input type="checkbox" v-model="rescan_metadata">
Rescan metadata for unmodified files
</label>
</div>
</div>
<div v-else>
<p class="mb-3">Library update in progress ...</p>
</div>
</template>
</modal-dialog>
<div class="is-overlay" v-show="show_settings_menu"
style="z-index:10; width: 100vw; height:100vh;"
@click="show_settings_menu = false"></div>
@ -103,19 +80,15 @@
<script>
import NavbarItemLink from './NavbarItemLink'
import ModalDialog from '@/components/ModalDialog'
import * as types from '@/store/mutation_types'
import webapi from '@/webapi'
export default {
name: 'NavbarTop',
components: { NavbarItemLink, ModalDialog },
components: { NavbarItemLink },
data () {
return {
show_settings_menu: false,
show_update_library: false,
rescan_metadata: false
show_settings_menu: false
}
},
@ -179,6 +152,15 @@ export default {
return this.$store.state.show_player_menu
},
show_update_dialog: {
get () {
return this.$store.state.show_update_dialog
},
set (value) {
this.$store.commit(types.SHOW_UPDATE_DIALOG, value)
}
},
zindex () {
if (this.show_player_menu) {
return 'z-index: 20'
@ -190,14 +172,6 @@ export default {
methods: {
on_click_outside_settings () {
this.show_settings_menu = !this.show_settings_menu
},
update_library () {
if (this.rescan_metadata) {
webapi.library_rescan()
} else {
webapi.library_update()
}
}
},

View File

@ -26,33 +26,7 @@
<!-- Right side -->
<div class="level-right">
<div v-if="library.updating"><a class="button is-small is-loading">Update</a></div>
<div v-else class="dropdown is-right" :class="{ 'is-active': show_update_dropdown }" v-click-outside="onClickOutside">
<div class="dropdown-trigger">
<div class="buttons has-addons">
<a @click="update" class="button is-small">Update</a>
<a @click="show_update_dropdown = !show_update_dropdown" class="button is-small">
<span class="icon"><i class="mdi" :class="{ 'mdi-chevron-down': !show_update_dropdown, 'mdi-chevron-up': show_update_dropdown }"></i></span>
</a>
</div>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
<div class="dropdown-item">
<a @click="update" class="has-text-dark">
<strong>Update</strong><br>
<span class="is-size-7">Adds new, removes deleted and updates modified files.</span>
</a>
</div>
<hr class="dropdown-divider">
<div class="dropdown-item">
<a @click="update_meta" class="has-text-dark">
<strong>Rescan metadata</strong><br>
<span class="is-size-7">Same as update, but also rescans unmodified files.</span>
</a>
</div>
</div>
</div>
</div>
<div v-else><a @click="showUpdateDialog()" class="button is-small">Update</a></div>
</div>
</nav>
@ -105,14 +79,15 @@
</template>
<script>
import webapi from '@/webapi'
import * as types from '@/store/mutation_types'
export default {
name: 'PageAbout',
data () {
return {
show_update_dropdown: false
show_update_dropdown: false,
show_update_library: false
}
},
@ -129,15 +104,8 @@ export default {
onClickOutside (event) {
this.show_update_dropdown = false
},
update: function () {
this.show_update_dropdown = false
webapi.library_update()
},
update_meta: function () {
this.show_update_dropdown = false
webapi.library_rescan()
showUpdateDialog () {
this.$store.commit(types.SHOW_UPDATE_DIALOG, true)
}
},

View File

@ -43,6 +43,12 @@
</template>
<template slot="heading-right">
<div class="buttons is-centered">
<a v-if="rss.tracks > 0" class="button is-small" @click="update_rss">
<span class="icon">
<i class="mdi mdi-refresh"></i>
</span>
<span>Update</span>
</a>
<a class="button is-small" @click="open_add_podcast_dialog">
<span class="icon">
<i class="mdi mdi-rss"></i>
@ -72,6 +78,7 @@ import ListItemTrack from '@/components/ListItemTrack'
import ListAlbums from '@/components/ListAlbums'
import ModalDialogTrack from '@/components/ModalDialogTrack'
import ModalDialogAddRss from '@/components/ModalDialogAddRss'
import * as types from '@/store/mutation_types'
import RangeSlider from 'vue-range-slider'
import webapi from '@/webapi'
@ -106,6 +113,12 @@ export default {
}
},
computed: {
rss () {
return this.$store.state.rss_count
}
},
methods: {
play_track: function (track) {
webapi.player_play_uri(track.uri, false)
@ -138,6 +151,11 @@ export default {
this.albums = data
this.reload_new_episodes()
})
},
update_rss: function () {
this.$store.commit(types.UPDATE_DIALOG_SCAN_KIND, 'rss')
this.$store.commit(types.SHOW_UPDATE_DIALOG, true)
}
}
}

View File

@ -23,6 +23,7 @@ export default new Vuex.Store({
},
audiobooks_count: { },
podcasts_count: { },
rss_count: { },
outputs: [],
player: {
state: 'stop',
@ -59,7 +60,9 @@ export default new Vuex.Store({
albums_sort: 'Name',
show_only_next_items: false,
show_burger_menu: false,
show_player_menu: false
show_player_menu: false,
show_update_dialog: false,
update_dialog_scan_kind: ''
},
getters: {
@ -141,6 +144,9 @@ export default new Vuex.Store({
[types.UPDATE_LIBRARY_PODCASTS_COUNT] (state, count) {
state.podcasts_count = count
},
[types.UPDATE_LIBRARY_RSS_COUNT] (state, count) {
state.rss_count = count
},
[types.UPDATE_OUTPUTS] (state, outputs) {
state.outputs = outputs
},
@ -217,6 +223,12 @@ export default new Vuex.Store({
},
[types.SHOW_PLAYER_MENU] (state, showPlayerMenu) {
state.show_player_menu = showPlayerMenu
},
[types.SHOW_UPDATE_DIALOG] (state, showUpdateDialog) {
state.show_update_dialog = showUpdateDialog
},
[types.UPDATE_DIALOG_SCAN_KIND] (state, scanKind) {
state.update_dialog_scan_kind = scanKind
}
},

View File

@ -4,6 +4,7 @@ export const UPDATE_SETTINGS_OPTION = 'UPDATE_SETTINGS_OPTION'
export const UPDATE_LIBRARY_STATS = 'UPDATE_LIBRARY_STATS'
export const UPDATE_LIBRARY_AUDIOBOOKS_COUNT = 'UPDATE_LIBRARY_AUDIOBOOKS_COUNT'
export const UPDATE_LIBRARY_PODCASTS_COUNT = 'UPDATE_LIBRARY_PODCASTS_COUNT'
export const UPDATE_LIBRARY_RSS_COUNT = 'UPDATE_LIBRARY_RSS_COUNT'
export const UPDATE_OUTPUTS = 'UPDATE_OUTPUTS'
export const UPDATE_PLAYER_STATUS = 'UPDATE_PLAYER_STATUS'
export const UPDATE_QUEUE = 'UPDATE_QUEUE'
@ -26,3 +27,5 @@ export const ALBUMS_SORT = 'ALBUMS_SORT'
export const SHOW_ONLY_NEXT_ITEMS = 'SHOW_ONLY_NEXT_ITEMS'
export const SHOW_BURGER_MENU = 'SHOW_BURGER_MENU'
export const SHOW_PLAYER_MENU = 'SHOW_PLAYER_MENU'
export const SHOW_UPDATE_DIALOG = 'SHOW_UPDATE_DIALOG'
export const UPDATE_DIALOG_SCAN_KIND = 'UPDATE_DIALOG_SCAN_KIND'

View File

@ -27,12 +27,20 @@ export default {
return axios.get('./api/library')
},
library_update () {
return axios.put('./api/update')
library_update (scanKind) {
const params = {}
if (scanKind) {
params.scan_kind = scanKind
}
return axios.put('./api/update', undefined, { params: params })
},
library_rescan () {
return axios.put('./api/rescan')
library_rescan (scanKind) {
const params = {}
if (scanKind) {
params.scan_kind = scanKind
}
return axios.put('./api/rescan', undefined, { params: params })
},
library_count (expression) {