API Docs for:
Show:

File: library.js

/*******************************************************************************
 * Copyright (c) 2017 Genialist Software Ltd.
 * All rights reserved.
 ******************************************************************************/

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

pLibraryUpdateInstance = function(p_tracePrefix, p_update_ms, p_element_id) {
	var that = this, m_id = ''+Math.random(), m_element = p_element_id, m_update = !pDevice.isMobile(), m_update_previous, updatems = p_update_ms;
	this.tracePrefix = p_tracePrefix;
	this.previous = null;
	
	objects.put(m_id, this);

	// "id" property
	Object.defineProperty(this, "id", { get: function() { return m_id; }});

	// "element" property
	Object.defineProperty(this, "element", { get: function() { return m_element; }});
	
	this.url = null;
	
	this.onerror = function(i_xml_http_request) { 
		pElement.setInnerHTML(m_element, '<div class="box"><span class="warning">Failed to contact server...</span></div>'); 
	};
 	
	this.toggleUpdate = function() {
		m_update = !m_update;
		that.update();
	};
	
	this.update = function() {
		m_element = pElement.x(m_element);
		return new Promise(function(resolve, reject) {
			return that.updateImpl(resolve, reject, that);
		});
	};
	
	this.updateImpl = function(resolve, reject, obj) {};
	
	var registered_visibilityChange;
	this.nextUpdate = function() {
		if (!registered_visibilityChange) {
			pDocument.addOnVisibilityChange(function() {
				if (updatems && m_update && pDocument.pageIsHidden()===false) {
					pConsole.info(that, "Resume updating as page is visible...");
					that.update();
				}
			});
			registered_visibilityChange = true;
		}
		
		if (updatems && m_update && pDocument.pageIsHidden()===false) {
			pDocument.setTimeout(m_id, that.update, updatems);
		}
		else if (pDocument.pageIsHidden()===true)
			pConsole.info(that, "Stop updating as page is not visible...");

	};
	
	var lastRefresh;
	this.needsRefresh = function(data) {
		var r = !that.previous || that.previous != data || m_update != m_update_previous;
		if (!r && lastRefresh && lastRefresh + 60000 < Date.now()) r = true;
		if (r) lastRefresh = Date.now();
		return r;
	};
	
	this.cache = function(data) {
		that.previous = data;
		m_update_previous = m_update;
	};
		
	this.renderRefreshText = function() {
		var h = '<div class="desc">', i_id = "'" + m_id + "'";
		if (updatems && m_update) {
			h += 'This page is refreshed every '+((updatems == 1000)? 'second' : pTime.msToLongDuration(updatems))+', click ';
			h += '<span class="link link-strong" onclick="objects.getValue('+i_id+').update()">here</span>';
			h += ' to refresh now..., click ';
			h += '<span class="link link-strong" onclick="objects.getValue('+i_id+').toggleUpdate()">here</span>';
			h += ' to stop automatic refreshing...</div>';
		}
		else {
			h += 'Click ';
			h += '<span class="link link-strong" onclick="objects.getValue('+i_id+').update()">here</span>';
			h += ' to refresh now..., click ';
			h += '<span class="link link-strong" onclick="objects.getValue('+i_id+').toggleUpdate()">here</span>';
			h += ' to start automatic refreshing...</div>';
		}
		return h;
	};
	
	this.bind = function(name, f) {
		that[name] = f.bind(that);
	};
		
	pDocument.addOnVisibilityChange(function() {
		if (updatems && m_update && pDocument.pageIsHidden()===false) {
			pConsole.info(that, "Resume updating as page is visible...");
			that.update();
		}
	});
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pLibraryErrorList = new pLibraryUpdateInstance('LibraryErrorList', 1000, 'errors');
pLibraryErrorList.url = window.location.pathname;
pLibraryErrorList.bind("remove", function(id) {
	pHTTPRequest.get(this.url+'?action=delete-error&id='+id).then(this.update);
});

pLibraryErrorList.updateImpl = function(resolve, reject, that) {

	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = that.url+'?action=get-errors&v='+Math.random();
	pConsole.info(that, "GetErrors: " + u);
	pHTTPRequest.invoke({ url: u, onerror: that.onerror }).then(function(i_request) {

		//if (i_request.status == 307 || i_request.status == 200)

		//handle errors
		if (pROSE.handleHTTPResponse(i_request) === false)
			return false;

		if (i_request.status == 200  && that.needsRefresh(i_request.responseText)) {

			var o = pJSON.parse(i_request.responseText), h = that.renderRefreshText();

			h += '<table cellpadding="5" cellspacing="5" class="table-library-errors"><tr><th/>';
			[ 'When', 'Context', 'Error' ].forEach(function(n) {
				h += '<th><a class="link" title="Click here to sort this column" href="javascript:sortTableString(\''+n+'\')">'+n+'</a></th>';
			});
			h += '</tr>';

			o.errors.forEach(function(e) {
				h += '<tr>';
				h += '<td><div class="icon icon-user-error"><!----></div></td>';
				h += '<td data-label="When">'+pHumanText.toDateTime(pDate.create(e.date))+'</td>';
				h += '<td data-label="Context">'+pString.encodeHTML(e.context)+'</td>';
				h += '<td data-label="Error">'+pString.encodeHTML(e.exception)+'</td>';
				h += '<td data-label="Delete"><a class="link" href="javascript:objects.getValue(\''+that.id+'\', null).remove(\''+e.id+'\')">&times;</a></td>';
				h += '</tr>';
			});
			h += '</table>';

			pElement.setInnerHTML(that.element, h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();
		
		//*** CALL NEXT UPDATE
		that.nextUpdate();

	});
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pLibraryStatus = new pLibraryUpdateInstance('LibraryStatus', 10000, 'server_status');
pLibraryStatus.url = window.location.pathname;
pLibraryStatus.updateImpl = function(resolve, reject, that) {

	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = that.url + '?action=get-status&contents=object';//&v='+Math.random();
	pConsole.info(that, "GetStatus: " + u);
	pHTTPRequest.invoke({ url: u, onerror: that.onerror }).then(function(i_request) {

		//handle errors
		if (pROSE.handleHTTPResponse(i_request) === false)
			return false;

		if (i_request.status == 200 && that.needsRefresh(i_request.responseText)) {

			var o = pJSON.parse(i_request.responseText), h = '<div class="box">'+that.renderRefreshText()+'</div>';

			h += '<div id="library-status-uptime" class="box">Library Opened: '+pHumanText.toDateTime(pDate.create(o.opened))+'</div>';
			h += '<div id="library-status-uptime" class="box">Library Loaded in: '+pTime.msToDuration((pDate.create(o.load_completed)).getTime() - (pDate.create(o.load_started)).getTime())+'</div>';
			h += '<div id="library-status-uptime" class="box">Library Saved: '+pHumanText.toDateTime(pDate.create(o.saved))+'</div>';
			h += '<div id="library-status-uptime" class="box">Summary: '+pString.encodeHTML(o.summary)+'</div>';

			pElement.setInnerHTML(that.element.id+'-content', h);
			
			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();
		
		//*** CALL NEXT UPDATE
		that.nextUpdate();

	});
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pLibraryJobList = new pLibraryUpdateInstance('LibraryJobList', 60000, 'jobs');
pLibraryJobList.url = window.location.pathname;
pLibraryJobList.bind("remove", function(id) {
	pTransaction.simpleTransaction({
		trace: this, url: pURL.addQueryParameter(this.url, [ 'action', 'delete-autoadd-user-job', 'job-id', id, 'v', Math.random() ]), 
		method: "DELETE", method_name: "DeleteJob",
		msg_start: 'Deleting task...',
		msg_end: this.update
	});
});

pLibraryJobList.bind("start", function(id) {
	pTransaction.transaction({
		trace: this, url: pURL.addQueryParameter(this.url, [ 'action', 'start-autoadd-user-job', 'job-id', id, 'v', Math.random() ]), 
		method_name: "StartJob",
		msg_start: 'Starting task...',
		msg_end: this.update
	});
});

pLibraryJobList.updateImpl = function(resolve, reject, that) {

	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = that.url+'?action=get-autoadd-user-jobs&v='+Math.random();
	pConsole.info(that, "GetJobs: " + u);
	pHTTPRequest.invoke({ url: u, onerror: that.onerror }).then(function(i_request) {

		//handle errors
		if (pROSE.handleHTTPResponse(i_request) === false)
			return false;

		if (i_request.status == 200 && that.needsRefresh(i_request.responseText)) {

			var o = pJSON.parse(i_request.responseText), h = that.renderRefreshText();
			
			h += '<div>Tasks executed automatically and repeateadly. You can create your own to rescan your media files regularly to pick up updates or new files...</div>';

			h += '<table cellpadding="5" cellspacing="5" class="table-library-jobs"><tr><th/>';
			[ 'Name', 'Enabled', '', 'Last', 'Next' ].forEach(function(n) {
				if (n!='')
					h += '<th><a class="link" title="Click here to sort this column" href="javascript:sortTableString(\''+n+'\')">'+n+'</a></th>';
				else
					h += '<th/>';
			});
			h += '</tr>';

			o.jobs.forEach(function(j) {
				h += '<tr>';
				h += '<td><div class="icon icon-user-job"><!----></div></td>';
				h += '<td data-label="Name"><a class="link" href="javascript:void(pUserJobManager.getProperties(\''+j.id+'\'))">'+pString.encodeHTML(j.display_name)+'</a></td>';
				h += '<td data-label="Enabled">' + ((j.enabled)? 'Yes' : '') +'</td>';
				h += '<td><a class="link" href="javascript:void(objects.getValue(\''+that.id+'\', null).start(\''+j.id+'\'))">Execute</a></td>';
				
				if (j.last_start)
					h += '<td data-label="Last" data-sort=\"'+j.last_start+'\">'+pHumanText.toDateTime(pDate.create(j.last_start))+'</td>'
				else
					h += '<td data-label="Last"/>';

				if (j.next)
					h += '<td data-label="Next" data-sort=\"'+j.last_start+'\">'+pHumanText.toDateTime(pDate.create(j.next))+'</td>'
				else
					h += '<td data-label="Next"/>';
				
				h += '<td data-label="Delete" >' + ((j.readonly || !j.delete_enabled)? '' : '<a class="link" href="javascript:void(objects.getValue(\''+that.id+'\', null).remove(\''+j.id+'\'))">&times</a>')+'</td>';
				h += '</tr>';
			});
			h += '</table>';

			pElement.setInnerHTML(that.element, h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

		//*** CALL NEXT UPDATE
		that.nextUpdate();

	});
};
	
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pLibraryThreadList = new pLibraryUpdateInstance('LibraryThreadList', 1000, 'threads');
pLibraryThreadList.url = window.location.pathname;
pLibraryThreadList.updateImpl = function(resolve, reject, that) {

	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = that.url+'?action=get-threads&v='+Math.random();
	pConsole.info(that, "GetThreads: " + u);
	pHTTPRequest.invoke({ url: u, onerror: that.onerror }).then(function(i_request) {

		//handle errors
		if (pROSE.handleHTTPResponse(i_request) === false)
			return false;

		if (i_request.status == 200 && that.needsRefresh(i_request.responseText)) {

			var o = pJSON.parse(i_request.responseText);
			
			var h = that.renderRefreshText();
			h += '<div>Tasks currently executed:</div>';
			
			h += '<table cellpadding="5" cellspacing="5" class="table-library-jobs"><tr><th/>';
			[ 'State', 'Started', 'Task' ].forEach(function(n) {
				h += '<th style="text-align: left;"><a class="link" title="Click here to sort this column" href="javascript:sortTableString(\''+n+'\')">'+n+'</a></th>';
			});
			h += '</tr>';

			o.threads.forEach(function(t) {
				h += '<tr style="text-align: left;">';
				h += '<td>';
				if (t.executed)
					h += '<div class="icon icon-task-running"><!----></div>';
				else
					h += '<div class="icon icon-task-waiting"><!----></div>';
				h += '</td>';
				h += '<td data-label="State">'+(t.executed? 'Running':'Waiting')+'...</td>';
				h += '<td data-label="Started" data-sort=\"'+t.started+'\">'+(t.started? pHumanText.toDateTime(pDate.create(t.started)):'')+'</td>';
				h += '<td data-label="Task">'+pString.encodeHTML(t.title)+(pString.v(t.status)? '<br>'+pString.encodeHTML(t.status) : '')+'</td>';
				h += '</tr>';
			});
			h += '</table>';

			pElement.setInnerHTML(that.element, h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

		//*** CALL NEXT UPDATE
		that.nextUpdate();

	});
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pLibrary = (new function() {
	var that = this;
	this.tracePrefix = 'Library';

	this.getProperties = function(p_doc_url, p_artwork_url) {

		pDocument.wait();

		var i_data = {
			library: this,
			doc_url: p_doc_url || window.location.pathname,
			org_artworks: [ p_artwork_url ]
		};
		var i_url = i_data.doc_url+'?action=get-properties&format=json&contents=values&v='+Math.random();

		pConsole.info(this, "GetProperties: " + i_url);
		pHTTPRequest.get(i_url, function(i_request, i_data) {

			//handle errors
			if (pROSE.handleHTTPResponse(i_request) === false)
				return false;

			//console.log(i_request.responseText);
			//pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));
			var i_obj = pJSON.parse(i_request.responseText);
			//pConsole.debug(that, "GetProperties: response: " + pJSON.pretty(i_request.responseText));

			var i_values = new pMap(i_obj.values);

			var i_dialog = {
				id: 'lib-properties',
				options: [
					'ok', 'cancel',

					{ id: 'file', tab: true, text: 'Library' },
					{ id: 'library_id', input: { readonly: true }, text: 'ID:', value: p_doc_url },
					{ id: 'library_name', input: { readonly: true }, text: 'Name:', value: '' },
					'<hr>',
					{ id: 'library_folder', input: { readonly: true }, text: 'Folder:', value: '' },
					'This is the folder where the library data is stored.<p>To move it:<ol><li>Close the library,<li>Move the folder with your operating system tools,<li>Open the library in the new folder location...</ol>',
					/*(i_obj.file)? { id: 'file-sep2', sep: true, text: '<hr>' } : null,
					(i_obj.file && i_obj.file.os_type)? { id: 'file-type', input: { disabled: true }, text: 'Type of File:', value: i_obj.file.os_type } : null,
					(i_obj.file && i_obj.file.os_type)? { id: 'file-sep2', sep: true, text: '<hr>' } : null,
					(i_obj.file)? { id: 'file-location', input: { disabled: true }, text: 'Location:', value: pString.dirname(i_obj.file.path) } : null,
					(i_obj.file)? { id: 'file-size', input: { disabled: true }, text: 'Size:', value: i_obj.file.size+' bytes' } : null,
					(i_obj.file)? { id: 'file-sep2', sep: true, text: '<hr>' } : null,
					(i_obj.file && i_obj.file.lastCreated)? { id: 'file-lastCreated', input: { disabled: true }, text: 'Modified:', value: pDate.create(i_obj.file.lastCreated) } : null,
					(i_obj.file && i_obj.file.lastModified)? { id: 'file-lastModified', input: { disabled: true }, text: 'Modified:', value: pDate.create(i_obj.file.lastModified) } : null,
					(i_obj.file && i_obj.file.lastAccessed)? { id: 'file-lastAccessed', input: { disabled: true }, text: 'Modified:', value: pDate.create(i_obj.file.lastAccessed) } : null,
					(i_obj.file)? { id: 'file-sep3', sep: true, text: '<hr>' } : null,*/

					{ id: 'tab-library', tab: true, text: 'Media Files' },
					pDialog.newCheckbox("library_saveMetadata_enabled", 'Save library changes into media files'),
					'If selected and depending on the media file format, information will be copied inside the file.<p>Either way, information is saved inside a "metadata.cfg" file in the same folder as the media files...',
					'<hr>',
					pDialog.newCheckbox("library_saveMetadata_enabled_metadata_rating", 'Save current user rating into media files'),
					'If selected the file will only contains the latest rating chosen by ANY user. We recomment to unselect this option unless you have only one user on the server.',
					'<hr>',
					pDialog.newCheckbox("library_saveChildrenImages_enabled", 'Save Movie and TV Season image changes into media files'),
					'If selected, poster and backdrop image will be saved in sub-folders of the media files.',
					'<hr>',
					pDialog.newCheckbox("library_saveMetadata_playcount_enabled", 'Save playcount changes (and video end playing time) into media files'),
					
					'<hr>',
					pDialog.newCheckbox("library_pictures_readMetadata_enabled", 'Read metadata from Picture files'),
					'If selected, tags read from each picture file will be added as tags of the containing "Picture Folder".',

					{ id: 'file-sep3', tab: true, text: 'Privacy' },
					pDialog.newCheckbox("library_findMetadata_enabled", 'Find information from the internet'),
					'If unselected, you can still click on the "Find Information" button from the page toolbar to find it...',
					'<hr>',
					pDialog.newCheckbox("library_findOnNetflix_enabled", 'Experimental: Find movies on netflix.com and add them to this library'),
					'If movies you added to this library are available on netflix, a link to netflix will be added to the library...',

					{ id: 'tab-lists', tab: true, text: 'Lists' },
					{ id: 'desc-warn', sep: true, className: 'warning', text: 'Users must reconnect to see the change<p>' },
					'This configures which media categories and groups are visible or not (users can still access the page if they know the URL):<p>',
					
					{ id: "library_list_(music_medias)_enabled", input: { type: 'checkbox', onchange: function() {
						pDialog.resources.f_enableIfEnabled('lib-properties', 'library_list_(music_medias)_enabled', [ 'library_list_(music_genres)_enabled' ]);				
					}}, text_after: 'Music', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(music_medias)_enabled", input: { type: 'checkbox' }, text_after: 'Music Files', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(music_albums)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Music Albums', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(music_artists)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Music Artists', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(music_genres)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Music Genres', text_after_class: 'library-properties-checkbox', value: false },
					'<hr>',
					{ id: "library_list_(movies_medias)_enabled", input: { type: 'checkbox', onchange: function() {
						pDialog.resources.f_enableIfEnabled('lib-properties', 'library_list_(movies_medias)_enabled', [ 'library_list_(movies_genres)_enabled', 'library_list_(movies_tags)_enabled', 'library_list_(movies_actors)_enabled' ]);				
					} }, text_after: 'Movies', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(movies_medias)_enabled", input: { type: 'checkbox' }, text_after: 'Movie Files', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(movies_genres)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Movie Genres', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(movies_groups)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Movie Groups', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(movies_tags)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Movie Tags', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(movies_actors)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Movie Actors', text_after_class: 'library-properties-checkbox', value: false },
					'<hr>',
					{ id: "library_list_(tvshows_medias)_enabled", input: { type: 'checkbox', onchange: function() {
						pDialog.resources.f_enableIfEnabled('lib-properties', 'library_list_(tvshows_medias)_enabled', [ 'library_list_(tvshows_genres)_enabled' ]);				
					} }, text_after: 'TV Shows', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(tvshows_genres)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'TV Show Genres', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(tvshows_medias)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'TV Show Episodes', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(tvshows_actors)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'TV Show Actors', text_after_class: 'library-properties-checkbox', value: false },
					'<hr>',
					{ id: "library_list_(books_medias)_enabled", input: { type: 'checkbox', onchange: function() {
						pDialog.resources.f_enableIfEnabled('lib-properties', 'library_list_(books_medias)_enabled', [ 'library_list_(books_genres)_enabled', 'library_list_(books_tags)_enabled', 'library_list_(books_authors)_enabled' ]);				
					} }, text_after: 'Books', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(books_groups)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Book Groups', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(books_genres)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Book Genres', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(books_tags)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Book Tags', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(books_authors)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Book Authors', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(group_playlists_medias)_enabled", input: { type: 'checkbox' }, text_after: 'Books', text_after_class: 'library-properties-checkbox', value: false },
					'<hr>',	
					{ id: "library_list_(pictures_medias)_enabled", input: { type: 'checkbox', onchange: function() {
						pDialog.resources.f_enableIfEnabled('lib-properties', 'library_list_(pictures_medias)_enabled', [ 'library_list_(pictures_authors)_enabled', 'library_list_(pictures_genres)_enabled', 'library_list_(pictures_tags)_enabled', 'library_list_(pictures_models)_enabled' ]);				
					} }, text_after: 'Pictures', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(pictures_authors)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Picture Authors', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(pictures_genres)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Picture Genres', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(pictures_groups)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Picture Groups', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(pictures_tags)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Picture Tags', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(pictures_models)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Picture Models', text_after_class: 'library-properties-checkbox', value: false },
					
					'<hr>',
					{ id: "library_list_(homevideos_medias)_enabled", input: { type: 'checkbox', onchange: function() {
						pDialog.resources.f_enableIfEnabled('lib-properties', 'library_list_(homevideos_medias)_enabled', [ 'library_list_(homevideos_tags)_enabled' ]);				
					} }, text_after: 'Home Videos', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(homevideos_medias)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Home Video Files', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(homevideos_tags)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Home Video Tags', text_after_class: 'library-properties-checkbox', value: false },
					
					'<hr>',
					{ id: "library_list_(radios_medias)_enabled", input: { type: 'checkbox', onchange: function() {
						pDialog.resources.f_enableIfEnabled('lib-properties', 'library_list_(radios_medias)_enabled', [ 'library_list_(radios_genres)_enabled' ]);				
					} }, text_after: 'Radios', text_after_class: 'library-properties-checkbox', value: false },
					//{ id: "library_list_(homevideos_medias)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Home Video Files', text_after_class: 'library-properties-checkbox', value: false },
					{ id: "library_list_(radios_genres)_enabled", className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Radio Genres', text_after_class: 'library-properties-checkbox', value: false },
					
					'<hr>',
					{ id: "library_list_(playlists_medias)_enabled", input: { type: 'checkbox' }, text_after: 'Playlists', text_after_class: 'library-properties-checkbox', value: false },

					{ id: 'tab-advanced', tab: true, text: 'Advanced' },
					'Movies',
					pDialog.newCheckbox("library_play_movie_all_tracks_enabled", 'Play all movie tracks/medias in sequence'),
					//{ id: "library_play_movie_nopage_enabled", input: { type: 'checkbox' }, text_after: 'Play movies from list page', text_after_class: 'library-properties-checkbox', value: false },
					//'Access Rights',
					//{ id: "metadata_library_private", input: { type: 'checkbox' }, text_after: 'Private: only <b>admin</b> or the owner can see this library', text_after_class: 'library-properties-checkbox', value: false }

					//'Home Videos',
					//{ id: "library_play_homevideo_nopage_enabled", input: { type: 'checkbox' }, text_after: 'Play home videos from list page', text_after_class: 'library-properties-checkbox', value: false },

					'<hr>',
					'Library Files',
					pDialog.newCheckbox('library_backup_enabled', 'Back up library'),
					pDialog.newCheckbox("library_file_lock", 'Lock library folder (so other applications do not open it)')

				],
				submitChangedInputsOnly: true,
				fcall: function(p_dialog) {
					var u = pURL.addQueryParameter(i_data.doc_url, [ 'action', 'set-properties', 'v', Math.random() ]);

					pConsole.info(this, "SetProperties: " + u);
					pHTTPRequest.post({ url: u, headers: pHTTPRequest.headers_post, body: pURL.toFormSubmitBody(p_dialog.inputs, pMetadataDialog.transformToServer) });
				}.bind(i_data)
			};

			//*** UPDATE VALUES
			i_dialog.options.forEach(function(op) {
				if (op) 
					if (op.id)
						if (op.id.indexOf('file-')<0)
							if (op.id.indexOf('library-')<0)
								op.value = pMetadataDialog.transformFromServer(op.id, i_values.getValue(op.id, ''));
			});

			pDocument.stopwait(); //TODO: inside dialogQuestion...
			pDialog.dialogQuestion(i_dialog);//.doc_url = i_data.doc_url;

		}, false, i_data);
	};
		
	this.close = function(p_library_url) {
		var u = p_library_url || window.location.pathname;
		
		pDialog.question({ id: 'close-library' }).then(function() {
			pTransaction.transaction({
				trace: that, url: pURL.addQueryParameter(u, [ 'action', 'close', 'v', Math.random(), 'wait', 1 ]), method_name: "CloseLibrary",
				msg_start: 'Closing library...',
				msg_error: 'Closing library... Failure',
				msg_end: [ 'Closing library... Done.', function() { pLocation.assign('/server?show=server_libraries'); } ]
			});
		});
	};

	this.rename = function(p_library_url, p_name) {
		return new Promise(function(resolve, reject) {
			var p_request = { url: p_library_url || window.location.pathname };
			pDialog.dialogQuestion({
				id: 'rename-library',
				icon: { src: pElement.getSrc('img-pLibrary-rename') },
				options: [
					{ id: 'name', input: { type: 'text', required: true }, text: 'Library Name:', value: p_name || pPageData.getValue('s-name-'+pMediaLibrary.getLibraryIDfromURL(p_request.url)) },
					'ok', 'cancel'
				],
				fcall: function(p_dialog, p_value) {
					var n = p_dialog.inputs.getValue('name', '').trim();
					if (!pString.v(n))
						return;
	
					pTransaction.simpleTransaction({
						trace: this, url: pURL.addQueryParameter(this.url, [ 'action', 'rename', 'name', n, 'v', Math.random() ]), method_name: "RenameLibrary",
						msg_start: 'Renaming Media Library...',
						msg_end: function() {
							pPageData.put('s-name-'+pMediaLibrary.getLibraryIDfromURL(p_request.url), n);
							resolve(n);
						}
					});
				}.bind(p_request)
			});
		});
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pPageParser = new (function() {
	var that = this;
	this.tracePrefix = 'PageParser';
	
	const r_titles = [ /^.*Watch (.*) \(([0-9]{4})\)[ ]*online.*$/i,
		            /^.*Watch (.*) \(([0-9]{4})\)[ ]*instantly.*$/i,
		            /^.*Watch (.*) \(([0-9]{4})\)[ ]*\| Prime.*$/i,
		            /^.*Watch (.*)[ ]*online.*$/i, 
		            /^.*Watch (.*)[ ]*instantly.*$/i,
		            /^.*Watch (.*)[ ]*\| Prime.*.*$/i,
		            /^(.*) \(([0-9]{4})\)[ ]*\(With Bonus Content\).*$/i,
		            /^(.*) \(([0-9]{4})\)[ ]*\(Plus Bonus Content\).*$/i,
		            /^(.*)[ ]*\(With Bonus Content\).*$/i,
		            /^(.*)[ ]*\(Plus Bonus Content\).*$/i,
		            ///^(.*) \(([0-9]{4})\)[ ]*\(With Bonus Content\)[ ]*:[ ]*Watch.*$/i,
		            ///^(.*) \(([0-9]{4})\)[ ]*\(Plus Bonus Content\)[ ]*:[ ]*Watch.*$/i,
		            /^(.*) \(([0-9]{4})\)[ ]*\(Theatrical Version\).*$/i,
		            /^(.*)[ ]*\(Theatrical Version\).*$/i,
		            ///^(.*) \(([0-9]{4})\)[ ]*\(Theatrical Version\)[ ]*:[ ]*Watch.*$/i,
		            /^(.*) \(([0-9]{4})\)[ ]*:[ ]*Watch.*$/i,
		            /^(.*)[ ]*:[ ]*Watch.*$/i,
		            /^(.*) .+ Movies.*$/i,
		            /^(.*) .+ Flixster Video.*$/i,
		            /^(.*) .+ Rakuten TV.*$/i,
		            /^(.*) \|.*$/i
		          ],
		r_providers = [
			{ id: 'netflix', re: [ /^.*\.netflix\.com$/i ] },
			{ id: "youtube", re: [ /^.*\.youtube\.com$/i, /^.*\.youtu\.be$/i ] },
			{ id: "amazon", re: [ /^.*\.amazon\.co\.[^.]+$/i, /^.*\.amazon\.[^.]+$/i ] },
			{ id: "TalkTalk TV Store", re: [ /^.*\.talktalktvstore\.co\.uk$/i ] },
			{ id: "Google Play", re: [ /^play\.google\.[^.]+$/i ] },
			{ id: "Google Drive", re: [ /^drive.google.com$/ ] },
			{ id: "Rakuten TV", re: [ /^.*\.rakuten\.tv$/i, /^rakuten\.tv$/i ] },
			{ id: "Flixster Video", re: [ /^.*\.flixstervideo.com$/i ] }
		];
	
	function f_getProvider(url) {
		var h = pLocation.hostname(url);
		var p = r_providers.find(function(pp) { return pp.re.find(function(re) { return re.test(h); }); });
		return p? p.id : null;
	}
	
	function f_getMetaByAttribute(i_doc, p_attr, p_name) {
		var i_metas = i_doc.getElementsByTagName('meta');
		for(var i=0 ; i<i_metas.length ; i++) {
			var i_meta = i_metas[i];
			if (i_meta.getAttribute(p_attr) == p_name)
				return i_meta.getAttribute('content');
		}
		//return null;
	}
	
	function f_getMetaByName(i_doc, p_name) {
		return f_getMetaByAttribute(i_doc, 'name', p_name);
	}
	
	function f_getMetaByPropertyName(i_doc, p_name) {
		return f_getMetaByAttribute(i_doc, 'property', p_name);
	}
	
	function f_getMetaByItemPropName(i_doc, p_name) {
		return f_getMetaByAttribute(i_doc, 'itemprop', p_name);
	}
	
	const r_durations = [
			{ re: /^([0-9]{1,2}) HR ([0-9]{1,2}) MINS?$/i, parse: function(c) {
				if (this.re.test(c)) {
					var e = this.re.exec(c);
					return e[1]+':'+e[2]+':00';
				}
			}},
			{ re: /^([0-9]{1,2}) MINS?$/i, parse: function(c) {
				if (this.re.test(c)) {
					var e = this.re.exec(c);
					return ((parseInt(e[1])<10)? '0':'')+e[1]+':00';
				}
			}},
			{ re: /^([0-9]{1,2}) hours?, ([0-9]{1,2}) minutes?$/i, parse: function(c) {
				if (this.re.test(c)) {
					var e = this.re.exec(c);
					return e[1]+':'+((parseInt(e[2])<10)? '0':'')+e[2]+':00';
				}
			}},
			{ re: /^([0-9]{1,2})h ([0-9]{1,2})m$/i, parse: function(c) {
				if (this.re.test(c)) {
					var e = this.re.exec(c);
					return e[1]+':'+((parseInt(e[2])<10)? '0':'')+e[2]+':00';
				}
			}},
			{ re: /^([0-9]{1,3}) min(ute)?s?$/i, parse: function(c) {
				if (this.re.test(c)) {
					var e = this.re.exec(c), m = parseInt(e[1]);
					if (m<10)
						return '0'+m+':00';
					else if (m<60)
						return m+':00';
					else {
						var h = Math.floor(m / 60); m = m % 60;
						return h + ':' + (m<10? '0':'')+m+':00';
					}
				}
			}},
			{ re: /^([0-9]+)M([0-9]+)S$/i, parse: function(c) {
				if (this.re.test(c)) {
					var e = this.re.exec(c);
					if (e) {
						var m = e[1], s = e[2];
	
						if (pString.v(m) && pString.v(s)) {
							m = parseInt(m.trim());
							var h = Math.trunc(m / 60); m = m % 60;
							s = parseInt(s.trim());
							return ((h<10)? '0':'')+h+':'+((m<10)? '0':'')+m+':'+((s<10)? '0':'')+s;
						}
					}
				}
			}}
		];
	
	function f_parseDuration(c) {
		var r = null;
		c = (c || '').trim();
		r_durations.find(function(re) {
			if (r = re.parse(c))
				return true;
		});
		return r;
	}
	
	function f_parseYear(c) {
		if (c) c = c.split('-')[0];
		var y = parseInt((c || '').trim());
		if (!isNaN(y) && y>1900 && y<2999)
			return y;
	}
	
	r_durations.forEach(function(r) { r.parse = r.parse.bind(r); });
	
	this.parse = function(p_url) {
		return new Promise(function(resolve, reject) { 
			var u_url = p_url, i_url = pURL.addQueryParameter(window.location.path, [ 'action', 'download-url', 'url', p_url, 'v', ''+Math.random() ]);
			if (f_getProvider(p_url) == "Google Drive")
				i_url = p_url;
			
			pConsole.info(that, 'Download: ' + i_url);
				pHTTPRequest.get(i_url, function(p_request) {
			
				var i_values = new pMap();
				
				i_values.put('metadata_provider', f_getProvider(u_url));
				/*var i_hostname = new pLocationInstance(this.url).host;
				for(var i=0 ; i<r_providers.length ; i+=2) {
					var i_provider = r_providers[i];
					
					if (r_providers[i+1].find(function(re) { return re.test(i_hostname) })) {
						i_values.put('metadata_provider', i_provider);
						break;
					}
				}*/
				
				var i_txt = p_request.responseText, i_doc = pElement.create('div', null, null, i_txt);
				//console.log(p_request.responseText);
	
				var i_title;
				
				var x = pElement.t(i_doc, 'title');
				if (x.length>0)
					i_title = x[0].innerHTML;
				
				if (!pString.v(i_title))
					i_title = f_getMetaByPropertyName(i_doc, 'og:title') || f_getMetaByName(i_doc, 'title')  || f_getMetaByAttribute(i_doc, 'itemprop', 'name');
				if (pString.v(i_title)) {
					r_titles.find(function(re) {
						console.log(i_title + ' - ' + re + '...');
						if (re.test(i_title)) {
							var e = re.exec(i_title);
							if (e) {
								i_title = e[1];
								i_year = e[2];
								
								console.log(re + '... Match');
	
								if (pString.v(i_year))
									i_values.put('metadata_year', i_year.trim());
								if (pString.v(i_title))
									i_values.put('metadata_title', i_title.trim());
								
								return true;
							}
						}
					});
				}
				
				if (!pString.v(i_title)) {
					var x = '"title":"', pos = i_txt.indexOf(x), i_escape = false;
					if (pos>0) {
						pos += x.length;
						i_title = i_txt.substring(pos);
					}
					i_title = '';
					while(true) {
						var c = i_txt.charAt(pos);
						if (c == '"') {
							if (i_escape) {
								i_title += '"';
								i_escape = false;
							}
							else {
								break;
							}	
						}
						else if (c == '\\') {
							if (i_escape) {
								i_title += '\\';
								i_escape = false;
							}
							else
								i_escape = true;
						}
						else if (i_escape)
							i_title += c;
						else
							i_title += c;
						pos++;
					}
				}
				if (pString.v(i_title))
					i_values.put('metadata_title', i_title.trim());
				
				var i_w = parseInt(f_getMetaByPropertyName(i_doc, 'og:video:width'));
				var i_h = parseInt(f_getMetaByPropertyName(i_doc, 'og:video:height'));
				if (!isNaN(i_w) && !isNaN(i_h))
					i_values.put('metadata_dimension', ''+i_w+'x'+i_h);
				
				//duration google play
				//var i_duration = f_getMetaByItemPropName(i_doc, 'duration');
				var v = f_parseDuration(f_getMetaByItemPropName(i_doc, 'duration'));
				if (v)
					i_values.put('metadata_length', v);
				else
					pElement.t(i_doc, 'span').forEach(function(x) {
						if (x.className.indexOf('duration')>=0) {
							var c = x.innerHTML, v;
							if (v = f_parseDuration(c))
								i_values.put('metadata_length', v);
						}
					});
				
				/*if (r_duration_xsd.test(i_duration)) {
					var e = r_duration_xsd.exec(i_duration);
					if (e) {
						var i_min = e[1];
						var i_sec = e[2];
	
						if (pString.v(i_min) && pString.v(i_sec)) {
							i_min = parseInt(i_min.trim());
							var i_h = Math.trunc(i_min / 60); i_min = i_min % 60;
							i_sec = parseInt(i_sec.trim());
							i_values.put('metadata_length', ((i_h<10)? '0':'')+i_h+':'+((i_min<10)? '0':'')+i_min+':'+((i_sec<10)? '0':'')+i_sec);
						}
					}
				}*/
				
				// duration and year talktalk
				pElement.t(i_doc, 'li').forEach(function(i_meta) {
					if (i_meta.className.indexOf('c-assetHeader__item--billboard')>0) {
						var c = i_meta.innerHTML, v;
						if (v = f_parseDuration(c))
							i_values.put('metadata_length', v);
						else if (v = f_parseYear(c))
							i_values.put('metadata_year', v);
					}
				});
				
				// duration amazon
				if (null==i_values.getValue('metadata_length')) {
					pElement.t(i_doc, 'dd').find(function(x) {
						var c = x.innerHTML.trim(), v;
						if (v = f_parseDuration(c)) {
							i_values.put('metadata_length', v);
							return true;
						}
					});
				}
				
				var spans = null;
				
				if (null==i_values.getValue('metadata_length')) {
					(spans || (spans = pElement.t(i_doc, 'span'))).find(function(x) {
						var c = x.innerHTML.trim();
						if (v = f_parseDuration(c)) {
							i_values.put('metadata_length', v);
							return true;
						}
					});
				}
				
				// year amazon: <span class="release-year">2014</span>
				if (null==i_values.getValue('metadata_year'))
					(spans || (spans = pElement.t(i_doc, 'span'))).find(function(x) {
						if (x.className.indexOf('release-year')>=0 || (x.getAttribute('data-automation-id') || '').indexOf("release-year")>=0) {
							var v = f_parseYear(x.innerHTML);
							if (v) {
								i_values.put('metadata_year', v);
								return true;
							}
						}
					});
				
				// length rakuten.tv <div...><span class="icon__duration"
				if (null==i_values.getValue('metadata_length'))
					(spans || (spans = pElement.t(i_doc, 'span'))).find(function(x) {
						if (x.className=='icon__duration') {
							var c = x.parentElement;
							c.removeChild(x);
							c = c.innerHTML.replace(re_xml_comment, '');
							if (c = f_parseDuration(c)) {
								i_values.put('metadata_length', c);
								return true;
							}
						}
					});
				
				// year rakuten.tv
				if (null==i_values.getValue('metadata_year'))
					(spans || (spans = pElement.t(i_doc, 'span'))).find(function(x) {
						if (x.className=='icon__calendar') {
							var c = x.parentElement;
							c.removeChild(x);
							c = c.innerHTML.replace(re_xml_comment, '');
							if (c = f_parseYear(c)) {
								i_values.put('metadata_year', c);
								return true;
							}
						}
					});
				
				// year flixster
				if (null==i_values.getValue('metadata_year')) {
					var c = f_parseYear(f_getMetaByItemPropName(i_doc, 'datePublished'));
					if (c)
						i_values.put('metadata_year', c);
				}
				
				// year netflix
				if (null==i_values.getValue('metadata_year'))
					(spans || (spans = pElement.t(i_doc, 'span'))).find(function(x) {
						if (x.className=='year') {
							c = x.innerHTML.replace(re_xml_comment, '');
							if (c = f_parseYear(c)) {
								i_values.put('metadata_year', c);
								return true;
							}
						}
					});
				
				resolve(i_values);
			});
		});
	};
});

//console.log('TEST: pPageParser.parse(\'https://www.amazon.co.uk/gp/video/detail/B01MQR4TJT/ref=dv_web_yvl_list_pr_2\') = ' + pPageParser.parse('https://www.amazon.co.uk/gp/video/detail/B01MQR4TJT/ref=dv_web_yvl_list_pr_2'));

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/