Add:Tools tab on library modal, api endpoint to remove all metadata files from library item folders

This commit is contained in:
advplyr 2023-10-17 17:46:43 -05:00
parent 0d5792405f
commit b4ce5342c0
4 changed files with 131 additions and 2 deletions

View File

@ -12,9 +12,9 @@
</div> </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"> <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"> <div class="flex justify-end">
<ui-btn @click="submit">{{ buttonText }}</ui-btn> <ui-btn @click="submit">{{ buttonText }}</ui-btn>
</div> </div>
@ -57,6 +57,9 @@ export default {
mediaType() { mediaType() {
return this.libraryCopy?.mediaType return this.libraryCopy?.mediaType
}, },
libraryId() {
return this.library?.id
},
tabs() { tabs() {
return [ return [
{ {
@ -78,6 +81,11 @@ export default {
id: 'schedule', id: 'schedule',
title: this.$strings.HeaderSchedule, title: this.$strings.HeaderSchedule,
component: 'modals-libraries-schedule-scan' component: 'modals-libraries-schedule-scan'
},
{
id: 'tools',
title: this.$strings.HeaderTools,
component: 'modals-libraries-library-tools'
} }
].filter((tab) => { ].filter((tab) => {
return tab.id !== 'scanner' || this.mediaType === 'book' return tab.id !== 'scanner' || this.mediaType === 'book'

View 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>

View File

@ -854,6 +854,56 @@ class LibraryController {
res.send(opmlText) 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 * Middleware that is not using libraryItems from memory
* @param {import('express').Request} req * @param {import('express').Request} req

View File

@ -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/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.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/order', LibraryController.reorder.bind(this))
this.router.post('/libraries/:id/remove-metadata', LibraryController.middleware.bind(this), LibraryController.removeAllMetadataFiles.bind(this))
// //
// Item Routes // Item Routes