* Improve the performance of the media importer, so that it doesn't become much slower after adding many programs.

* Progress Wheel during program import.
* Collections support
* Ability to import whole libraries (get ready to experience the joys of having huge channels)
This commit is contained in:
vexorian 2020-08-14 23:36:06 -04:00
parent 65fa8a22dd
commit b1163e2d00
4 changed files with 175 additions and 38 deletions

View File

@ -13,7 +13,17 @@ module.exports = function (plex, dizquetv, $timeout) {
if ( typeof(scope.limit) == 'undefined') {
scope.limit = 1000000000;
}
scope.pending = 0;
scope.allowedIndexes = [];
for (let i = -10; i <= -1; i++) {
scope.allowedIndexes.push(i);
}
scope.selection = []
scope.wait = (t) => {
return new Promise((resolve, reject) => {
$timeout(resolve,t);
});
}
scope.selectServer = function (server) {
scope.plexServer = server
updateLibrary(server)
@ -31,15 +41,36 @@ module.exports = function (plex, dizquetv, $timeout) {
scope.visible = false
}
}
scope.selectItem = (item) => {
return new Promise((resolve, reject) => {
$timeout(async () => {
item.streams = await plex.getStreams(scope.plexServer, item.key)
scope.selection.push(JSON.parse(angular.toJson(item)))
scope.$apply()
resolve()
}, 0)
})
scope.selectItem = async (item, single) => {
await scope.wait(0);
scope.pending += 1;
try {
item.streams = await plex.getStreams(scope.plexServer, item.key)
scope.selection.push(JSON.parse(angular.toJson(item)))
} finally {
scope.pending -= 1;
}
if (single) {
scope.$apply()
}
}
scope.selectLibrary = async (library) => {
await scope.fillNestedIfNecessary(library);
let p = library.nested.length;
scope.pending += library.nested.length;
try {
for (let i = 0; i < library.nested.length; i++) {
//await scope.selectItem( library.nested[i] );
if (library.nested[i].type !== 'collection') {
await scope.selectShow( library.nested[i] );
}
scope.pending -= 1;
p -= 1;
}
} finally {
scope.pending -= p;
scope.$apply()
}
}
dizquetv.getPlexServers().then((servers) => {
if (servers.length === 0) {
@ -66,10 +97,14 @@ module.exports = function (plex, dizquetv, $timeout) {
console.log(err)
})
}
scope.getNested = (list) => {
scope.fillNestedIfNecessary = async (x, isLibrary) => {
if ( (typeof(x.nested) === 'undefined') && (x.type !== 'collection') ) {
x.nested = await plex.getNested(scope.plexServer, x.key, isLibrary);
}
}
scope.getNested = (list, isLibrary) => {
$timeout(async () => {
if (typeof list.nested === 'undefined')
list.nested = await plex.getNested(scope.plexServer, list.key)
await scope.fillNestedIfNecessary(list, isLibrary);
list.collapse = !list.collapse
scope.$apply()
}, 0)
@ -78,34 +113,53 @@ module.exports = function (plex, dizquetv, $timeout) {
scope.selectSeason = (season) => {
return new Promise((resolve, reject) => {
$timeout(async () => {
if (typeof season.nested === 'undefined')
season.nested = await plex.getNested(scope.plexServer, season.key)
for (let i = 0, l = season.nested.length; i < l; i++)
await scope.selectItem(season.nested[i])
scope.$apply()
resolve()
await scope.fillNestedIfNecessary(season);
let p = season.nested.length;
scope.pending += p;
try {
for (let i = 0, l = season.nested.length; i < l; i++) {
await scope.selectItem(season.nested[i], false)
scope.pending -= 1;
p -= 1;
}
resolve();
} catch (e) {
reject(e);
} finally {
scope.pending -= p;
scope.$apply()
}
}, 0)
})
}
scope.selectShow = (show) => {
return new Promise((resolve, reject) => {
$timeout(async () => {
if (typeof show.nested === 'undefined')
show.nested = await plex.getNested(scope.plexServer, show.key)
for (let i = 0, l = show.nested.length; i < l; i++)
await scope.selectSeason(show.nested[i])
scope.$apply()
resolve()
await scope.fillNestedIfNecessary(show);
let p = show.nested.length;
scope.pending += p;
try {
for (let i = 0, l = show.nested.length; i < l; i++) {
await scope.selectSeason(show.nested[i])
scope.pending -= 1;
p -= 1;
}
resolve();
} catch (e) {
reject(e);
} finally {
scope.pending -= p;
scope.$apply()
}
}, 0)
})
}
scope.selectPlaylist = async (playlist) => {
return new Promise((resolve, reject) => {
$timeout(async () => {
if (typeof playlist.nested === 'undefined')
playlist.nested = await plex.getNested(scope.plexServer, playlist.key)
await scope.fillNestedIfNecessary(playlist);
for (let i = 0, l = playlist.nested.length; i < l; i++)
await scope.selectItem(playlist.nested[i])
await scope.selectItem(playlist.nested[i], false)
scope.$apply()
resolve()
}, 0)

View File

@ -72,6 +72,7 @@
font-size: 80%;
font-weight: 400;
font-family: monospace;
white-space: nowrap;
}
.program-row {
align-items: start;
@ -92,4 +93,26 @@
font-size: .875rem;
line-height: 1.0;
margin-right: 0.5rem;
}
.loader {
width: 1em;
height: 1em;
border: 0.3em solid #f3f3f3;
border-radius: 50%;
display: inline-block;
border-top: 0.25em solid #3498db;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
}
/* Safari */
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@ -33,16 +33,19 @@
<hr />
<ul class="list-group list-group-root plex-panel" ng-init="setHeight = {'height': height + 'px'}" ng-style="setHeight" lazy-img-container>
<li class="list-group-item" ng-repeat="a in libraries">
<div class="{{ displayImages ? 'w_images' : 'wo_images' }}" ng-click="getNested(a);">
<div class="flex-container {{ displayImages ? 'w_images' : 'wo_images' }}" ng-click="getNested(a, true);">
<span class="fa {{ a.collapse ? 'fa-chevron-down' : 'fa-chevron-right' }} tab"></span>
<img ng-if="displayImages" lazy-img="{{a.icon}}" />
<span>{{a.title}}</span><!-- Library -->
<span ng-if="a.type === 'show' || a.type === 'movie'" class="flex-pull-right" ng-click='$event.stopPropagation(); selectLibrary(a)'>
<span class="fa fa-plus btn"></span>
</span>
</div>
<ul ng-if="a.collapse" class="list-group">
<li class="list-group-item {{ b.type !== 'movie' ? 'list-group-item-secondary' : 'list-group-item-video' }}"
ng-repeat="b in a.nested">
<div class="flex-container"
ng-click="b.type !== 'movie' ? getNested(b) : selectItem(b)">
ng-click="b.type !== 'movie' ? getNested(b) : selectItem(b, true)">
<span ng-if="b.type === 'movie'" class="fa fa-plus-circle tab"></span>
<span ng-if="b.type !== 'movie'" class="tab"></span>
<span ng-if="b.type !== 'movie'" class="fa {{ b.collapse ? 'fa-chevron-down' : 'fa-chevron-right' }} tab"></span>
@ -54,7 +57,7 @@
<span ng-if="b.type === 'playlist'" class="flex-pull-right" ng-click="$event.stopPropagation(); selectPlaylist(b);">
<span class="fa fa-plus btn"></span>
</span>
<span ng-if="b.type === 'show'" class="flex-pull-right" ng-click="$event.stopPropagation(); selectShow(b);">
<span ng-if="b.type === 'show' || b.type === 'collection'" class="flex-pull-right" ng-click="$event.stopPropagation(); selectShow(b);">
<span class="fa fa-plus btn"></span>
</span>
</div>
@ -62,7 +65,7 @@
<li ng-repeat="c in b.nested"
class="list-group-item {{ c.type !== 'movie' && c.type !== 'episode' ? 'list-group-item-dark' : 'list-group-item-video' }}">
<div class="flex-container"
ng-click="c.type !== 'movie' && c.type !== 'episode' ? getNested(c) : selectItem(c)">
ng-click="c.type !== 'movie' && c.type !== 'episode' ? getNested(c) : selectItem(c, true)">
<span ng-if="c.type === 'movie' || c.type === 'episode'"
class="fa fa-plus-circle tab"></span>
<span ng-if="c.type !== 'movie' && c.type !== 'episode'"
@ -85,7 +88,7 @@
<ul ng-if="c.collapse" class="list-group">
<li class="list-group-item list-group-item-video"
ng-repeat="d in c.nested">
<div class="flex-container" ng-click="selectItem(d)">
<div class="flex-container" ng-click="selectItem(d, true)">
<span class="fa fa-plus-circle tab"></span>
<img ng-if="displayImages" lazy-img="{{d.episodeIcon}}" />
E{{ d.episode.toString().padStart(2,'0')}} - {{d.title}}
@ -101,13 +104,15 @@
</li>
</ul>
<hr/>
<h6>Selected Items</h6>
<div class="loader" ng-if="pending &gt; 0" ></div> <h6 style='display:inline-block'>Selected Items</h6>
<div class="text-info small" ng-show='selection.length &gt; 10'>{{ selection.length }} elements added in total. Only the last 10 elements are displayed:</div>
<ul class="list-group list-group-root" style="height: 180px; overflow-y: scroll" dnd-list="selection" scroll-glue>
<div ng-if="selection.length === 0">Select media items from your plex library above.</div>
<li class="list-group-item" ng-repeat="x in selection" style="cursor:default;" dnd-draggable="x" dnd-moved="selection.splice($index, 1)" dnd-effect-allowed="move">
{{ (x.type !== 'episode') ? x.title : (x.showTitle + ' - S' + x.season.toString().padStart(2,'0') + 'E' + x.episode.toString().padStart(2,'0'))}}
<li ng-if="selection.length + x &gt;= 0" class="list-group-item" ng-repeat="x in allowedIndexes" style="cursor:default;" dnd-draggable="x" dnd-moved="selection.splice(selection.length + x, 1)" dnd-effect-allowed="move">
{{ (selection[selection.length + x].type !== 'episode') ? selection[selection.length + x].title : (selection[selection.length + x].showTitle + ' - S' + selection[selection.length + x].season.toString().padStart(2,'0') + 'E' + selection[selection.length + x].episode.toString().padStart(2,'0'))}}
<span class="pull-right">
<span class="btn fa fa-trash" ng-click="selection.splice($index,1)"></span>
<span class="btn fa fa-trash" ng-click="selection.splice(selection.length + x,1)"></span>
</span>
</li>
</ul>

View File

@ -1,7 +1,7 @@
const Plex = require('../../src/plex');
module.exports = function ($http, $window, $interval) {
return {
let exported = {
login: async () => {
const headers = {
'Accept': 'application/json',
@ -119,11 +119,12 @@ module.exports = function ($http, $window, $interval) {
return streams
})
},
getNested: async (server, key) => {
getNested: async (server, key, includeCollections) => {
var client = new Plex(server)
const res = await client.Get(key)
var nested = []
var seenFiles = {};
var collections = {};
for (let i = 0, l = typeof res.Metadata !== 'undefined' ? res.Metadata.length : 0; i < l; i++) {
// Skip any videos (movie or episode) without a duration set...
if (typeof res.Metadata[i].duration === 'undefined' && (res.Metadata[i].type === "episode" || res.Metadata[i].type === "movie"))
@ -178,11 +179,65 @@ module.exports = function ($http, $window, $interval) {
program.episode = 1
program.season = 1
}
if (typeof (res.Metadata[i].Collection) !== 'undefined') {
let coll = res.Metadata[i].Collection;
for (let j = 0; j < coll.length; j++) {
let tag = coll[j].tag;
if ( (typeof(tag)!== "undefined") && (tag.length > 0) ) {
let collection = collections[tag];
if (typeof(collection) === 'undefined') {
collection = [];
collections[tag] = collection;
}
collection.push( program );
}
}
}
nested.push(program)
}
if (includeCollections === true) {
let nestedCollections = [];
let keys = [];
Object.keys(collections).forEach(function(key,index) {
keys.push(key);
});
for (let k = 0; k < keys.length; k++) {
let key = keys[k];
if (collections[key].length <= 1) {
//it's pointless to include it.
continue;
}
let collection = {
title: key,
key: "#collection",
icon : "",
type : "collection",
nested: collections[key],
}
if (res.viewGroup === 'show') {
collection.title = collection.title + " Collection";
//nest the seasons directly because that's way too many depth levels already
let shows = collection.nested;
let collectionContents = [];
for (let i = 0; i < shows.length; i++) {
let seasons = await exported.getNested(server, shows[i].key, false);
for (let j = 0; j < seasons.length; j++) {
seasons[j].title = shows[i].title + " - " + seasons[j].title;
collectionContents.push(seasons[j]);
}
}
collection.nested = collectionContents;
}
nestedCollections.push( collection );
}
nested = nestedCollections.concat(nested);
}
return nested
}
}
return exported;
}
function msToTime(duration) {