Merge pull request #470 from WebTools-NG/#459-Viewstate

#459 viewstate
This commit is contained in:
Tommy Mikkelsen 2022-04-27 01:03:40 +02:00 committed by GitHub
commit 54f6a1868a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 917 additions and 147 deletions

View file

@ -1,6 +1,6 @@
# ![Logo](https://github.com/WebTools-NG/WebTools-NG/blob/master/src/assets/WebTools-48x48.png) WebTools-ng Change log
## V0.3.15 (Not released yet)
## V0.3.16 ( Not Released Yet)
**Note**: This version is an Beta version
@ -11,6 +11,20 @@
**Changes**:
* [#440 Dvr Backup and Restore](https://github.com/WebTools-NG/WebTools-NG/issues/440)
## V0.3.15 (20220413)
**Note**: This version is an Beta version
**Note 2**: In this version, the following is disabled:
* Export to xlsx format ([See #331](https://github.com/WebTools-NG/WebTools-NG/issues/331))
* Photo Library export
**Changes**:
* [#453 Kickstart Butler Scheduled tasks](https://github.com/WebTools-NG/WebTools-NG/issues/453)
* [#456 ET Custom levels doesn't use include params](https://github.com/WebTools-NG/WebTools-NG/issues/456)
* Internal: More easy way to hide stuff in progress

View file

@ -1,7 +1,7 @@
{
"name": "webtools-ng",
"productName": "WebTools-NG",
"version": "0.3.15",
"version": "0.3.16",
"description": "WebTools Next Generation 4 Plex",
"author": "dane22 & CPSO",
"license": "MPL-2.0",

View file

@ -117,6 +117,7 @@
}
},
"Ok": "OK",
"Cancel": "Cancel",
"Reload": "Reload",
"Update": {
"Title": "Update detected",
@ -397,8 +398,39 @@
"ttselDVR": "Here you select the DVR you want to make a backup of",
"lblBtnBackup": "Backup",
"lblBtnRestore": "Restore",
"BackupDone": "DVR has been saved"
}
"BackupDone": "DVR has been saved",
"selRestoreFile": "Select DVR file to restore",
"confirmRestore": "Confirm restore",
"restoreMsg": "Are you sure you want to restore the DVR named: '{0}'",
"selAlredyPresentMsg": "The selected DVR: '{0}' already exist on your PMS. So we will abort",
"selAlredyPresentTitle": "Aborting",
"selMissingDevice": "The selected backup file is missing devices",
"selDeviceAlreadyUsed": "The device is in use with an existing DVR"
},
"ViewState": {
"Name": "View State Copy",
"Description": "@:Modules.PMS.ViewState.Name module allows you to copy a users list of seen medias to another user on the same server. Note: It's important, that both users has access to the medias",
"selSourceUsr": "Select User to copy from",
"ttSelSourceUsr": "Here you select the user you wants to copy view states from",
"selTargetUsr": "Select User to copy to",
"ttSelTargetUsr": "Here you select the user you wants to copy view states to",
"Owner": "Server owner",
"Managed": "Managed User",
"lblBtnCopy": "Copy",
"Status": {
"Msg": {
"CollectUserInfo": "Collecting user info...Please wait",
"Processing": "Processing",
"Idle": "Idle",
"GatheringLibs": "Gathering libraries to process"
},
"Names": {
"Status": "Status",
"LibsToProcess": "Libraries to process",
"StartTime": "Start time"
}
}
}
},
"PlexTV": {
"Name": "Plex.TV",
@ -416,6 +448,6 @@
"ExportUsr": "Export user to CSV",
"ExportAllUsr": "Export all users to CSV",
"Settings": "To configure column separator and 'Not Avail', use @:Modules.ET.Name settings"
}
}
}
}

View file

