/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* In this module:
* 1) Loading of program options and post-processing scripts options;
* 2) Settings tab;
* 3) Function "Reload".
*/
/*** OPTIONS AND CONFIGS (FROM CONFIG FILES) **************************************/
var Options = (new function($)
{
'use strict';
// Properties (public)
this.options;
this.postParamConfig = [];
this.categories = [];
this.restricted = false;
// State
var _this = this;
var serverTemplateData = null;
var serverValues;
var loadComplete;
var loadConfigError;
var loadServerTemplateError;
var shortScriptNames = [];
var HIDDEN_SECTIONS = ['DISPLAY (TERMINAL)', 'POSTPROCESSING-PARAMETERS', 'POST-PROCESSING-PARAMETERS', 'POST-PROCESSING PARAMETERS'];
var POSTPARAM_SECTIONS = ['POSTPROCESSING-PARAMETERS', 'POST-PROCESSING-PARAMETERS', 'POST-PROCESSING PARAMETERS'];
this.init = function()
{
}
this.update = function()
{
// RPC-function "config" returns CURRENT configuration settings loaded in NZBGet
RPC.call('config', [], function(_options)
{
_this.options = _options;
initCategories();
_this.restricted = _this.option('ControlPort') === '***';
RPC.next();
});
// loading config templates and build list of post-processing parameters
_this.postParamConfig = [];
RPC.call('configtemplates', [false], function(data)
{
initPostParamConfig(data);
RPC.next();
});
}
this.cleanup = function()
{
serverTemplateData = null;
serverValues = null;
}
this.option = function(name)
{
var opt = findOption(this.options, name);
return opt ? opt.Value : null;
}
function initCategories()
{
_this.categories = [];
for (var i=0; i < _this.options.length; i++)
{
var option = _this.options[i];
if ((option.Name.toLowerCase().substring(0, 8) === 'category') && (option.Name.toLowerCase().indexOf('.name') > -1))
{
_this.categories.push(option.Value);
}
}
}
/*** LOADING CONFIG ********************************************************************/
this.loadConfig = function(callbacks)
{
loadComplete = callbacks.complete;
loadConfigError = callbacks.configError;
loadServerTemplateError = callbacks.serverTemplateError;
// RPC-function "loadconfig" reads the configuration settings from NZBGet configuration file.
// that's not neccessary the same settings returned by RPC-function "config". This could be the case,
// for example, if the settings were modified but NZBGet was not restarted.
RPC.call('loadconfig', [], serverValuesLoaded, loadConfigError);
}
function serverValuesLoaded(data)
{
serverValues = data;
RPC.call('configtemplates', [true], serverTemplateLoaded, loadServerTemplateError);
}
function serverTemplateLoaded(data)
{
serverTemplateData = data;
complete();
}
function complete()
{
initShortScriptNames(serverTemplateData);
if (serverTemplateData === null)
{
// the loading was cancelled and the data was discarded (via method "cleanup()")
return;
}
var config = [];
config.values = serverValues;
readWebSettings(config);
var serverConfig = readConfigTemplate(serverTemplateData[0].Template, undefined, HIDDEN_SECTIONS, '');
mergeValues(serverConfig.sections, serverValues);
config.push(serverConfig);
// read scripts configs
for (var i=1; i < serverTemplateData.length; i++)
{
var scriptName = serverTemplateData[i].Name;
var scriptConfig = readConfigTemplate(serverTemplateData[i].Template, undefined, HIDDEN_SECTIONS, scriptName + ':');
scriptConfig.scriptName = scriptName;
scriptConfig.id = Util.makeId(scriptName);
scriptConfig.name = scriptName.substr(0, scriptName.lastIndexOf('.')) || scriptName; // remove file extension
scriptConfig.name = scriptConfig.name.replace(/\\/, ' \\ ').replace(/\//, ' / ');
scriptConfig.shortName = shortScriptName(scriptName);
scriptConfig.shortName = scriptConfig.shortName.replace(/\\/, ' \\ ').replace(/\//, ' / ');
scriptConfig.post = serverTemplateData[i].PostScript;
scriptConfig.scan = serverTemplateData[i].ScanScript;
scriptConfig.queue = serverTemplateData[i].QueueScript;
scriptConfig.scheduler = serverTemplateData[i].SchedulerScript;
scriptConfig.defscheduler = serverTemplateData[i].TaskTime !== '';
scriptConfig.feed = serverTemplateData[i].FeedScript;
mergeValues(scriptConfig.sections, serverValues);
config.push(scriptConfig);
}
serverValues = null;
loadComplete(config);
}
function readWebSettings(config)
{
var webTemplate = '### WEB-INTERFACE ###\n\n';
var webValues = [];
for (var optname in UISettings.description)
{
var descript = UISettings.description[optname];
var value = UISettings[optname];
optname = optname[0].toUpperCase() + optname.substring(1);
if (value === true) value = 'yes';
if (value === false) value = 'no';
descript = descript.replace(/\n/g, '\n# ').replace(/\n# \n/g, '\n#\n');
webTemplate += '# ' + descript + '\n' + optname + '=' + value + '\n\n';
webValues.push({Name: optname, Value: value.toString()});
}
var webConfig = readConfigTemplate(webTemplate, undefined, '', '');
mergeValues(webConfig.sections, webValues);
config.push(webConfig);
}
this.reloadConfig = function(_serverValues, _complete)
{
loadComplete = _complete;
serverValues = _serverValues;
complete();
}
/*** PARSE CONFIG AND BUILD INTERNAL STRUCTURES **********************************************/
function readConfigTemplate(filedata, visiblesections, hiddensections, nameprefix)
{
var config = { description: '', nameprefix: nameprefix, sections: [] };
var section = null;
var description = '';
var firstdescrline = '';
var data = filedata.split('\n');
for (var i=0, len=data.length; i < len; i++)
{
var line = data[i].replace(/\r+$/,''); // remove possible trailing CR-characters
if (line.substring(0, 4) === '### ')
{
var section = {};
section.name = line.substr(4, line.length - 8).trim();
section.id = Util.makeId(nameprefix + section.name);
section.options = [];
description = '';
section.hidden = !(hiddensections === undefined || (hiddensections.indexOf(section.name) == -1)) ||
(visiblesections !== undefined && (visiblesections.indexOf(section.name) == -1));
section.postparam = POSTPARAM_SECTIONS.indexOf(section.name) > -1;
config.sections.push(section);
}
else if (line.substring(0, 2) === '# ' || line === '#')
{
if (description !== '')
{
description += ' ';
}
if (line[2] === ' ' && line[3] !== ' ' && description.substring(description.length-4, 4) != '\n \n ')
{
description += '\n';
}
description += line.substr(1, 10000).trim();
var lastchar = description.substr(description.length - 1, 1);
if (lastchar === '.' && firstdescrline === '')
{
firstdescrline = description;
description = '';
}
if ('.;:'.indexOf(lastchar) > -1 || line === '#')
{
description += '\n';
}
}
else if (line.indexOf('=') > -1 || line.indexOf('@') > -1)
{
if (!section)
{
// bad template file; create default section.
section = {};
section.name = 'OPTIONS';
section.id = Util.makeId(nameprefix + section.name);
section.options = [];
description = '';
config.sections.push(section);
}
var option = {};
var enabled = line.substr(0, 1) !== '#';
var command = line.indexOf('=') === -1 && line.indexOf('@') > -1;
option.caption = line.substr(enabled ? 0 : 1, line.indexOf(command ? '@' : '=') - (enabled ? 0 : 1)).trim();
if (command)
{
var optpos = option.caption.indexOf('[');
option.commandopts = optpos > -1 ? option.caption.substring(optpos + 1, option.caption.indexOf(']')).toLowerCase() : 'settings';
option.caption = optpos > -1 ? option.caption.substring(0, optpos) : option.caption;
}
option.name = (nameprefix != '' ? nameprefix : '') + option.caption;
option.defvalue = line.substr(line.indexOf(command ? '@' : '=') + 1, 1000).trim();
option.value = null;
option.sectionId = section.id;
option.select = [];
var pstart = firstdescrline.lastIndexOf('(');
var pend = firstdescrline.lastIndexOf(')');
if (pstart > -1 && pend > -1 && pend === firstdescrline.length - 2)
{
var paramstr = firstdescrline.substr(pstart + 1, pend - pstart - 1);
var params = paramstr.split(',');
for (var pj=0; pj < params.length; pj++)
{
option.select.push(params[pj].trim());
}
firstdescrline = firstdescrline.substr(0, pstart).trim() + '.';
}
if (option.name.substr(nameprefix.length, 1000).indexOf('1.') > -1)
{
section.multi = true;
section.multiprefix = option.name.substr(0, option.name.indexOf('1.'));
}
if (!section.multi || option.name.indexOf('1.') > -1)
{
section.options.push(option);
}
if (section.multi)
{
option.template = true;
}
option.description = firstdescrline + description;
description = '';
firstdescrline = '';
}
else
{
if (!section && firstdescrline !== '')
{
config.description = firstdescrline + description;
}
else if (section && section.options.length === 0)
{
section.description = firstdescrline + description;
}
description = '';
firstdescrline = '';
}
}
return config;
}
function findOption(options, name)
{
if (!options)
{
return null;
}
name = name.toLowerCase();
for (var i=0; i < options.length; i++)
{
var option = options[i];
if ((option.Name && option.Name.toLowerCase() === name) ||
(option.name && option.name.toLowerCase() === name))
{
return option;
}
}
return null;
}
this.findOption = findOption;
function mergeValues(config, values)
{
// copy values
for (var i=0; i < config.length; i++)
{
var section = config[i];
if (section.multi)
{
// multi sections (news-servers, scheduler)
var subexists = true;
for (var k=1; subexists; k++)
{
subexists = false;
for (var m=0; m < section.options.length; m++)
{
var option = section.options[m];
if (option.name.indexOf('1.') > -1)
{
var name = option.name.replace(/1/, k);
var val = findOption(values, name);
if (val)
{
subexists = true;
break;
}
}
}
if (subexists)
{
for (var m=0; m < section.options.length; m++)
{
var option = section.options[m];
if (option.template)
{
var name = option.name.replace(/1/, k);
// copy option
var newoption = $.extend({}, option);
newoption.name = name;
newoption.caption = option.caption.replace(/1/, k);
newoption.template = false;
newoption.multiid = k;
newoption.value = null;
section.options.push(newoption);
var val = findOption(values, name);
if (val)
{
newoption.value = val.Value;
}
}
}
}
}
}
else
{
// simple sections
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
option.value = null;
var val = findOption(values, option.name);
if (val && !option.commandopts)
{
option.value = val.Value;
}
}
}
}
}
this.mergeValues = mergeValues;
function initShortScriptNames(configTemplatesData)
{
for (var i=1; i < configTemplatesData.length; i++)
{
shortScriptNames[configTemplatesData[i].Name] = configTemplatesData[i].DisplayName;
}
}
function shortScriptName(scriptName)
{
var shortName = shortScriptNames[scriptName];
return shortName ? shortName : scriptName;
}
this.shortScriptName = shortScriptName;
function initPostParamConfig(data)
{
initShortScriptNames(data);
// Create one big post-param section. It consists of one item for every post-processing script
// and additionally includes all post-param options from post-param section of each script.
var section = {};
section.id = 'PP-Parameters';
section.options = [];
section.description = '';
section.hidden = false;
section.postparam = true;
_this.postParamConfig = [section];
for (var i=1; i < data.length; i++)
{
if (data[i].PostScript || data[i].QueueScript)
{
var scriptName = data[i].Name;
var sectionId = Util.makeId(scriptName + ':');
var option = {};
option.name = scriptName + ':';
option.caption = shortScriptName(scriptName);
option.caption = option.caption.replace(/\\/, ' \\ ').replace(/\//, ' / ');
option.defvalue = 'no';
option.description = (data[i].Template.trim().split('\n')[0].substr(1, 1000).trim() || 'Extension script ' + scriptName + '.');
option.value = null;
option.sectionId = sectionId;
option.select = ['yes', 'no'];
section.options.push(option);
var templateData = data[i].Template;
var postConfig = readConfigTemplate(templateData, POSTPARAM_SECTIONS, undefined, scriptName + ':');
for (var j=0; j < postConfig.sections.length; j++)
{
var sec = postConfig.sections[j];
if (!sec.hidden)
{
for (var n=0; n < sec.options.length; n++)
{
var option = sec.options[n];
option.sectionId = sectionId;
section.options.push(option);
}
}
}
}
}
}
}(jQuery));
/*** SETTINGS TAB (UI) *********************************************************/
var Config = (new function($)
{
'use strict';
// Controls
var $ConfigNav;
var $ConfigData;
var $ConfigTabBadge;
var $ConfigTabBadgeEmpty;
var $ConfigContent;
var $ConfigInfo;
var $ConfigAbout;
var $ConfigTitle;
var $ConfigTable;
var $ViewButton;
var $LeaveConfigDialog;
var $Body;
// State
var config = null;
var filterText = '';
var lastSection;
var reloadTime;
var updateTabInfo;
var restored = false;
var compactMode = false;
var configSaved = false;
var leaveTarget;
this.init = function(options)
{
updateTabInfo = options.updateTabInfo;
$Body = $('html, body');
$ConfigNav = $('#ConfigNav');
$ConfigData = $('#ConfigData');
$ConfigTabBadge = $('#ConfigTabBadge');
$ConfigTabBadgeEmpty = $('#ConfigTabBadgeEmpty');
$ConfigContent = $('#ConfigContent');
$ConfigInfo = $('#ConfigInfo');
$ConfigAbout = $('#ConfigAbout');
$ConfigTitle = $('#ConfigTitle');
$ViewButton = $('#Config_ViewButton');
$LeaveConfigDialog = $('#LeaveConfigDialog');
$('#ConfigTable_filter').val('');
Util.show('#ConfigBackupSafariNote', $.browser.safari);
$('#ConfigTable_filter').val('');
compactMode = UISettings.read('$Config_ViewCompact', 'no') == 'yes';
setViewMode();
$(window).bind('beforeunload', userLeavesPage);
$ConfigNav.on('click', 'li > a', navClick);
$ConfigTable = $('#ConfigTable');
$ConfigTable.fasttable(
{
filterInput: $('#ConfigTable_filter'),
filterClearButton: $("#ConfigTable_clearfilter"),
filterInputCallback: filterInput,
filterClearCallback: filterClear
});
}
this.config = function()
{
return config;
}
this.show = function()
{
removeSaveBanner();
$('#ConfigSaved').hide();
$('#ConfigLoadInfo').show();
$('#ConfigLoadServerTemplateError').hide();
$('#ConfigLoadError').hide();
$ConfigContent.hide();
configSaved = false;
}
this.shown = function()
{
Options.loadConfig({
complete: buildPage,
configError: loadConfigError,
serverTemplateError: loadServerTemplateError
});
}
this.hide = function()
{
Options.cleanup();
config = null;
$ConfigNav.children().not('.config-static').remove();
$ConfigData.children().not('.config-static').remove();
}
function loadConfigError(message, resultObj)
{
$('#ConfigLoadInfo').hide();
$('#ConfigLoadError').show();
if (resultObj && resultObj.error && resultObj.error.message)
{
message = resultObj.error.message;
}
$('#ConfigLoadErrorText').text(message);
}
function loadServerTemplateError()
{
$('#ConfigLoadInfo').hide();
$('#ConfigLoadServerTemplateError').show();
var optConfigTemplate = Options.option('ConfigTemplate');
$('#ConfigLoadServerTemplateErrorEmpty').toggle(optConfigTemplate === '');
$('#ConfigLoadServerTemplateErrorNotFound').toggle(optConfigTemplate !== '');
$('#ConfigLoadServerTemplateErrorWebDir').text(Options.option('WebDir'));
$('#ConfigLoadServerTemplateErrorConfigFile').text(Options.option('ConfigFile'));
}
function findOptionByName(name)
{
name = name.toLowerCase();
for (var k=0; k < config.length; k++)
{
var sections = config[k].sections;
for (var i=0; i < sections.length; i++)
{
var section = sections[i];
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template &&
((option.Name && option.Name.toLowerCase() === name) ||
(option.name && option.name.toLowerCase() === name)))
{
return option;
}
}
}
}
return null;
}
this.findOptionByName = findOptionByName;
function findOptionById(formId)
{
for (var k=0; k < config.length; k++)
{
var sections = config[k].sections;
for (var i=0; i < sections.length; i++)
{
var section = sections[i];
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (option.formId === formId)
{
return option;
}
}
}
}
return null;
}
function findSectionById(sectionId)
{
for (var k=0; k < config.length; k++)
{
var sections = config[k].sections;
for (var i=0; i < sections.length; i++)
{
var section = sections[i];
if (section.id === sectionId)
{
return section;
}
}
}
return null;
}
this.processShortcut = function(key)
{
switch (key)
{
case 'Shift+F': $('#ConfigTable_filter').focus(); return true;
case 'Shift+C': $('#ConfigTable_clearfilter').click(); return true;
}
}
/*** GENERATE HTML PAGE *****************************************************************/
function buildOptionsContent(section, extensionsec)
{
var html = '';
var lastmultiid = 1;
var firstmultioption = true;
var hasoptions = false;
for (var i=0, op=0; i < section.options.length; i++)
{
if (i > 0 && extensionsec && Options.restricted)
{
// in restricted mode don't show any options for extension scripts,
// option's content is hidden content anyway (***)
break;
}
var option = section.options[i];
if (!option.template)
{
if (section.multi && option.multiid !== lastmultiid)
{
// new set in multi section
html += buildMultiRowEnd(section, lastmultiid, true, true);
lastmultiid = option.multiid;
firstmultioption = true;
}
if (section.multi && firstmultioption)
{
html += buildMultiRowStart(section, option.multiid, option);
firstmultioption = false;
}
html += buildOptionRow(option, section);
hasoptions = true;
op++;
}
}
if (section.multi)
{
html += buildMultiRowEnd(section, lastmultiid, false, hasoptions);
}
return html;
}
this.buildOptionsContent = buildOptionsContent;
function buildMultiSetContent(section, multiid)
{
var html = '';
var firstmultioption = true;
var hasoptions = false;
for (var i=0, op=0; i < section.options.length; i++)
{
var option = section.options[i];
if (!option.template && option.multiid === multiid)
{
if (firstmultioption)
{
html += buildMultiRowStart(section, multiid, option);
firstmultioption = false;
}
html += buildOptionRow(option, section);
hasoptions = true;
op++;
}
}
html += buildMultiRowEnd(section, multiid, true, hasoptions);
return html;
}
function buildOptionRow(option, section)
{
var value = option.value;
if (option.value === null)
{
value = option.defvalue;
}
option.formId = (option.name.indexOf(':') == -1 ? 'S_' : '') + Util.makeId(option.name);
var caption = option.caption;
if (section.multi)
{
caption = '<span class="config-multicaption">' + caption.substring(0, caption.indexOf('.') + 1) + '</span>' + caption.substring(caption.indexOf('.') + 1);
}
var html =
'<div class="control-group ' + option.sectionId + (section.multi ? ' multiid' + option.multiid + ' multiset' : '') + '">'+
'<label class="control-label">' +
'<a class="option-name" href="#" data-optid="' + option.formId + '" '+
'onclick="Config.scrollToOption(event, this)">' + caption + '</a>' +
(option.value === null && !section.postparam && !option.commandopts ?
' <a data-toggle="modal" href="#ConfigNewOptionHelp" class="label label-info">new</a>' : '') + '</label>'+
'<div class="controls">';
if (option.nocontent)
{
option.type = 'info';
html += '<div class="" id="' + option.formId + '"/>';
}
else if (option.select.length > 1)
{
option.type = 'switch';
html += '<div class="btn-group btn-switch" id="' + option.formId + '">';
var valfound = false;
for (var j=0; j < option.select.length; j++)
{
var pvalue = option.select[j];
if (value && pvalue.toLowerCase() === value.toLowerCase())
{
html += '<input type="button" class="btn btn-primary" value="' + Util.textToAttr(pvalue) + '" onclick="Config.switchClick(this)">';
valfound = true;
}
else
{
html += '<input type="button" class="btn" value="' + Util.textToAttr(pvalue) + '" onclick="Config.switchClick(this)">';
}
}
if (!valfound)
{
html += '<input type="button" class="btn btn-primary" value="' + Util.textToAttr(value) + '" onclick="Config.switchClick(this)">';
}
html +='</div>';
}
else if (option.select.length === 1)
{
option.type = 'numeric';
html += '<div class="input-append">'+
'<input type="text" id="' + option.formId + '" value="' + Util.textToAttr(value) + '" class="editnumeric">'+
'<span class="add-on">'+ option.select[0] +'</span>'+
'</div>';
}
else if (option.caption.toLowerCase().indexOf('password') > -1 &&
option.name.toLowerCase() !== '*unpack:password')
{
option.type = 'password';
html += '<div class="password-field input-append">' +
'<input type="password" id="' + option.formId + '" value="' + Util.textToAttr(value) + '" class="editsmall">'+
'<span class="add-on">'+
'<label class="checkbox">'+
'<input type="checkbox" onclick="Config.togglePassword(this, \'' + option.formId + '\')" /> Show'+
'</label>'+
'</span>'+
'</div>';
}
else if (option.caption.toLowerCase().indexOf('username') > -1 ||
(option.caption.indexOf('IP') > -1 && option.name.toLowerCase() !== 'authorizedip'))
{
option.type = 'text';
html += '<input type="text" id="' + option.formId + '" value="' + Util.textToAttr(value) + '" class="editsmall">';
}
else if (option.editor)
{
option.type = 'text';
html += '<table class="editor"><tr><td>';
html += '<input type="text" id="' + option.formId + '" value="' + Util.textToAttr(value) + '">';
html += '</td><td>';
html += '<button type="button" id="' + option.formId + '_Editor" class="btn" onclick="' + option.editor.click + '($(\'input\', $(this).closest(\'table\')).attr(\'id\'))">' + option.editor.caption + '</button>';
html += '</td></tr></table>';
}
else if (option.commandopts)
{
option.type = 'command';
html += '<button type="button" id="' + option.formId + '" class="btn ' +
(option.commandopts.indexOf('danger') > -1 ? 'btn-danger' : 'btn-inverse') +
'" onclick="Config.commandClick(this)">' + value + '</button>';
}
else
{
option.type = 'text';
html += '<input type="text" id="' + option.formId + '" value="' + Util.textToAttr(value) + '" class="editlarge">';
}
if (option.description !== '')
{
var htmldescr = option.description;
htmldescr = htmldescr.replace(/NOTE: do not forget to uncomment the next line.\n/, '');
// replace option references
var exp = /\<([A-Z0-9\.]*)\>/ig;
htmldescr = htmldescr.replace(exp, '<a class="option" href="#" onclick="Config.scrollToOption(event, this)">$1</a>');
htmldescr = htmldescr.replace(/&/g, '&');
// add extra new line after Examples not ended with dot
htmldescr = htmldescr.replace(/Example:.*/g, function (match) {
return match + (Util.endsWith(match, '.') ? '' : '\n');
});
// replace URLs
exp = /(http:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
htmldescr = htmldescr.replace(exp, "<a href='$1'>$1</a>");
// highlight first line
htmldescr = htmldescr.replace(/\n/, '</span>\n');
htmldescr = '<span class="help-option-title">' + htmldescr;
htmldescr = htmldescr.replace(/\n/g, '<br>');
htmldescr = htmldescr.replace(/NOTE: /g, '<span class="label label-warning">NOTE:</span> ');
htmldescr = htmldescr.replace(/INFO: /g, '<span class="label label-info">INFO:</span> ');
if (htmldescr.indexOf('INFO FOR DEVELOPERS:') > -1)
{
htmldescr = htmldescr.replace(/INFO FOR DEVELOPERS:<br>/g, '<input class="btn btn-mini" value="Show more info for developers" type="button" onclick="Config.showSpoiler(this)"><span class="hide">');
htmldescr += '</span>';
}
if (htmldescr.indexOf('MORE INFO:') > -1)
{
htmldescr = htmldescr.replace(/MORE INFO:<br>/g, '<input class="btn btn-mini" value="Show more info" type="button" onclick="Config.showSpoiler(this)"><span class="hide">');
htmldescr += '</span>';
}
if (section.multi)
{
// replace strings like "TaskX.Command" and "Task1.Command"
htmldescr = htmldescr.replace(new RegExp(section.multiprefix + '[X|1]\.', 'g'), '');
}
html += '<p class="help-block">' + htmldescr + '</p>';
}
html += '</div>';
html += '</div>';
return html;
}
function buildMultiRowStart(section, multiid, option)
{
var name = option.caption;
var setname = name.substr(0, name.indexOf('.'));
var html = '<div class="config-settitle ' + section.id + ' multiid' + multiid + ' multiset">' + setname + '</div>';
return html;
}
function buildMultiRowEnd(section, multiid, hasmore, hasoptions)
{
var name = section.options[0].caption;
var setname = name.substr(0, name.indexOf('1'));
var html = '';
if (hasoptions)
{
html += '<div class="' + section.id + ' multiid' + multiid + ' multiset multiset-toolbar">';
html += '<button type="button" class="btn config-button config-delete" data-multiid="' + multiid + '" ' +
'onclick="Config.deleteSet(this, \'' + setname + '\',\'' + section.id + '\')">Delete ' + setname + multiid + '</button>';
html += ' <button type="button" class="btn config-button" data-multiid="' + multiid + '" ' +
'onclick="Config.moveSet(this, \'' + setname + '\',\'' + section.id + '\', \'up\')">Move Up</button>';
html += ' <button type="button" class="btn config-button" data-multiid="' + multiid + '" ' +
'onclick="Config.moveSet(this, \'' + setname + '\',\'' + section.id + '\', \'down\')">Move Down</button>';
if (setname.toLowerCase() === 'feed')
{
html += ' <button type="button" class="btn config-button" data-multiid="' + multiid + '" ' +
'onclick="Config.previewFeed(this, \'' + setname + '\',\'' + section.id + '\')">Preview Feed</button>';
}
if (setname.toLowerCase() === 'server')
{
html += ' <button type="button" class="btn config-button" data-multiid="' + multiid + '" ' +
'onclick="Config.testConnection(this, \'' + setname + '\',\'' + section.id + '\')">Test Connection</button>';
html += ' <button type="button" class="btn config-button" data-multiid="' + multiid + '" ' +
'onclick="Config.serverStats(this, \'' + setname + '\',\'' + section.id + '\')">Volume Statistics</button>';
}
html += '<hr>';
html += '</div>';
}
if (!hasmore)
{
html += '<div class="' + section.id + '">';
html += '<button type="button" class="btn config-add ' + section.id + ' multiset" onclick="Config.addSet(\'' + setname + '\',\'' + section.id +
'\')">Add ' + (hasoptions ? 'another ' : '') + setname + '</button>';
html += '</div>';
}
return html;
}
function buildPage(_config)
{
config = _config;
extendConfig();
$ConfigNav.children().not('.config-static').remove();
$ConfigData.children().not('.config-static').remove();
for (var k=0; k < config.length; k++)
{
if (k == 1 || k == 2)
{
$ConfigNav.append('<li class="divider"></li>');
}
var conf = config[k];
var added = false;
for (var i=0; i < conf.sections.length; i++)
{
var section = conf.sections[i];
if (!section.hidden)
{
var html = $('<li><a href="#' + section.id + '">' + section.name + '</a></li>');
$ConfigNav.append(html);
var content = buildOptionsContent(section, k > 1);
$ConfigData.append(content);
added = true;
}
}
if (!added)
{
var html = $('<li><a href="#' + conf.id + '">' + conf.name + '</a></li>');
$ConfigNav.append(html);
}
}
notifyChanges();
$ConfigNav.append('<li class="divider hide ConfigSearch"></li>');
$ConfigNav.append('<li class="hide ConfigSearch"><a href="#Search">SEARCH RESULTS</a></li>');
$ConfigNav.toggleClass('long-list', $ConfigNav.children().length > 20);
showSection('Config-Info', false);
if (filterText !== '')
{
filterInput(filterText);
}
$('#ConfigLoadInfo').hide();
$ConfigContent.show();
}
function extendConfig()
{
for (var i=2; i < config.length; i++)
{
var conf = config[i];
var firstVisibleSection = null;
var visibleSections = 0;
for (var j=0; j < conf.sections.length; j++)
{
if (!conf.sections[j].hidden)
{
if (!firstVisibleSection)
{
firstVisibleSection = conf.sections[j];
}
visibleSections++;
}
}
// rename sections
for (var j=0; j < conf.sections.length; j++)
{
var section = conf.sections[j];
section.name = conf.shortName.toUpperCase() + (visibleSections > 1 ? ' - ' + section.name.toUpperCase() + '' : '');
section.caption = conf.name.toUpperCase() + (visibleSections > 1 ? ' - ' + section.name.toUpperCase() + '' : '');
}
if (!firstVisibleSection)
{
// create new section for virtual option "About".
var section = {};
section.name = conf.shortName.toUpperCase();
section.caption = conf.name.toUpperCase();
section.id = conf.id + '_';
section.options = [];
firstVisibleSection = section;
conf.sections.push(section);
}
// create virtual option "About" with scripts description.
var option = {};
var shortName = conf.scriptName.replace(/^.*[\\\/]/, ''); // leave only file name (remove path)
shortName = shortName.substr(0, shortName.lastIndexOf('.')) || shortName; // remove file extension
option.caption = 'About ' + shortName;
option.name = conf.nameprefix + option.caption;
option.value = '';
option.defvalue = '';
option.sectionId = firstVisibleSection.id;
option.select = [];
var description = conf.description;
option.description = description !== '' ? description : 'No description available.';
option.nocontent = true;
firstVisibleSection.options.unshift(option);
}
// register editors for certain options
var conf = config[1];
for (var j=0; j < conf.sections.length; j++)
{
var section = conf.sections[j];
for (var k=0; k < section.options.length; k++)
{
var option = section.options[k];
var optname = option.name.toLowerCase();
if (optname === 'scriptorder')
{
option.editor = { caption: 'Reorder', click: 'Config.editScriptOrder' };
}
if (optname === 'extensions')
{
option.editor = { caption: 'Choose', click: 'Config.editExtensions' };
}
if (optname.indexOf('category') > -1 && optname.indexOf('.extensions') > -1)
{
option.editor = { caption: 'Choose', click: 'Config.editCategoryExtensions' };
}
if (optname.indexOf('feed') > -1 && optname.indexOf('.extensions') > -1)
{
option.editor = { caption: 'Choose', click: 'Config.editFeedExtensions' };
}
if (optname.indexOf('task') > -1 && optname.indexOf('.param') > -1)
{
option.editor = { caption: 'Choose', click: 'Config.editSchedulerScript' };
}
if (optname.indexOf('task') > -1 && optname.indexOf('.command') > -1)
{
option.onchange = Config.schedulerCommandChanged;
}
if (optname.indexOf('.filter') > -1)
{
option.editor = { caption: 'Change', click: 'Config.editFilter' };
}
}
}
}
function notifyChanges()
{
for (var k=0; k < config.length; k++)
{
var sections = config[k].sections;
for (var i=0; i < sections.length; i++)
{
var section = sections[i];
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (option.onchange && !option.template)
{
option.onchange(option);
}
}
}
}
}
function scrollOptionIntoView(optFormId)
{
var option = findOptionById(optFormId);
// switch to tab and scroll the option into view
showSection(option.sectionId, false);
var element = $('#' + option.formId);
var parent = $('html,body');
parent[0].scrollIntoView(true);
var offsetY = 15;
if ($('body').hasClass('navfixed')) {
offsetY = 55;
}
parent.animate({ scrollTop: parent.scrollTop() + element.offset().top - parent.offset().top - offsetY }, { duration: 'slow', easing: 'swing' });
}
this.switchClick = function(control)
{
$('.btn', $(control).parent()).removeClass('btn-primary');
$(control).addClass('btn-primary');
// not for page Postprocess in download details
if (config)
{
var optFormId = $(control).parent().attr('id');
var option = findOptionById(optFormId);
if (option.onchange)
{
option.onchange(option);
}
}
}
function switchGetValue(control)
{
var state = $('.btn-primary', control).val();
return state;
}
function switchSetValue(control, value)
{
$('.btn', control).removeClass('btn-primary');
$('.btn@[value=' + value + ']', control).addClass('btn-primary');
}
this.togglePassword = function(control, target)
{
var checked = $(control).is(':checked');
$('#'+target).prop('type', checked ? 'text' : 'password');
}
/*** CHANGE/ADD/REMOVE OPTIONS *************************************************************/
function navClick(event)
{
event.preventDefault();
var sectionId = $(this).attr('href').substr(1);
showSection(sectionId, true);
}
function showSection(sectionId, animateScroll)
{
var link = $('a[href="#' + sectionId + '"]', $ConfigNav);
$('li', $ConfigNav).removeClass('active');
link.closest('li').addClass('active');
$ConfigContent.removeClass('search');
Util.show($ViewButton, sectionId !== 'Config-Info' && sectionId !== 'Config-About');
$ConfigInfo.hide();
$ConfigAbout.hide();
if (sectionId === 'Search')
{
search();
return;
}
lastSection = sectionId;
if (sectionId === 'Config-Info')
{
$ConfigInfo.show();
$ConfigData.children().hide();
$ConfigTitle.text('INFO: SETTINGS');
return;
}
if (sectionId === 'Config-About')
{
$ConfigAbout.show();
$ConfigData.children().hide();
$ConfigTitle.text('NZBGET ' + Options.option('version'));
return;
}
if (sectionId === 'Config-System')
{
$ConfigData.children().hide();
$('.config-system', $ConfigData).show();
markLastControlGroup();
$ConfigTitle.text('SYSTEM');
return;
}
$ConfigData.children().hide();
var opts = $('.' + sectionId, $ConfigData);
opts.show();
markLastControlGroup();
var section = findSectionById(sectionId);
$ConfigTitle.text(section.caption ? section.caption : section.name);
$Body.animate({ scrollTop: 0 }, { duration: animateScroll ? 'slow' : 0, easing: 'swing' });
}
this.deleteSet = function(control, setname, sectionId)
{
var multiid = parseInt($(control).attr('data-multiid'));
$('#ConfigDeleteConfirmDialog_Option').text(setname + multiid);
ConfirmDialog.showModal('ConfigDeleteConfirmDialog', function()
{
deleteOptionSet(setname, multiid, sectionId);
});
}
function deleteOptionSet(setname, multiid, sectionId)
{
// remove options from page, using a temporary div for slide effect
var opts = $('.' + sectionId + '.multiid' + multiid, $ConfigData);
var div = $('<div></div>');
opts.first().before(div);
div.append(opts);
div.slideUp('normal', function()
{
div.remove();
});
// remove option set from config
var section = findSectionById(sectionId);
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template && option.multiid === multiid)
{
section.options.splice(j, 1);
j--;
}
}
// reformat remaining sets (captions, input IDs, etc.)
reformatSection(section, setname);
section.modified = true;
}
function reformatSection(section, setname)
{
var hasOptions = false;
var lastMultiId = 0;
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template)
{
if (option.multiid !== lastMultiId && option.multiid !== lastMultiId + 1)
{
reformatSet(section, setname, option.multiid, lastMultiId + 1);
}
lastMultiId = option.multiid;
hasOptions = true;
}
}
// update add-button
var addButton = $('.config-add.' + section.id, $ConfigData);
addButton.text('Add ' + (hasOptions ? 'another ' : '') + setname);
}
function reformatSet(section, setname, oldMultiId, newMultiId)
{
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template && option.multiid == oldMultiId)
{
// reformat multiid
var div = $('#' + setname + oldMultiId);
div.attr('id', setname + newMultiId);
// update captions
$('.config-settitle.' + section.id + '.multiid' + oldMultiId, $ConfigData).text(setname + newMultiId);
$('.' + section.id + '.multiid' + oldMultiId + ' .config-multicaption', $ConfigData).text(setname + newMultiId + '.');
$('.' + section.id + '.multiid' + oldMultiId + ' .config-delete', $ConfigData).text('Delete ' + setname + newMultiId);
//update data id
$('.' + section.id + '.multiid' + oldMultiId + ' .config-button', $ConfigData).attr('data-multiid', newMultiId);
//update class
$('.' + section.id + '.multiid' + oldMultiId, $ConfigData).removeClass('multiid' + oldMultiId).addClass('multiid' + newMultiId);
// update input id
var oldFormId = option.formId;
option.formId = option.formId.replace(new RegExp(option.multiid), newMultiId);
$('#' + oldFormId).attr('id', option.formId);
// update label data-optid
$('a[data-optid=' + oldFormId + ']').attr('data-optid', option.formId);
// update editor id
$('#' + oldFormId + '_Editor').attr('id', option.formId + '_Editor');
// update name
option.name = option.name.replace(new RegExp(option.multiid), newMultiId);
option.multiid = newMultiId;
}
}
}
this.addSet = function(setname, sectionId)
{
// find section
var section = findSectionById(sectionId);
// find max multiid
var multiid = 0;
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template && option.multiid > multiid)
{
multiid = option.multiid;
}
}
multiid++;
// create new multi set
var addedOptions = [];
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (option.template)
{
var name = option.name.replace(/1/, multiid);
// copy option
var newoption = $.extend({}, option);
newoption.name = name;
newoption.caption = option.caption.replace(/1/, multiid);
newoption.template = false;
newoption.multiid = multiid;
section.options.push(newoption);
addedOptions.push(newoption);
}
}
section.modified = true;
// visualize new multi set
var html = buildMultiSetContent(section, multiid);
var addButton = $('.config-add.' + section.id, $ConfigData);
addButton.text('Add another ' + setname);
// insert before add-button, using a temporary div for slide effect
var div = $('<div>' + html + '</div>');
div.hide();
addButton.parent().before(div);
for (var j=0; j < addedOptions.length; j++)
{
var option = addedOptions[j];
if (option.onchange)
{
option.onchange(option);
}
}
div.slideDown('normal', function()
{
var opts = div.children();
opts.detach();
div.after(opts);
div.remove();
});
}
this.moveSet = function(control, setname, sectionId, direction)
{
var id1 = parseInt($(control).attr('data-multiid'));
var id2 = direction === 'down' ? id1 + 1 : id1 - 1;
// swap options in two sets
var opts1 = $('.' + sectionId + '.multiid' + (direction === 'down' ? id1 : id2), $ConfigData);
var opts2 = $('.' + sectionId + '.multiid' + (direction === 'down' ? id2 : id1), $ConfigData);
if (opts1.length === 0 || opts2.length === 0)
{
return;
}
opts1.first().before(opts2);
// reformat remaining sets (captions, input IDs, etc.)
var section = findSectionById(sectionId);
reformatSet(section, setname, id2, 10000 + id2);
reformatSet(section, setname, id1, id2);
reformatSet(section, setname, 10000 + id2, id1);
section.modified = true;
}
this.viewMode = function()
{
compactMode = !compactMode;
UISettings.write('$Config_ViewCompact', compactMode ? 'yes' : 'no');
setViewMode();
}
function setViewMode()
{
$('#Config_ViewCompact i').toggleClass('icon-ok', compactMode).toggleClass('icon-empty', !compactMode);
$ConfigContent.toggleClass('hide-help-block', compactMode);
}
/*** OPTION SPECIFIC EDITORS *************************************************/
this.editScriptOrder = function(optFormId)
{
var option = findOptionById(optFormId);
ScriptListDialog.showModal(option, config, null);
}
this.editExtensions = function(optFormId)
{
var option = findOptionById(optFormId);
ScriptListDialog.showModal(option, config, ['post', 'scan', 'queue', 'defscheduler']);
}
this.editCategoryExtensions = function(optFormId)
{
var option = findOptionById(optFormId);
ScriptListDialog.showModal(option, config, ['post', 'scan', 'queue']);
}
this.editFeedExtensions = function(optFormId)
{
var option = findOptionById(optFormId);
ScriptListDialog.showModal(option, config, ['feed']);
}
this.editSchedulerScript = function(optFormId)
{
var option = findOptionById(optFormId);
var command = getOptionValue(findOptionById(optFormId.replace(/Param/, 'Command')));
if (command !== 'Script')
{
alert('This button is to choose scheduler scripts when option TaskX.Command is set to "Script".');
return;
}
ScriptListDialog.showModal(option, config, ['scheduler']);
}
this.schedulerCommandChanged = function(option)
{
var command = getOptionValue(option);
var btnId = option.formId.replace(/Command/, 'Param_Editor');
Util.show('#' + btnId, command === 'Script');
}
this.commandClick = function(button)
{
var optFormId = $(button).attr('id');
var option = findOptionById(optFormId);
var script = option.name.substr(0, option.name.indexOf(':'));
var command = option.name.substr(option.name.indexOf(':') + 1, 1000);
var changedOptions = prepareSaveRequest(true, false, true);
function execScript()
{
ExecScriptDialog.showModal(script, command, 'SETTINGS', changedOptions);
}
if (option.commandopts.indexOf('danger') > -1)
{
$('#DangerScriptConfirmDialog_OK').text(option.defvalue);
$('#DangerScriptConfirmDialog_Command').text(command);
ConfirmDialog.showModal('DangerScriptConfirmDialog', execScript);
}
else
{
execScript();
}
}
/*** RSS FEEDS ********************************************************************/
this.editFilter = function(optFormId)
{
var option = findOptionById(optFormId);
FeedFilterDialog.showModal(
option.multiid,
getOptionValue(findOptionByName('Feed' + option.multiid + '.Name')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.URL')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.Filter')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.Backlog')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.PauseNzb')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.Category')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.Priority')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.Interval')),
getOptionValue(findOptionByName('Feed' + option.multiid + '.Extensions')),
function(filter)
{
var control = $('#' + option.formId);
control.val(filter);
});
}
this.previewFeed = function(control, setname, sectionId)
{
var multiid = parseInt($(control).attr('data-multiid'));
FeedDialog.showModal(multiid,
getOptionValue(findOptionByName('Feed' + multiid + '.Name')),
getOptionValue(findOptionByName('Feed' + multiid + '.URL')),
getOptionValue(findOptionByName('Feed' + multiid + '.Filter')),
getOptionValue(findOptionByName('Feed' + multiid + '.Backlog')),
getOptionValue(findOptionByName('Feed' + multiid + '.PauseNzb')),
getOptionValue(findOptionByName('Feed' + multiid + '.Category')),
getOptionValue(findOptionByName('Feed' + multiid + '.Priority')),
getOptionValue(findOptionByName('Feed' + multiid + '.Interval')),
getOptionValue(findOptionByName('Feed' + multiid + '.Extensions')));
}
/*** TEST SERVER ********************************************************************/
var connecting = false;
this.testConnection = function(control, setname, sectionId)
{
if (connecting)
{
return;
}
connecting = true;
$('#Notif_Config_TestConnectionProgress').fadeIn(function() {
var multiid = parseInt($(control).attr('data-multiid'));
var timeout = Math.min(parseInt(getOptionValue(findOptionByName('ArticleTimeout'))), 10);
RPC.call('testserver', [
getOptionValue(findOptionByName('Server' + multiid + '.Host')),
parseInt(getOptionValue(findOptionByName('Server' + multiid + '.Port'))),
getOptionValue(findOptionByName('Server' + multiid + '.Username')),
getOptionValue(findOptionByName('Server' + multiid + '.Password')),
getOptionValue(findOptionByName('Server' + multiid + '.Encryption')) === 'yes',
getOptionValue(findOptionByName('Server' + multiid + '.Cipher')),
timeout
],
function(errtext) {
$('#Notif_Config_TestConnectionProgress').fadeOut(function() {
if (errtext == '')
{
PopupNotification.show('#Notif_Config_TestConnectionOK');
}
else
{
AlertDialog.showModal('Connection test failed', errtext);
}
});
connecting = false;
},
function(message, resultObj) {
$('#Notif_Config_TestConnectionProgress').fadeOut(function() {
if (resultObj && resultObj.error && resultObj.error.message)
{
message = resultObj.error.message;
}
AlertDialog.showModal('Connection test failed', message);
connecting = false;
});
});
});
}
/*** DOWNLOADED VOLUMES FOR A SERVER *******************************************************/
this.serverStats = function(control, setname, sectionId)
{
var multiid = parseInt($(control).attr('data-multiid'));
// Because the settings page isn't saved yet and user can reorder servers
// we cannot just use the server-id in teh call to "StatDialog".
// Instead we need to find the server in loaded options and get its ID from there.
var serverName = getOptionValue(findOptionByName('Server' + multiid + '.Name'));
var serverHost = getOptionValue(findOptionByName('Server' + multiid + '.Host'));
var serverPort = getOptionValue(findOptionByName('Server' + multiid + '.Port'));
var serverId = 0;
// searching by name
var opt = undefined;
for (var i = 1; opt !== null; i++)
{
var opt = Options.option('Server' + i + '.Name');
if (opt === serverName)
{
serverId = i;
break;
}
}
if (serverId === 0)
{
// searching by host:port
var host = undefined;
for (var i = 1; host !== null; i++)
{
host = Options.option('Server' + i + '.Host');
var port = Options.option('Server' + i + '.Port');
if (host === serverHost && port === serverPort)
{
serverId = i;
break;
}
}
}
if (serverId === 0)
{
AlertDialog.showModal('Downloaded volumes', 'No statistics available for that server yet.');
return;
}
StatDialog.showModal(serverId);
}
/*** SAVE ********************************************************************/
function getOptionValue(option)
{
var control = $('#' + option.formId);
if (option.type === 'switch')
{
return switchGetValue(control);
}
else
{
return control.val();
}
}
this.getOptionValue = getOptionValue;
function setOptionValue(option, value)
{
var control = $('#' + option.formId);
if (option.type === 'switch')
{
switchSetValue(control, value);
}
else
{
control.val(value);
}
}
this.setOptionValue = setOptionValue;
// Checks if there are obsolete or invalid options
function invalidOptionsExist()
{
var hiddenOptions = ['ConfigFile', 'AppBin', 'AppDir', 'Version'];
for (var i=0; i < Options.options.length; i++)
{
var option = Options.options[i];
var confOpt = findOptionByName(option.Name);
if (!confOpt && hiddenOptions.indexOf(option.Name) === -1)
{
return true;
}
}
return false;
}
function prepareSaveRequest(onlyUserChanges, webSettings, onlyChangedOptions)
{
var modified = false;
var request = [];
for (var k = (webSettings ? 0 : 1); k < (webSettings ? 1 : config.length); k++)
{
var sections = config[k].sections;
for (var i=0; i < sections.length; i++)
{
var section = sections[i];
if (!section.postparam)
{
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template && !(option.type === 'info') && !option.commandopts)
{
var oldValue = option.value;
var newValue = getOptionValue(option);
if (section.hidden)
{
newValue = oldValue;
}
if (newValue != null)
{
if (onlyUserChanges)
{
var optmodified = oldValue != newValue && oldValue !== null;
}
else
{
var optmodified = (oldValue != newValue) || (option.value === null);
}
modified = modified || optmodified;
if (optmodified || !onlyChangedOptions)
{
var opt = {Name: option.name, Value: newValue};
request.push(opt);
}
}
}
modified = modified || section.modified;
}
}
}
}
return modified || (!onlyUserChanges && invalidOptionsExist()) || restored ? request : [];
}
this.saveChanges = function()
{
$LeaveConfigDialog.modal('hide');
var serverSaveRequest = prepareSaveRequest(false, false);
var webSaveRequest = prepareSaveRequest(false, true);
if (serverSaveRequest.length === 0 && webSaveRequest.length === 0)
{
PopupNotification.show('#Notif_Config_Unchanged');
return;
}
showSaveBanner();
Util.show('#ConfigSaved_Reload, #ConfigReload', serverSaveRequest.length > 0);
if (webSaveRequest.length > 0)
{
saveWebSettings(webSaveRequest);
}
if (serverSaveRequest.length > 0)
{
$('#Notif_Config_Failed_Filename').text(Options.option('ConfigFile'));
RPC.call('saveconfig', [serverSaveRequest], saveCompleted);
}
else
{
// only web-settings were changed, refresh page
document.location.reload(true);
}
}
function showSaveBanner()
{
$('#Config_Save').attr('disabled', 'disabled');
}
function removeSaveBanner()
{
$('#Config_Save').removeAttr('disabled');
}
function saveCompleted(result)
{
removeSaveBanner();
if (result)
{
$ConfigContent.fadeOut(function() { $('#ConfigSaved').fadeIn(); });
}
else
{
PopupNotification.show('#Notif_Config_Failed');
}
configSaved = true;
}
function saveWebSettings(values)
{
for (var i=0; i < values.length; i++)
{
var option = values[i];
var optname = option.Name;
var optvalue = option.Value;
optname = optname[0].toLowerCase() + optname.substring(1);
if (optvalue === 'yes') optvalue = true;
if (optvalue === 'no') optvalue = false;
UISettings[optname] = optvalue;
}
UISettings.save();
}
this.canLeaveTab = function(target)
{
if (!config || prepareSaveRequest(true).length === 0 || configSaved)
{
return true;
}
leaveTarget = target;
$LeaveConfigDialog.modal({backdrop: 'static'});
return false;
}
function userLeavesPage(e)
{
if (config && !configSaved && !UISettings.connectionError && prepareSaveRequest(true).length > 0)
{
return "Discard changes?";
}
}
this.discardChanges = function()
{
configSaved = true;
$LeaveConfigDialog.modal('hide');
leaveTarget.click();
}
this.scrollToOption = function(event, control)
{
event.preventDefault();
if ($(control).hasClass('option-name') && !$ConfigContent.hasClass('search'))
{
// Click on option title scrolls only from Search-page, not from regual pages
return;
}
var optid = $(control).attr('data-optid');
if (!optid)
{
var optname = $(control).text();
var option = findOptionByName(optname);
if (option)
{
optid = option.formId;
}
}
if (optid)
{
scrollOptionIntoView(optid);
}
}
this.showSpoiler = function(control)
{
$(control).hide();
$(control).next().show();
}
function filterInput(value)
{
filterText = value;
if (filterText.trim() !== '')
{
$('.ConfigSearch').show();
showSection('Search', true);
}
else
{
filterClear();
}
}
function filterClear()
{
filterText = '';
showSection(lastSection, true);
$('.ConfigSearch').hide();
$ConfigTabBadge.hide();
$ConfigTabBadgeEmpty.show();
}
var searcher = new FastSearcher();
function search()
{
$ConfigTabBadge.show();
$ConfigTabBadgeEmpty.hide();
$ConfigContent.addClass('search');
$ConfigData.children().hide();
searcher.compile(filterText);
var total = 0;
var available = 0;
for (var k=0; k < config.length; k++)
{
var sections = config[k].sections;
for (var i=0; i < sections.length; i++)
{
var section = sections[i];
if (!section.hidden)
{
for (var j=0; j < section.options.length; j++)
{
var option = section.options[j];
if (!option.template)
{
total++;
if (filterOption(option))
{
available++;
var opt = $('#' + option.formId).closest('.control-group');
opt.show();
}
}
}
}
}
}
filterStaticPages();
markLastControlGroup();
$ConfigTitle.text('SEARCH RESULTS');
$Body.animate({ scrollTop: 0 }, { duration: 0 });
updateTabInfo($ConfigTabBadge, { filter: true, available: available, total: total});
}
function filterOption(option)
{
return searcher.exec({ name: option.caption, description: option.description, value: (option.value === null ? '' : option.value), _search: ['name', 'description', 'value'] });
}
function filterStaticPages()
{
$ConfigData.children().filter('.config-static').each(function(index, element)
{
var name = $('.control-label', element).text();
var description = $('.controls', element).text();
var found = searcher.exec({ name: name, description: description, value: '', _search: ['name', 'description'] });
Util.show(element, found);
});
}
function markLastControlGroup()
{
$ConfigData.children().removeClass('last-group');
$ConfigData.children().filter(':visible').last().addClass('last-group');
}
/*** RELOAD ********************************************************************/
function restart(callback)
{
Refresher.pause();
$('#ConfigReloadInfoNotes').hide();
$('body').fadeOut(function()
{
$('#Navbar, #MainContent').hide();
$('#ConfigSaved').hide();
$('body').toggleClass('navfixed', false);
$('body').show();
$('#ConfigReloadInfo').fadeIn();
reloadTime = new Date();
callback();
});
}
this.reloadConfirm = function()
{
ConfirmDialog.showModal('ReloadConfirmDialog', Config.reload);
}
this.reload = function()
{
$('#ConfigReloadAction').text('Stopping all activities and reloading...');
restart(function() { RPC.call('reload', [], reloadCheckStatus); });
}
function reloadCheckStatus()
{
RPC.call('status', [], function(status)
{
// OK, checking if it is a restarted instance
if (status.UpTimeSec >= Status.status.UpTimeSec)
{
// the old instance is not restarted yet
// waiting 0.5 sec. and retrying
setTimeout(reloadCheckStatus, 500);
reloadCheckNotes();
}
else
{
// restarted successfully
$('#ConfigReloadAction').text('Reloaded successfully. Refreshing the page...');
// refresh page
document.location.reload(true);
}
},
function()
{
// Failure, waiting 0.5 sec. and retrying
setTimeout(reloadCheckStatus, 500);
reloadCheckNotes();
});
}
function reloadCheckNotes()
{
// if reload takes more than 30 sec. show additional tips
if (new Date() - reloadTime > 30000)
{
$('#ConfigReloadInfoNotes').show(1000);
}
}
this.applyReloadedValues = function(values)
{
Options.reloadConfig(values, buildPage);
restored = true;
}
/*** SHUTDOWN ********************************************************************/
this.shutdownConfirm = function()
{
ConfirmDialog.showModal('ShutdownConfirmDialog', Config.shutdown);
}
this.shutdown = function()
{
$('#ConfigReloadTitle').text('Shutdown NZBGet');
$('#ConfigReloadAction').text('Stopping all activities...');
restart(function() { RPC.call('shutdown', [], shutdownCheckStatus); });
}
function shutdownCheckStatus()
{
RPC.call('version', [], function(version)
{
// the program still runs, waiting 0.5 sec. and retrying
setTimeout(shutdownCheckStatus, 500);
},
function()
{
// the program has been stopped
$('#ConfigReloadTransmit').hide();
$('#ConfigReloadAction').text('The program has been stopped.');
});
}
/*** UPDATE ********************************************************************/
this.checkUpdates = function()
{
UpdateDialog.showModal();
}
}(jQuery));
/*** CHOOSE SCRIPT DIALOG *******************************************************/
var ScriptListDialog = (new function($)
{
'use strict'
// Controls
var $ScriptListDialog;
var $ScriptTable;
var option;
var config;
var kind;
var scriptList;
var allScripts;
var orderChanged;
var orderMode;
this.init = function()
{
$ScriptListDialog = $('#ScriptListDialog');
$('#ScriptListDialog_Save').click(save);
$ScriptTable = $('#ScriptListDialog_ScriptTable');
$ScriptTable.fasttable(
{
pagerContainer: '#ScriptListDialog_ScriptTable_pager',
infoEmpty: 'No scripts found. If you just changed option "ScriptDir", save settings and reload NZBGet.',
pageSize: 1000
});
$ScriptListDialog.on('hidden', function()
{
// cleanup
$ScriptTable.fasttable('update', []);
});
}
this.showModal = function(_option, _config, _kind)
{
option = _option;
config = _config;
kind = _kind;
orderChanged = false;
orderMode = option.name === 'ScriptOrder';
if (orderMode)
{
$('#ScriptListDialog_Title').text('Reorder extensions');
$('#ScriptListDialog_Instruction').text('Hover mouse over table elements for reorder buttons to appear.');
}
else
{
$('#ScriptListDialog_Title').text('Choose extensions');
$('#ScriptListDialog_Instruction').html('Select extension scripts for option <strong>' + option.name + '</strong>.');
}
$ScriptTable.toggleClass('table-hidecheck', orderMode);
$ScriptTable.toggleClass('table-check table-cancheck', !orderMode);
$('#ScriptListDialog_OrderInfo').toggleClass('alert alert-info', !orderMode);
Util.show('#ScriptListDialog_OrderInfo', orderMode, 'inline-block');
buildScriptList();
var selectedList = Util.parseCommaList(Config.getOptionValue(option));
updateTable(selectedList);
$ScriptListDialog.modal({backdrop: 'static'});
}
function updateTable(selectedList)
{
var reorderButtons = '<div class="btn-row-order-block"><div class="btn-row-order icon-top" onclick="ScriptListDialog.move(this, \'top\')"></div><div class="btn-row-order icon-up" onclick="ScriptListDialog.move(this, \'up\')"></div><div class="btn-row-order icon-down" onclick="ScriptListDialog.move(this, \'down\')"></div><div class="btn-row-order icon-bottom" onclick="ScriptListDialog.move(this, \'bottom\')"></div></div>';
var data = [];
for (var i=0; i < scriptList.length; i++)
{
var scriptName = scriptList[i];
var scriptShortName = Options.shortScriptName(scriptName);
var fields = ['<div class="check img-check"></div>', '<span data-index="' + i + '">' + scriptShortName + '</span>' + reorderButtons];
var item =
{
id: scriptName,
fields: fields,
};
data.push(item);
if (!orderMode && selectedList && selectedList.indexOf(scriptName) > -1)
{
$ScriptTable.fasttable('checkRow', scriptName, true);
}
}
$ScriptTable.fasttable('update', data);
}
function buildScriptList()
{
var orderList = Util.parseCommaList(Config.getOptionValue(Config.findOptionByName('ScriptOrder')));
var availableScripts = [];
var availableAllScripts = [];
for (var i=2; i < config.length; i++)
{
availableAllScripts.push(config[i].scriptName);
var accept = !kind;
if (!accept)
{
for (var j=0; j < kind.length; j++)
{
accept = accept || config[i][kind[j]];
}
}
if (accept)
{
availableScripts.push(config[i].scriptName);
}
}
availableScripts.sort();
availableAllScripts.sort();
scriptList = [];
allScripts = [];
// first add all scripts from orderList
for (var i=0; i < orderList.length; i++)
{
var scriptName = orderList[i];
if (availableScripts.indexOf(scriptName) > -1)
{
scriptList.push(scriptName);
}
if (availableAllScripts.indexOf(scriptName) > -1)
{
allScripts.push(scriptName);
}
}
// add all other scripts of this kind from script list
for (var i=0; i < availableScripts.length; i++)
{
var scriptName = availableScripts[i];
if (scriptList.indexOf(scriptName) === -1)
{
scriptList.push(scriptName);
}
}
// add all other scripts of other kinds from script list
for (var i=0; i < availableAllScripts.length; i++)
{
var scriptName = availableAllScripts[i];
if (allScripts.indexOf(scriptName) === -1)
{
allScripts.push(scriptName);
}
}
return scriptList;
}
function save(e)
{
e.preventDefault();
if (!orderMode)
{
var selectedList = '';
var checkedRows = $ScriptTable.fasttable('checkedRows');
for (var i=0; i < scriptList.length; i++)
{
var scriptName = scriptList[i];
if (checkedRows[scriptName])
{
selectedList += (selectedList == '' ? '' : ', ') + scriptName;
}
}
var control = $('#' + option.formId);
control.val(selectedList);
}
if (orderChanged)
{
var scriptOrderOption = Config.findOptionByName('ScriptOrder');
var control = $('#' + scriptOrderOption.formId);
// preserving order of scripts of other kinds which were not visible in the dialog
var orderList = [];
for (var i=0; i < allScripts.length; i++)
{
var scriptName = allScripts[i];
if (orderList.indexOf(scriptName) === -1)
{
if (scriptList.indexOf(scriptName) > -1)
{
orderList = orderList.concat(scriptList);
}
else
{
orderList.push(scriptName);
}
}
}
control.val(orderList.join(', '));
}
$ScriptListDialog.modal('hide');
}
this.move = function(control, direction)
{
var index = parseInt($('span', $(control).closest('tr')).attr('data-index'));
if ((index === 0 && (direction === 'up' || direction === 'top')) ||
(index === scriptList.length-1 && (direction === 'down' || direction === 'bottom')))
{
return;
}
switch (direction)
{
case 'up':
case 'down':
{
var newIndex = direction === 'up' ? index - 1 : index + 1;
var tmp = scriptList[newIndex];
scriptList[newIndex] = scriptList[index];
scriptList[index] = tmp;
break;
}
case 'top':
case 'bottom':
{
var tmp = scriptList[index];
scriptList.splice(index, 1);
if (direction === 'top')
{
scriptList.unshift(tmp);
}
else
{
scriptList.push(tmp);
}
break;
}
}
if (!orderChanged && !orderMode)
{
$('#ScriptListDialog_OrderInfo').fadeIn(500);
}
orderChanged = true;
updateTable();
}
}(jQuery));
/*** BACKUP/RESTORE SETTINGS *******************************************************/
var ConfigBackupRestore = (new function($)
{
'use strict'
// State
var settings;
var filename;
this.init = function(options)
{
$('#Config_RestoreInput')[0].addEventListener('change', restoreSelectHandler, false);
}
/*** BACKUP ********************************************************************/
this.backupSettings = function()
{
var settings = '';
for (var i=0; i < Config.config().values.length; i++)
{
var option = Config.config().values[i];
if (option.Value !== null)
{
settings += settings==='' ? '' : '\n';
settings += option.Name + '=' + option.Value;
}
}
var pad = function(arg) { return (arg < 10 ? '0' : '') + arg }
var dt = new Date();
var datestr = dt.getFullYear() + pad(dt.getMonth() + 1) + pad(dt.getDate()) + '-' + pad(dt.getHours()) + pad(dt.getMinutes()) + pad(dt.getSeconds());
var filename = 'nzbget-' + datestr + '.conf';
if (!Util.saveToLocalFile(settings, "text/plain;charset=utf-8", filename))
{
alert('Unfortunately your browser doesn\'t support access to local file system.\n\n'+
'To backup settings you can manually save file "nzbget.conf" (' +
Options.option('ConfigFile')+ ').');
}
}
/*** RESTORE ********************************************************************/
this.restoreSettings = function()
{
if (!window.FileReader)
{
alert("Unfortunately your browser doesn't support FileReader API.");
return;
}
var testreader = new FileReader();
if (!testreader.readAsBinaryString && !testreader.readAsDataURL)
{
alert("Unfortunately your browser doesn't support neither \"readAsBinaryString\" nor \"readAsDataURL\" functions of FileReader API.");
return;
}
var inp = $('#Config_RestoreInput');
// Reset file input control (needed for IE10)
inp.wrap('<form>').closest('form').get(0).reset();
inp.unwrap();
inp.click();
}
function restoreSelectHandler(event)
{
if (!event.target.files)
{
alert("Unfortunately your browser doesn't support direct access to local files.");
return;
}
if (event.target.files.length > 0)
{
restoreFromFile(event.target.files[0]);
}
}
function restoreFromFile(file)
{
var reader = new FileReader();
reader.onload = function (event)
{
if (reader.readAsBinaryString)
{
settings = event.target.result;
}
else
{
var base64str = event.target.result.replace(/^data:[^,]+,/, '');
settings = atob(base64str);
}
filename = file.name;
if (settings.indexOf('MainDir=') < 0)
{
alert('File ' + filename + ' is not a valid NZBGet backup.');
return;
}
RestoreSettingsDialog.showModal(Config.config(), restoreExecute);
};
if (reader.readAsBinaryString)
{
reader.readAsBinaryString(file);
}
else
{
reader.readAsDataURL(file);
}
}
function restoreExecute(selectedSections)
{
$('#Notif_Config_Restoring').show();
setTimeout(function() {
var values = restoreValues(selectedSections);
Config.applyReloadedValues(values);
$('#Notif_Config_Restoring').hide();
$('#SettingsRestoredDialog').modal({backdrop: 'static'});
}, 50);
}
function restoreValues(selectedSections)
{
var config = Config.config();
var values = config.values;
settings = settings.split('\n');
for (var i=0; i < settings.length; i++)
{
var optstr = settings[i];
var ind = optstr.indexOf('=');
var option = { Name: optstr.substr(0, ind).trim(), Value: optstr.substr(ind+1, 100000).trim() };
settings[i] = option;
}
function removeValue(name)
{
var name = name.toLowerCase();
for (var i=0; i < values.length; i++)
{
if (values[i].Name.toLowerCase() === name)
{
values.splice(i, 1);
return true;
}
}
return false;
}
function addValue(name)
{
var name = name.toLowerCase();
for (var i=0; i < settings.length; i++)
{
if (settings[i].Name.toLowerCase() === name)
{
values.push(settings[i]);
return true;
}
}
return false;
}
function restoreOption(option)
{
if (!option.template && !option.multiid)
{
removeValue(option.name);
addValue(option.name);
}
else if (option.template)
{
// delete all multi-options
for (var j=1; ; j++)
{
var optname = option.name.replace('1', j);
if (!removeValue(optname))
{
break;
}
}
// add all multi-options
for (var j=1; ; j++)
{
var optname = option.name.replace('1', j);
if (!addValue(optname))
{
break;
}
}
}
}
for (var k=0; k < config.length; k++)
{
var conf = config[k];
for (var i=0; i < conf.sections.length; i++)
{
var section = conf.sections[i];
if (!section.hidden && selectedSections[section.id])
{
for (var m=0; m < section.options.length; m++)
{
restoreOption(section.options[m]);
}
}
}
}
return values;
}
}(jQuery));
/*** RESTORE SETTINGS DIALOG *******************************************************/
var RestoreSettingsDialog = (new function($)
{
'use strict'
// Controls
var $RestoreSettingsDialog;
var $SectionTable;
// State
var config;
var restoreClick;
this.init = function()
{
$RestoreSettingsDialog = $('#RestoreSettingsDialog');
$('#RestoreSettingsDialog_Restore').click(restore);
$SectionTable = $('#RestoreSettingsDialog_SectionTable');
$SectionTable.fasttable(
{
pagerContainer: $('#RestoreSettingsDialog_SectionTable_pager'),
rowSelect: UISettings.rowSelect,
infoEmpty: 'No sections found.',
pageSize: 1000
});
$RestoreSettingsDialog.on('hidden', function()
{
// cleanup
$SectionTable.fasttable('update', []);
});
}
this.showModal = function(_config, _restoreClick)
{
config = _config;
restoreClick = _restoreClick;
updateTable();
$RestoreSettingsDialog.modal({backdrop: 'static'});
}
function updateTable()
{
var data = [];
for (var k=0; k < config.length; k++)
{
var conf = config[k];
for (var i=0; i < conf.sections.length; i++)
{
var section = conf.sections[i];
if (!section.hidden)
{
var fields = ['<div class="check img-check"></div>', '<span data-section="' + section.id + '">' + section.name + '</span>'];
var item =
{
id: section.id,
fields: fields,
};
data.push(item);
}
}
}
$SectionTable.fasttable('update', data);
}
function restore(e)
{
e.preventDefault();
var checkedRows = $SectionTable.fasttable('checkedRows');
var checkedCount = $SectionTable.fasttable('checkedCount');
if (checkedCount === 0)
{
PopupNotification.show('#Notif_Config_RestoreSections');
return;
}
checkedRows = $.extend({}, checkedRows); // clone
$RestoreSettingsDialog.modal('hide');
setTimeout(function() { restoreClick(checkedRows); }, 0);
}
}(jQuery));
/*** UPDATE DIALOG *******************************************************/
var UpdateDialog = (new function($)
{
'use strict'
// Controls
var $UpdateDialog;
var $UpdateProgressDialog;
var $UpdateProgressDialog_Log;
var $UpdateDialog_Close;
// State
var VersionInfo;
var PackageInfo;
var UpdateInfo;
var lastUpTimeSec;
var installing = false;
var logReceived = false;
var foreground = false;
this.init = function()
{
$UpdateDialog = $('#UpdateDialog');
$('#UpdateDialog_InstallStable,#UpdateDialog_InstallTesting,#UpdateDialog_InstallDevel').click(install);
$UpdateProgressDialog = $('#UpdateProgressDialog');
$UpdateProgressDialog_Log = $('#UpdateProgressDialog_Log');
$UpdateDialog_Close = $('#UpdateDialog_Close');
$UpdateDialog.on('hidden', resumeRefresher);
$UpdateProgressDialog.on('hidden', resumeRefresher);
}
function resumeRefresher()
{
if (!installing && foreground)
{
Refresher.resume();
}
}
this.showModal = function()
{
foreground = true;
this.performCheck();
$UpdateDialog.modal({backdrop: 'static'});
}
this.checkUpdate = function()
{
var lastCheck = new Date(parseInt(UISettings.read('LastUpdateCheck', '0')));
var hoursSinceLastCheck = Math.abs(new Date() - lastCheck) / (60*60*1000);
if (Options.option('UpdateCheck') !== 'none' && hoursSinceLastCheck > 12)
{
UISettings.write('LastUpdateCheck', new Date() / 1);
this.performCheck();
}
}
this.performCheck = function()
{
$('#UpdateDialog_Install').hide();
$('#UpdateDialog_CheckProgress').show();
$('#UpdateDialog_CheckFailed').hide();
$('#UpdateDialog_Versions').hide();
$('#UpdateDialog_UpdateAvail').hide();
$('#UpdateDialog_DownloadAvail').hide();
$('#UpdateDialog_UpdateNotAvail').hide();
$('#UpdateDialog_InstalledInfo').show();
$UpdateDialog_Close.text(foreground ? 'Close' : 'Remind Me Later');
$('#UpdateDialog_VerInstalled').text(Options.option('Version'));
PackageInfo = {};
VersionInfo = {};
UpdateInfo = {};
installing = false;
if (foreground)
{
Refresher.pause();
}
RPC.call('readurl', ['http://nzbget.net/info/nzbget-version.json?nocache=' + new Date().getTime(), 'nzbget version info'], loadedUpstreamInfo, error);
}
function error(e)
{
$('#UpdateDialog_CheckProgress').hide();
$('#UpdateDialog_CheckFailed').show();
}
function parseJsonP(jsonp)
{
var p = jsonp.indexOf('{');
var obj = JSON.parse(jsonp.substr(p, 10000));
return obj;
}
function loadedUpstreamInfo(data)
{
VersionInfo = parseJsonP(data);
if (VersionInfo['devel-version'] || !foreground)
{
loadPackageInfo();
}
else
{
loadGitVerData(loadPackageInfo);
}
}
function loadGitVerData(callback)
{
// fetching devel version number from github web-site
RPC.call('readurl', ['https://github.com/nzbget/nzbget', 'nzbget git revision info'],
function(gitRevData)
{
RPC.call('readurl', ['https://raw.githubusercontent.com/nzbget/nzbget/develop/configure.ac', 'nzbget git branch info'],
function(gitBranchData)
{
var html = document.createElement('DIV');
html.innerHTML = gitRevData;
html = html.textContent || html.innerText || '';
html = html.replace(/(?:\r\n|\r|\n)/g, ' ');
var rev = html.match(/([0-9\,]*)\s*commits/);
if (rev && rev.length > 1)
{
rev = rev[1].replace(',', '');
var ver = gitBranchData.match(/AC_INIT\(nzbget, (.*), .*/);
if (ver && ver.length > 1)
{
VersionInfo['devel-version'] = ver[1] + '-r' + rev;
}
}
callback();
}, callback);
}, callback);
}
function loadPackageInfo()
{
$.get('package-info.json', loadedPackageInfo, 'html').fail(loadedAll);
}
function loadedPackageInfo(data)
{
PackageInfo = parseJsonP(data);
if (PackageInfo['update-info-link'])
{
RPC.call('readurl', [PackageInfo['update-info-link'], 'nzbget update info'], loadedUpdateInfo, loadedAll);
}
else if (PackageInfo['update-info-script'])
{
RPC.call('checkupdates', [], loadedUpdateInfo, loadedAll);
}
else
{
loadedAll();
}
}
function loadedUpdateInfo(data)
{
UpdateInfo = parseJsonP(data);
loadedAll();
}
function formatTesting(str)
{
return str.replace('-testing-', '-');
}
function revision(version)
{
var rev = version.match(/.*r(\d+)/);
return rev && rev.length > 1 ? parseInt(rev[1]) : 0;
}
function vernumber(version)
{
var ver = version.match(/([\d.]+).*/);
return ver && ver.length > 1 ? parseFloat(ver[1]) : 0;
}
function loadedAll()
{
var installedVersion = Options.option('Version');
$('#UpdateDialog_CheckProgress').hide();
$('#UpdateDialog_Versions').show();
$('#UpdateDialog_InstalledInfo').show();
$('#UpdateDialog_CurStable').text(VersionInfo['stable-version'] ? VersionInfo['stable-version'] : 'no data');
$('#UpdateDialog_CurTesting').text(VersionInfo['testing-version'] ? formatTesting(VersionInfo['testing-version']) : 'no data');
$('#UpdateDialog_CurDevel').text(VersionInfo['devel-version'] ? formatTesting(VersionInfo['devel-version']) : 'no data');
$('#UpdateDialog_CurNotesStable').attr('href', VersionInfo['stable-release-notes']);
$('#UpdateDialog_CurNotesTesting').attr('href', VersionInfo['testing-release-notes']);
$('#UpdateDialog_CurNotesDevel').attr('href', VersionInfo['devel-release-notes']);
$('#UpdateDialog_DownloadStable').attr('href', VersionInfo['stable-download']);
$('#UpdateDialog_DownloadTesting').attr('href', VersionInfo['testing-download']);
Util.show('#UpdateDialog_CurNotesStable', VersionInfo['stable-release-notes']);
Util.show('#UpdateDialog_CurNotesTesting', VersionInfo['testing-release-notes']);
Util.show('#UpdateDialog_CurNotesDevel', VersionInfo['devel-release-notes']);
$('#UpdateDialog_AvailStable').text(UpdateInfo['stable-version'] ? UpdateInfo['stable-version'] : 'not available');
$('#UpdateDialog_AvailTesting').text(UpdateInfo['testing-version'] ? formatTesting(UpdateInfo['testing-version']) : 'not available');
$('#UpdateDialog_AvailDevel').text(UpdateInfo['devel-version'] ? formatTesting(UpdateInfo['devel-version']) : 'not available');
if (UpdateInfo['stable-version'] === VersionInfo['stable-version'] &&
UpdateInfo['testing-version'] === VersionInfo['testing-version'])
{
$('#UpdateDialog_AvailStableBlock,#UpdateDialog_AvailTestingBlock,#UpdateDialog_AvailDevelBlock').hide();
$('#UpdateDialog_AvailRow .update-row-name').text('');
$('#UpdateDialog_AvailRow td').css('border-style', 'none');
}
$('#UpdateDialog_DownloadRow td').css('border-style', 'none');
$('#UpdateDialog_AvailNotesStable').attr('href', UpdateInfo['stable-package-info']);
$('#UpdateDialog_AvailNotesTesting').attr('href', UpdateInfo['testing-package-info']);
$('#UpdateDialog_AvailNotesDevel').attr('href', UpdateInfo['devel-package-info']);
Util.show('#UpdateDialog_AvailNotesStableBlock', UpdateInfo['stable-package-info']);
Util.show('#UpdateDialog_AvailNotesTestingBlock', UpdateInfo['testing-package-info']);
Util.show('#UpdateDialog_AvailNotesDevelBlock', UpdateInfo['devel-package-info']);
var installedVer = vernumber(installedVersion);
var installedRev = revision(installedVersion);
var installedTesting = installedRev > 0 || installedVersion.indexOf('testing') > -1;
var canInstallStable = UpdateInfo['stable-version'] &&
((installedVer < vernumber(UpdateInfo['stable-version'])) ||
(installedTesting && installedVer === vernumber(UpdateInfo['stable-version'])));
var canInstallTesting = UpdateInfo['testing-version'] &&
((installedVer < vernumber(UpdateInfo['testing-version'])) ||
(installedTesting && installedVer === vernumber(UpdateInfo['testing-version'])) &&
installedRev < revision(UpdateInfo['testing-version']));
var canInstallDevel = UpdateInfo['devel-version'] &&
((installedVer < vernumber(UpdateInfo['devel-version'])) ||
(installedTesting && installedVer === vernumber(UpdateInfo['devel-version'])) &&
installedRev < revision(UpdateInfo['devel-version']));
Util.show('#UpdateDialog_InstallStable', canInstallStable);
Util.show('#UpdateDialog_InstallTesting', canInstallTesting);
Util.show('#UpdateDialog_InstallDevel', canInstallDevel);
var canDownloadStable =
((installedVer < vernumber(VersionInfo['stable-version'])) ||
(installedTesting && installedVer === vernumber(VersionInfo['stable-version'])));
var canDownloadTesting =
((installedVer < vernumber(VersionInfo['testing-version'])) ||
(installedTesting && installedVer === vernumber(VersionInfo['testing-version']) &&
installedRev < revision(VersionInfo['testing-version'])));
Util.show('#UpdateDialog_DownloadStable', canDownloadStable);
Util.show('#UpdateDialog_DownloadTesting', canDownloadTesting);
var hasUpdateSource = PackageInfo['update-info-link'] || PackageInfo['update-info-script'];
var hasUpdateInfo = UpdateInfo['stable-version'] || UpdateInfo['testing-version'] || UpdateInfo['devel-version'];
var canUpdate = canInstallStable || canInstallTesting || canInstallDevel;
var canDownload = canDownloadStable || canDownloadTesting;
Util.show('#UpdateDialog_UpdateAvail', canUpdate);
Util.show('#UpdateDialog_UpdateNotAvail', !canUpdate && !canDownload);
Util.show('#UpdateDialog_CheckFailed', hasUpdateSource && !hasUpdateInfo);
Util.show('#UpdateDialog_DownloadRow,#UpdateDialog_DownloadAvail', canDownload && !canUpdate);
$('#UpdateDialog_AvailRow').toggleClass('hide', !hasUpdateInfo);
if (!foreground &&
(((canInstallStable || canDownloadStable) && notificationAllowed('stable')) ||
(Options.option('UpdateCheck') === 'testing' && installedRev > 0 &&
(canInstallTesting || canDownloadTesting) && notificationAllowed('testing'))))
{
$UpdateDialog.modal({backdrop: 'static'});
loadDevelVersionInfo();
}
}
function loadDevelVersionInfo()
{
if (!VersionInfo['devel-version'])
{
$('#UpdateDialog_CurDevel').text('loading...');
loadGitVerData(function()
{
$('#UpdateDialog_CurDevel').text(VersionInfo['devel-version'] ? formatTesting(VersionInfo['devel-version']) : 'no data');
});
}
}
function notificationAllowed(branch)
{
// We don't want to update all users on release day.
// Parameter "update-rate" controls the spreading speed of the release.
// It contains comma-separated list of percentages of users, which should get
// notification about new release at a given day after release.
var rateCurve = UpdateInfo[branch + '-update-rate'] ? UpdateInfo[branch + '-update-rate'] : VersionInfo[branch + '-update-rate'];
if (!rateCurve)
{
return true;
}
var rates = rateCurve.split(',');
var releaseDate = new Date(UpdateInfo[branch + '-date'] ? UpdateInfo[branch + '-date'] : VersionInfo[branch + '-date']);
var daysSinceRelease = Math.floor((new Date() - releaseDate) / (1000*60*60*24));
var coverage = rates[Math.min(daysSinceRelease, rates.length-1)];
var dice = Math.floor(Math.random() * 100);
return dice <= coverage;
}
function install(e)
{
e.preventDefault();
var kind = $(this).attr('data-kind');
var script = PackageInfo['install-script'];
if (!script)
{
alert('Something is wrong with the package configuration file "package-info.json".');
return;
}
RPC.call('status', [], function(status)
{
lastUpTimeSec = status.UpTimeSec;
RPC.call('startupdate', [kind], updateStarted);
});
}
function updateStarted(started)
{
if (!started)
{
PopupNotification.show('#Notif_StartUpdate_Failed');
return;
}
installing = true;
$UpdateDialog.fadeOut(250, function()
{
$UpdateProgressDialog_Log.text('');
$UpdateProgressDialog.fadeIn(250, function()
{
$UpdateDialog.modal('hide');
$UpdateProgressDialog.modal({backdrop: 'static'});
updateLog();
});
});
}
function updateLog()
{
RPC.call('logupdate', [0, 100], function(data)
{
logReceived = logReceived || data.length > 0;
if (logReceived && data.length === 0)
{
terminated();
}
else
{
updateLogTable(data);
setTimeout(updateLog, 500);
}
}, terminated);
}
function terminated()
{
// rpc-failure: the program has been terminated. Waiting for new instance.
setLogContentAndScroll($UpdateProgressDialog_Log.html() + '\n' + 'NZBGet has been terminated. Waiting for restart...');
setTimeout(checkStatus, 500);
}
function setLogContentAndScroll(html)
{
var scroll = $UpdateProgressDialog_Log.prop('scrollHeight') - $UpdateProgressDialog_Log.prop('scrollTop') === $UpdateProgressDialog_Log.prop('clientHeight');
$UpdateProgressDialog_Log.html(html);
if (scroll)
{
$UpdateProgressDialog_Log.scrollTop($UpdateProgressDialog_Log.prop('scrollHeight'));
}
}
function updateLogTable(messages)
{
var html = '';
for (var i=0; i < messages.length; i++)
{
var message = messages[i];
var text = Util.textToHtml(message.Text);
if (message.Kind === 'ERROR')
{
text = '<span class="update-log-error">' + text + '</span>';
}
html = html + text + '\n';
}
setLogContentAndScroll(html);
}
function checkStatus()
{
RPC.call('status', [], function(status)
{
// OK, checking if it is a restarted instance
if (status.UpTimeSec >= lastUpTimeSec)
{
// the old instance is not restarted yet
// waiting 0.5 sec. and retrying
if ($('#UpdateProgressDialog').is(':visible'))
{
setTimeout(checkStatus, 500);
}
}
else
{
// restarted successfully, refresh page
setLogContentAndScroll($UpdateProgressDialog_Log.html() + '\n' + 'Successfully started. Refreshing the page...');
setTimeout(function()
{
document.location.reload(true);
}, 1000);
}
},
function()
{
// Failure, waiting 0.5 sec. and retrying
if ($('#UpdateProgressDialog').is(':visible'))
{
setTimeout(checkStatus, 500);
}
});
}
}(jQuery));
/*** EXEC SCRIPT DIALOG *******************************************************/
var ExecScriptDialog = (new function($)
{
'use strict'
// Controls
var $ExecScriptDialog;
var $ExecScriptDialog_Log;
var $ExecScriptDialog_Title;
var $ExecScriptDialog_Status;
// State
var visible = false;
this.init = function()
{
$ExecScriptDialog = $('#ExecScriptDialog');
$ExecScriptDialog_Log = $('#ExecScriptDialog_Log');
$ExecScriptDialog_Title = $('#ExecScriptDialog_Title');
$ExecScriptDialog_Status = $('#ExecScriptDialog_Status');
$ExecScriptDialog.on('hidden', function() { visible = false; });
}
this.showModal = function(script, command, context, changedOptions)
{
$ExecScriptDialog_Title.text('Executing script ' + script);
$ExecScriptDialog_Log.text('');
$ExecScriptDialog_Status.show();
$ExecScriptDialog.modal({backdrop: 'static'});
visible = true;
RPC.call('startscript', [script, command, context, changedOptions],
function (result)
{
if (result)
{
updateLog();
}
else
{
setLogContentAndScroll('<span class="script-log-error">Script start failed</span>');
$ExecScriptDialog_Status.hide();
}
}
);
}
function updateLog()
{
RPC.call('logscript', [0, 100], function(data)
{
updateLogTable(data);
if (visible)
{
setTimeout(updateLog, 500);
}
});
}
function setLogContentAndScroll(html)
{
var scroll = $ExecScriptDialog_Log.prop('scrollHeight') - $ExecScriptDialog_Log.prop('scrollTop') === $ExecScriptDialog_Log.prop('clientHeight');
$ExecScriptDialog_Log.html(html);
if (scroll)
{
$ExecScriptDialog_Log.scrollTop($ExecScriptDialog_Log.prop('scrollHeight'));
}
}
function updateLogTable(messages)
{
var html = '';
for (var i=0; i < messages.length; i++)
{
var message = messages[i];
var text = Util.textToHtml(message.Text);
if (text.substr(0, 7) === 'Script ')
{
$ExecScriptDialog_Status.fadeOut(500);
if (message.Kind === 'INFO')
{
text = '<span class="script-log-success">' + text + '</span>';
}
}
if (message.Kind === 'ERROR')
{
text = '<span class="script-log-error">' + text + '</span>';
}
html = html + text + '\n';
}
setLogContentAndScroll(html);
}
}(jQuery));