API Docs for:
Show:

File: server.js

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

// depends: library-min.js 

const pServer = new (function() {
	var that = this, m_id = ''+Math.random();
	
	//*** INIT
	objects.put(m_id, this);
	
	function f_login() {
		pDocument.wait();
		pConsole.info(that, "Contacting server...");

		var i_request = new XMLHttpRequest();
		i_request.timeout = 2000;
		i_request.ontimeout = function() {
			pDocument.wait();
			pConsole.info(that, "Timeout on contacting server...");
			setTimeout(f_login, 1000);
		};
		i_request.onerror = function() {
			pDocument.wait();
			pConsole.info(that, "Error on contacting server...");
			setTimeout(f_login, 1000);
		};
		i_request.onreadystatechange = function(p_event) {
			var i_xml_http_request = p_event.target;
			if (i_xml_http_request.readyState === 4) {
				if (pROSE.handleHTTPResponse(i_xml_http_request) === false)
					return false;
			}
		};
		i_request.open("GET", "/server", true);
		i_request.send();
	}

	this.reboot = function() {
		pDialog.question({ id: 'reboot-server' }).then(function() {
			pDocument.wait();
			pHTTPRequest.get(pURL.addQueryParameter('/server', [ 'action', 'reboot', 'v', Math.random() ]), function(i_request) {
				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;
				
				//this is login URL...
				setTimeout(f_login, 30000);
				/*setTimeout(function() {
					windows.location.reload(true);
					var i_request = new XMLHttpRequest();
					i_request.timeout = 10000;
					i_request.ontimeout(function() {
					}
				}, 10000);*///pLocation.assign(this.url); }, 60000);
				
			}, false);
		});
	};

	this.shutdown = function() {
		pDialog.question({ id: 'shutdown-server' }).then(function() {
			pDocument.wait();
			pHTTPRequest.get(pURL.addQueryParameter('/server', [ 'action', 'shutdown', 'v', Math.random() ]), function(i_request) {
				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				//this is login URL...
				var i_url = "/login?url="+encodeURI(pLocation.getPathAndQuery());
				setTimeout(function() { pLocation.assign(i_url); }, 10000);

			}, false);
		});
	};

	this.clearCache = function() {
		return new Promise(function(resolve, reject) {
			pTransaction.transaction({
				trace: that, url: pURL.addQueryParameter('/server', [ 'action', 'clear-cache', 'v', Math.random() ]), method_name: "ClearCache", responseType: 'text',
				msg_start: 'Clearing Server Cache...',
				msg_error: [ 'Clearing Server Cache... Failure', reject ],
				msg_end: resolve || c_do_nothing
			});
		});
	};

	this.reclaimMemory = function() {
		return new Promise(function(resolve, reject) {
			pTransaction.transaction({
				trace: that, url: pURL.addQueryParameter('/server', [ 'action', 'reclaim-memory', 'v', Math.random() ]), method_name: "ReclaimMemory", responseType: 'text',
				msg_start: 'Reclaiming JVM Memory...',
				msg_error: [ 'Reclaiming JVM Memory... Failure', reject ],
				msg_end: resolve || c_do_nothing
			});
		});
	};
	
	this.createPortNumberDialogOption = function(id, text) {
		return { id: id, className: 'dialog-question-input-inline', input: { type: 'number', min: 1, max: 65535 }, text: text };
	};

	this.startAuth = function(p_id, p_params) {
		pTransaction.transaction({
			trace: this, url: pURL.addQueryParameter(pURL.addQueryParameter('/server', [ 'action', 'start-auth', 'id', p_id, 'v', Math.random() ]), p_params), method_name: "Authenticate",
			msg_start: 'Authenticating...',
			msg_error: 'Authenticating... Failure',
			msg_end: function(p_request) {
				
				//handle errors
				if (pROSE.handleHTTPResponse(p_request) === false)
					return false;

				var i_obj = pJSON.parse(p_request.responseText);
				if (i_obj.url)
					pLocation.assign(i_obj.url);
			}
		});
	};
		
	this.getProperties = function(p_request_pp) {

		if (!pApplicationUI.canShowDialog())
			return;

		pDocument.wait();

		if (!p_request_pp) p_request_pp = {};
		p_request_pp.library = this;

		var i_url = /*'http://' + pLocation.getHostname() + ':' + server_nossl_port +*/ pURL.addQueryParameter('/server', [ '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) {
			var p_request_pp = this;

			//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_api_key_input = { type: 'text', placeholder: 'Enter your own, if empty, the server will use a default value...' };
			var i_warn = { id: 'desc-warn', sep: true, className: 'warning', text: 'This tab settings will only apply after you restart the server.<p>' };
			var i_style = 'width: 90px; min-width: 90px !important; max-width: 90px !important;';
			var f_update = function() {
				pDialog.resources.f_enableIfEnabled('server-properties', 'http_ssl_enabled', [ 'http_ssl_port_enabled', 'http_ssl_port', 'http_ssl_protocol', 'http_ssl_jks_file', 'http_ssl_jks_type', 'http_ssl_jks_provider', 'http_ssl_jks_password', 'http_ssl_key_password' ]);
						
				pDialog.resources.f_disableIfEnabled('server-properties', 'java_net_useSystemProxies', [ 'http_proxyHost', 'http_proxyPort', 'https_proxyHost', 'https_proxyPort', 'http_nonProxyHosts' ]);
				pDialog.resources.f_enableIfEnabled('server-properties', 'jmdns_enabled', [ 'name' ]);
				pDialog.resources.f_enableIfEnabled('server-properties', 'http_transfer_encoding_gzip_enabled', [ 'http_transfer_encoding_gzip_minsize', 'http_transfer_encoding_gzip_mime_types' ]);
				pDialog.resources.f_disableIfEnabled('server-properties', 'transcoder_ffmpeg_exe_plugin', [ 'transcoder_ffmpeg_exe_file' ]);
			};
			var i_dialog = {
				id: 'server-properties',
				title: p_request_pp.title,
				options: [
					'ok',
					'cancel',

					{ id: 'tab-http', tab: true, text: 'HTTP' },
					i_warn,
					'The hostname that clients must use to connect.<ul><li>:: means they can use any IP4 or IP6 hostname or address.<li>127.0.0.1 or 0.0.0.0 means they can use any IP4 hostname or address.',
					{ id: "http_transport_default_host", className: 'dialog-question-input-inline2', input: { type: 'text', placeholder: 'Hostname/IP Address to listen to...' }, text: 'Hostname:' },
					pServer.createPortNumberDialogOption("http_transport_default_port", 'Port Number:'),
/*"http_auth_default_enabled",false,
"http_auth_default_realm","Media Library HTTP Server (Authentication Required)",
"http_auth_default_login","admin",
"http_auth_default_password","admin",*/
					'<hr>',
					'<div style="margin-bottom: 8px;">SSL</div>',
					{ id: "http_ssl_enabled", input: { type: 'checkbox', onchange: f_update }, text_after: 'Enable SSL', text_after_class: 'library-properties-checkbox' },
					{ id: "http_ssl_port_enabled", input: { type: 'checkbox' }, text_after: 'Enable SSL on a separate port (below)', text_after_class: 'library-properties-checkbox' },
					//{ id: "http_ssl_port", className: 'dialog-question-input-inline', input: { type: 'number', min: 1, max: 65535 }, text: 'SSL Port Number:' },
					pServer.createPortNumberDialogOption("http_ssl_port", 'SSL Port Number:'),
					{ id: "http_ssl_protocol", className: 'dialog-question-input-inline', input: { type: 'select', options: [ 'TLSv1.2', 'TLSv1.1', 'TLSv1', 'TLS', 'SSLv3', 'SSLv2', 'SSL' ] }, text: 'Protocol:' },

					'<hr>',
					'<div style="margin-bottom: 8px;">SSL Keystore</div>',
					 'The file containing the private key and public certificate the server will present to clients.<br>If empty, the server will generate a random one (with a self-signed certificate) when starting.<p>',
					{ id: "http_ssl_jks_file", input: { type: 'server-file' }, text: 'File:' },
					{ id: "http_ssl_jks_type", className: 'dialog-question-input-inline', input: { type: 'select', options: [ 'JCEKS', 'JKS', 'DKS', 'PKCS11', 'PKCS12' ]}, text: ' File Type/Format:' },
					{ id: "http_ssl_jks_provider", className: 'dialog-question-input-inline', input: { type: 'select', options: i_obj.security_providers.map(function(i_pr) { return { text: i_pr.name, title: (i_pr.info)? i_pr.info + ((i_pr.version)? ', Version '+i_pr.version:'') : null }; }) }, text: 'Security Provider:' },
					{ id: "http_ssl_jks_password", input: { type: 'password' }, text: 'Keystore Password:' },
					'<hr>',
					//{ id: "http_ssl_key_name", input: { }, text: 'Key/Alias Name:' },
					{ id: "http_ssl_key_password", input: { type: 'password' }, text: 'Key/Alias Password:' },

					{ id: 'tab-http-advanced', tab: true, text: 'HTTP Advanced' },
					i_warn,
					'<div style="margin-bottom: 8px;">Threads</div>',
					{ id: 'http_thread_pool_minsize', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Minimum pool size:', label: { style: 'width: 240px;' } },
					{ id: 'http_thread_pool_maxsize', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Maximum pool size:', label: { style: 'width: 240px;' } },
					{ id: 'http_thread_pool_timeout', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Actitity Timeout (ms):', label: { style: 'width: 240px;' } },
					{ id: 'http_thread_pool_prestart_one', xclassName: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Start one thread upon startup', text_after_class: 'library-properties-checkbox' },
					{ id: 'http_thread_pool_prestart_all', xclassName: 'dialog-question-input-inline', input: { type: 'checkbox' }, text_after: 'Start all threads upon startup', text_after_class: 'library-properties-checkbox' },

					'<hr>',
					'<div style="margin-bottom: 8px;">Timeouts</div>',
					{ id: 'http_connection_read_timeout_ms', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 655350, xstyle: i_style }, text: 'Read Timeout (ms):', label: { style: 'width: 240px;' } },
					{ id: 'http_connection_read_request_timeout_ms', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Read Request Timeout (ms):', label: { style: 'width: 240px;' } },

					'<hr>',
					'<div style="margin-bottom: 8px;">Socket Options</div>',
					{ id: 'http_connection_so_rvcbuf_size', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 655350, xstyle: i_style }, text: 'Receiver Buffer Size (bytes):', label: { style: 'width: 240px;' } },
					{ id: 'http_connection_so_sndbuf_size', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 655350, xstyle: i_style }, text: 'Send Buffer Size (bytes):', label: { style: 'width: 240px;' } },
					pDialog.newCheckbox('http_connection_so_linger_enabled', 'Linger Timeout'),
					{ id: 'http_connection_so_linger_ms', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Linger Timeout (ms):', label: { style: 'width: 240px;' } },

					pDialog.newCheckbox('http_connection_so_tcp_nodelay', 'TCP No Delay'),
					pDialog.newCheckbox('http_connection_so_reuse_address', 'Reuse Address'),
					{ id: 'http_connection_so_timeout_ms', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Socket Timeout (ms):', label: { style: 'width: 240px;' } },

					'<hr>',
					'<div style="margin-bottom: 8px;">Server Socket Options</div>',
					//{ id: 'http_transport_default_so_rvcbuf_size', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 655350, xstyle: i_style }, text: 'Receiver Buffer Size (bytes):', label: { style: 'width: 240px;' } },
					{ id: 'http_transport_default_so_reuse_address', input: { type: 'checkbox' }, text_after: 'Reuse Address', text_after_class: 'library-properties-checkbox' },
					//{ id: 'http_transport_default_so_timeout_ms', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 655350, xstyle: i_style }, text: 'Socket Timeout (ms):', label: { style: 'width: 240px;' } },
					{ id: 'http_transport_default_so_backlog', className: 'dialog-question-input-inline', input: { type: 'number', min: -1, max: 65535, xstyle: i_style }, text: 'Backlog Queue Size:', label: { style: 'width: 240px;' } },

					{ id: 'tab-http-content', tab: true, text: 'HTTP Content' },
					i_warn,
					pDialog.newCheckbox('http_transfer_encoding_gzip_enabled', 'Compress Replies (with gzip)', f_update),
					{ id: 'http_transfer_encoding_gzip_minsize', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Minimum size before compression happens (bytes):', label: { style: 'width: 380px;'} },
					{ id: 'http_transfer_encoding_gzip_mime_types', input: { }, text: 'Compress for those MIME types:' },
					'<hr>',
					{ id: 'http_transfer_encoding_br_enabled', input: { type: 'checkbox' }, text_after: 'Compress Replies (with brotli): This feature is unstable and may crash the server...', text_after_class: 'library-properties-checkbox' },
					{ id: 'http_transfer_encoding_br_minsize', className: 'dialog-question-input-inline', input: { type: 'number', min: 0, max: 65535, xstyle: i_style }, text: 'Minimum size before compression happens (bytes):', label: { style: 'width: 380px;'} },
					{ id: 'http_transfer_encoding_br_mime_types', input: { }, text: 'Compress for those MIME types:' },

					{ id: 'tab-transcoder', tab: true, text: '<img width="16" heigh="16" src="/resources/html/images/16x16/microformats.png" style="padding-right: 10px;"/>Transcoding' },
					'Transcoding transforms videos on the fly to match your device and network capabilities in term of format and performance.<p>Transcoded videos remain cached on disk.'+
					'<p>You can see which files have been or are currently being transcoded by selecting <a href="/server?show=transcodings" target="_blank">Transcoding</a> in the Server Settings page...',
					//{ id: "transcoder_object_(*)_enabled", input: { type: 'checkbox' }, text_after: 'Enable transcoding', text_after_class: 'library-properties-checkbox' },

					'<hr>',
					'<div style="margin-bottom: 8px;">FFmpeg (the software that transcodes)</div>',
					pDialog.newCheckbox("transcoder_ffmpeg_exe_plugin", 'Use default executable', f_update),
					{ id: "transcoder_ffmpeg_exe_file", input: { type: 'server-file' }, text: 'Use external program:' },
					'<hr>',
					'<div style="margin-bottom: 8px;">Disk Cache</div>',
					{ id: 'transcoder_cache_(*)_size_max_bytes', className: 'dialog-question-input-inline', input: { type: 'number', min: 0 }, text: 'Cache size (bytes):' },
					pDialog.newCheckbox('transcoder_cache_(*)_purge_failed_at_startup', 'Delete failed transcodings at startup'),

					//{ id: 'tab-metadata', tab: true, text: 'Find Information' },
					//{ id: 'tab-imdb', className: 'toolbar-imdb', sep: true, text: 'IMDB', style: 'margin-bottom: 8px;' },
					//'Those settings configure the server plug-in for IMDB.<p>This plug-in allows retrieving information from the web site <a href="https://www.imdb.com" target="_blank">IMDB.com</a>.<p>',
					//{ id: "pIMDBApi_api_(*)_url_base", input: i_api_key_input, text: 'Base URL' },
					//'<hr>',
					/*
					{ id: 'tab-tmdb', className: 'toolbar-themoviedb', sep: true, text: 'The Movie Database', style: 'margin-bottom: 8px;' },
					'Those settings configure the server plug-in for The Movie Database.<p>This plug-in allows retrieving information from the web site <a href="https://www.themoviedb.org" target="_blank">The Movie Database</a>.',
					{ id: "pAPIClient_TMDB_api_(*)_api_key", input: i_api_key_input, text: 'API Key:' },
					{ id: "pAPIClient_TMDB_api_(*)_url_base", input: i_api_key_input, text: 'Base URL:' },
					'<hr>',

					{ id: 'tab-ttvdb', className: 'toolbar-thetvdb', sep: true, text: 'TheTVDB.com', style: 'margin-bottom: 8px;' },
					'Those settings configure the server plug-in for thetvdb.com.<p>This plug-in allows retrieving TV information from the web site <a href="http://www.thetvdb.com" target="_blank">TheTVDB.com</a>.',
					{ id: "pAPIClient_TTVDB_api_(*)_api_key", input: i_api_key_input, text: 'API Key:' },
					{ id: "pAPIClient_TTVDB_api_(*)_url_base", input: i_api_key_input, text: 'Base URL:' },
					'<hr>',

					{ id: 'tab-wiki', className: 'toolbar-wikipedia', sep: true, text: 'Wikipedia', style: 'margin-bottom: 8px;' },
    				'Those settings configure the server plug-in for Wikipedia.<p>This plug-in allows retrieving information from the web site <a href="https://www.wikipedia.org" target="_blank">Wikipedia</a>.',
					{ id: "pAPIClient_WikipediaMedia_api_(*)_url_base", input: i_api_key_input, text: 'Base URL:' },
					'<hr>',
					
					{ id: 'tab-discogs', className: 'toolbar-discogs', sep: true, text: 'Discogs', style: 'margin-bottom: 8px;' },
    				'Those settings configure the server plug-in for Discogs.<p>This plug-in allows retrieving music information from the web site <a href="https://www.discogs.com" target="_blank">Discogs.com</a>.',
    				{ id: "pDiscogsApi_api_(*)_api_key", input: i_api_key_input, text: 'API Key:' },
	    			{ id: "pDiscogsApi_api_(*)_api_secret", input: i_api_key_input, text: 'API Secret:' },
    				{ id: "pDiscogsApi_api_(*)_url_base", input: i_api_key_input, text: 'Base URL:' },
    				'<hr>',

					{ id: 'tab-lastfm', className: 'toolbar-lastfm', sep: true, text: 'last.fm', style: 'margin-bottom: 8px;' },
    				'Those settings configure the server plug-in for last.fm.<p>This plug-in allows retrieving music information from the web site <a href="https://www.last.fm.com" target="_blank">last.fm</a>.',
    				{ id: "pAPIClient_LastFM_api_(*)_api_key", input: i_api_key_input, text: 'API Key:' },
    				{ id: "pAPIClient_LastFM_api_(*)_url_base", input: i_api_key_input, text: 'Base URL:' },*/
    				//'Authenticate: <a href="javascript:void(pServer.startAuth(\''+i_obj.auths[0]+'\'))">here</a>',
					
    				{ id: 'tab-tracing', tab: true, text: 'Log Files' },
    				'Configure what information appear in the log file:',
    				pDialog.newCheckbox('trace_error', 'Trace Errors', null, i_obj.trace_error),
    				pDialog.newCheckbox('trace_warn', 'Trace Warnings', null, i_obj.trace_warn),
    				pDialog.newCheckbox('trace_info', 'Trace Information', null, i_obj.trace_info),
    				pDialog.newCheckbox('trace_debug', 'Trace Debugging', null, i_obj.trace_debug),

    				{ id: 'tab-security', tab: true, text: 'Security', title: 'This is about security' },
    				//i_warn,
    				'You can limit all admin tasks (importing medias, changing them, managing the server, etc...) to web browsers and devices running on the server and using the address localhost...<p>This will prevent changes coming from any other machine.',
    				pDialog.newCheckbox('admin_loopback_only', 'Limit administration actions to localhost/loopback only'),

    				'<hr>',
    				{ id: 'desc-warn', sep: true, className: 'warning', text: 'These settings below will only apply after you restart the server.<p>' },
    				
    				'<hr>You can limit connections from other machines by specifying a comma-separated list of IP addresses or hostnames below.<p>The blacklist contains addresses for which connection will be refused.<p>* in the blacklist mean no other machine can connect.<p>The whitelist contains addresses for which connection will be accepted, even if they match the addresses in the blacklist.<p>',
    				{ id: "http_hostname_blacklist", input: { type: 'textarea' }, text: 'Address Blacklist:' },
    				{ id: "http_hostname_whitelist", input: { type: 'textarea' }, text: 'Address Whitelist:' },
    				
    				{ id: 'tab-proxy', tab: true, text: 'HTTP Proxy' },
    				'The server will use the proxy below for browsing the internet (if the hostname is not empty or "Use the Operating System Proxy" is selected):<p>',
    				{ id: 'http_proxyHost', className: 'dialog-question-input-inline2', input: {}, text: 'HTTP Hostname' },
    				pServer.createPortNumberDialogOption("http_proxyPort", 'HTTP Port:'),
    				//{ id: 'http_proxyPort', input: { type: 'number', min: 1, max: 65535 }, text: 'HTTP Port' },
    				'',
    				{ id: 'https_proxyHost', className: 'dialog-question-input-inline2', input: {}, text: 'HTTPS Hostname' },
    				pServer.createPortNumberDialogOption("https_proxyPort", 'HTTPS Port:'),
    				//{ id: 'https_proxyPort', input: { type: 'number', min: 1, max: 65535 }, text: 'HTTPS Port' },
    				'<hr>You can specify a comma-separated list of hostnames (with wildcard characters) that the server will browse without the proxy:<p>',
    				{ id: 'http_nonProxyHosts', input: {}, text: 'No Proxy' },
    				'<hr>',
    				pDialog.newCheckbox('java_net_useSystemProxies', 'Use the Operating System Proxy', f_update),

    				{ id: 'tab-advanced', tab: true, text: 'Advanced' },
    				'<div style="margin-bottom: 8px;">Apple Bonjour Network</div>',
    				pDialog.newCheckbox("jmdns_enabled", 'Show the server on the network', f_update),
    				{ id: "name", input: { type: 'text' }, text: 'Server friendly name' }
    				
				].filter(pObject.isNotNull),
				submitChangedInputsOnly: true,
				fcall: function(p_dialog) {
					var i_data = this; //pMediaLibray.getMetadata

					var i_url = pURL.addQueryParameter('/server', [ 'action', 'set-properties', 'v', Math.random() ]);
					pConsole.info(this, "SetProperties: " + i_url);
					pHTTPRequest.post({	url: i_url, headers: pHTTPRequest.headers_post, body: pURL.toFormSubmitBody(p_dialog.inputs, pMetadataDialog.transformToServer) });
				}.bind(p_request_pp)
			};
			
			if (window.server_ui_show_dlna_settings == 'true')
				pArray.append(i_dialog.options, [
                   '<hr>',
				   '<div style="margin-bottom: 8px;">DLNA/uPNP</div>',
				   { id: "dlna_enabled", input: { type: 'checkbox', onchange: f_update }, text_after: 'Enable the DLNA/uPNP Server', text_after_class: 'library-properties-checkbox' },
				   
				   'The hostname that clients must use to connect.<ul><li>:: means they can use any IP4 or IP6 hostname or address.<li>127.0.0.1 or 0.0.0.0 means they can use any IP4 hostname or address.',
					{ id: "dlna_hostname", className: 'dialog-question-input-inline2', input: { type: 'text', placeholder: 'Hostname/IP Address to listen to...' }, text: 'Hostname:' },
				   
               	]);

			//*** UPDATE VALUES
			i_dialog.options.forEach(function(op) {
				if (op.id) {
					if (op.id.indexOf('file-')==0) return;
					if (op.id.indexOf('library-')==0) return;
					//if (op.id.endsWith('_api_key')) return;
					//if (op.id.endsWith('_api_secret')) return;
					if (op.id.indexOf('trace_')==0) return;
	
					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;
			f_update();
			
		}.bind(p_request_pp), false);
	};

	this.getParentalControls = function(p_name) {

		pDocument.wait();

		var i_data = {
			library: this,
			user_id: p_name || pResources.get('managed-username')
		};
		var i_url = pURL.addQueryParameter('/server', [ 'action', 'get-user', 'id', i_data.user_id, 'format', 'json', 'contents', 'values', 'v', Math.random() ]);

		pConsole.info(this, "GetUser: " + 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);
			//, "GetUser: response: " + pJSON.pretty(i_request.responseText));

			var i_values = new pMap(i_obj.values);
			var i_score = parseInt(i_values.getValue('pc.medias.rating.score.max', 0));
			var i_dialog = {
				id: 'user-pc',
				radio: true,
				options: [
					'ok', 'cancel',

					{ id: 'tab-main', tab: true, text: 'Main' },
					{ id: "pc.medias.unrated.videos.hide", input: { type: 'checkbox' }, text_after: 'Hide unrated medias', text_after_class: 'library-properties-checkbox' },
					{ id: "pc.medias.adult.hide", input: { type: 'checkbox' }, text_after: 'Hide adult medias', text_after_class: 'library-properties-checkbox' },
					{ id: "pc.medias.explicit.hide", input: { type: 'checkbox' }, text_after: 'Hide explicit medias', text_after_class: 'library-properties-checkbox' },

					{ id: 'tab-cert', tab: true, text: 'Certificates' },
					{ id: "pc.medias.rating.score.max.hide", input: { type: 'checkbox' }, text_after: 'Hide medias with certification up to:', text_after_class: 'library-properties-checkbox' },
				],
				submitChangedInputsOnly: true,
				fcall: function(p_dialog) {
					var i_data = this;
					pTransaction.simpleTransaction({
						trace: this, url: pURL.addQueryParameter('/user-update', [ 'user', i_data.user_id, 'v', Math.random() ]), method_name: "UpdateUser",
						msg_start: pPageData.resolve('Updating User {resources.managed-fullname}...'),
						headers: pHTTPRequest.headers_post, 
						body: pURL.toFormSubmitBody(p_dialog.inputs)
					});
				}.bind(i_data)
			};

			var i_option_prev = null;
			var i_score_done = false;
			i_obj.content_ratings.forEach(function(i_rating) {
				if (i_rating.country && i_rating.country.code == i_obj.country && i_rating.type == 'movie') {
					i_dialog.options[i_dialog.options.length] = pMediaLibrary.createContentRatingOption(i_rating, true);
					if (!i_score_done && i_rating.score > i_score && i_option_prev) {
						i_option_prev.selected = true;
						i_score_done = true;
					}
					i_option_prev = i_dialog.options[i_dialog.options.length-1];
				}
			});

			//*** UPDATE VALUES
			i_dialog.options.forEach(function(op) {
				if (op && op.id)
					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.setAccess = function(p_doc_url) {

		pDocument.wait();

		var i_data = {
			library: this,
			doc_url: p_doc_url || window.location.pathname
		};
		pHTTPRequest.get(pURL.addQueryParameter(i_data.doc_url, [ 'action', 'get-access', 'format', 'json', 'v', Math.random() ]), function(i_request, i_data) {

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

			//try {
				//console.log(i_request.responseText);
				var i_obj = pJSON.parse(i_request.responseText);

				i_data.obj = i_obj;
				i_data.allow = i_obj.allow;
				i_data.deny = i_obj.deny;
				i_data.user_map = new pMap();
				i_data.user_fullname_map = new pMap();
				for(var i=0 ; i<i_obj.users.length ; i++) {
					i_data.user_map.put(i_obj.users[i].name, i_obj.users[i]);
					i_data.user_fullname_map.put(i_obj.users[i].fullname, i_obj.users[i]);
				}
					//alert("reply :" + pJSON.pretty(i_request.responseText));
					//var i_data = pHTTPRequest.remove(i_requbannerest);

					var i_dialog = {
						id: 'set-access',
						title: "Set Access...",
						options: [
							{ id: 'tab-allow', tab: true, text: 'Allow Access...' },
							{ id: "allow-text", sep: true, text: 'Users in this list will have access to this page even if:<ul><li>they are in the denied list below<li>or parental control settings would prevent them from accessing it...</ul>' },
						    { id: "allow", input: { oninput: function() {
								var i_dialog = pDialog.getValue('set-access');
								if (!i_dialog)
									return;

								i_data.allow = pMediaLibrary.parseUserList(pDialog.getInputValue('set-access', 'allow').split(','), i_data.user_fullname_map).split(',');
								pDialog.setInputValue('set-access', 'allow', pMediaLibrary.formatUserList(i_data.allow, i_data.user_map));

							}, icons: [
							{
								className: 'dialog-question-button-adduser',
								title: 'Click here to add a user...',
								onclick: function(oid, iid){
									pMediaLibrary.selectUser(i_data.obj_users).then(function(u) {
										if (!i_data.allow.includes(u))
											i_data.allow.push(u);
										pElement.x(iid).value = pMediaLibrary.formatUserList(i_data.allow, i_data.user_map);
									});
								}
							}
							]}, text: 'Users:', value: pMediaLibrary.formatUserList(i_data.allow, i_data.user_map) },

							{ id: 'tab-deny', tab: true, text: 'Deny Access...', selected: true },
							{ id: "deny-text", sep: true, text: 'Users in this list will not have access to this page even if:<ul><li>parental control settings would allow them accessing it...</ul>' },
							{ id: "deny", input: { oninput: function() {
								var i_dialog = pDialog.getValue('set-access');
								if (!i_dialog)
									return;

								i_data.deny = pMediaLibrary.parseUserList(pDialog.getInputValue('set-access', 'deny').split(','), i_data.user_fullname_map).split(',');
								pDialog.setInputValue('set-access', 'deny', pMediaLibrary.formatUserList(i_data.deny, i_data.user_map));

							}, icons: [
							{
								className: 'dialog-question-button-adduser',
								title: 'Click here to add a user...',
								onclick: function(oid, iid){
									pMediaLibrary.selectUser(i_data.obj.users).then(function(u) {
										if (!i_data.deny.includes(u))
											i_data.deny.push(u);
										pElement.x(iid).value = pMediaLibrary.formatUserList(i_data.deny, i_data.user_map);
									});
								}
							}
							]}, text: 'Users:', value: pMediaLibrary.formatUserList(i_data.deny, i_data.user_map) },

							'ok', 'cancel'
						],
						multiple: true,
						fcall: function(p_dialog, p_value) {
							//alert('here: ' + pString.concat(p_value, ','));
							//for a multiple choice dialog, p_value is an array of the selected values...
							var i_data = this;
							//alert('deny: '+ pString.concat(i_data.deny, ','));

							var i_url = pURL.addQueryParameter(i_data.doc_url, [ 'action', 'set-access', 'v', Math.random() ]);

							pConsole.info(this, "SetAccess: " + i_url);
							pHTTPRequest.invoke({
								method: 'POST',
								url: i_url,
								headers: pHTTPRequest.headers_post,
								body: 'allow='+pURL.fixedEncodeURIComponent(pString.concat(i_data.allow, ','))+'&deny='+pURL.fixedEncodeURIComponent(pString.concat(i_data.deny, ',')) 
							});

							//i_data.library.setMetadata(i_data.doc_url, 'metadata_tags', pString.concat(p_value, '|'));
						}.bind(i_data)
					};

					pDocument.stopwait();
					pDialog.dialogQuestion(i_dialog);
				//}
			}, false, i_data);
	};

	this.setLangs = function(p_doc_url) {

		pDocument.wait();

		var i_data = {};
		pHTTPRequest.get(pURL.addQueryParameter('/server', [ 'action', 'get-properties', 'format', 'json', 'contents', 'values', 'v', Math.random() ]), function(i_request, i_data) {

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

			//try {
				//console.log(i_request.responseText);
				var i_obj = pJSON.parse(i_request.responseText), i_values = new pMap(i_obj.values);

				i_data.obj = i_obj;
				i_data.langs = pString.split(i_values.getValue("langs_preferred", "en"), '|').map(function(i_lang) { return pLang.getLongName(i_lang).toLowerCase() });

				var i_dialog = {
					id: 'server-langs',
					options: [
						{ id: "langs-text", sep: true, text: 'The preferred languages are used when:<ul><li>retrieving media information from the internet...<li>when playing a movie with multiple language audio or subtitle tracks...</ul>' },
						{ id: "langs", input: { oninput: function() {
							var i_dialog = pDialog.getValue('server-langs');
							if (!i_dialog)
								return;

							i_data.langs = pString.split(pServer.parseLangList(pString.split(pDialog.getInputValue('server-langs', 'langs'), ',')), ',');
							pDialog.setInputValue('server-langs', 'langs', pServer.formatLangList(i_data.langs));

						}, icons: [
							{
								className: 'dialog-question-button-addlang',
								title: 'Click here to add a language...',
								onclick: function(oid, iid){
									var i_data = this;
									pServer.selectLang(i_data.obj.langs, i_data.langs).then(function(l) {
										l = l.toLowerCase();
										if (!i_data.langs.includes(l))
											i_data.langs.push(l);
										pElement.x(iid).value = pServer.formatLangList(i_data.langs);
									});
								}.bind(i_data)
							}
						], datalist: (pApplicationUI.OPTION_DATALISTS)? pLang.getLangs().map(pString.capitalize).sort() : null 
						}, text: 'Preferred Languages:', value: pServer.formatLangList(i_data.langs) },

						'ok', 'cancel'
					],
					fcall: function(p_dialog, p_value) {
						pTransaction.simpleTransaction({
							trace: this, url: pURL.addQueryParameter('/server', [ 'action', 'set-properties', 'v', Math.random() ]), methodName: 'SetPreferredLangs',
							msg_start: 'Setting preferred languages...',
							headers: pHTTPRequest.headers_post,
							body: 'langs_preferred='+pURL.fixedEncodeURIComponent(pString.concat(this.langs, '|', false, pLang.getISOName))
						});
						
					}.bind(i_data)
				};

				pDocument.stopwait();
				pDialog.dialogQuestion(i_dialog);
			//}
		}, false, i_data);
	};

	this.parseLangList = function(p_obj_users) {
		return pString.concat(p_obj_users, ", ", false);
	};
	this.formatLangList = function(p_obj_users) {
		return pString.concat(p_obj_users, ", ", false, pLang.display);//pString.capitalize);
	};
	this.selectLang = function(langs, excludes) {
		return new Promise(function(resolve, reject) {
			var d = {
				id: 'select-lang',
				options: ['cancel'],
			};
			
			langs.sort().forEach(function(l) {
				if (pString.v(l) && !(excludes || []).includes(l.toLowerCase())) {
					var iso = pLang.getISOName(l);//, nat = iso? pLang.nativex.getValue(iso) : null;
					//if (iso)
					//	nat = pLang.nativex.getValue(iso);
					d.options.push({
						id: l,
						value: l,
						text: pLang.display(l),//(nat && nat!=l.toLowerCase())? pString.capitalize(nat) + ' ('+l+')' : l,
						className: 'dialog-lang',
						fcall: function(d, v) { 
							resolve(v);
						}
					});
				}
			});
			
			pDocument.stopwait();
			pDialog.dialogQuestion(d);
		});
	};
	
	this.openLibrary = function() {
		pOpenFileDialog.selectFolder({
			title: 'Open Library... Select the folder containing the library...',
			callback: function(p) {
				var u = pURL.addQueryParameter('/server', [ 'action', 'open-library', 'path', p, 'v', Math.random() ]);

				pConsole.info(that, "OpenLibrary: " + u);
				pHTTPRequest.get(u, null, false);
				
				if (pLibraryList) pLibraryList.update();
			},
			getOkText: function(p_dialog, p_file) {
				//if (p_file.files)
					if ((p_file.files || []).some(function(f) { return f.name == "4178918784091587936" }))
						return "Open this Library";
					/*for(var i=0 ; i<p_file.files.length ; i++) {
						if (p_file.files[i].name == "4178918784091587936")
							return "Open this Library";
					}*/
				//return null;
			},
			recents: pLibraryList.recents
		});
	};
});

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

const pServerStatus = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('ServerStatus', 1000) : {};
pServerStatus.licenseExpirationText = function(i_obj) {
	if (i_obj.expires) {
		if (i_obj.expire_days) {
			if (i_obj.expire_days==0)
				return '<div class="warning">Your evaluation license will end at: '+pHumanText.toDateTime(pDate.create(i_obj.expires))+'.<p>'+
				'Please visit <a class="link" href="https://www.genialist.tv" target="_blank">https://www.genialist.tv</a> to purchase a full license.<p>'+
				'If you have downloaded your license ticket, click here for the server to acknowledge it: <span class="link" onclick="pServerStatus.getenv()">Refresh License<span></div>';
			else if (i_obj.expire_days==1)
				return '<div class="warning">Your evaluation license will end in 1 day.<p>'+
				'Please visit <a class="link" href="https://www.genialist.tv" target="_blank">https://www.genialist.tv</a> to purchase a full license.<p>'+
				'If you have downloaded your license ticket, click here for the server to acknowledge it: <span class="link" onclick="pServerStatus.getenv()">Refresh License<span></div>';
			else
				return '<div class="warning">Your evaluation license will end in '+i_obj.expire_days+' days.<p>'+
				'Please visit <a class="link" href="https://www.genialist.tv" target="_blank">https://www.genialist.tv</a> to purchase a full license.<p>'+
				'If you have downloaded your license ticket, click here for the server to acknowledge it: <span class="link" onclick="pServerStatus.getenv()">Refresh License<span></div>';
		}
		return '<div class="warning">Your evaluation license will end at: '+pDate.toLocaleString(pDate.create(i_obj.expires))+'.<p>'+
			'Please visit <a class="link" href="https://www.genialist.tv" target="_blank">https://www.genialist.tv</a> to purchase a full license.<p>'+
			'If you have downloaded your license ticket, click here for the server to acknowledge it: <span class="link" onclick="pServerStatus.getenv()">Refresh License<span></div>';
	}
	return '';
};
pServerStatus.getenv = function() {
	pHTTPRequest.get('/server?action=refresh-license=&v='+Math.random()).then(pServerStatus.update).then(pAbout.update);
};
pServerStatus.updateImpl = function(resolve, reject, that) {
	if (pDocument.isShown('server_status')===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?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 i_obj = pJSON.parse(i_request.responseText);

			var i_total_pc = (100 * i_obj.memory.total ) / i_obj.memory.max;
			var i_used_pc = (100 * (i_obj.memory.total - i_obj.memory.free)) / i_obj.memory.total;

			var h = '';

			h += '<div id="server-status-uptime" class="box">';
			h += that.renderRefreshText();

			h += '<div>Server started since: '+pHumanText.toDateTime(pDate.create(i_obj.started))+'</div>';
			h += pServerStatus.licenseExpirationText(i_obj);
			h += '</div>';

			h += '<div id="server-status-memory" class="box">';
			h += '<div id="server-status-memory-title">JVM Memory</div>';

			h += '<div id="server-status-memory-content">';
			h += '<div class="mem-item mem-used" >Used: '+pHumanText.toByteSize(i_obj.memory.total - i_obj.memory.free)+'</div>';
			h += '<div class="mem-item mem-total">Total: '+pHumanText.toByteSize(i_obj.memory.total)+'</div>';
			h += '<div class="mem-item mem-max">Maximum: '+pHumanText.toByteSize(i_obj.memory.max)+'</div>';
			//h += '<div class="mem-item mem-reclaim link" onclick="pServer.reclaimMemory()">Reclaim Memory</div>';
			h += '<div id="mem-max"><div id="mem-total" style="width: '+i_total_pc+'%"><div id="mem-used" style="width: '+i_used_pc+'%"/></div></div></div>';
			h += '<div class="mem-item mem-reclaim link" onclick="pServer.reclaimMemory().then(pServerStatus.update)">Reclaim Memory</div>';
			h += '</div>';
			h += '</div>';

			//h += '<div class="box">';
			//h += '<div id="server-status-memory-title">Internal Cache</div>';
			//h += '<div class="mem-item mem-reclaim link" onclick="pServer.clearCache()">Clear Cache</div>';
			//h += '</div>';

			if (i_obj.properties) {
				h += '<div class="box">';
				h += '<div id="server-status-memory-title">JVM System Properties</div>';
				h += '<table class="simple table-name-value"><tr><th style="text-align: left;">Name</th><th style="text-align: left;">Value</th></tr>';
				h += that.renderProperties(i_obj.properties);
				h += '</table>';
				h += '</div>';
			}
			
			if (i_obj.env) {
				h += '<div class="box">';
				h += '<div id="server-status-memory-title">Environment Variables</div>';
				h += '<table class="simple table-name-value"><tr><th style="text-align: left;">Name</th><th style="text-align: left;">Value</th></tr>';
				h += that.renderProperties(i_obj.env);
				h += '</table>';
				h += '</div>';
			}
			
			if (i_obj.fwrk_folder_temp)
				h += '<div class="box">Temporary Folder: ' + i_obj.fwrk_folder_temp + '</div>';
			
			if (i_obj.fwrk_folder_conf)
				h += '<div class="box">Configuration Folder: ' + i_obj.fwrk_folder_conf + '</div>';
			
			if (i_obj.fwrk_folder_local)
				h += '<div class="box">Local Folder: ' + i_obj.fwrk_folder_local + '</div>';

			pElement.setInnerHTML('server-status-content', h);
			
			that.cache(i_request.responseText);
		}
		
		//*** RESOLVE PROMISE
		resolve();

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

	});
};

pServerStatus.renderProperties = function(p_properties) {
	var h = '', i_map = new pMap(p_properties);
	
	i_map.keys().slice(0).sort().forEach(function(n) {
		h += '<tr><td data-label="Name">'+pString.encodeHTML(n)+'</td><td data-label="Value">';
		if (n.toLowerCase().endsWith('path')) {
			var i_value = i_map.getValue(n);
			for(var j=0 ; j<i_value.length ; j++) {
				h += pString.encodeHTML(i_value.charAt(j));
				if (i_value.charAt(j)==';' || i_value.charAt(j)==';')
					h += '<br/>';
			}
		}
		else if (n == 'sun.java.command') {
			var i_commands = i_map.getValue(n).split(' ');
			for(var j=0 ; j<i_commands.length ; j++) {
				h += pString.encodeHTML(i_commands[j]);
				if (j+1<i_commands.length) h += '<br/>';
			}
		}
		else
			h += pString.encodeHTML(i_map.getValue(n));
		h += '</td></tr>';
	});
	return h;
};

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

const pNetworkStatus = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('NetworkStatus', 10000) : {};
pNetworkStatus.f_filter = function(i_net) { return  i_net.addresses && i_net.addresses.length>0 };
pNetworkStatus.f_sort = function(a, b) { return (a.display < b.display)? -1 : (a.display > b.display)? 1 : 0; };

pNetworkStatus.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown('network_status')===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-networks&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 i_obj = pJSON.parse(i_request.responseText);
			var h = '<div class="box">';

			h += that.renderRefreshText();

			h += '<div>IP Address: ' + pString.encodeHTML(i_obj.nonssl_address) + '</div>';
			h += '<div class="desc">Address the server is listening on. <br>If it equals to 0.0.0.0 (IPv4) or 0:0:0:0:0:0:0:0 (IPv6), the server can receive requests whatever the hostname clients are using in the HTTP/HTTPS url. <br>Otherwise, clients must use the specified address.</div>';
			h += '</div>';

			h += '<div class="box">Available Network Interfaces: <ol>';
			i_obj.networks.filter(that.f_filter).sort(that.f_sort).forEach(function(i_net) {
				h += '<li>'+pString.encodeHTML(i_net.display)+' ('+pString.encodeHTML(i_net.name)+')';
				h += '<ul>';
				i_net.addresses.forEach(function(i_addr) { h += '<li><span class="desc">'+pString.encodeHTML(i_addr)+'</span></li>'; });
				h += '</ul></li>';
			});
			h += '</ol></div>';

			pElement.setInnerHTML('network-status-content', h);
			
			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

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

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

const pPluginList = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('PluginList', 60000, 'plugins') : {};
pPluginList.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-status&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) {
			//alert(pJSON.pretty(i_request.responseText));

			const i_label_product = 'Product';
			const i_label_version = 'Version';
			const i_label_started = 'Started';

			var i_obj = pJSON.parse(i_request.responseText);

			var h = '';

			h += that.renderRefreshText();

			h += '<table class="table-plugins"><tr>';
			//h += '<th/>';
			h += '<th style="text-align: left;">'+i_label_product+'</th>';
			h += '<th style="text-align: left;">'+i_label_version+'</th>';
			h += '<th style="text-align: left;">'+i_label_started+'</th><th/>	</tr>';

			for(var i=0 ; i<i_obj.fwrk.plugins.plugins.length ; i++) {
				var i_plugin = i_obj.fwrk.plugins.plugins[i].plugin;
				var i_id = "'" + i_obj.fwrk.plugins.plugins[i].id + "'";
				var i_pd = "'" + i_plugin.product + "'";
				var i_v = "'" + i_plugin.version + "'";
				
				h += '<tr>';

					//h += '<td>';
					//h += '<img class="image toolbar-icon" src="/resources/html/images/16x16/add_on.png"/>';
					//h += '</td>';

				h += '<td data-label="'+i_label_product+'"><img class="image toolbar-icon" src="/resources/html/images/16x16/add_on.png" style="vertical-align: middle; padding-right: 4px;"/>'+pString.encodeHTML(i_plugin.product)+'</td>';
				h += '<td data-label="'+i_label_version+'">'+pString.encodeHTML(i_plugin.version)+'</td>';
				h += '<td data-label="'+i_label_started+'">'+(i_plugin.started? 'yes':'no')+'</td>';
				h += '<td>';
				if (i_plugin.started === true && i_plugin.canStop === true)
					h += '<span class="link" onclick="pPluginList.stop('+i_id+','+i_pd+','+i_v+')">Stop</span>';
				else if (i_plugin.started === false && i_plugin.canStart === true)
					h += '<span class="link" onclick="pPluginList.start('+i_id+','+i_pd+','+i_v+')">Start</span>';
				h += '</td>';
				h += '</tr>';
			}
			h += '</table>';

			pElement.setInnerHTML(that.element, h);
		}

		//*** RESOLVE PROMISE
		resolve();

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

pPluginList.bind("start", function(p_id, p_name, p_version) {
	var i_url = '/server?action=start-plugin&id='+p_id+'&v='+Math.random()+'&wait=1';

	pTransaction.transaction({
		trace: this, url: i_url, method_name: "StopPlugin",
		msg_start: 'Starting plugin '+p_name+' ('+p_version+')...',
		msg_error: 'Plugin '+p_name+' ('+p_version+') has NOT been started...',
		msg_end: 'Plugin '+p_name+' ('+p_version+') has been started...',
	});
});

pPluginList.bind("stop", function(p_id, p_name, p_version) {
	var i_url = '/server?action=stop-plugin&id='+p_id+'&v='+Math.random()+'&wait=1';

	pTransaction.transaction({
		trace: this, url: i_url, method_name: "StopPlugin",
		msg_start: 'Stopping plugin '+p_name+' ('+p_version+')...',
		msg_error: 'Plugin '+p_name+' ('+p_version+') has NOT been stopped...',
		msg_end: 'Plugin '+p_name+' ('+p_version+') has been stopped...',
	});
});

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

const pAbout = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('About', null, 'about') : {};
pAbout.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-status&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 i_obj = pJSON.parse(i_request.responseText);
			//alert(pJSON.pretty(JSON.stringify(i_obj.fwrk.plugins)));

			var h = '';
			h += '<div class="box">';
			h += '<div class="header-title">';
			h += '<div><img src="/favicon.png"/></div>\n';
			h += '<div style="padding-top: 0px;">'+pString.encodeHTML(i_obj.name)+'</div>';
			h += '</div>';
			h += '<div class="desc" style="display: block;">Version: ' + pString.encodeHTML(i_obj.version)+'</div>';
			h += '<div>Copyright (c) 2017, Genialist Software Ltd. All rights reserved.</div>';
			h += '</div>';

			h += '<div class="box"><div>Information</div>';
			h += '<dl><dd><div class="desc" style="display: block; xpadding-left: 0px;">License agreement: <a class="link" href="/licenses/plugins/license-agreement.txt" target="_blank">License Agreement</a></div>';
			h += '<dd><div class="desc" style="display: block; xpadding-left: 0px;">Web-Site: <a class="link" href="https://www.genialist.tv" target="_blank">https://www.genialist.tv</a></div>';
			h += '</div>';
			
			var i_expires = pServerStatus.licenseExpirationText(i_obj);
			if (pString.v(i_expires)) 
				h += '<div class="box">' + i_expires + '</div>';
			
			h += '<div class="box"><div>Acknowledgements</div>';
			h += '<dl><dd><div class="desc" style="display: block;">This software uses <a class="link" href="https://www.themoviedb.org" target="_blank">The Movie Database</a> web site and the TMDb API but is not endorsed or certified by TMDb.</div>';
			h += '<dd><div class="desc" style="display: block;">This software uses <a class="link" href="http://www.thetvdb.com" target="_blank">TheTVDB.com</a> web site and the TheTVDB.com API but is not endorsed or certified by TheTVDB.com.</div>';
			h += '<dd><div class="desc" style="display: block;">This software uses <a class="link" href="https://www.wikipedia.org" target="_blank">Wikipedia</a> web site but is not endorsed or certified by Wikipedia.</div>';
			h += '<dd><div class="desc" style="display: block;">This software uses <a class="link" href="https://www.discogs.com" target="_blank">Discogs.com</a> web site and the Discogs.com API but is not endorsed or certified by Discogs.com.</div>';
			h += '<dd><div class="desc" style="display: block;">This software uses <a class="link" href="https://www.last.fm.com" target="_blank">last.fm</a> web site and the last.fm API but is not endorsed or certified by last.fm.</div>';
			
			h += '</div>';

			h += '<div class="box"><div>Third Party Licenses</div>';
			h += '<ul>'
			h += '<li><div class="desc">Icons provided by Fat Cow Web Hosting, for more information, please visit <a class="link" href="http://www.fatcow.com/free-icons" target="_blank">http://www.fatcow.com/free-icons</a>.</div></li>';
			
			var i_licenses = new pMap();
			var i_names = new pMap();
			i_obj.fwrk.plugins.plugins.forEach(function(p) {
				if (p.licenses)
					p.licenses.forEach(function(l) {
						i_licenses.put(l.name+l.filename, l);
						
						var i_l = i_names.getValue(l.name);
						if (i_l) {
							i_l.put(l.name+l.filename, l);
						}
						else
							i_names.put(l.name, new pMap([ l.name+l.filename, l ]));
					});
			});
			i_names.keys().sort(pString.compareIgnoreCase).forEach(function(n) {
				var i_l = i_names.getValue(n);
				if (i_l.length>1) {
					h += '<li class="desc">'+n+'<ul>';
					i_l.values().forEach(function(l) {
						var i_path = l.relpath;
						if (i_path.startsWith("http://") || i_path.startsWith("https://"))
							h += '<li><div class="desc license"><a class="link xdesc" href="'+i_path+'\" target="_blank">'+pString.encodeHTML(pString.substring_after(l.filename, './'))+'</a></div></li>';
						else
							h += '<li><div class="desc license"><a class="link xdesc" href="/licenses/'+i_path+'\" target="_blank">'+pString.encodeHTML(pString.substring_after(l.filename, './'))+'</a></div></li>';
					});
					h += '</ul></li>';
				}
				else {
					var i_path = i_l.values()[0].relpath;
					if (i_path && (i_path.startsWith("http://") || i_path.startsWith("https://")))
						h += '<li><div class="desc license"><a class="link xdesc" href="'+i_path+'\" target="_blank">'+pString.encodeHTML(n)+'</a></div></li>';
					else
						h += '<li><div class="desc license"><a class="link xdesc" href="/licenses/'+i_path+'\" target="_blank">'+pString.encodeHTML(n)+'</a></div></li>';
				}
			});
			/*		
			i_licenses.keys().sort(pString.compareIgnoreCase).forEach(function(l) { 
				var i_path = i_licenses.getValue(l).relpath;
				if (i_path) {
					//console.log(i_path);
					if (i_path.startsWith("http://") || i_path.startsWith("https://"))
						h += '<li><div class="desc"><a class="link xdesc" href="'+i_path+'\" target="_blank">'+i_licenses.getValue(l).name+'</a></div></li>';
					else
						h += '<li><div class="desc"><a class="link xdesc" href="/licenses/'+i_path+'\" target="_blank">'+i_licenses.getValue(l).name+'</a></div></li>';
				}
			});*/
			h += '</ul>'
			h += '</div>';

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

		//*** RESOLVE PROMISE
		resolve();

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

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

const re_lines = /(\r\n)|(\n)/;
const re_html = /</;

const pLogFile = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('LogFile', 500) : { vprivate: {}};
pLogFile.maxlines = 1000;
pLogFile.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown('server_logfile')===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

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

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

		if (i_request.status == 200) {
			if (!that.previous || that.previous != i_request.responseText) {

				if (that.maxlines<1)
					that.maxlines = 1;

				var i_class = null;
				var h = '';
				var i_lines = i_request.responseText.split(re_lines);
				//if (i_lines.length<2) i_lines = i_request.responseText.split('\n');

				for(var i=(i_lines.length<that.maxlines)? 0 : i_lines.length-that.maxlines ; i<i_lines.length ; i++) {
					var i_line = i_lines[i];
					if (!i_line) continue;

					if (i_line.indexOf('Rebooting')>0)
						i_class = "log-line-rebooting";
					else if (i_line.indexOf('[ERR')>0)
						i_class = "log-line-error";
					else if (i_line.indexOf('[WARN')>0)
						i_class = "log-line-warning";
					else if (i_line.indexOf('[INFO')>0)
						i_class = null;
					else if (i_line.indexOf('[INFO')>0)
						i_class = null;
					else if (i_line.indexOf('[DEBG')>0)
						i_class = null;
						
					if (i_class)
						h += '<span class="'+i_class+'">' + i_line.replace(re_html, '&lt;') + '</span>';
					else
						h += '<span>'+i_line.replace(re_html, '&lt;')+'</span>';
				}

				pElement.setInnerHTML('server-logfile-content', '<pre><code>'+h/*i_request.responseText*/+'</pre></code>');
				if (that.lockscroll === false)
					pElement.scrollToBottom('server-logfile-content');

				that.previous = i_request.responseText;
			}
		}

		//*** RESOLVE PROMISE
		resolve();

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

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

function onerror_libraryicon(img) {
	img.src='/resources/html/images/32x32/music.png';
}

const pLibraryList = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('LibraryList', 1000, 'server_libraries') : {};
pLibraryList.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-libraries&v='+Math.random();
	pConsole.info(that, "GetLibraries: " + 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 i_obj = pJSON.parse(i_request.responseText);

			pLibraryList.recents = i_obj.recents;

			var h = '<div class="box">';

			h += that.renderRefreshText();

			h += '<div style="clear: both;"><!----></div>';
			h += '<div class="listview-item">';
			h += '<a class="image" href="javascript:pServer.openLibrary();" tabindex="-1">';
			h += '<img class="image listview-image" src="/resources/html/images/32x32/folder_blue.png"/>';
			h += '</a>';
			h += '<a class="listview-text" href="javascript:pServer.openLibrary();">Open Library...</a>';
			h += '</div>';

			i_obj.libraries = pString.sortByName(i_obj.libraries);
			for(var i=0 ; i<i_obj.libraries.length ; i++) {
				var i_library = i_obj.libraries[i];

				if (i_library.id) {
					h += '<div class="listview-item">';
					h += '<a class="image" href="'+url_prefix+i_library.id+'/library/" tabindex="-1">';
					h += '<img class="image listview-image" src="'+i_library.thumbnail_uri+'" onError="onerror_libraryicon(this)"/>';
					h += '</a>';
					h += '<a class="listview-text" href="'+url_prefix+i_library.id+'/library/">'+pString.encodeHTML(i_library.name)+'</a>';
					h += '</div>';
				}
				else {
					h += '<div class="listview-item">';
					if (i_library.state == "STOPPED")
						h += '<img class="image listview-image" src="/resources/html/images/32x32/delete.png"/>';
					else
						h += '<img class="image listview-image" src="/resources/html/images/line-wait.gif"/>';
					h += '<span class="listview-text">'+pString.encodeHTML(i_library.name)+'</span>';
					h += '</div>';
				}
			}
			h += '</div>';

			pElement.setInnerHTML(that.element.id+'-content', h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

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

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

const pUserList = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('UserList', 10000, 'users') : {};
pUserList.cols = [ 'Name', 'Login', 'Enabled', 'Administrator', 'Last Connection' ];
pUserList.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-users&v='+Math.random();
	pConsole.info(that, "GetUsers: " + 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 i_obj = pJSON.parse(i_request.responseText);
			var h = '<div class="box">';
			h += that.renderRefreshText();

			h += '<table cellpadding="5" cellspacing="5" class="table-users" id="table-users"><tr><th/>';
			that.cols.forEach(function(n) { 
				h += '<th><span class="link" title="Click here to sort this column" onclick="pTable.sortString('+sq+'table-users'+sq+', this.innerHTML, 0, true)">'+n+'</span></th>' });
			h += '</tr>';

			i_obj.users.forEach(function(u) {
				h += '<tr>';
				h += '<td><div class="icon icon-user user-'+u.name+'"><!----></div></td>';
				h += '<td data-label="Name"><a class="link" href=\"/user?user='+u.name+'\" title="Go to this User page">'+pString.encodeHTML(u.fullname)+'</a></td>';
				h += '<td data-label="Login">'+pString.encodeHTML(u.name)+'</td>';
				if (u.visible != "true") h += '<td data-label="Enabled"><span class="warning">NO</span></td>'; else h += '<td data-label="Enabled"/>';
				if (u.role_admin == "true") h += '<td data-label="Admin">YES</td>'; else h += '<td data-label="Admin"/>';
				if (u.last_connection && !u.last_connection.startsWith("1970")) h += '<td data-label="Last Connection" data-sort=\"'+u.last_connection+'\">'+pHumanText.toDateTime(pDate.create(u.last_connection))+'</td>';
				h += '</tr>';
			});
			h += '</table>';
			h += '</div>';

			pElement.setInnerHTML(that.element.id+'-content', h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

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

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

const pSessionList = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('SessionList', 10000, 'user_sessions') : {};

pSessionList.bind("close", function(p_session_id) {
	pHTTPRequest.sendDELETE("?action=delete-session&id="+p_session_id+"&v="+Math.random()).then(this.update);
});

pSessionList.view = function(p_session_id) {
	pHTTPRequest.get('?action=get-session&id='+p_session_id, function(p_request) {
		pConsole.info(p_request.responseText);
		pDialog.dialogQuestion({ id: 'session-details', classes: 'dialog-main-menu', title: 'Session Details', options: [ 'ok', { id: 'details', sep: true, text: '<pre><samp>'+pJSON.pretty(p_request.responseText) + '</samp></pre>' }]});
	});
};

pSessionList.cols = [ 'Name', 'Login', 'Connected', 'Expires', 'Device', 'Address' ];
pSessionList.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-sessions&v='+Math.random();
	pConsole.info(that, "GetSessions: " + 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 i_obj = pJSON.parse(i_request.responseText);

			var h = '<div class="box">';

			h += that.renderRefreshText();

			h += '<table cellpadding="5" cellspacing="5" class="table-sessions" id="table-sessions"><tr><th/>';
			that.cols.forEach(function(n) {
				h += '<th><span class="link" title="Click here to sort this column" onclick="pTable.sortString('+sq+'table-sessions'+sq+', this.innerHTML, 0, true)">'+n+'</span></th>' });
			h += '</tr>';

			var i_oid = "'"+that.id+"'";
			i_obj.sessions.forEach(function(s) {//for(var i=0 ; i<i_obj.sessions.length ; i++) {
				//var s = i_obj.sessions[i];
				var i_id = "'"+s.id+"'", t = pDevice.type(s.user_agent);

				h += '<tr>';
				h += '<td><div class="icon icon-user"><!----></div></td>';
				h += '<td data-label="Name"><a class="link" href=\"/user?user='+s.name+'\" title="Go to this User page">'+pString.encodeHTML(s.fullname)+'</a></td>';
				h += '<td data-label="Login">'+pString.encodeHTML(s.name)+'</td>';
				h += '<td data-label="Connected">'+pHumanText.toDateTime(pDate.create(s.connected))+'</td>';
				h += '<td data-label="Expires" data-sort=\"'+s.expires+'\">'+pHumanText.toDateTime(pDate.create(s.expires))+'</td>';
				h += '<td data-label="Device">';

				h += '<span class=\"device-'+t.toLowerCase()+'\">'+t+' - </span>';
				h += '<span>'+pString.encodeHTML(s.user_agent)+'</span>';
				h += '</td>';
				h += '<td data-label="Address">'+pString.encodeHTML(s.address)+'</td>';
				//h += '<td><a class="link" href="javascript:objects.getValue('+i_id+', null).view(\''+s.id+'\')">View this session</a></td>';
				h += '<td><a class="link" href=\"javascript:objects.getValue('+i_oid+', null).close('+i_id+')\" title="Close this session">&times;</a></td>';
				h += '</tr>';
			});
			h += '</table>';
			h += '</div>';

			pElement.setInnerHTML(that.element.id+'-content', h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

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

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

const pTranscodingList = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('TranscodingList', 1000, 'transcodings') : {};

pTranscodingList.bind("remove", function(p_id) {
	pHTTPRequest.sendDELETE("?action=remove-transcoding&id="+p_id+"&v="+Math.random()).then(this.update);
});

pTranscodingList.status = function(s) {
	return s? s.split(' ').filter(function(s) { return s.startsWith('Start') || s.startsWith('frame=') || s.startsWith('fps=') || s.startsWith('out_time=') || s.startsWith('speed=')}).join(', ') : '';
};

pTranscodingList.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-transcodings&v='+Math.random();
	pConsole.info(that, "GetTranscodings: " + 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 i_obj = pJSON.parse(i_request.responseText);
			var h = '';

			h += '<div class="box">';

			h += that.renderRefreshText();

			h += '<div>';
			//h += 'You can configure transcoding by selecting <a href="javascript:void(doShow(\'server-settings\'))" xtarget="_blank">Server Settings</a>...';
			h += '<p>Transcoded files or files being currently transcoded (current size: '+pHumanText.toByteSize(i_obj.bytes)+', max size: '+pHumanText.toByteSize(i_obj.max_bytes)+'):</div>';

			h += '<table cellpadding="5" cellspacing="5" class="table-transcodings" id="table-transcodings"><tr><th/>';
			[ 'File', 'State', 'Status', 'Started', 'Ended' ].forEach(function(n) {
				h += '<th><span class="link" title="Click here to sort this column" onclick="pTable.sortString('+sq+'table-transcodings'+sq+', this.innerHTML, 0, true)">'+n+'</span></th>';
			});
			h += '</tr>';

			var i_oid = "'"+that.id+"'";
			i_obj.transcoders.forEach(function(s) {
				var i_id = "'"+s.id+"'";
				
				h += '<tr title="'+s.file+'">';
				h += '<td>';
				h += '<img class="image toolbar-icon" src="/resources/html/images/16x16/microformats.png"/>';
				h += '</td>';

				h += '<td data-label="File">.../' + pString.encodeHTML(pString.basename(pString.dirname(s.file))) + '/' + pString.encodeHTML(pString.basename(s.file)) + '</td>';
				//h += '<td></td>';// + s.uri + '</td>';
				h += '<td data-label="State">' + pString.encodeHTML(s.state) + '</td>';
				h += '<td data-label="Status">' + pString.encodeHTML(pTranscodingList.status(s.status)) + '</td>';
				h += '<td data-label="Started" data-sort="'+s.start+'">' + ((s.start)? pHumanText.toDateTime(pDate.create(s.start)) : '') + '</td>';
				h += '<td data-label="Ended" data-sort="'+s.end+'">' + ((s.end)? pHumanText.toDateTime(pDate.create(s.end)) : '') + '</td>';

				h += '<td><span class="link" onclick="objects.getValue('+i_oid+', null).remove('+i_id+')" title="Delete that trancoding data">&times;</span></td>';
				h += '</tr>';
			});
			h += '</table>';
			h += '</div>';

			pElement.setInnerHTML(that.element.id+'-content', h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

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

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

window.pStaticTranscodingList = (window.pLibraryUpdateInstance)? new pLibraryUpdateInstance('StaticTranscodingList', 1000, 'static-transcodings') : {};

pStaticTranscodingList.bind("remove", function(p_id) {
	pHTTPRequest.sendDELETE("?action=remove-static-transcoding&id="+p_id+"&v="+Math.random()).then(this.update);
});

pStaticTranscodingList.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = '/server?action=get-static-transcodings&v='+Math.random();
	pConsole.info(that, "GetStaticTranscodings: " + 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 i_obj = pJSON.parse(i_request.responseText);
			var h = '<div class="box">';

			h += that.renderRefreshText();

			h += '<div>';
			//h += 'You can configure transcoding by selecting <a href="javascript:doShow(\'server-settings\')" xtarget="_blank">Server Settings</a>...';
			h += '<p>Transcoded files or files being currently transcoded (current size: '+pHumanText.toByteSize(i_obj.bytes)+'):</div>';

			h += '<table cellpadding="5" cellspacing="5" class="table-transcodings" id="table-transcodings"><tr><th/>';
			[ 'File', 'State', 'Status', 'Started', 'Ended' ].forEach(function(n) {
				h += '<th><span class="link" title="Click here to sort this column" onclick="pTable.sortString('+sq+'table-transcodings'+sq+', this.innerHTML, 0, true)">'+n+'</span></th>';
			});
			h += '</tr>';

			var i_oid = "'"+that.id+"'";
			i_obj.transcoders.forEach(function(s) {
				var i_id = "'"+s.id+"'";

				h += '<tr title="'+s.file+'">';
				h += '<td>';
				h += '<img class="image toolbar-icon" src="/resources/html/images/16x16/microformats.png"/>';
				h += '</td>';

				h += '<td data-label="File">.../' + pString.encodeHTML(pString.basename(pString.dirname(s.file))) + '/' + pString.encodeHTML(pString.basename(s.file)) + '</td>';
				//h += '<td></td>';// + s.uri + '</td>';
				h += '<td data-label="State">' + pString.encodeHTML(s.state) + '</td>';
				h += '<td data-label="Status">' + pString.encodeHTML(pTranscodingList.status(s.status)) + '</td>';
				h += '<td data-label="Started" data-sort="'+s.start+'">' + ((s.start)? pHumanText.toDateTime(pDate.create(s.start)) : '') + '</td>';
				h += '<td data-label="Ended" data-sort="'+s.end+'">' + ((s.end)? pHumanText.toDateTime(pDate.create(s.end)) : '') + '</td>';

				h += '<td><span class="link" onclick="objects.getValue('+i_oid+', null).remove('+i_id+')" title="Delete this trancoding data">&times;</span></td>';
				h += '</tr>';
			});
			h += '</table>';
			h += '</div>';

			pElement.setInnerHTML(that.element.id+'-content', h);

			that.cache(i_request.responseText);
		}

		//*** RESOLVE PROMISE
		resolve();

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

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

const pMediaConversionList = new pLibraryUpdateInstance('MediaConversionList', 1000, 'media-conversions');
pMediaConversionList.url = window.location.pathname;
pMediaConversionList.updateImpl = function(resolve, reject, that) {
	
	if (pDocument.isShown(that.element)===false) {
		pDocument.clearTimeout(that.id);
		return;
	}

	var u = that.url+'?action=get-media-conversions&v='+Math.random();
	pConsole.info(that, "GetMediaConversions: " + 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 = '<div class="box">';
			h += that.renderRefreshText();
			h += '<div>Media conversions currently executed:</div>';
			
			h += '<table cellpadding="5" cellspacing="5" class="table-library-jobs"><tr><th/>';
			[ 'Task', 'State', 'Started', 'Ended' ].forEach(function(n) {
				h += '<th style="text-align: left;"><a class="link" title="Click here to sort this column" href="javascript:sortTableString('+sq+n+sq+')">'+n+'</a></th>';
			});
			h += '</tr>';

			o.threads.forEach(function(t) {
				h += '<tr style="text-align: left;">';
				h += '<td>';
				if (t.finished)
					h += '<img class="image toolbar-icon" src="/resources/html/images/16x16/microformats.png"/>';
				else 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="Task">'+pString.encodeHTML(t.title)+pString.encodeHTML(pString.v(t.status)? '<br>'+t.status : '')+'</td>';
				h += '<td data-label="State">'+(t.finished? 'COMPLETED' : (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="Ended" data-sort="'+t.ended+'">' + ((t.ended)? pHumanText.toDateTime(pDate.create(t.ended)) : '') + '</td>';
				h += '</tr>';
			});
			h += '</table></div>';

			pElement.setInnerHTML(that.element.id+'-content', h);

			that.cache(i_request.responseText);
		}
		
		//*** RESOLVE PROMISE
		resolve();

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

/******************************************************************************/
/***  END OF FILE  ************************************************************/
/******************************************************************************/