diff --git a/CHANGELOG.md b/CHANGELOG.md index 22a707b..3826027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ * [#498 Add preferences to shows export](https://github.com/WebTools-NG/WebTools-NG/issues/498) * [#500 Library Path Mapping](https://github.com/WebTools-NG/WebTools-NG/issues/500) +* [#505 FindMedia](https://github.com/WebTools-NG/WebTools-NG/issues/505) + ## V0.3.17 (20220601) diff --git a/package-lock.json b/package-lock.json index c0bddde..2b1e3aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "webtools-ng", - "version": "0.3.17", + "version": "0.3.18", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "webtools-ng", - "version": "0.3.17", + "version": "0.3.18", "hasInstallScript": true, "license": "MPL-2.0", "dependencies": { - "@fortawesome/fontawesome-free": "^5.15.2", + "@fortawesome/fontawesome-free": "^5.15.4", "axios": "^0.21.1", "bootstrap": "^4.5.3", "bootstrap-vue": "^2.20.1", @@ -4595,14 +4595,20 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001291", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001291.tgz", - "integrity": "sha512-roMV5V0HNGgJ88s42eE70sstqGW/gwFndosYrikHthw98N5tLnOTxFqMLQjZVRxTWFlJ4rn+MsgXrR7MDPY4jA==", + "version": "1.0.30001357", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001357.tgz", + "integrity": "sha512-b+KbWHdHePp+ZpNj+RDHFChZmuN+J5EvuQUlee9jOQIUAdhv9uvAZeEtUeLAknXbkiu1uxjQ9NLp1ie894CuWg==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -25155,9 +25161,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001291", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001291.tgz", - "integrity": "sha512-roMV5V0HNGgJ88s42eE70sstqGW/gwFndosYrikHthw98N5tLnOTxFqMLQjZVRxTWFlJ4rn+MsgXrR7MDPY4jA==", + "version": "1.0.30001357", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001357.tgz", + "integrity": "sha512-b+KbWHdHePp+ZpNj+RDHFChZmuN+J5EvuQUlee9jOQIUAdhv9uvAZeEtUeLAknXbkiu1uxjQ9NLp1ie894CuWg==", "dev": true }, "case-sensitive-paths-webpack-plugin": { diff --git a/package.json b/package.json index c7f5a40..a9a8a0b 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "main": "background.js", "dependencies": { - "@fortawesome/fontawesome-free": "^5.15.2", + "@fortawesome/fontawesome-free": "^5.15.4", "axios": "^0.21.1", "bootstrap": "^4.5.3", "bootstrap-vue": "^2.20.1", diff --git a/public/locales/en.json b/public/locales/en.json index dd7aba3..af66b10 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -173,7 +173,7 @@ "Name": "ExportTools", "ErrorServer": "No server", "Settings": { - "Settings": "Settings", + "Name": "@:Modules.ET.Name Settings", "Description": "Here you can define the settings for @:Modules.ET.Name", "ArraySep": "Delimiter for multiple info in same field", "ColumnSep": "Delimiter between columns in CSV file (Use 9 for TAB)", @@ -460,15 +460,33 @@ "lblBtnCopy": "Copy", "genRep": "Generate a report" }, - "FindMissing": { - "Name": "Find Missing", - "Description": "@:Modules.PMS.FindMissing.Name module allows you search both filesystem and Plex database to locate medias missing" + "FindMedia": { + "Name": "Find Media", + "Description": "@:Modules.PMS.FindMedia.Name module allows you search both filesystem and Plex database to locate medias missing from either of them", + "RunTask": "Run", + "MissingMapTitle": "Missing a mapped path", + "MissingMapDesc": "One or more defined paths for library is not defined. Goto '@:Modules.PMS.LibMapping.Name' to correct", + "ttSettings": "Settings for @:Modules.PMS.FindMedia.Name module", + "ScanningFS": "Scanning File System", + "ScanningLib": "Scanning Library", + "Settings": { + "Name": "Find Media Settings", + "Description": "@:Modules.PMS.FindMedia.Name settings allows you to customize how @:Modules.PMS.FindMedia.Name works", + "Note": "Note: After changing a setting, please restart @:Common.AppName", + "Ext": "Comma separated file extensions to look for", + "ignoreDirs": "Comma separated list of directories to skip", + "Return": "Save & Return", + "Reset": "Reset to factory standard", + "IgnoreHidden": "Ignore Hidden files/directories on Linux/Mac", + "IgnoreExtras": "Ignore local extras" + } }, "LibMapping": { "Name": "Library Mapping", "Description": "@:Modules.PMS.LibMapping.Name allows you map the defined Library folders path to match a path for this workstation", "ClickToDefine": "Click to define", - "SelectMapDirPath": "Select mapped path" + "SelectMapDirPath": "Select mapped path", + "TTSelectLibrary": "Here you select the library to map" } }, "PlexTV": { diff --git a/src/components/layout/Menu.vue b/src/components/layout/Menu.vue index 84498b9..dd1d16b 100644 --- a/src/components/layout/Menu.vue +++ b/src/components/layout/Menu.vue @@ -101,22 +101,22 @@ icon: 'fas fa-tv', }, { - href: { path: '/pms/findmissing' }, - title: this.$t("Modules.PMS.FindMissing.Name"), - hidden: wtutils.hideMenu('pmsFindMissing'), - icon: 'fas fa-tv' + href: { path: '/pms/findmedia' }, + title: this.$t("Modules.PMS.FindMedia.Name"), + hidden: wtutils.hideMenu('pmsFindMedia'), + icon: 'fas fa-search' }, { href: '/pms/libmapping', hidden: wtutils.hideMenu('pmsLibMapping'), title: this.$t("Common.Menu.Sidebar.PMS.LibMapping"), - icon: 'fa fa-cog' + icon: 'fas fa-hdd' }, { href: '/pms/settings', hidden: wtutils.hideMenu('pmsSettings'), title: this.$t("Common.Menu.Sidebar.PMS.Settings"), - icon: 'fa fa-cog' + icon: 'fas fa-cog' }, { href: { path: '/pms/viewstate' }, @@ -136,13 +136,13 @@ title: this.$t("Common.Menu.Sidebar.Language.NavTitle"), hidden: wtutils.hideMenu('Language'), //icon: 'fas fa-language' - icon: 'fa fa-globe' + icon: 'fas fa-globe' }, { href: '/settings', title: this.$t("Common.Menu.Sidebar.Settings.NavTitle"), hidden: wtutils.hideMenu('Settings'), - icon: 'fa fa-cog' + icon: 'fas fa-cog' }, { href: { path: '/about' }, diff --git a/src/components/modules/ExportTools/defs/def-Levels.json b/src/components/modules/ExportTools/defs/def-Levels.json index cd146ce..080ac0f 100644 --- a/src/components/modules/ExportTools/defs/def-Levels.json +++ b/src/components/modules/ExportTools/defs/def-Levels.json @@ -61,7 +61,6 @@ "Suggest Naming": "includeGuids=1&checkFiles=0&includeRelated=0&includeExtras=0&includeBandwidths=0&includeChapters=0&excludeElements=Actor,Collection,Country,Director,Genre,Label,Mood,Producer,Similar,Writer,Role&excludeFields=summary,tagline", "all": "checkFiles=1&includeExtras=1&includeBandwidths=1&includeChapters=1" } - }, "2": { "levels": { diff --git a/src/components/modules/General/pms.js b/src/components/modules/General/pms.js new file mode 100644 index 0000000..cd215d5 --- /dev/null +++ b/src/components/modules/General/pms.js @@ -0,0 +1,48 @@ +/* +This file contains different functions and methods +that we use fo PMS. + */ + +const log = require('electron-log'); +console.log = log.log; +const {JSONPath} = require('jsonpath-plus'); + +import store from '../../../store'; + +const pms = new class PMS { + constructor() { + } + + async getPMSSections( libTypes ){ + /* + This function will return an array of libraries of specified types + Param: libTypes Like: ['movie', 'show'] + */ + await store.dispatch('fetchSections'); + const sections = await store.getters.getPmsSections; + const result = []; + this.selLib = ""; + if (Array.isArray(sections) && sections.length) { + sections.forEach(req => { + if ( libTypes.includes(req.type)){ + log.debug(`[pms.js] (getPMSSections) - pushing library: ${req.title} to results`); + let item = []; + let itemVal = {}; + itemVal['key'] = JSONPath({path: '$..key', json: req})[0]; + itemVal['location'] = JSONPath({path: '$..path', json: req.location}); + itemVal['type'] = JSONPath({path: '$..type', json: req})[0]; + item['text']=req.title; + //item['value']=JSONPath({path: '$..path', json: req.location}); + item['value']=itemVal; + result.push(Object.assign({}, item)); + } + }); + } else { + log.error("[pms.js] (getPMSSections) - No Library found"); + } + log.debug(`[pms.js] (getPMSSections) - reslut: ${JSON.stringify(result)}`); + return result; + } +} + +export { pms }; \ No newline at end of file diff --git a/src/components/modules/General/wtutils.js b/src/components/modules/General/wtutils.js index 9b12c13..44d758c 100644 --- a/src/components/modules/General/wtutils.js +++ b/src/components/modules/General/wtutils.js @@ -235,6 +235,12 @@ const wtutils = new class WTUtils { } } + async sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + } + hideMenu(menu) { let retVal = false; @@ -289,11 +295,6 @@ const wtutils = new class WTUtils { if ( wtconfig.get('Menu.About', 'N/A') == 'N/A' ){ wtconfig.set('Menu.About', false) } - - - - - // General section if ( wtconfig.get('General.username', 'N/A') == 'N/A' ){ wtconfig.set('General.username', '') @@ -351,6 +352,19 @@ const wtutils = new class WTUtils { if ( wtconfig.get('PMS.ContainerSize.3001', 'N/A') == 'N/A' ){ wtconfig.set('PMS.ContainerSize.3001', 20) } + if ( wtconfig.get('PMS.FindMedia.Settings.Ext', 'N/A') == 'N/A' ){ + wtconfig.set('PMS.FindMedia.Settings.Ext', [ + "3g2","3gp","asf","asx","avc", + "avi","avs","bivx","bup","divx", + "dv","dvr-ms","evo","fli","flv", + "m2t","m2ts","m2v","m4v","mkv", + "mov","mp4","mpeg","mpg","mts", + "nsv","nuv","ogm","ogv","tp", + "pva","qt","rm","rmvb","sdp", + "svq3","strm","ts","ty","vdr", + "viv","vob","vp3","wmv","wpl", + "wtv","xsp","xvid","webm"]) + } // ET Settings if ( wtconfig.get('ET.ChReturn', 'N/A') == 'N/A' ){ wtconfig.set('ET.ChReturn', '') diff --git a/src/components/modules/PMS/FindMedia/FindMedia.vue b/src/components/modules/PMS/FindMedia/FindMedia.vue new file mode 100644 index 0000000..4abb850 --- /dev/null +++ b/src/components/modules/PMS/FindMedia/FindMedia.vue @@ -0,0 +1,162 @@ + + + + + + diff --git a/src/components/modules/PMS/FindMedia/Settings/FindMediaSettings.vue b/src/components/modules/PMS/FindMedia/Settings/FindMediaSettings.vue new file mode 100644 index 0000000..e550e12 --- /dev/null +++ b/src/components/modules/PMS/FindMedia/Settings/FindMediaSettings.vue @@ -0,0 +1,145 @@ + + + + + + diff --git a/src/components/modules/PMS/FindMedia/scripts/FindMedia.js b/src/components/modules/PMS/FindMedia/scripts/FindMedia.js new file mode 100644 index 0000000..a0a2b04 --- /dev/null +++ b/src/components/modules/PMS/FindMedia/scripts/FindMedia.js @@ -0,0 +1,413 @@ +// This file holds generic FM functions + +const log = require('electron-log'); +console.log = log.log; + +const fs = require("fs"); +const path = require("path"); +const {JSONPath} = require('jsonpath-plus'); +var sanitize = require("sanitize-filename"); + +import store from '../../../../../store'; +import { wtutils,wtconfig } from '../../../General/wtutils'; +import { status } from '../../../General/status'; +import i18n from '../../../../../i18n'; +import { resolve } from 'path'; +import axios from 'axios'; +import {csv} from '../../../ExportTools/scripts/csv'; + + +const validDir = function( dirName ) { + /* Will check if directory is not hidden or should be ignored + returns true or false */ + log.silly(`Checking if ${dirName} is valid`); + // Got a hidden one? + if ( wtconfig.get('PMS.FindMedia.Settings.IgnoreHidden', true) === 'true' ){ + if ( dirName.startsWith('.') ){ + log.silly(`We do not allow hidden dirs like: ${dirName}`); + return false; + } + } + // Got an Extra dir? + if ( findMedia.settingsIgnoreExtras ){ + if ( findMedia.ExtraDirs.includes( dirName )){ + log.silly(`We do not allow extra dirs like: ${dirName}`); + return false; + } + } + // Got a dir to ignore? + if ( findMedia.settingsIgnoreDirs.includes( dirName ) ){ + log.silly(`We do not allow ignore dirs like: ${dirName}`); + return false; + } + log.silly(`${dirName} is valid`); + return true; +} + +const validFile = function( fileName ) { + /* Will check if file is valid or not + returns true or false */ + log.silly(`[FindMedia.js] (validFile) - Checking file: ${fileName}`) + if ( findMedia.validExt.includes(path.extname(fileName).toLowerCase().slice(1))){ + log.silly(`[FindMedia.js] (validFile) - Valid ext for file: ${fileName}`); + if ( findMedia.settingsIgnoreExtras ){ + log.silly(`[FindMedia.js] (validFile) - Checking IgnoreExtras for file: ${fileName}`) + for (let eFile of findMedia.Extrafiles) { + if ( path.parse(fileName).name.endsWith(eFile) ){ + log.silly(`We ignore extra file: ${fileName}`) + return false; + } + } + return true; + } else { return true } + } else { + log.silly(`[FindMedia.js] (validFile) - Ext not valid for file: ${fileName}`) + return false + } +} + +const getAllFiles = function( dirPath, orgDirPath, arrayOfFiles ) { + /* + Recursive scanning of a filepath + Takes dirPath and orgDirPath as parameter + for the starting dir. (Should be the same) + Will return an raw array, as well as populate + findMedia.filesFound array + */ + var files = fs.readdirSync(dirPath); + // Set array if needed + arrayOfFiles = arrayOfFiles || []; + files.forEach(function(curFile) { + // Is this a directory? + if (fs.statSync(dirPath + "/" + curFile).isDirectory()) { + // Check if valid dir, then call req. + if ( validDir( curFile ) ){ + arrayOfFiles = getAllFiles(path.join(dirPath, curFile), orgDirPath, arrayOfFiles) + } + } else { + // We found a file + if ( validFile( curFile ) ){ + // Force forward slash + let lookupPath = path.join(dirPath, curFile).replaceAll('\\', '/'); + log.silly(`[FindMedia.js] (getAllFiles) - Adding ${lookupPath.slice(orgDirPath.length + 1)}: ${ path.join(dirPath, curFile) }`); + //status.updateStatusMsg( status.RevMsgType.Item, `GED Found file: ${curFile}`); + findMedia.filesFound[lookupPath.slice(orgDirPath.length + 1)] = path.join(dirPath, curFile); + } + } + }) + + return arrayOfFiles +} + + + + + + + +const findMedia = new class FINDMEDIA { + constructor() { + this.validExt = [ + '3g2','3gp','asf','asx','avc','avi','avs','bivx','bup','divx','dv','dvr-ms', + 'evo','fli','flv','m2t','m2ts','m2v','m4v', + 'mkv','mov','mp4','mpeg','mpg','mts','nsv','nuv','ogm','ogv','tp','pva','qt','rm','rmvb','sdp','svq3', + 'strm','ts','ty','vdr','viv','vob','vp3','wmv','wpl','wtv','xsp','xvid','webm' + ]; + this.ExtraDirs = [ + 'Behind The Scenes', 'Deleted Scenes', 'Featurettes', + 'Interviews', 'Scenes', 'Shorts', 'Trailers', 'Other' + ]; + this.Extrafiles = [ + '-behindthescenes', '-deleted', '-featurette', + '-interview', '-scene', '-short', '-trailer', '-other' + ]; + this.ignoreDirs = [ + 'lost+found' + ]; + this.libPaths = []; + this.filePath = []; + this.validExt = []; + this.currentLibPathLength; + this.filesFound = {}; + this.libFiles = []; + this.PMSLibPaths = []; + this.csvFile = ''; + this.libName = ''; + this.csvStream; + this.settingsIgnoreExtras; + this.ignoreDirs; + + } + + // Generate the filename for an export + async getFileName({ Type, libKey }){ + const dateFormat = require('dateformat'); + const OutDir = wtconfig.get('General.ExportPath'); + const timeStamp = dateFormat(new Date(), "yyyy.mm.dd_h.MM.ss"); + const path = require('path'); + let arrFile = []; + arrFile.push(store.getters.getSelectedServer.name); //PMSName + arrFile.push(JSONPath({path: `$.[?(@.key==${libKey})].title`, json: store.getters.getPmsSections})); //libName + arrFile.push(timeStamp + '.' + Type + '.tmp'); + let outFile = sanitize(arrFile.join('_')); + const targetDir = path.join(OutDir, wtutils.AppName, i18n.t('Modules.PMS.Name'), i18n.t('Modules.PMS.FindMedia.Name')); + const outFileWithPath = path.join(targetDir, outFile); + // Make sure target dir exists + const fs = require('fs') + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); + } + log.info(`[FindMedia.js] (getFileName) - OutFile is: ${outFileWithPath}`); + return outFileWithPath; + } + + async makeOutFile( libKey ){ + let csvFile; + const intSep = '{*WTNG-ET*}'; + // Get Header fields + let fields = ['Title', 'PMS file', 'Filesystem']; + var fs = require('fs'); + csvFile = await this.getFileName({ Type: 'csv', libKey: libKey }); + this.csvStream = fs.createWriteStream(csvFile, {flags:'a'}); + await csv.addHeaderToTmp({ stream: this.csvStream, item: fields}); + const NA = wtconfig.get('ET.NotAvail', 'N/A'); + // Add only on File System + for(var key in this.filesFound){ + //console.log(key+": "+this.filesFound[key]); + let row = NA + intSep + NA + intSep + this.filesFound[key]; + row = row.replaceAll(intSep, wtconfig.get("ET.ColumnSep", '|')); + csv.addRowToTmp({ stream: this.csvStream, item: row}); + } + // Add only in PMS + for( key in this.libFiles){ + console.log( JSON.stringify(this.libFiles[key])); + let row = this.libFiles[key]['title'] + intSep + this.libFiles[key]['file'] + intSep + NA; + row = row.replaceAll(intSep, wtconfig.get("ET.ColumnSep", '|')); + csv.addRowToTmp({ stream: this.csvStream, item: row}); + } + // Close filestream + this.csvStream.end(); + // Rename outFile to real name + let newFile; + newFile = csvFile.replace('.tmp', '') + fs.renameSync(csvFile, newFile); + return newFile; + } + + async findMedia( libpaths, libKey, libType ){ + status.updateStatusMsg( status.RevMsgType.Status, i18n.t('Common.Status.Msg.Processing')); + // Get settings needed + this.validExt = await wtconfig.get('PMS.FindMedia.Settings.Ext'); + this.settingsIgnoreExtras = await wtconfig.get('PMS.FindMedia.Settings.IgnoreExtras'); + this.settingsIgnoreDirs = await wtconfig.get('PMS.FindMedia.Settings.ignoreDirs'); + + + // Scan file system + this.filesFound = []; + await findMedia.scanFileSystemPaths( libpaths ); + // Scan library + await findMedia.scanPMSLibrary(libKey, libType); + // Create output file + let outFile = await this.makeOutFile( libKey ); + + status.clearStatus(); + status.updateStatusMsg( status.RevMsgType.Status, i18n.t('Common.Status.Msg.Finished')); + status.updateStatusMsg( status.RevMsgType.OutFile, outFile); + +libType + + } + + async checkPathMapping( paths ){ + /* + This will check paths if mapped + will return 'ok' if all is good, or + else the path that needs a mapping + */ + return new Promise((resolve) => { + let gotError = false; + log.info(`[FindMedia.js] (checkPathMapping) Starting`); + log.verbose(`[FindMedia.js] (checkPathMapping) We will check the following filePaths: ${ JSON.stringify(paths)}`); + // First we need to check if libPath Mappings has been defined, and if not, alert user + // To do this, we need the selected servers ID + // Get ServerID + let retVal = 'ok' + const serverID = store.getters.getSelectedServer.clientIdentifier; + paths.forEach(libPath => { + // Escape . in libPath + libPath = libPath.replace('.', '\\.'); + let mappedLibPath = wtconfig.get(`PMS.LibMapping.${serverID}.${libPath}`, 'WTNG_ERROR_WTNG'); + if ( mappedLibPath === 'WTNG_ERROR_WTNG'){ + log.error(`[FindMedia.js] (checkPathMapping) - missing: ${libPath}`); + gotError = true; + } + }); + log.info(`[FindMedia.js] (checkPathMapping) All done`); + if ( gotError){ + retVal = 'WTNG_ERROR_WTNG'; + } + resolve (retVal); + }); + } + + async scanFileSystemPaths( paths ){ + /// This will scan the filesystem for medias + status.updateStatusMsg( status.RevMsgType.Info, i18n.t('Modules.PMS.FindMedia.ScanningFS')); + await new Promise(resolve => setTimeout(resolve, 50)); + return new Promise((resolve) => { + log.info(`[FindMedia.js] (scanFileSystemPaths) - Starting`); + log.debug(`[FindMedia.js] (scanFileSystemPaths) - We will scan the following filePaths: ${ JSON.stringify(paths)}`); + // Reset output + findMedia.filePath = []; + this.filesFound = {}; + const serverID = store.getters.getSelectedServer.clientIdentifier; + //Walk each paths + paths.forEach(async libPath => { + let mappedLibPath = wtconfig.get(`PMS.LibMapping.${serverID}.${libPath.replace('.', '\\.')}`, 'WTNG_ERROR_WTNG'); + status.updateStatusMsg( status.RevMsgType.Info, `Now Scanning ${mappedLibPath}`); + log.debug(`[FindMedia.js] (scanFileSystemPaths) - PMS path is: ${libPath}`); + log.debug(`[FindMedia.js] (scanFileSystemPaths) - Wkstn path is: ${mappedLibPath}`); + //findMedia.filePath.push(...getAllFiles( mappedLibPath, mappedLibPath )); + findMedia.filePath.push(...getAllFiles( mappedLibPath, mappedLibPath )); + }); + log.info(`[FindMedia.js] (scanFileSystemPaths) - End`); + resolve(); + }); + } + + async scanPMSLibrary( library, libType ){ + /* + This will scan the PMS library, and add result to this.libPaths, + if not present in this.filePath. + If present, then pop it from this.filePath + */ + log.info(`[FindMedia.js] (scanPMSLibrary) - Starting`); + log.verbose(`[FindMedia.js] (scanPMSLibrary) - We will scan library with a key of: ${ library } and a type of: ${libType}`); + status.updateStatusMsg( status.RevMsgType.Info, i18n.t('Modules.PMS.FindMedia.ScanningLib')); + await findMedia.getPMSPathArr(); + findMedia.libFiles = []; + // We need to find type of lib, and total count as well + let index = 0; + let step = 0; + let size = 0; + let totalSize = 0; + let mediaType = 1; + if ( libType === 'show'){ + mediaType = 4; + } + let url = `${store.getters.getSelectedServerAddress}/library/sections/${library}/all?excludeElements=Genre,Director,Writer,Country,Role,Producer,Collections&excludeFields=summary,tagline,rating,contentRating,audienceRatingImage&X-Plex-Container-Start=${index}&X-Plex-Container-Size=${step}&type=${mediaType}` + let header = wtutils.PMSHeader; + header['X-Plex-Token'] = store.getters.getSelectedServer.accessToken; + await axios({ + method: 'get', + url: url, + headers: header + }) + .then((response) => { + log.debug('[FindMedia.js] (scanPMSLibrary) - Response recieved'); + log.silly(`[FindMedia.js] (scanPMSLibrary) - Response returned as: ${JSON.stringify(response.data)}`); + totalSize = JSONPath({path: `$.MediaContainer.totalSize`, json: response.data})[0]; + if ( JSONPath({path: `$.MediaContainer.viewGroup`, json: response.data})[0] === 'show'){ + mediaType = 4; + } + }) + .catch(function (error) { + if (error.response) { + log.error(`[FindMedia.js] (scanPMSLibrary) - ${JSON.stringify(error.response.data)}`); + alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message); + } else if (error.request) { + log.error(`[FindMedia.js] (scanPMSLibrary) - ${JSON.stringify(error.request)}`); + } else { + log.error(`[FindMedia.js] (scanPMSLibrary) - ${JSON.stringify(error.message)}`); + } + }); + +// totalSize, size +// resolve(); + + + step = wtconfig.get("PMS.ContainerSize." + mediaType, 20); + let metaData; + do { + url = `${store.getters.getSelectedServerAddress}/library/sections/${library}/all?excludeElements=Genre,Director,Writer,Country,Role,Producer,Collections&excludeFields=summary,tagline,rating,contentRating,audienceRatingImage&X-Plex-Container-Start=${index}&X-Plex-Container-Size=${step}&type=${mediaType}`; + status.updateStatusMsg( status.RevMsgType.Items, i18n.t('Common.Status.Msg.ProcessItem_0_1', {count: index, total: totalSize})); + log.verbose(`[FindMedia.js] (scanPMSLibrary) - Calling url: ${ url } `); + await axios({ + method: 'get', + url: url, + headers: header + }) + .then((response) => { + log.debug('[FindMedia.js] (scanPMSLibrary) - Response recieved'); + log.silly(`[FindMedia.js] (scanPMSLibrary) - Response returned as: ${JSON.stringify(response.data)}`); + size = JSONPath({path: `$.MediaContainer.size`, json: response.data})[0]; + metaData = JSONPath({path: `$.MediaContainer.Metadata`, json: response.data})[0]; + for (var idxMetaData in metaData) + { + var title = JSONPath({path: `$..title`, json: metaData[parseInt(idxMetaData)]})[0]; + var files = JSONPath({path: `$..Part[*].file`, json: metaData[parseInt(idxMetaData)]}); + for (var idxFiles in files){ + if (this.validExt.includes(path.extname(files[idxFiles]).toLowerCase().slice(1))){ + for (var idxPMSLibPaths in this.PMSLibPaths){ + if (files[idxFiles].startsWith(this.PMSLibPaths[idxPMSLibPaths])){ + // Slice to lookup in files found + var lookup = files[idxFiles].slice(this.PMSLibPaths[idxPMSLibPaths].length + 1) + lookup = lookup.replaceAll('\\', '/'); + if ( Object.prototype.hasOwnProperty.call(this.filesFound, lookup)) { + // We need to remove from detected files, since we found it + delete this.filesFound[lookup]; + } + else { + // Not found, so only in PMS + let entry = {} + entry['title'] = title; + entry['file'] = files[idxFiles] + this.libFiles.push(entry); + } + } + } + } + } + } + index += step; + }) + .catch(function (error) { + if (error.response) { + log.error(`[FindMedia.js] (scanPMSLibrary) - ${JSON.stringify(error.response.data)}`); + alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message); + return + } else if (error.request) { + log.error(`[FindMedia.js] (scanPMSLibrary) - ${JSON.stringify(error.request)}`); + return + } else { + log.error(`[FindMedia.js] (scanPMSLibrary) - ${JSON.stringify(error.message)}`); + return + } + }); + } while ( size == step ); + log.info(`[FindMedia.js] (scanPMSLibrary) - End`); + resolve(); + } + + async getPMSPathArr(){ + /* + This will populate this.PMSLibPaths + with an array of library paths used + */ + log.info(`[FindMedia.js] (getPMSPathArr) - Start`); + // Reset property + this.PMSLibPaths = []; + // Start by getting Server ID + const serverID = store.getters.getSelectedServer.clientIdentifier; + // Now lookup defined mappings, so we can add them to the array + let mappedLibPaths = wtconfig.get(`PMS.LibMapping.${serverID}`, 'WTNG_ERROR_WTNG'); + for (var idxPath in Object.keys(mappedLibPaths)){ + this.PMSLibPaths.push(Object.keys(mappedLibPaths)[idxPath]); + } + log.debug(`[FindMedia.js] (getPMSPathArr) - PMSLibPaths is ${this.PMSLibPaths}`); + log.info(`[FindMedia.js] (getPMSPathArr) - End`); + resolve(); + } +} + +export { findMedia }; \ No newline at end of file diff --git a/src/components/modules/PMS/FindMissing/FindMissing.vue b/src/components/modules/PMS/FindMissing/FindMissing.vue deleted file mode 100644 index c269ebb..0000000 --- a/src/components/modules/PMS/FindMissing/FindMissing.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - diff --git a/src/components/modules/PMS/LibraryMapping/LibraryMapping.vue b/src/components/modules/PMS/LibraryMapping/LibraryMapping.vue index 1c10fae..77eee94 100644 --- a/src/components/modules/PMS/LibraryMapping/LibraryMapping.vue +++ b/src/components/modules/PMS/LibraryMapping/LibraryMapping.vue @@ -6,10 +6,11 @@

+
- {{ $t('Modules.PMS.Butler.TTSelectTask') }} + {{ $t('Modules.PMS.LibMapping.TTSelectLibrary') }} { - if ( ['movie', 'show'].includes(req.type)){ - log.debug(`[LibraryMapping.vue] (getPMSSections) - pushing library: ${req.title} to results`); - let item = []; - item['text']=req.title; - item['value']=JSONPath({path: '$..path', json: req.location}); - result.push(Object.assign({}, item)); - } - }); - } else { - log.error("[LibraryMapping.vue] (getPMSSections) - No Library found"); - result.push["No Library found"]; - } - this.selLibOptions = result; + this.selLibOptions = await pms.getPMSSections(['movie', 'show']); }, /* Check if a server is selected, and if not tell user, and disable backup */ @@ -138,24 +124,30 @@ const serverID = store.getters.getSelectedServer.clientIdentifier; log.info(`[LibraryMapping.vue] (getLibPath) ServerID is: ${serverID}`); const fs = require("fs"); - arguments[0].forEach(element => { - let entry; - const wkstnPath = wtconfig.get(`PMS.LibMapping.${serverID}.${element}`, this.$t("Modules.PMS.LibMapping.ClickToDefine")); + arguments[0]['location'].forEach(element => { + let entry, virtualElement; + virtualElement = element.replace('.', '\\.'); + //const wkstnPath = wtconfig.get(`PMS.LibMapping.${serverID}.${element}`, this.$t("Modules.PMS.LibMapping.ClickToDefine")); + const wkstnPath = wtconfig.get(`PMS.LibMapping.${serverID}.${virtualElement}`, this.$t("Modules.PMS.LibMapping.ClickToDefine")); log.info(`[LibraryMapping.vue] (getLibPath) Saved path is: ${wkstnPath}`); // Check if path exists if (fs.existsSync(wkstnPath)) { log.debug(`[LibraryMapping.vue] (getLibPath) Saved path existed`); + //element = element.replace('\\.', '.') entry = {PMS: element, Workstation: wkstnPath, _rowVariant: 'success'}; + //entry = {PMS: virtualElement, Workstation: wkstnPath, _rowVariant: 'success'}; } else { log.debug(`[LibraryMapping.vue] (getLibPath) Saved path not defined`); if (fs.existsSync(element)) { log.debug(`[LibraryMapping.vue] (getLibPath) PMS path existed`); wtconfig.set(`PMS.LibMapping.${serverID}.${element}`, element); entry = {PMS: element, Workstation: element, _rowVariant: 'success'}; + //entry = {PMS: virtualElement, Workstation: element, _rowVariant: 'success'}; } else { log.error(`[LibraryMapping.vue] (getLibPath) PMS path unknown`); entry = {PMS: element, Workstation: wkstnPath, _rowVariant: 'danger'}; + //entry = {PMS: virtualElement, Workstation: wkstnPath, _rowVariant: 'danger'}; } } arrPath.push(entry); diff --git a/src/components/modules/PMS/PMS.vue b/src/components/modules/PMS/PMS.vue index e27a2be..ce4fa61 100644 --- a/src/components/modules/PMS/PMS.vue +++ b/src/components/modules/PMS/PMS.vue @@ -21,10 +21,10 @@
* {{ $t("Modules.PMS.DVR.Description") }}

-
-

{{ $t("Modules.PMS.FindMissing.Name") }} +

+

{{ $t("Modules.PMS.FindMedia.Name") }}
- * {{ $t("Modules.PMS.FindMissing.Description") }}

+ * {{ $t("Modules.PMS.FindMedia.Description") }}

{{ $t("Modules.PMS.LibMapping.Name") }} @@ -64,7 +64,7 @@ selLevel: "", showButler: !(wtutils.hideMenu('pmsButler')), showDVR: !(wtutils.hideMenu('pmsDVR')), - showFindMissing: !(wtutils.hideMenu('pmsFindMissing')), + showFindMedia: !(wtutils.hideMenu('pmsFindMedia')), showLibMapping: !(wtutils.hideMenu('pmsLibMapping')), showSettings: !(wtutils.hideMenu('pmsSettings')), showViewState: !(wtutils.hideMenu('pmsViewState')) diff --git a/src/router/index.js b/src/router/index.js index 30239d2..c05ae09 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -12,7 +12,8 @@ import Butler from '../components/modules/PMS/Butler/butler'; import Language from '../components/modules/Main/Language.vue'; import GlobalSettings from '../components/modules/Main/GlobalSettings'; import DVR from '../components/modules/PMS/DVR/DVR'; -import FindMissing from '../components/modules/PMS/FindMissing/FindMissing'; +import FindMedia from '../components/modules/PMS/FindMedia/FindMedia'; +import FindMediaSettings from '../components/modules/PMS/FindMedia/Settings/FindMediaSettings'; import LibraryMapping from '../components/modules/PMS/LibraryMapping/LibraryMapping'; import ViewState from '../components/modules/PMS/ViewState/ViewState'; import About from '../components/modules/Main/About'; @@ -97,9 +98,15 @@ Vue.use(VueRouter) meta: {requiresAuth: true} }, { - path: '/pms/findmissing', - name: "FindMissing", - component: FindMissing, + path: '/pms/findmedia', + name: "FindMedia", + component: FindMedia, + meta: {requiresAuth: true} + }, + { + path: '/pms/findmedia/Settings', + name: "FindMediaSettings", + component: FindMediaSettings, meta: {requiresAuth: true} }, {