diff --git a/client/components/modals/libraries/EditModal.vue b/client/components/modals/libraries/EditModal.vue index 1fd011cf..09c0fc1d 100644 --- a/client/components/modals/libraries/EditModal.vue +++ b/client/components/modals/libraries/EditModal.vue @@ -12,9 +12,9 @@
- + -
+
{{ buttonText }}
@@ -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' diff --git a/client/components/modals/libraries/LibraryTools.vue b/client/components/modals/libraries/LibraryTools.vue new file mode 100644 index 00000000..d1e62dd4 --- /dev/null +++ b/client/components/modals/libraries/LibraryTools.vue @@ -0,0 +1,70 @@ + + + \ No newline at end of file diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 2b76d6b3..9c593ff2 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -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 diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index a90d1873..03a0696c 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -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