// code: language=javascript insertSpaces=True tabSize=4
/* Internet Explorer 11 lacks String.startsWith() */
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position) {
position = position || 0;
return this.substr(position, searchString.length) === searchString;
};
}
/* Internet Explorer 8 lacks Array.indexOf */
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(what, i) {
i = i || 0;
var L = this.length;
while (i < L) {
if (this[i] === what) return i;
++i;
}
return -1;
};
}
/* Add [].remove */
Array.prototype.remove = function() {
var what, a = arguments,
L = a.length,
ax;
while (L && this.length) {
what = a[--L];
while ((ax = this.indexOf(what)) !== -1) {
this.splice(ax, 1);
}
}
return this;
};
function bySortedValue(obj, callback, context) {
// get a list of keys, sorted by their value, descending
var keys = [];
for (var key in obj) keys.push(key);
keys.sort(function(a, b) { return obj[b] - obj[a] });
for (var i = 0; i < keys.length; i++) {
callback.call(context, keys[i], obj[keys[i]]);
}
}
/* get all checked items in a file browser by its ID */
function get_browser_selected(id) {
var unshown_selections = $j(id).data('unshown-selections');
var hex_slash = ($j(id).data('post-action') == 'listdir');
var shown = [];
var selected = [];
// get the list of selected folders
$j.each($j(id.concat(' :input')), function(i, input) {
var input = $j(input);
shown.push(input.val());
if (input.is(':checked')) {
selected.push(input.val());
}
});
// remove any items from unshown_selections which the browser has loaded,
// then add the remaining to selected
$j.each(unshown_selections, function(i, unshown_item) {
if (shown.indexOf(unshown_item) != -1) {
unshown_selections.remove(unshown_item);
}
});
$j.each(unshown_selections, function(i, unshown_item) {
selected.push(unshown_item);
});
// iterate over them again to set .prop('indeterminate', true) as needed
$j.each($j(id.concat(' :input')), function(i, input) {
var input = $j(input);
if (input.is(':checked')) {
input.prop('indeterminate', false);
} else { // if this item is unchecked
var indeterminate = false;
var unchecked_path = input.val();
$j.each(selected, function(sel_i, selected_item) {
// if selected_item is a child of unchecked_path, it should be set to indeterminate
// example: unchecked_path="/root" and selected_item="/root/something/something"
if (is_parent_path(unchecked_path, selected_item, hex_slash)) {
indeterminate = true;
}
});
input.prop('indeterminate', indeterminate);
}
});
return selected;
}
/* check if all items were selected */
function all_selected(id) {
var inputs = $j(id.concat(' :input'));
for (let i = 0; i < inputs.length; i++) {
if (! $j(inputs[i]).is(':checked')){
return false;
}
}
return true;
}
function is_parent_path(parent, child, hex_slash) {
// ensure one and only one trailing slash
if (hex_slash) {
// '2f' = utf-8 of '/' in hex
var child = child.replace(/(?:2f)+$/, "").concat('2f');
var parent = parent.replace(/(?:2f)+$/, "").concat('2f');
} else {
var child = child.replace(/[\/]+$/, "").concat('/');
var parent = parent.replace(/[\/]+$/, "").concat('/');
}
if (child == parent) {
return false;
}
return child.startsWith(parent);
}
/* called when document is ready, this is called */
function init_file_browse(browser_id, snap, geo) {
// this tracks which folders are currently trying to fetch contents
// to prevent unnecssary API calls
var browser_div = $j(browser_id);
browser_div.data('processing', []);
var post_uri = browser_div.data('post-uri');
var action = browser_div.data('post-action');
if (action == 'listdir') {
// loading items from local disk
init_from_local_listdir(browser_div);
} else {
// loading items from restic
init_from_restic_browse(action, post_uri, browser_div, snap, geo);
}
}
function hex2str(hex) {
var pairs = hex.match(/(..?)/g);
var arr = [];
$j.each(pairs, function(i, pair) {
arr.push(parseInt(pair, 16));
});
return new TextDecoder().decode(new Uint8Array(arr));
};
function init_from_local_listdir(browser_div) {
var all_sizes = $j('#settings_tab').data('subpaths')
var browser_id = browser_div.attr('id');
var selections = browser_div.data('orig-selections');
browser_div.empty();
// create the main ul where file/folders are displayed
var ul = $j('
', { style: 'list-style-type: none;' });
bySortedValue($j('#settings_tab').data('home-items').dirs, function(dir, size_mb) { // for each directory
var checked = selections.indexOf(dir) != -1;
if (checked) selections.remove(dir);
all_sizes[dir] = size_mb;
ul.append(create_browser_li({ browser_id: browser_id, folder: true, path: dir, toplevel: true, size: size_mb, checked: checked }));
});
bySortedValue($j('#settings_tab').data('home-items').files, function(path, size_mb) { // for each file
var checked = selections.indexOf(path) != -1;
if (checked) selections.remove(path);
all_sizes[path] = size_mb;
ul.append(create_browser_li({ browser_id: browser_id, folder: false, path: path, toplevel: true, size: size_mb, checked: checked }));
});
browser_div.append(ul);
// Any selections previously made which haven't been expanded yet
browser_div.data('unshown-selections', selections);
browser_div.data('all-sizes', all_sizes);
}
function init_from_restic_browse(action, post_uri, browser_div, snap, geo) {
var browser_id = browser_div.attr('id');
browser_div.data('unshown-selections', []); // not relevant to restic's browser; just set it empty
browser_div.empty();
if (snap == undefined) {
console.log('There appears to be no file backups to browse; not initializing the browser widget');
return;
}
var temp_p = $j('
', { text: 'Invalid JSON from server', class: 'browser-msg text-danger' }));
console.log('could not decode server response as JSON: '.concat(data));
return;
}
browser_div.empty();
if (data.status != 0) { // successfully contacted server, but ran into an error
browser_div.append($j('
', { text: data.data }));
console.log(data);
return;
}
// create the main ul where file/folders are displayed
var ul = $j('
', { style: 'list-style-type: none;' });
$j.each(data.data.dirs, function(dir_index, dir) { // for each directory
if (home == '/') {
var path = home.concat(dir);
} else {
var path = home.concat('/').concat(dir);
}
ul.append(create_browser_li({ folder: true, browser_id: browser_id, snap: snap, geo: geo, path: path, toplevel: true }));
});
$j.each(data.data.files, function(file_index, file) { // for each file
if (home == '/') {
var path = home.concat(file);
} else {
var path = home.concat('/').concat(file);
}
ul.append(create_browser_li({ folder: false, browser_id: browser_id, path: path, toplevel: true }));
});
browser_div.append(ul);
})
.fail(function (xhr, statusText, errorThrown) { // could not contact server
console.log(xhr);
console.log(errorThrown);
temp_p.text('Server error - '.concat(statusText));
});
}
function update_sel_count() {
$j('.filebrowser-count').each(function(index, span) {
var span = $j(span);
var browser_name = span.data('browser');
var num_items = get_browser_selected(browser_name).length;
span.text(num_items);
if (num_items > 0) {
span.parent().removeClass('count-zero');
} else {
span.parent().addClass('count-zero');
}
var has_items_func = window[span.data('has-items-func')];
has_items_func(num_items > 0);
});
}
/* create a
for the browser. params:
folder: whether this is a folder which can be expanded
browser_id: id for the browser div (only if folder=true)
snap: optional snapshot ID to set in data for the li
path: file/folder path
checked: whether to set prop checked (default: false)
toplevel: whether to include the browser-toplevel class (default: false)
size: size to display for the item (optional) */
function create_browser_li(opts) {
var post_action = $j('#'.concat(opts.browser_id)).data('post-action');
if (post_action == 'listdir') {
var path = hex2str(opts.path);
} else {
var path = opts.path;
}
if (opts.size === undefined) {
var label = path;
} else if (opts.size === null) {
var label = path;
} else {
var label = path.concat(' (').concat((opts.size / 1024).toFixed(2)).concat('GB)');
}
var li_args = {};
if (opts.toplevel) li_args['class'] = 'browser-toplevel';
if (opts.folder) {
li_args['style'] = 'list-style-image: url("'.concat(window.filebrowse_img_root).concat('/directory.png")');
li_args['onclick'] = 'browse_expand(event, this);';
} else {
li_args['style'] = 'list-style-image: url("'.concat(icon_for(path)).concat('")');
}
var check_label = $j('