mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Add:Tools tab on library modal, api endpoint to remove all metadata files from library item folders
This commit is contained in:
parent
0d5792405f
commit
b4ce5342c0
@ -12,9 +12,9 @@
|
||||
</div>
|
||||
|
||||
<div class="px-2 md:px-4 w-full text-sm pt-2 md:pt-6 pb-20 rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||
<component v-if="libraryCopy && show" ref="tabComponent" :is="tabName" :is-new="!library" :library="libraryCopy" :processing.sync="processing" @update="updateLibrary" @close="show = false" />
|
||||
<component v-if="libraryCopy && show" ref="tabComponent" :is="tabName" :is-new="!library" :library="libraryCopy" :library-id="libraryId" :processing.sync="processing" @update="updateLibrary" @close="show = false" />
|
||||
|
||||
<div class="absolute bottom-0 left-0 w-full px-4 py-4 border-t border-white border-opacity-10">
|
||||
<div v-show="selectedTab !== 'tools'" class="absolute bottom-0 left-0 w-full px-4 py-4 border-t border-white border-opacity-10">
|
||||
<div class="flex justify-end">
|
||||
<ui-btn @click="submit">{{ buttonText }}</ui-btn>
|
||||
</div>
|
||||
@ -57,6 +57,9 @@ export default {
|
||||
mediaType() {
|
||||
return this.libraryCopy?.mediaType
|
||||
},
|
||||
libraryId() {
|
||||
return this.library?.id
|
||||
},
|
||||
tabs() {
|
||||
return [
|
||||
{
|
||||
@ -78,6 +81,11 @@ export default {
|
||||
id: 'schedule',
|
||||
title: this.$strings.HeaderSchedule,
|
||||
component: 'modals-libraries-schedule-scan'
|
||||
},
|
||||
{
|
||||
id: 'tools',
|
||||
title: this.$strings.HeaderTools,
|
||||
component: 'modals-libraries-library-tools'
|
||||
}
|
||||
].filter((tab) => {
|
||||
return tab.id !== 'scanner' || this.mediaType === 'book'
|
||||
|
70
client/components/modals/libraries/LibraryTools.vue
Normal file
70
client/components/modals/libraries/LibraryTools.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="w-full h-full px-1 md:px-4 py-1 mb-4">
|
||||
<ui-btn class="mb-4" @click.stop="removeAllMetadataClick('json')">Remove all metadata.json files in library item folders</ui-btn>
|
||||
<ui-btn @click.stop="removeAllMetadataClick('abs')">Remove all metadata.abs files in library item folders</ui-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
library: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
libraryId: String,
|
||||
processing: Boolean
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
librarySettings() {
|
||||
return this.library.settings || {}
|
||||
},
|
||||
mediaType() {
|
||||
return this.library.mediaType
|
||||
},
|
||||
isBookLibrary() {
|
||||
return this.mediaType === 'book'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeAllMetadataClick(ext) {
|
||||
const payload = {
|
||||
message: `Are you sure you want to remove all metadata.${ext} files in your library item folders?`,
|
||||
persistent: true,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.removeAllMetadataInLibrary(ext)
|
||||
}
|
||||
},
|
||||
type: 'yesNo'
|
||||
}
|
||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||
},
|
||||
removeAllMetadataInLibrary(ext) {
|
||||
this.$emit('update:processing', true)
|
||||
this.$axios
|
||||
.$post(`/api/libraries/${this.libraryId}/remove-metadata?ext=${ext}`)
|
||||
.then((data) => {
|
||||
if (!data.found) {
|
||||
this.$toast.info(`No metadata.${ext} files were found in library`)
|
||||
} else if (!data.removed) {
|
||||
this.$toast.success(`No metadata.${ext} files removed`)
|
||||
} else {
|
||||
this.$toast.success(`Successfully removed ${data.removed} metadata.${ext} files`)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to remove metadata files', error)
|
||||
this.$toast.error('Failed to remove metadata files')
|
||||
})
|
||||
.finally(() => {
|
||||
this.$emit('update:processing', false)
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
@ -854,6 +854,56 @@ class LibraryController {
|
||||
res.send(opmlText)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all metadata.json or metadata.abs files in library item folders
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
async removeAllMetadataFiles(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryController] Non-admin user attempted to remove all metadata files`, req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const fileExt = req.query.ext === 'abs' ? 'abs' : 'json'
|
||||
const metadataFilename = `metadata.${fileExt}`
|
||||
const libraryItemsWithMetadata = await Database.libraryItemModel.findAll({
|
||||
attributes: ['id', 'libraryFiles'],
|
||||
where: [
|
||||
{
|
||||
libraryId: req.library.id
|
||||
},
|
||||
Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(libraryFiles) WHERE json_valid(libraryFiles) AND json_extract(json_each.value, "$.metadata.filename") = "${metadataFilename}")`), {
|
||||
[Sequelize.Op.gte]: 1
|
||||
})
|
||||
]
|
||||
})
|
||||
if (!libraryItemsWithMetadata.length) {
|
||||
Logger.info(`[LibraryController] No ${metadataFilename} files found to remove`)
|
||||
return res.json({
|
||||
found: 0
|
||||
})
|
||||
}
|
||||
|
||||
Logger.info(`[LibraryController] Found ${libraryItemsWithMetadata.length} ${metadataFilename} files to remove`)
|
||||
|
||||
let numRemoved = 0
|
||||
for (const libraryItem of libraryItemsWithMetadata) {
|
||||
const metadataFilepath = libraryItem.libraryFiles.find(lf => lf.metadata.filename === metadataFilename)?.metadata.path
|
||||
if (!metadataFilepath) continue
|
||||
Logger.debug(`[LibraryController] Removing file "${metadataFilepath}"`)
|
||||
if ((await fileUtils.removeFile(metadataFilepath))) {
|
||||
numRemoved++
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
found: libraryItemsWithMetadata.length,
|
||||
removed: numRemoved
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that is not using libraryItems from memory
|
||||
* @param {import('express').Request} req
|
||||
|
@ -84,6 +84,7 @@ class ApiRouter {
|
||||
this.router.get('/libraries/:id/recent-episodes', LibraryController.middleware.bind(this), LibraryController.getRecentEpisodes.bind(this))
|
||||
this.router.get('/libraries/:id/opml', LibraryController.middleware.bind(this), LibraryController.getOPMLFile.bind(this))
|
||||
this.router.post('/libraries/order', LibraryController.reorder.bind(this))
|
||||
this.router.post('/libraries/:id/remove-metadata', LibraryController.middleware.bind(this), LibraryController.removeAllMetadataFiles.bind(this))
|
||||
|
||||
//
|
||||
// Item Routes
|
||||
|
Loading…
Reference in New Issue
Block a user