#440 Backup done

This commit is contained in:
Tommy Mikkelsen 2022-03-29 13:20:28 +02:00
parent 2fff17ed86
commit 27c3e3605c
11 changed files with 358 additions and 361 deletions

View file

@ -327,6 +327,8 @@
"Description": "@:Modules.PMS.Name module allows you to manage your server",
"ErrorNoServerSelectedTitle": "No server selected",
"ErrorNoServerSelectedMsg": "You need to select a server on the top of the screen",
"ExportDoneBody": "Check {0}",
"ExportDoneTitle": "Export finished",
"Butler": {
"Title": "Butler Scheduled Tasks",
"Description": "Here you can kickstart a scheduled task",
@ -388,8 +390,14 @@
"Value": "Value"
}
},
"ExportDoneBody": "Check {0}",
"ExportDoneTitle": "Export finished"
"DVR": {
"Name": "DVR",
"Description": "@:Modules.PMS.DVR.Name module allows you to backup and restore your DVR settings",
"selDVR": "Select DVR to backup",
"ttselDVR": "Here you select the DVR you want to make a backup of",
"lblBtnBackup": "Backup",
"BackupDone": "DVR has been saved"
}
},
"PlexTV": {
"Name": "Plex.TV",
@ -407,14 +415,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"
},
"DVR": {
"Name": "DVR",
"Description": "@:Modules.DVR.Name module allows you to backup and restore your DVR settings",
"selDVR": "Select DVR to backup",
"ttselDVR": "Here you select the DVR you want to make a backup of",
"lblBtnBackup": "Backup"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,18 +0,0 @@
# Release Note
For a complete manual, see the [Wiki](https://github.com/WebTools-NG/WebTools-NG/wiki)
What's new:
Please see the [Change Log](https://github.com/WebTools-NG/WebTools-NG/blob/master/CHANGELOG.md)
This is a BETA release
Note that the binaries are not signed, since we cannot afford a signing certificate nor a dev membership with Apple
* See [Special MAC Work Around](https://github.com/WebTools-NG/WebTools-NG/wiki/Known-Issues#-mac-os)
Depending on your workstation OS, grab the following file from the assets below:
Windows: Use the exe file
Mac: Use the dmg file
Linux: Use the AppImage file

View file

@ -13,7 +13,6 @@
import etIcon from '@/assets/ET-256.png';
import pmsIcon from '@/assets/plex-pms-icon.png';
import plextvIcon from '@/assets/plex-app-icon.png';
import plexDVRIcon from '@/assets/dvr-256.png';
export default {
@ -66,14 +65,9 @@
icon: 'fa fa-tasks'
},
{
href: { path: '/dvr' },
title: this.$t("Modules.DVR.Name"),
// icon: 'fas fa-file-export',
icon: {
//adjust element
element: 'img',
attributes: { src: plexDVRIcon },
}
href: { path: '/pms/dvr' },
title: this.$t("Modules.PMS.DVR.Name"),
icon: 'fas fa-tv',
}
]
},

View file

@ -1,119 +0,0 @@
<template>
<div class="col-lg-10 col-md-12 col-xs-12">
<h3>{{ $t("Modules.DVR.Name") }} <br>
</h3>
{{ $t("Modules.DVR.Description") }}
<br>
<br>
<b-form-row> <!-- Select Export type -->
<b-col> <!-- Main type -->
<div class="d-flex align-items-center">
<b-form-group id="dvrSelDVRGroup" v-bind:label="$t('Modules.DVR.selDVR')" label-size="lg" label-class="font-weight-bold pt-0" name="dvrSelDVRGroup">
<b-tooltip target="dvrSelDVRGroup" triggers="hover">
{{ $t('Modules.DVR.ttselDVR') }}
</b-tooltip>
<b-form-select
v-model="selDVR"
id="selDVR"
:options="optSelDVR"
name="selDVR">
</b-form-select>
</b-form-group>
<b-button
type="is-primary"
@click="dvrBackup"
icon-left="fas fa-file-download"
icon-pack="fas"
:disabled="btnDisable == true"
variant="success"
>
{{ $t("Modules.DVR.lblBtnBackup") }}
</b-button>
</div>
</b-col>
</b-form-row>
</div>
</template>
<script>
import i18n from '../../../i18n';
//import store from '../../../store';
//import { wtconfig } from '../General/wtutils';
import { dvr } from "./scripts/dvr";
i18n, dvr
const log = require("electron-log");
export default {
data() {
return {
optSelDVR: [],
selDVR: "",
selLibraryWait: true,
btnDisable: false,
selMediaType: "movie",
selLibrary: "",
selLibraryOptions: [],
selLevel: "",
};
},
created() {
log.info("DVR Created");
this.serverSelected();
},
watch: {
// Watch for when selected server address is updated
selectedServerAddress: async function(){
log.info("DVR Selected server changed");
}
},
computed: {
// We need this computed, for watching for changes to selected server
selectedServerAddress: function(){
return this.$store.getters.getSelectedServerAddress
}
},
methods: {
async dvrBackup() {
log.info("DVR Backup started");
},
/* Check if a server is selected, and if not
tell user, and disable backup */
async serverSelected() {
let serverCheck = this.$store.getters.getSelectedServer;
if (serverCheck == "none") {
log.debug("serverCheck is none");
this.btnDisable = true;
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'
});
}
else
{
this.btnDisable = false;
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#sync-button {
margin-left: 1em;
}
</style>

View file

@ -1,15 +0,0 @@
// Helper file for dvr.tv module
const log = require('electron-log');
console.log = log.log;
import {wtconfig, wtutils} from '../../General/wtutils';
import i18n from '../../../../i18n';
import store from '../../../../store';
i18n, wtconfig, wtutils, store
const dvr = new class DVR {
}
dvr

View file

@ -151,59 +151,6 @@ const et = new class ET {
this.OutFile = null
}
async GEDDELETE_getSectionData()
{
const sectionData = []
// Find LibType steps
const step = wtconfig.get("PMS.ContainerSize." + this.expSettings.libType, 20)
log.debug(`!!!! et (getSectionData): Got Step size as: ${step}`)
let element
// Now read the fields and level defs
// Current item
let idx = 0
// Now let's walk the section
let chuncks, postURI
let size
do {
switch (this.expSettings.libType) {
case et.ETmediaType.Photo:
element = '/library/sections/' + this.expSettings.selLibKey + '/all';
postURI = `?addedAt>>=-2208992400&X-Plex-Container-Start=${idx}&X-Plex-Container-Size=${step}&type=${this.expSettings.libTypeSec}&${this.uriParams}`;
break;
case et.ETmediaType.Playlist:
element = '/playlists/' + this.expSettings.selLibKey;
postURI = `/items?X-Plex-Container-Start=${idx}&X-Plex-Container-Size=${step}`;
break;
case et.ETmediaType.Libraries:
element = '/library/sections/all';
postURI = `?X-Plex-Container-Start=${idx}&X-Plex-Container-Size=${step}`;
break;
case et.ETmediaType.Playlists:
element = '/playlists/all';
postURI = `?X-Plex-Container-Start=${idx}&X-Plex-Container-Size=${step}`;
break;
default:
element = '/library/sections/' + this.expSettings.selLibKey + '/all';
postURI = `?X-Plex-Container-Start=${idx}&X-Plex-Container-Size=${step}&type=${this.expSettings.libTypeSec}&${this.uriParams}`;
}
log.info(`Calling getSectionData url ${this.expSettings.baseURL + element + postURI}`);
chuncks = await et.getItemData({baseURL: this.expSettings.baseURL, accessToken: this.expSettings.accessToken, element: element, postURI: postURI});
size = JSONPath({path: '$.MediaContainer.size', json: chuncks});
const totalSize = JSONPath({path: '$.MediaContainer.totalSize', json: chuncks});
log.info(`getSectionData chunck size is ${size} and idx is ${idx} and totalsize is ${totalSize}`)
// et.updateStatusMsg(et.rawMsgType.Info, i18n.t('Modules.ET.Status.GetSectionItems', {idx: idx, chunck: size, totalSize: totalSize}))
et.updateStatusMsg(et.rawMsgType.Info, i18n.t('Modules.ET.Status.GetSectionItems', {chunck: step, totalSize: totalSize}))
sectionData.push(chuncks)
log.debug(`Pushed chunk as ${JSON.stringify(chuncks)}`)
idx = idx + step;
} while (size > 1);
log.debug(`SectionData to return is:`);
log.debug(JSON.stringify(sectionData));
return sectionData;
}
getRealLevelName(level, libType) {
// First get the real name of the level, and not just the display name
let levelName
@ -228,47 +175,6 @@ const et = new class ET {
return levelName;
}
async DELOLD_getSections(address, accessToken)
{
// Returns an array of json, as:
// [{"title":"DVR Movies","key":31,"type":"movie"}]
const result = [];
let url = address + '/library/sections/all'
this.PMSHeader["X-Plex-Token"] = accessToken;
let response = await fetch(url, { method: 'GET', headers: this.PMSHeader});
let resp = await response.json();
let respJSON = await Promise.resolve(resp);
let sections = await JSONPath({path: '$..Directory', json: respJSON})[0];
let section;
for (section of sections){
const subItem = {}
subItem['title'] = JSONPath({path: '$..title', json: section})[0];
subItem['key'] = parseInt(JSONPath({path: '$..key', json: section})[0]);
subItem['type'] = JSONPath({path: '$..type', json: section})[0];
subItem['scanner'] = JSONPath({path: '$..scanner', json: section})[0];
subItem['agent'] = JSONPath({path: '$..agent', json: section})[0];
result.push(subItem)
}
await Promise.resolve(result);
url = address + '/playlists';
response = await fetch(url, { method: 'GET', headers: this.PMSHeader});
resp = await response.json();
respJSON = await Promise.resolve(resp);
if (JSONPath({path: '$..size', json: respJSON})[0] > 0)
{
sections = await JSONPath({path: '$..Metadata', json: respJSON})[0];
for (section of sections){
const subItem = {}
subItem['title'] = JSONPath({path: '$..title', json: section})[0];
subItem['key'] = parseInt(JSONPath({path: '$..ratingKey', json: section})[0]);
subItem['type'] = JSONPath({path: '$..type', json: section})[0];
subItem['playlistType'] = JSONPath({path: '$..playlistType', json: section})[0];
result.push(subItem)
}
}
return result
}
getLevelDisplayName(level, libType){
// return displayname for the buildin levels
if (libType == et.ETmediaType.Playlist)

View file

@ -0,0 +1,129 @@
<template>
<b-container fluid>
<div class="col-lg-10 col-md-12 col-xs-12">
<h1>{{ $t("Modules.PMS.DVR.Name") }}</h1>
<p>{{ $t("Modules.PMS.DVR.Description") }}</p>
</div>
<div class="d-flex align-items-center">
<b-form-group id="dvrSelDVRGroup" v-bind:label="$t('Modules.PMS.DVR.selDVR')" label-size="lg" label-class="font-weight-bold pt-0" name="dvrSelDVRGroup">
<b-tooltip target="dvrSelDVRGroup" triggers="hover">
{{ $t('Modules.PMS.DVR.ttselDVR') }}
</b-tooltip>
<b-form-select
v-model="selDVR"
id="selDVR"
:options="optSelDVR"
name="selDVR">
</b-form-select>
</b-form-group>
</div>
<br>
<br>
<div class="buttons">
<!-- Buttons -->
<div id="buttons" class="text-center">
<b-button-group >
<b-button
type="is-primary"
@click="dvrBackup"
icon-left="fas fa-file-download"
icon-pack="fas"
:disabled="this.selDVR == ''"
variant="success"
>
{{ $t("Modules.PMS.DVR.lblBtnBackup") }}
</b-button>
</b-button-group>
</div>
</div>
</b-container>
</template>
<script>
import i18n from '../../../../i18n';
//import store from '../../../store';
//import { wtconfig } from '../General/wtutils';
import { dvr } from "./scripts/dvr";
i18n, dvr
const log = require("electron-log");
export default {
data() {
return {
optSelDVR: [],
selDVR: ""
};
},
created() {
log.info("DVR Created");
this.serverSelected();
this.optSelDVR = this.getDVRList();
},
watch: {
// Watch for when selected server address is updated
selectedServerAddress: async function(){
log.info("DVR Selected server changed");
this.optSelDVR = this.getDVRList();
},
doneDVRBackup: async function(){
if (this.$store.getters.doneDVRBackup!='')
{
this.$bvToast.toast(this.$t(this.$store.getters.doneDVRBackup), {
title: this.$t("Modules.PMS.DVR.BackupDone"),
autoHideDelay: 4000,
solid: true,
variant: 'primary',
toaster: 'b-toaster-bottom-right'
});
}
}
},
computed: {
// We need this computed, for watching for changes to selected server
selectedServerAddress: function(){
return this.$store.getters.getSelectedServerAddress
},
doneDVRBackup: function(){
return this.$store.getters.doneDVRBackup
}
},
methods: {
async dvrBackup() {
log.info("DVR Backup started");
dvr.backupDVR( {'dvrName': this.selDVR} );
},
async getDVRList() {
this.optSelDVR = await dvr.getDVRList();
},
/* Check if a server is selected, and if not
tell user, and disable backup */
async serverSelected() {
let serverCheck = this.$store.getters.getSelectedServer;
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'
});
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#sync-button {
margin-left: 1em;
}
</style>

View file

@ -0,0 +1,113 @@
// Helper file for dvr.tv module
const log = require('electron-log');
const {JSONPath} = require('jsonpath-plus');
console.log = log.log;
import {wtconfig, wtutils} from '../../../General/wtutils';
import i18n from '../../../../../i18n';
import store from '../../../../../store';
import axios from 'axios';
i18n, wtconfig
const dvr = new class DVR {
async getDVRList(){
log.info(`Getting list of DVRs`);
// Placeholder for return value
let arrDVR = [];
let header = wtutils.PMSHeader;
header['X-Plex-Token'] = store.getters.getSelectedServerToken;
const url = `${store.getters.getSelectedServerAddress}/livetv/dvrs`;
log.verbose(`Url to query is: ${url}`);
let DVRs;
await axios({
method: 'get',
url: url,
headers: header
})
.then((response) => {
log.debug('Response from getDVRList recieved');
DVRs = JSONPath({path: '$..Dvr[*]', json: response.data});
// Save to store
store.commit('UPDATE_DVR_SETTINGS', DVRs);
log.verbose('DVRs added to store')
})
.catch(function (error) {
if (error.response) {
log.error('getDVRList: ' + error.response.data);
alert(error.response.data.errors[0].code + " " + error.response.data.errors[0].message);
} else if (error.request) {
log.error('getDVRList: ' + error.request);
} else {
log.error('getDVRList: ' + error.message);
}
});
DVRs.forEach(dvr => {
arrDVR.push({
"value": JSONPath({path: '$.uuid', json: dvr}),
"text": JSONPath({path: '$.lineupTitle', json: dvr})
})
});
return arrDVR;
}
async backupDVR( { dvrName } ){
let fileName = await this.getFileName( {'dvrName': dvrName} );
const fs = require('fs');
let data = await this.getDVR( dvrName );
fs.writeFileSync(fileName, JSON.stringify(data, null, 2));
store.commit('UPDATE_doneDVRBackup', fileName);
log.debug(`DVR backedup to ${fileName}`);
}
async getDVR( uuid ){
let DVRs = store.getters.getDVRs;
let retVal='';
DVRs.forEach( dvr => {
if (dvr['uuid']==uuid){
retVal = dvr;
}
})
return retVal;
}
async getDVRName( { uuid } ){
let DVRs = store.getters.getDVRs;
let retVal='';
DVRs.forEach( dvr => {
if (dvr['uuid']==uuid){
retVal = dvr['lineupTitle'];
}
})
return retVal;
}
async getFileName( { dvrName } ){
/*
Will create the output directory if it doesn't exists
Will return a string with the filename to use
*/
const realDVRName = await this.getDVRName( { 'uuid': dvrName })
const path = require('path');
const Module = path.join('Plex Media Server', 'DVR');
const dateFormat = require('dateformat');
const OutDir = wtconfig.get('General.ExportPath');
const timeStamp=dateFormat(new Date(), "yyyy.mm.dd_h.MM.ss");
let outFile = store.getters.getSelectedServer['name'] + '_' + realDVRName + '_' + timeStamp + '.json';
const targetDir = path.join(
OutDir, wtutils.AppName, Module);
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(`OutFile is ${outFileWithPath}`)
return outFileWithPath;
}
}
export { dvr };

View file

@ -11,7 +11,7 @@ import PMSSettings from '../components/modules/PMS/Settings/settings';
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/DVR/DVR';
import DVR from '../components/modules/PMS/DVR/DVR';
import About from '../components/modules/Main/About';
import Store from '../store/index.js';
@ -83,7 +83,7 @@ Vue.use(VueRouter)
meta: {requiresAuth: true}
},
{
path: '/dvr',
path: '/pms/dvr',
name: "dvr",
component: DVR,
meta: {requiresAuth: true}

View file

@ -6,17 +6,27 @@ const log = require('electron-log');
const {JSONPath} = require('jsonpath-plus');
const state = {
settings: {}
settings: {},
DVRs: {},
doneDVRBackup: ''
};
const mutations = {
UPDATE_PMS_SETTINGS(state, payload) {
state.settings = payload;
},
UPDATE_DVR_SETTINGS(state, payload) {
state.DVRs = payload;
},
UPDATE_doneDVRBackup(state, payload) {
state.doneDVRBackup = payload;
}
};
const getters = {
getPMSSettings: state => state.settings
getPMSSettings: state => state.settings,
getDVRs: state => state.DVRs,
doneDVRBackup: state => state.doneDVRBackup
}
const actions = {
@ -78,7 +88,6 @@ const actions = {
});
},
async fetchPMSSettings({ commit }, payload) {
let header = wtutils.PMSHeader;
header['X-Plex-Token'] = payload.Token;
@ -167,8 +176,6 @@ const actions = {
}
};
const serverModule = {
state,
mutations,