@ -97,15 +97,15 @@ export default {
},
methods: {
fetchServers(){
log.info("fetching servers")
log.info("[Headers.vue] (fetchServers) fetching servers")
this.$store.dispatch('fetchPlexServers', store.getters.getAuthToken);
},
active2(e) {
log.info("active2 called")
log.info("[Headers.vue] (active2) active2 called")
this.active = e;
},
selected: function () {
log.info('Selected server: ' + this.selectedOption.name)
log.info(`[Headers.vue] (selected) Selected server: ${this.selectedOption.name}`)
ptv.checkServerConnect(this.selectedOption)
this.$store.commit("UPDATE_SELECTED_SERVER", this.selectedOption);
},

View file

@ -1,5 +1,5 @@
<template>
<sidebar-menu
<sidebar-menu
:menu="menu"
:collapsed="collapsed"
@toggle-collapse="onToggleCollapse"
@ -10,8 +10,8 @@
const log = require('electron-log');
console.log = log.log;
import '@fortawesome/fontawesome-free/css/all.css';
import etIcon from '@/assets/ET-256.png';
import pmsIcon from '@/assets/plex-pms-icon.png';
import etIcon from '@/assets/ET-256.png';
import pmsIcon from '@/assets/plex-pms-icon.png';
import plextvIcon from '@/assets/plex-app-icon.png';
import {wtconfig, wtutils} from '../../components/modules/General/wtutils';
@ -20,15 +20,15 @@
export default {
data() {
return {
collapsed: false
return {
collapsed: false
}
},
computed: {
menu(){
return [
{
href: { path: '/home' },
href: { path: '/home' },
title: this.$t("Common.Menu.Sidebar.Home.NavTitle"),
icon: 'fa fa-home'
},
@ -36,7 +36,7 @@
header: true,
title: this.$t("Common.Menu.Sidebar.NavSections.Tools"),
hiddenOnCollapse: true
},
},
{
href: { path: '/plextv' },
title: this.$t("Modules.PlexTV.Name"),
@ -46,7 +46,7 @@
//adjust element
element: 'img',
attributes: { src: plextvIcon },
}
}
},
{
href: { path: '/pms' },
@ -59,15 +59,15 @@
attributes: { src: pmsIcon },
},
child: [
{
{
href: '/pms/settings',
hidden: wtutils.hideMenu('pmsSettings'),
title: this.$t("Common.Menu.Sidebar.PMS.Settings"),
title: this.$t("Common.Menu.Sidebar.PMS.Settings"),
icon: 'fa fa-cog'
},
{
{
href: '/pms/butler',
title: this.$t("Common.Menu.Sidebar.PMS.Butler"),
title: this.$t("Common.Menu.Sidebar.PMS.Butler"),
hidden: wtutils.hideMenu('pmsButler'),
icon: 'fa fa-tasks'
},
@ -75,7 +75,14 @@
href: { path: '/pms/dvr' },
title: this.$t("Modules.PMS.DVR.Name"),
hidden: wtutils.hideMenu('pmsDVR'),
icon: 'fas fa-tv',
icon: 'fas fa-tv',
}
,
{
href: { path: '/pms/viewstate' },
title: this.$t("Modules.PMS.ViewState.Name"),
hidden: wtutils.hideMenu('pmsViewState'),
icon: 'fas fa-tv',
}
]
},
@ -87,23 +94,23 @@
icon: {
//adjust element
element: 'img',
attributes: { src: etIcon },
attributes: { src: etIcon },
},
child: [
{
{
href: '/export/settings',
title: this.$t("Common.Menu.Sidebar.ET.Settings"),
hidden: wtutils.hideMenu('etSettings'),
hidden: wtutils.hideMenu('etSettings'),
icon: 'fa fa-cog'
},
{
{
href: '/export/custom',
title: this.$t("Common.Menu.Sidebar.ET.Custom"),
hidden: wtutils.hideMenu('etCustom'),
hidden: wtutils.hideMenu('etCustom'),
icon: 'fa fa-cog'
}
]
},
{
header: true,
@ -119,8 +126,8 @@
},
{
href: '/settings',
title: this.$t("Common.Menu.Sidebar.Settings.NavTitle"),
hidden: wtutils.hideMenu('Settings'),
title: this.$t("Common.Menu.Sidebar.Settings.NavTitle"),
hidden: wtutils.hideMenu('Settings'),
icon: 'fa fa-cog'
},
{
@ -142,7 +149,7 @@
onToggleCollapse(collapsed) {
this.$emit("e-iscollapsed", collapsed);
log.info(collapsed)
}
}
}
}
</script>

View file

@ -69,16 +69,16 @@ function setQualifier( {str:str})
function cleanupSuggestedFile( tmpFileName )
{
const unWantedChars = '.-*_[](){}';
log.verbose(`etHelper (cleanupSuggestedFile) - starting Param: ${tmpFileName}`);
log.verbose(`etHelper (cleanupSuggestedFile) - starting Param: ${tmpFileName}`);
// Now replace square brackets if present with a dot
tmpFileName = tmpFileName.replaceAll("[", ".");
tmpFileName = tmpFileName.replaceAll("]", ".");
tmpFileName = tmpFileName.replaceAll("]", ".");
// Start by trimming the string
tmpFileName = tmpFileName.trim();
tmpFileName = tmpFileName.trim();
while ( unWantedChars.indexOf(tmpFileName.charAt(0)) > -1)
{
tmpFileName = tmpFileName.slice(1).trim();
if ( tmpFileName.length === 0 ) break;
tmpFileName = tmpFileName.slice(1).trim();
if ( tmpFileName.length === 0 ) break;
}
// Remove from end of the string
while ( unWantedChars.indexOf(tmpFileName.charAt(tmpFileName.length-1)) > -1)
@ -91,8 +91,8 @@ function cleanupSuggestedFile( tmpFileName )
// Now delete empty brackets
tmpFileName = tmpFileName.replaceAll("()", "");
tmpFileName = tmpFileName.replaceAll("{}", "");
log.verbose(`etHelper (cleanupSuggestedFile) - Returning: ${tmpFileName}`);
return tmpFileName;
log.verbose(`etHelper (cleanupSuggestedFile) - Returning: ${tmpFileName}`);
return tmpFileName;
}
// Returns a suggested title for a media
@ -124,8 +124,8 @@ function getSuggestedYear( data )
// Returns a suggested id for a media
function getSuggestedId( data )
{
log.verbose(`etHelper (getSuggestedId) - Started. To see Param, switch to Silly logging`);
log.silly(`etHelper (getSuggestedId) - Starting with param: ${JSON.stringify(data)}`);
log.verbose(`etHelper (getSuggestedId) - Started. To see Param, switch to Silly logging`);
log.silly(`etHelper (getSuggestedId) - Starting with param: ${JSON.stringify(data)}`);
let imdb = String(JSONPath({path: "$.data.Guid[?(@.id.startsWith('imdb'))].id", json: data}));
let tmdb = String(JSONPath({path: "$.data.Guid[?(@.id.startsWith('tmdb'))].id", json: data}));
let tvdb = String(JSONPath({path: "$.data.Guid[?(@.id.startsWith('tvdb'))].id", json: data}));
@ -142,7 +142,7 @@ function getSuggestedId( data )
{
tvdb = imdb;
}
let selId;
let selId;
if ( etHelper.Settings.libType === 1)
{
selId = wtconfig.get("ET.SelectedMoviesID", "imdb");
@ -151,9 +151,9 @@ function getSuggestedId( data )
{
selId = wtconfig.get("ET.SelectedShowsID", "tmdb");
}
log.silly(`etHelper (getSuggestedId) - imdb ID: ${imdb}`);
log.silly(`etHelper (getSuggestedId) - tmdb ID: ${tmdb}`);
log.silly(`etHelper (getSuggestedId) - tvdb ID: ${tvdb}`);
log.silly(`etHelper (getSuggestedId) - imdb ID: ${imdb}`);
log.silly(`etHelper (getSuggestedId) - tmdb ID: ${tmdb}`);
log.silly(`etHelper (getSuggestedId) - tvdb ID: ${tvdb}`);
let Id;
switch(selId) {
@ -179,43 +179,43 @@ function getSuggestedId( data )
{
Id = `{tvdb-${tvdb.slice(7)}}`;
}
break;
break;
}
log.debug(`etHelper (getSuggestedId) - Returning: "imdb": ${imdb}, "tmdb": ${tmdb}, "tvdb": ${tvdb}, "selId": ${Id}`);
log.debug(`etHelper (getSuggestedId) - Returning: "imdb": ${imdb}, "tmdb": ${tmdb}, "tvdb": ${tvdb}, "selId": ${Id}`);
return {"imdb": imdb, "tmdb": tmdb, "tvdb": tvdb, "selId": Id};
}
// Returns a string stripped for Year
function stripYearFromFileName( tmpFileName, year ){
const re = new RegExp(`\\b${year}\\b`, 'gi');
return tmpFileName.replace(re, "").trim();
return tmpFileName.replace(re, "").trim();
}
// Returns a string stripped for ID's
function stripIdFromFileName( param ){
function stripIdFromFileName( param ){
log.debug(`etHelper (stripIdFromFileName) - starting function with param as: ${JSON.stringify(param)}`);
let tmpFileName = param.tmpFileName;
let re;
const imdb = param.imdb.slice(7);
const tmdb = param.tmdb.slice(7);
const tvdb = param.tvdb.slice(7);
// Remove IMDB id
// Remove IMDB id
log.debug(`etHelper (stripIdFromFileName) - Imdb string is : ${imdb}`);
re = new RegExp(`\\b${imdb}\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
log.debug(`etHelper (stripIdFromFileName) - After imdb id is removed: ${tmpFileName}`);
tmpFileName = tmpFileName.replace(/imdb-/i, '');
log.debug(`etHelper (stripIdFromFileName) - After imdb string is removed: ${tmpFileName}`);
// Remove TMDB id
// Remove TMDB id
re = new RegExp(`\\b${tmdb}\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
// Remove TVDB id
// Remove TVDB id
re = new RegExp(`\\b${tvdb}\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
const idProviders = ['imdb', 'tmdb', 'tvdb'];
const idProviders = ['imdb', 'tmdb', 'tvdb'];
idProviders.forEach(element => {
re = new RegExp(`\\b${element}\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
tmpFileName = tmpFileName.replace(re, "");
});
return tmpFileName.replace(`{-}`, "").trim();
}
@ -223,33 +223,33 @@ function stripIdFromFileName( param ){
// Returns a filename without the title
function stripTitleFromFileName( tmpFileName, title )
{
let re;
let re;
// Title in filename separated with dots
let titleArray = tmpFileName.split(".");
for (let titleElement of titleArray) {
for (let titleElement of titleArray) {
if (title.toUpperCase().indexOf(titleElement.toUpperCase()) > -1) {
re = new RegExp(`\\b${titleElement}\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
}
}
if (titleElement.toUpperCase() === 'AND')
{
titleElement = '&'
titleElement = '&'
if (title.toUpperCase().indexOf(titleElement.toUpperCase()) > -1) {
re = new RegExp(`\\band\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
}
}
}
}
// Title in filename separated with spaces
titleArray = tmpFileName.split(" ");
for (let titleElement of titleArray) {
for (let titleElement of titleArray) {
if (title.toUpperCase().indexOf(titleElement.toUpperCase()) > -1) {
re = new RegExp(`\\b${titleElement}\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
}
if (titleElement == '&')
{
titleElement = 'AND'
titleElement = 'AND'
if (title.toUpperCase().indexOf(titleElement.toUpperCase()) > -1) {
re = new RegExp(`\\b${titleElement}\\b`, 'gi');
tmpFileName = tmpFileName.replace(re, "");
@ -266,7 +266,7 @@ function stripPartsFromFileName( tmpFileName, title ) {
let partName = '';
// Find stacked item if present
etHelper.StackedFilesName.forEach(element => {
// Got a hit?
// Got a hit?
if (tmpFileName.toLowerCase().indexOf(element) > -1) {
// Get index again
const idx = tmpFileName.toLowerCase().indexOf(element);
@ -287,14 +287,14 @@ function stripPartsFromFileName( tmpFileName, title ) {
log.warn(`etHelper (stripPartsFromFileName) - for the media with the title: "${title}" looking at the string: "${tmpFileName}" for stacked element: "${element}" but found entry not in range [1-8] so ignorring`)
log.warn(`etHelper (stripPartsFromFileName) - See: https://support.plex.tv/articles/naming-and-organizing-your-movie-media-files/`)
}
}
}
}
}
});
// Remove partName from tmpFile
// Remove partName from tmpFile
// Get index of partName
const idx = tmpFileName.toUpperCase().indexOf(partName.toUpperCase());
tmpFileName = tmpFileName.slice(0, idx) + tmpFileName.slice(idx + partName.length);
tmpFileName = tmpFileName.slice(0, idx) + tmpFileName.slice(idx + partName.length);
// Remove white spaces
tmpFileName = tmpFileName.trim();
if (tmpFileName.length === 1)
@ -307,7 +307,7 @@ function stripPartsFromFileName( tmpFileName, title ) {
}
}
log.verbose(`etHelper (stripPartsFromFileName) - Returning tmpFileName as: ${tmpFileName} *** Returning partName as: ${partName}`);
return {
return {
fileName: tmpFileName,
partName: partName
};
@ -466,24 +466,24 @@ const etHelper = new class ETHELPER {
log.verbose(`etHelper (getSuggestedFolderName) - Starting function. To see param, use Silly log level`);
log.silly(`etHelper (getSuggestedFolderName) - Data pased over as: ${JSON.stringify(data)}`);
const title = getSuggestedTitle( data );
const year = getSuggestedYear( data );
const Id = getSuggestedId( data ).selId;
// Get current folder name
const curFolderName = path.basename(path.dirname(String(JSONPath({path: "$.data.Media[0].Part[0].file", json: data}))))
const year = getSuggestedYear( data );
const Id = getSuggestedId( data ).selId;
// Get current folder name
const curFolderName = path.basename(path.dirname(String(JSONPath({path: "$.data.Media[0].Part[0].file", json: data}))))
// Compute suggested foldername
let foldername = `${title} (${year}) ${Id}`;
log.debug(`etHelper (getSuggestedFolderName) - Suggested folderName is: ${foldername}`);
if (curFolderName === foldername) {
return i18n.t("Modules.ET.FolderNameOK")
return i18n.t("Modules.ET.FolderNameOK")
}
else {
else {
return foldername
}
}
}
/// This will return a suggested filename, following Plex naming std
async getSuggestedFileName( data )
{
{
const title = getSuggestedTitle( data );
const year = getSuggestedYear( data );
const Ids = getSuggestedId( data );
@ -492,7 +492,7 @@ const etHelper = new class ETHELPER {
const tmdb = Ids.tmdb;
const tvdb = Ids.tvdb;
// Get current filename
// Get current filename
const curFileName = path.parse(String(JSONPath({path: '$.data.Media[0].Part[0].file', json: data}))).name;
// Compute suggested filename, and start by stripping known info from existing name
@ -503,7 +503,7 @@ const etHelper = new class ETHELPER {
tmpFileName = stripYearFromFileName( tmpFileName, year );
// Strip ID from fileName
tmpFileName = stripIdFromFileName( {tmpFileName: tmpFileName, imdb: imdb, tmdb: tmdb, tvdb: tvdb} );
tmpFileName = cleanupSuggestedFile(tmpFileName);
tmpFileName = cleanupSuggestedFile(tmpFileName);
// Get parts, if a stacked media
const parts = stripPartsFromFileName( tmpFileName, title );
tmpFileName = parts.fileName;
@ -519,7 +519,7 @@ const etHelper = new class ETHELPER {
tmpFileName = tmpFileName.replaceAll("{}", "");
// Now replace square brackets if present with a dot
tmpFileName = tmpFileName.replaceAll("[", ".");
tmpFileName = tmpFileName.replaceAll("]", ".");
tmpFileName = tmpFileName.replaceAll("]", ".");
// Remove double dots if present
tmpFileName = tmpFileName.replaceAll("..", ".");
tmpFileName = tmpFileName.trim();
@ -527,12 +527,12 @@ const etHelper = new class ETHELPER {
while(tmpFileName.charAt(0) === '.')
{
tmpFileName = tmpFileName.substring(1);
}
// Remove trailing dots if present
}
// Remove trailing dots if present
while(tmpFileName.slice(-1) === '.')
{
tmpFileName = tmpFileName.substring(0, tmpFileName.length - 1);
}
}
// Remove double square brackets if present
tmpFileName = tmpFileName.replaceAll("[]", "");
// Remove double square brackets with a space if present
@ -541,12 +541,12 @@ const etHelper = new class ETHELPER {
tmpFileName = tmpFileName.replaceAll(" ", " ");
// Remove space dot if present, and replace with dot
tmpFileName = tmpFileName.replaceAll(" .", ".");
tmpFileName = tmpFileName.trim();
tmpFileName = tmpFileName.trim();
*/
// Get filename part of remaining filename
if (tmpFileName.length >= 1)
@ -559,20 +559,20 @@ const etHelper = new class ETHELPER {
suggestedFileName = `${title} (${year}) ${Id} ${partName}`.trim();
}
else
{
{
suggestedFileName = `${title} (${year}) ${Id} ${tmpFileName} ${partName}`.trim();
}
// Remove double space if present
suggestedFileName = suggestedFileName.replaceAll(" ", " ");
const fileNameExt = path.parse(String(JSONPath({path: '$.data.Media[0].Part[0].file', json: data}))).ext;
suggestedFileName = suggestedFileName.replaceAll(" ", " ");
const fileNameExt = path.parse(String(JSONPath({path: '$.data.Media[0].Part[0].file', json: data}))).ext;
suggestedFileName = `${suggestedFileName}${fileNameExt}`;
log.debug(`etHelper (getSuggestedFileName) - returning ${suggestedFileName}`);
if (curFileName === path.parse(suggestedFileName).name) {
return i18n.t("Modules.ET.FileNameOK")
return i18n.t("Modules.ET.FileNameOK")
}
else {
else {
return suggestedFileName
}
}
}
async postProcess( {name, val, title="", data} ){
@ -596,19 +596,13 @@ const etHelper = new class ETHELPER {
break;
}
case "Suggested File Name":
retVal = await this.getSuggestedFileName( {data: data} );
retVal = await this.getSuggestedFileName( {data: data} );
break;
case "Suggested Folder Name":
retVal = await this.getSuggestedFolderName( {data: data} );
break;
retVal = await this.getSuggestedFolderName( {data: data} );
break;
case "Part File":
console.log('Ged 76-0 Part File in: ' + valArray)
console.log('Ged 76-1 Part File in: ' + JSON.stringify(valArray))
console.log('Ged 76-2 Part File Basename: ' + path.basename(valArray[0]))
for (x=0; x<valArray.length; x++) {
console.log('Ged 77-1 Part File in: ' + valArray[x])
console.log('Ged 77-2 Part File basename : ' + path.basename(valArray[x]))
retArray.push(path.basename(valArray[x]).slice(0, -1));
}
retVal = retArray.join(wtconfig.get('ET.ArraySep', ' * '))
@ -1563,7 +1557,7 @@ const etHelper = new class ETHELPER {
getIncludeInfo(){
let includeInfo;
log.debug(`etHelper (getIncludeInfo) - Started. libTypeSec is: ${this.Settings.libTypeSec} and levelName is: ${this.Settings.levelName}`);
log.debug(`etHelper (getIncludeInfo) - Started. libTypeSec is: ${this.Settings.libTypeSec} and levelName is: ${this.Settings.levelName}`);
try {
includeInfo = defLevels[this.Settings.libTypeSec]['Include'][this.Settings.levelName];
}

View file

@ -16,6 +16,7 @@ const wtutils = new class WTUtils {
constructor() {
this.logFileName = this.AppName + '.log';
this.plexTVApi = 'https://plex.tv/api/';
}
get ConfigFileName(){
@ -548,11 +549,27 @@ const dialog = new class Dialog {
buttonLabel : OKLabel,
title: Title
}
let dirName = dialog.showOpenDialogSync(WIN, options)
log.debug('Returned directoryname is: ' + dirName)
return dirName
}
SelectFile(Title, OKLabel, filter, defaultPath)
{
log.debug('Start SelectFile Dialog');
const {remote} = require('electron'),
dialog = remote.dialog,
WIN = remote.getCurrentWindow();
let options = {
properties:["openFile"],
buttonLabel : OKLabel,
title: Title,
filters: filter,
defaultPath: defaultPath
}
let fileName = dialog.showOpenDialogSync(WIN, options);
log.debug('Returned filename is: ' + fileName)
return fileName
}
SaveFile(title, defaultPath, OKLabel) {
@ -577,6 +594,20 @@ const dialog = new class Dialog {
log.debug('Returned filename is: ' + filename)
return filename
}
ShowMsgBox(message, type, title, buttons){
const {remote} = require('electron'),
dialog = remote.dialog,
WIN = remote.getCurrentWindow();
let options = {
message: message,
type: type,
title: title,
buttons: buttons
}
return dialog.showMessageBoxSync(WIN, options);
}
}
const github = new class GitHub {

View file

@ -41,6 +41,7 @@
@click="dvrRestore"
icon-left="fas fa-file-download"
icon-pack="fas"
:disabled=!this.serverIsSelected
variant="success"
>
{{ $t("Modules.PMS.DVR.lblBtnRestore") }}
@ -66,7 +67,8 @@
data() {
return {
optSelDVR: [],
selDVR: ""
selDVR: "",
serverIsSelected: false
};
},
created() {
@ -79,6 +81,7 @@
selectedServerAddress: async function(){
log.info("DVR Selected server changed");
this.optSelDVR = this.getDVRList();
this.serverIsSelected = ( this.$store.getters.getSelectedServer != "none" );
},
doneDVRBackup: async function(){
if (this.$store.getters.doneDVRBackup!='')
@ -121,6 +124,7 @@
tell user, and disable backup */
async serverSelected() {
let serverCheck = this.$store.getters.getSelectedServer;
this.serverIsSelected = ( this.$store.getters.getSelectedServer != "none" );
if (serverCheck == "none") {
log.debug("serverCheck is none");
this.selDVR = "";

View file

@ -4,15 +4,20 @@ const {JSONPath} = require('jsonpath-plus');
console.log = log.log;
import {wtconfig, wtutils} from '../../../General/wtutils';
import {wtconfig, wtutils, dialog} from '../../../General/wtutils';
import i18n from '../../../../../i18n';
import store from '../../../../../store';
import axios from 'axios';
import { electron } from 'process';
i18n, wtconfig
const dvr = new class DVR {
constructor() {
this.fileDVRRestore = '';
this.dvrList = [];
this.dvrKey = 0;
this.deviceKey = 0;
}
async getDVRList(){
log.info(`Getting list of DVRs`);
// Placeholder for return value
@ -27,8 +32,8 @@ const dvr = new class DVR {
url: url,
headers: header
})
.then((response) => {
log.debug('Response from getDVRList recieved');
.then((response) => {
log.debug('Response from getDVRList recieved');
DVRs = JSONPath({path: '$..Dvr[*]', json: response.data});
// Save to store
store.commit('UPDATE_DVR_SETTINGS', DVRs);
@ -44,27 +49,212 @@ const dvr = new class DVR {
log.error('getDVRList: ' + error.message);
}
});
DVRs.forEach(dvr => {
arrDVR.push({
"value": JSONPath({path: '$.uuid', json: dvr}),
"text": JSONPath({path: '$.lineupTitle', json: dvr})
})
DVRs.forEach(dvr => {
arrDVR.push({
"value": JSONPath({path: '$.uuid', json: dvr}),
"text": JSONPath({path: '$.lineupTitle', json: dvr})
})
});
return arrDVR;
this.dvrList = arrDVR;
return arrDVR;
}
async setDvrPrefs(){
log.debug('Start DVR Settings');
let url = '';
let arrSettings = []
const Settings = JSONPath({path: '$.Setting', json: this.fileDVRRestore});
Settings[0].forEach(Setting => {
arrSettings.push(Setting['id'] + '=' + Setting['value'])
});
url = `${store.getters.getSelectedServerAddress}/livetv/dvrs/${this.dvrKey}/prefs?` + arrSettings.join("&");
let header = wtutils.PMSHeader;
header['X-Plex-Token'] = store.getters.getSelectedServerToken;
await axios({
method: 'put',
url: url,
headers: header
})
.then((response) => {
log.debug('Response from setDvrPrefs recieved');
log.silly(`DVR Set as: ${JSON.stringify(response.data)}`);
})
.catch(function (error) {
if (error.response) {
log.error('setDvrPrefs: ' + error.response.data);
alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message);
} else if (error.request) {
log.error('setDvrPrefs: ' + error.request);
} else {
log.error('setDvrPrefs: ' + error.message);
}
});
}
async setDevicePrefs(){
log.debug('[dvr.js] Start Device Settings');
let url = '';
let arrSettings = []
const Settings = JSONPath({path: '$..Device[0].Setting', json: this.fileDVRRestore});
Settings[0].forEach(Setting => {
arrSettings.push(Setting['id'] + '=' + Setting['value'])
});
url = `${store.getters.getSelectedServerAddress}/media/grabbers/devices/${this.deviceKey}/prefs?` + arrSettings.join("&");
let header = wtutils.PMSHeader;
header['X-Plex-Token'] = store.getters.getSelectedServerToken;
await axios({
method: 'put',
url: url,
headers: header
})
.then((response) => {
log.debug('Response from setDevicePrefs recieved');
log.silly(`setDevicePrefs Set as: ${JSON.stringify(response.data)}`);
})
.catch(function (error) {
if (error.response) {
log.error('setDevicePrefs: ' + error.response.data);
alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message);
} else if (error.request) {
log.error('setDevicePrefs: ' + error.request);
} else {
log.error('setDvrPrefs: ' + error.message);
}
});
const DvrTitle = encodeURIComponent(JSONPath({path: '$..Device[0].title', json: this.fileDVRRestore}));
url = `${store.getters.getSelectedServerAddress}/media/grabbers/devices/${this.deviceKey}?title=${DvrTitle}&enabled=1`;
await axios({
method: 'put',
url: url,
headers: header
})
.then((response) => {
log.debug('Response from setDeviceTitle recieved');
log.silly(`setDeviceTitle Set as: ${JSON.stringify(response.data)}`);
})
.catch(function (error) {
if (error.response) {
log.error('setDeviceTitle: ' + error.response.data);
alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message);
} else if (error.request) {
log.error('setDeviceTitle: ' + error.request);
} else {
log.error('setDeviceTitle: ' + error.message);
}
});
}
async setDeviceMapping(){
console.log('Ged 0 ************ SET Device Mapping *********')
new Promise((resolve) => {
resolve
});
}
/// This will create a DVR
async createDVR(){
console.log('Ged 1 CreateDVR started')
let devices = [];
// Store array of devices
devices = JSONPath({path: '$..Device[*].uuid', json: this.fileDVRRestore});
// To create the DVR, we should only use the first DVR
const paramDevice = encodeURIComponent(devices[0]);
// Now we need the DVR lineup
const paramLineUp = encodeURIComponent(JSONPath({path: '$.lineup', json: this.fileDVRRestore}));
// Get DVR Language
const paramLang = JSONPath({path: '$.language', json: this.fileDVRRestore});
// Get DVR Country
const paramCountry = JSONPath({path: '$.country', json: this.fileDVRRestore});
const url = `${store.getters.getSelectedServerAddress}/livetv/dvrs?device=${paramDevice}&lineup=${paramLineUp}&language=${paramLang}&country=${paramCountry}`
let header = wtutils.PMSHeader;
header['X-Plex-Token'] = store.getters.getSelectedServerToken;
await axios({
method: 'post',
url: url,
headers: header
})
.then((response) => {
log.debug('Response from createDVR recieved');
log.silly(`DVR Created as: ${JSON.stringify(response.data)}`);
if (JSONPath({path: '$.MediaContainer.size', json: response.data}) == 0)
{
dialog.ShowMsgBox(i18n.t("Modules.PMS.DVR.selDeviceAlreadyUsed"), 'error', i18n.t("Modules.PMS.DVR.selAlredyPresentTitle"), [i18n.t("Common.Ok")]);
}
else
{
log.info(`DVR Created with UUID: ${JSONPath({path: '$.MediaContainer.Dvr[0].uuid', json: response.data})}`);
this.dvrKey = JSONPath({path: '$.MediaContainer.Dvr[0].key', json: response.data});
this.deviceKey = JSONPath({path: '$..Device[0].key', json: response.data});
log.info(`DVR Created with Key: ${this.dvrKey}`);
this.setDvrPrefs();
this.setDevicePrefs();
this.setDeviceMapping();
}
})
.catch(function (error) {
if (error.response) {
log.error('createDVR: ' + error.response.data);
alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message);
} else if (error.request) {
log.error('createDVR: ' + error.request);
} else {
log.error('createDVR: ' + error.message);
}
});
console.log('Ged 57: ' + JSON.stringify(url))
}
async restoreDVR (){
console.log('Ged 70 restore start')
const dialogConfig = {
title: 'Select a file',
buttonLabel: 'This one will do',
properties: ['openFile']
};
//const electron = require('electron');
electron.openDialog('showOpenDialog', dialogConfig)
.then(result => console.log(result));
log.info(`DVR Restore started`);
await this.getDVRList();
const filters = [{name: 'DVR Backups', extensions: ['json']}];
const path = require('path');
const defaultPath = path.join(wtconfig.get('General.ExportPath'), 'Plex Media Server', 'DVR', '\\');
const selectFile = dialog.SelectFile( i18n.t("Modules.PMS.DVR.selRestoreFile"), i18n.t("Common.Ok"), filters, defaultPath);
if (selectFile)
{
log.info(`Selected restore file is: ${selectFile}`);
var fs = require('fs');
this.fileDVRRestore = JSON.parse(fs.readFileSync(selectFile[0], 'utf8'));
log.silly(`DVR Backup file contents`);
log.silly(JSON.stringify(this.fileDVRRestore));
const dvrTitle = String(JSONPath({path: '$.lineupTitle', json: this.fileDVRRestore}));
//const msgBody = i18n.t("Modules.PMS.DVR.restoreMsg", [dvrTitle]);
if ( dialog.ShowMsgBox(i18n.t("Modules.PMS.DVR.restoreMsg", [dvrTitle]), 'question', i18n.t("Modules.PMS.DVR.confirmRestore"), [i18n.t("Common.Ok"), i18n.t("Common.Cancel")]) == 0 )
{
// Check if DVR to restore is already present
if ( JSON.stringify(this.dvrList).indexOf(dvrTitle) > -1 )
{
log.error(`Selected dvr was "${dvrTitle}" but is was already present on the PMS`)
dialog.ShowMsgBox(i18n.t("Modules.PMS.DVR.selAlredyPresentMsg", [dvrTitle]), 'error', i18n.t("Modules.PMS.DVR.selAlredyPresentTitle"), [i18n.t("Common.Ok")]);
}
else
{
log.info('Starting DVR Restore');
// Let's get the amount of devices
const deviceCount = JSONPath({path: '$..Device.length', json: this.fileDVRRestore});
if ( deviceCount < 1){
dialog.ShowMsgBox(i18n.t("Modules.PMS.DVR.selMissingDevice", [dvrTitle]), 'error', i18n.t("Modules.PMS.DVR.selAlredyPresentTitle"), [i18n.t("Common.Ok")]);
log.error('No devices found in backup file')
}
else
{
this.createDVR();
}
}
}
}
else
{
log.info(`No files selected`);
}
}
async backupDVR( { dvrName } ){
@ -94,7 +284,7 @@ const dvr = new class DVR {
if (dvr['uuid']==uuid){
retVal = dvr['lineupTitle'];
}
})
})
return retVal;
}

View file

@ -0,0 +1,196 @@
<template>
<b-container fluid>
<div class="col-lg-10 col-md-12 col-xs-12">
<h1>{{ $t("Modules.PMS.ViewState.Name") }}</h1>
<p>{{ $t("Modules.PMS.ViewState.Description") }}</p>
</div>
<div class="d-flex align-items-center">
<b-form-group id="ViewStateSelSourceUsrGroup" v-bind:label="$t('Modules.PMS.ViewState.selSourceUsr')" label-size="lg" label-class="font-weight-bold pt-0" name="ViewStateSelSourceUsrGroup">
<b-tooltip target="ViewStateSelSourceUsrGroup" triggers="hover">
{{ $t('Modules.PMS.ViewState.ttSelSourceUsr') }}
</b-tooltip>
<b-form-select
v-model="selSrcUsr"
id="selSrcUsr"
:options="optselSrcUsr"
@change="selSrcUsrChanged"
name="selSrcUsr">
</b-form-select>
</b-form-group>
</div>
<div ref="libSpinner" id="libSpinner" :hidden="WaitForUsers">
<b-spinner id="libLoad" class="ml-auto text-danger"></b-spinner>
</div>
<div class="d-flex align-items-center">
<b-form-group id="ViewStateSelTargetUsrGroup" v-bind:label="$t('Modules.PMS.ViewState.selTargetUsr')" label-size="lg" label-class="font-weight-bold pt-0" name="ViewStateSelTargetUsrGroup">
<b-tooltip target="ViewStateSelTargetUsrGroup" triggers="hover">
{{ $t('Modules.PMS.ViewState.ttSelTargetUsr') }}
</b-tooltip>
<b-form-select
v-model="selTargetUsr"
@change="selTargetUsrChanged"
id="selTargetUsr"
:options="optSelTargetUsr"
name="selTargetUsr">
</b-form-select>
</b-form-group>
</div>
<br>
<br>
<div class="buttons">
<!-- Buttons -->
<div id="buttons" class="text-center">
<b-button-group >
<b-button
class="mr-2"
type="is-primary"
@click="copyViewState"
icon-left="fas fa-file-download"
icon-pack="fas"
:disabled="this.DisableCopy"
variant="success"
>
{{ $t("Modules.PMS.ViewState.lblBtnCopy") }}
</b-button>
</b-button-group>
</div>
</div>
<br>
<b-container fluid> <!-- Status -->
<b-row>
<b-col sm="2">
<label for="status">{{ $t('Modules.PMS.ViewState.Status.Names.Status') }}:</label>
</b-col>
<b-col sm="10">
<b-form-textarea
id="status"
v-bind:placeholder="$t('Modules.PMS.ViewState.Status.Names.Status')"
v-model="statusMsg"
:disabled=true
rows="1"
max-rows="8">
</b-form-textarea>
</b-col>
</b-row>
</b-container>
</b-container>
</template>
<script>
import i18n from '../../../../i18n';
import store from '../../../../store';
//import { wtconfig } from '../General/wtutils';
import { viewstate } from "./scripts/viewstate";
const log = require("electron-log");
export default {
data() {
return {
optselSrcUsr: [],
optSelTargetUsr: [],
selTargetUsr: "",
selSrcUsr: "",
serverIsSelected: false,
WaitForUsers: false,
statusMsg: 'Idle'
};
},
async created() {
log.info("[ViewState.vue] viewState Created");
this.serverSelected();
if (store.getters.getSelectedServer != 'none'){
this.WaitForUsers = false;
await viewstate.getUsers();
this.optselSrcUsr = viewstate.viewStateUsers;
this.optSelTargetUsr = viewstate.viewStateUsers;
this.WaitForUsers = true;
}
},
watch: {
// Watch for when selected server address is updated
selectedServerAddress: async function(){
log.info("ViewState selected server changed");
console.log('Ged 1-1: ' + JSON.stringify(this.$store.getters.getViewStateStatus))
viewstate.clearStatus();
console.log('Ged 1-2: ' + JSON.stringify(this.$store.getters.getViewStateStatus))
viewstate.updateStatusMsg(1, i18n.t("Modules.PMS.ViewState.Status.Msg.CollectUserInfo"));
viewstate.SrcUsrKey = -1;
viewstate.TargetUsrKey = -1;
console.log('Ged 1-3: ' + JSON.stringify(this.$store.getters.getViewStateStatus))
this.serverIsSelected = ( this.$store.getters.getSelectedServer != "none" );
this.WaitForUsers = false;
await viewstate.getServerToken();
console.log('Ged 1-4: ' + JSON.stringify(this.$store.getters.getViewStateStatus))
await viewstate.getUsers();
console.log('Ged 1-5: ' + JSON.stringify(this.$store.getters.getViewStateStatus))
this.optselSrcUsr = viewstate.viewStateUsers;
this.optSelTargetUsr = viewstate.viewStateUsers;
this.selSrcUsr = '';
this.selTargetUsr = '',
this.WaitForUsers = true;
viewstate.updateStatusMsg(1, i18n.t("Modules.PMS.ViewState.Status.Msg.Idle"));
console.log('Ged 1-6: ' + JSON.stringify(this.$store.getters.getViewStateStatus))
},
// Watch for status update
viewStateStatus: function() {
this.statusMsg = this.$store.getters.getViewStateStatus;
}
},
computed: {
// We need this computed, for watching for changes to selected server
selectedServerAddress: function(){
return this.$store.getters.getSelectedServerAddress
},
DisableCopy : function(){
// Only enable copy btn. if we have a server, users are selected and not identical
return !(this.WaitForUsers&&(this.selTargetUsr)&&(this.selSrcUsr)&&(this.selTargetUsr!=this.selSrcUsr))
},
viewStateStatus: function(){
return this.$store.getters.getViewStateStatus;
}
},
methods: {
// SrcUsr changed
async selSrcUsrChanged() {
await viewstate.setKey( 'selSrcUsr', this.selSrcUsr);
await viewstate.getLibs( this.selSrcUsr, this.selTargetUsr );
},
// SrcUsr changed
async selTargetUsrChanged() {
await viewstate.setKey( 'selTargetUsr', this.selTargetUsr );
await viewstate.getLibs( this.selSrcUsr, this.selTargetUsr );
},
/* Check if a server is selected, and if not
tell user, and disable copy */
async serverSelected() {
let serverCheck = this.$store.getters.getSelectedServer;
this.serverIsSelected = ( this.$store.getters.getSelectedServer != "none" );
if (serverCheck == "none") {
log.debug("serverCheck is none");
this.selDVR = "";
this.$bvToast.toast(this.$t("Modules.PMS.ErrorNoServerSelectedMsg"), {
title: this.$t("Modules.PMS.ErrorNoServerSelectedTitle"),
autoHideDelay: 4000,
solid: true,
variant: 'primary',
toaster: 'b-toaster-bottom-right'
});
}
},
async copyViewState(){
viewstate.copyViewState( this.selSrcUsr, this.selTargetUsr );
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#sync-button {
margin-left: 1em;
}
</style>

View file

@ -0,0 +1,256 @@
// Helper file for dvr.tv module
const log = require('electron-log');
const {JSONPath} = require('jsonpath-plus');
console.log = log.log;
import {wtutils} from '../../../General/wtutils';
import i18n from '../../../../../i18n';
import store from '../../../../../store';
import axios from 'axios';
const viewstate = new class ViewState {
// Private Fields
#_FieldHeader = [];
#_StartTime = null;
#_EndTime = null;
#_statusmsg = {};
#_msgType = {
1: i18n.t("Modules.PMS.ViewState.Status.Names.Status"),
2: i18n.t("Modules.PMS.ViewState.Status.Names.LibsToProcess"),
3: i18n.t("Modules.PMS.ViewState.Status.Names.StartTime")
}
constructor() {
this.selServerServerToken = '',
this.viewStateUsers = [],
this.libs = {},
this.SrcUsrKey = -1,
this.TargetUsrKey = -1
}
async setKey( Usr, data ){
if ( Usr == 'selSrcUsr' ){
this.SrcUsrKey = JSONPath({path: `$..libs[0].key`, json: data})
if ( isNaN(this.SrcUsrKey) ){
this.SrcUsrKey = -1;
}
}
else {
this.TargetUsrKey = JSONPath({path: `$..libs[0].key`, json: data})
if ( isNaN(this.TargetUsrKey) ){
this.TargetUsrKey = -1;
}
}
}
async getNowTime(StartEnd){
let now = new Date();
if (StartEnd == 'start')
{
this.#_StartTime = now;
}
else
{
this.#_EndTime = now;
}
let hours = now.getHours();
let minutes = now.getMinutes();
let seconds = now.getSeconds();
if ( hours.toString().length < 2) { hours = '0' + hours}
if ( minutes.toString().length < 2) { minutes = '0' + minutes}
if ( seconds.toString().length < 2) { seconds = '0' + seconds}
return hours + ':' + minutes + ':' + seconds;
}
async copyViewState( SrcUsr, TargetUsr ){
log.info('[viewstate.js] Starting copyViewState');
const startTime = await this.getNowTime('start');
console.log('Ged 4 start time: ' + startTime)
this.updateStatusMsg(3, startTime);
console.log('Ged 1 SrcUsr: ' + JSON.stringify(SrcUsr))
console.log('Ged 2 TargetUsr: ' + JSON.stringify(TargetUsr))
this.updateStatusMsg(1, i18n.t("Modules.PMS.ViewState.Status.Msg.Processing"));
await this.getLibs( SrcUsr, TargetUsr );
this.updateStatusMsg(1, "Ged")
}
// Update status msg
async updateStatusMsg(msgType, msg)
{
// Update relevant key
this.#_statusmsg[msgType] = msg;
// Tmp store of new msg
let newMsg = '';
// Walk each current msg keys
Object.entries(this.#_statusmsg).forEach(([key, value]) => {
if ( value != '')
{
newMsg += this.#_msgType[key] + ': ' + value + '\n';
}
})
store.commit("UPDATE_viewStateStatus", newMsg);
console.log('Ged 7 Msg: ' + JSON.stringify(newMsg))
}
// Clear Status Window
async clearStatus()
{
this.#_statusmsg = {};
store.commit("UPDATE_viewStateStatus", '');
return;
}
async getLibs( SrcUsr, TargetUsr ){
log.info('[viewstate.js] Starting getLibs');
// Are both users selected ?
if ( this.TargetUsrKey < 0 || this.SrcUsrKey < 0 ){
return;
}
if ( JSON.stringify(SrcUsr) === JSON.stringify(TargetUsr) ){
this.clearStatus();
this.updateStatusMsg(1, i18n.t("Modules.PMS.ViewState.Status.Msg.Idle"));
log.info('[viewstate.js] Same user selected, so aborting');
return;
}
this.clearStatus();
this.updateStatusMsg(1, i18n.t("Modules.PMS.ViewState.Status.Msg.GatheringLibs"));
log.silly(`[viewstate.js] Source usr: ${JSON.stringify(SrcUsr)}`);
log.silly(`[viewstate.js] Target usr: ${JSON.stringify(TargetUsr)}`);
this.libs = {};
if ( JSONPath({path: `$..libs[0].key`, json: SrcUsr}) == 0 )
{
// We need to add all libs from target usr
log.debug(`[viewstate.js] SrcUsr is owner`);
for(let i of TargetUsr["libs"]) {
this.libs[i.key] = i.title;
}
}
else
{
if ( JSONPath({path: `$..libs[0].key`, json: TargetUsr}) == 0 ){
// We need to add all libs from Source usr
log.debug(`[viewstate.js] TargetUsr is owner`);
for(let i of SrcUsr["libs"]) {
this.libs[i.key] = i.title;
}
}
else {
for(let i of SrcUsr["libs"]) {
if ( JSON.stringify(TargetUsr["libs"]).indexOf(JSON.stringify(i)) > -1)
{
this.libs[i.key] = i.title;
}
}
}
}
let libstatus = []
for (let lib in this.libs){
libstatus.push(this.libs[lib])
}
this.updateStatusMsg(1, i18n.t("Modules.PMS.ViewState.Status.Msg.Idle"));
this.updateStatusMsg(2, libstatus.join(', '))
}
// Here we get the server token for the selected server
async getServerToken(){
// Start by finding selected servers clientIdentifier
const clientIdentifier = store.getters.getSelectedServer.clientIdentifier;
// Ask for a list of devices
let header = wtutils.PMSHeader;
header['X-Plex-Token'] = store.getters.getAuthToken;
const url = `${wtutils.plexTVApi}v2/devices`;
await axios({
method: 'get',
url: url,
headers: header
})
.then((response) => {
log.debug('[viewState.js] Response from getServerToken recieved');
//log.silly(`getServerToken returned as: ${JSON.stringify(response.data)}`);
this.selServerServerToken = JSONPath({path: `$[?(@.clientIdentifier== '${clientIdentifier}')].token`, json: response.data});
log.silly(`[viewState.js] selServerServerToken returned as: ${this.selServerServerToken}`);
})
.catch(function (error) {
if (error.response) {
log.error('[viewState.js] getServerToken: ' + error.response.data);
alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message);
} else if (error.request) {
log.error('[viewState.js] getServerToken: ' + error.request);
} else {
log.error('[viewState.js] getServerToken: ' + error.message);
}
});
}
// Get selected server sharelist
async getShareList(){
log.info('[viewState.js] Start getShareList');
// We can't use the normal header
let header = {
'Accept': 'application/json',
'X-Plex-Token': this.selServerServerToken[0]
};
const url = `${wtutils.plexTVApi}v2/server/sharing`;
let retVal
await axios({
method: 'get',
url: url,
headers: header
})
.then((response) => {
log.debug('[viewState.js] Response from getShareList recieved');
log.silly(`[viewState.js] getShareList returned as: ${JSON.stringify(response.data)}`);
retVal = JSON.stringify(response.data)
})
.catch(function (error) {
if (error.response) {
log.error('[viewState.js] getShareList1: ' + JSON.stringify(error.response.data));
alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message);
} else if (error.request) {
log.error('[viewState.js] getShareList2: ' + error.request);
} else {
log.error('[viewState.js] getShareList3: ' + error.message);
}
});
return retVal
}
// Get a list of users
async getUsers(){
log.info('[viewState.js] Start getUsers');
let Users = store.getters.getUsers;
// Already got the users ?
if (Object.keys(Users).length == 0){
await store.dispatch('fetchUsers');
Users = store.getters.getUsers;
}
await this.getServerToken();
this.viewStateUsers = [];
// Get a list of users only on this server
let shareList = await this.getShareList();
// Now we need to walk them one by one
for(const sharedUsr of JSON.parse(shareList)) {
let name = Users[sharedUsr['id']]["friendlyName"];
if (!name){
name = Users[sharedUsr['id']]["title"];
}
if (!Users[sharedUsr['id']]["email"])
{
name = `${name} ***( ${i18n.t("Modules.PMS.ViewState.Managed")} )***`
}
let usr = { value: { "id": sharedUsr['id'], "libs": sharedUsr['librarySections']}, text: name };
this.viewStateUsers.push(usr);
}
// Now we need to add Server Owner
let usr = { value: { "id": `${store.getters.getMeId}`, "libs": [{"id":null,"key":0,"title":"*","type":"*"}]}, text: `${store.getters.getPlexName} ***( ${i18n.t("Modules.PMS.ViewState.Owner")} )***` };
this.viewStateUsers.push(usr);
}
}
export { viewstate };

View file

@ -33,7 +33,7 @@
<b-form-input id="usrEmail" name="usrEmail" type="text" class="form-control" v-model="usrEmail" :disabled=true></b-form-input>
</b-input-group>
<b-input-group id="UserRestrictedGrp" :prepend="$t('Modules.PlexTV.UsrRestricted')" class="mt-3">
<b-form-input id="usrRestricted" name="usrRestricted" class="form-control" v-model="usrRestricted" :disabled=true></b-form-input>
<b-form-input id="usrRestricted" name="usrRestricted" class="form-control" v-model="usrRestricted" :disabled=true></b-form-input>
</b-input-group>
<b-input-group id="UserThumbGrp" :prepend="$t('Modules.PlexTV.UsrThumb')" class="mt-3">
<b-form-input id="usrThumb" name="usrThumb" type="text" class="form-control" v-model="usrThumb" :disabled=true></b-form-input>

View file

@ -15,14 +15,14 @@ function loadLocaleMessages () {
// Force read en lang, since it's the fallback
const langCode = 'en'
var langFile = wtutils.Home + '/locales/' + langCode + '.json'
log.debug(`Loading language: ${langCode}`)
log.debug(`[i18n.js] (loadLocaleMessages) Loading language: ${langCode}`)
messages[langCode] = JSON.parse(fs.readFileSync(langFile, 'utf8'));
log.debug(`Defined language: ${wtconfig.get('General.language')}`)
log.debug(`[i18n.js] (loadLocaleMessages) Defined language: ${wtconfig.get('General.language')}`)
if (wtconfig.get('General.language', 'en') != 'en'){
// We need to preload an additional language
const langCode = wtconfig.get('General.language')
langFile = wtutils.Home + '/locales/' + langCode + '.json'
log.debug(`Loading language: ${langCode}`)
log.debug(`[i18n.js] (loadLocaleMessages) Loading language: ${langCode}`)
messages[langCode] = JSON.parse(fs.readFileSync(langFile, 'utf8'));
}
return messages

View file

@ -32,9 +32,9 @@ log.transports.console.level = wtconfig.get('Log.consoleLevel', 'silly');
// Set logfile to 10Mb
log.transports.file.maxSize = wtconfig.get('Log.maxSize', 1048576);
log.info('*********************************')
log.info(`Starting ${wtutils.AppName} Version: ${wtutils.AppVersion}`);
log.info(`Running on ${wtutils.RunningOS}`);
log.info(`Log level set to ${log.transports.file.level}`);
log.info(`[main.js] Starting ${wtutils.AppName} Version: ${wtutils.AppVersion}`);
log.info(`[main.js] Running on ${wtutils.RunningOS}`);
log.info(`[main.js] Log level set to ${log.transports.file.level}`);
// Logging ended
// Log Commandline Params
@ -53,7 +53,7 @@ params.forEach(param => {
// Handles dev stuff from Json
const token = wtconfig.get('Developer.X-Plex-Token', '');
if ( token != ''){
log.verbose(`Logging in with Dev Token`);
log.verbose(`[main.js] Logging in with Dev Token`);
store.commit("UPDATE_AUTHTOKEN", token);
}
@ -67,19 +67,19 @@ if (wtconfig.get("general.version", "") != wtutils.AppVersion){
var fs = require('fs');
var prefs = JSON.parse(fs.readFileSync(wtutils.ConfigFileName, 'utf8'));
delete prefs ['Developer'];
log.verbose('***** Prefs *****');
log.verbose('[main.js] ***** Prefs *****');
log.verbose(prefs);
log.verbose('***** Prefs Ended *****');
log.verbose('[main.js] ***** Prefs Ended *****');
// Get saved language to use, and default to en
i18n.locale = wtconfig.get('General.language', 'en');
// App Menu Bar
log.verbose('Starting to build App Menu');
log.verbose('[main.js] Starting to build App Menu');
const menuTemplate = require('./components/layout/script/menubar');
const menu = require('electron').remote.Menu.buildFromTemplate(menuTemplate.default);
require('electron').remote.Menu.setApplicationMenu(menu);
log.verbose('App Menu builded');
log.verbose('[main.js] App Menu builded');
Vue.config.productionTip = false;

View file

@ -12,6 +12,7 @@ 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 ViewState from '../components/modules/PMS/ViewState/ViewState';
import About from '../components/modules/Main/About';
import Store from '../store/index.js';
@ -76,6 +77,12 @@ Vue.use(VueRouter)
component: Butler,
meta: {requiresAuth: true}
},
{
path: '/pms/viewstate',
name: "viewstate",
component: ViewState,
meta: {requiresAuth: true}
},
{
path: '/plextv',
name: "plextv",

View file

@ -6,6 +6,7 @@ import poeditor from './modules/poeditor';
import et from './modules/et';
import language from './modules/language';
import pms from './modules/pms';
import viewstate from './modules/viewstate';
Vue.use(Vuex);
@ -18,7 +19,7 @@ export default new Vuex.Store({
state: {
},
mutations: {},
getters: {
getters: {
},
actions: {},
modules: {
@ -26,7 +27,8 @@ export default new Vuex.Store({
poeditor,
et,
language,
pms
pms,
viewstate
},
plugins: [vuexLocal.plugin]
})

View file

@ -1,5 +1,3 @@
//import {et, excel2} from '../../components/modules/ExportTools/scripts/et'
//import {et} from '../../components/modules/ExportTools/scripts/et'
import { etHelper } from '../../components/modules/ExportTools/scripts/ethelper'
const log = require('electron-log');

View file

@ -15,7 +15,8 @@ const state = {
authToken: '',
avatar: '',
plexname: '',
users: {}
users: {},
MeId: ''
};
const mutations = {
@ -44,6 +45,9 @@ const mutations = {
UPDATE_PLEXNAME(state, value){
state.plexname = value;
},
UPDATE_MeId(state, value){
state.MeId = value;
},
UPDATE_USERS(state, value){
state.users = value;
}
@ -56,7 +60,7 @@ const actions = {
header['X-Plex-Token'] = getters.getAuthToken;
await axios({
method: 'get',
url: 'https://plex.tv/api/v2/friends',
url: `${wtutils.plexTVApi}v2/friends`,
headers: header
})
.then((response) => {
@ -84,7 +88,7 @@ const actions = {
header['X-Plex-Token'] = getters.getAuthToken;
axios({
method: 'get',
url: 'https://plex.tv/api/v2/resources',
url: `${wtutils.plexTVApi}v2/resources`,
headers: header,
params: {
'includeHttps' : '1',
@ -134,7 +138,7 @@ const actions = {
},
loginToPlex({ commit }, payload){
log.info("loginToPlex called")
var url = 'https://plex.tv/api/v2/users/signin';
var url = `${wtutils.plexTVApi}v2/users/signin`;
url = url + '?login=' + require('querystring').escape(payload.username);
url = url + '&password=' + require('querystring').escape(payload.password);
if ( payload.twoFA ){
@ -151,6 +155,7 @@ const actions = {
commit('UPDATE_AUTHENTICATED', true)
commit('UPDATE_AVATAR', response.data.thumb)
commit('UPDATE_PLEXNAME', response.data.username)
commit('UPDATE_MeId', response.data.id)
router.replace({name: "home"});
})
.catch(function (error) {
@ -183,18 +188,24 @@ const actions = {
},
loginToPlexWithToken({ commit }, payload){
log.info("loginToPlex called, using a Token")
let header = wtutils.PMSHeader;
//header['X-Plex-Token'] = payload.token;
//const url = `${wtutils.plexTVApi}v2/users/signin.json?X-Plex-Token=${payload.token}`;
const url = 'https://plex.tv/users/sign_in.json?X-Plex-Token=' + payload.token;
axios({
method: 'POST',
url: url,
headers: wtutils.PMSHeader
headers: header
})
.then(function (response) {
log.debug('loginToPlex: Response from fetchPlexServers recieved')
console.log('Gedd 66 Me: ' + JSON.stringify(response.data.user))
commit('UPDATE_AUTHTOKEN', response.data.user.authToken)
commit('UPDATE_AUTHENTICATED', true)
commit('UPDATE_AVATAR', response.data.user.thumb)
commit('UPDATE_PLEXNAME', response.data.user.username)
commit('UPDATE_MeId', response.data.id)
router.replace({name: "home"});
})
.catch(function (error) {
@ -227,6 +238,7 @@ const getters = {
getAuthToken: state => state.authToken,
getAvatar: state => state.avatar,
getPlexName: state => state.plexname,
getMeId: state => state.MeId,
getSelectedServer: state => state.selectedServer,
getSelectedServerAddress: state => state.selectedServerAddress,
getSelectedServerAddressUpdateInProgress: state => state.selectedServerAddressUpdateInProgress,

View file

@ -0,0 +1,27 @@
//import { etHelper } from '../../components/modules/ExportTools/scripts/ethelper'
const log = require('electron-log');
console.log = log.log;
const state = {
viewStateStatus: ''
};
const mutations = {
UPDATE_viewStateStatus(state, payload) {
state.viewStateStatus = payload
}
};
const getters = {
getViewStateStatus: state => state.viewStateStatus
};
const viewstateModule = {
state,
mutations,
getters
}
export default viewstateModule;