mirror of
https://github.com/duplicati/duplicati.git
synced 2025-11-28 11:30:24 +08:00
Re-implemented everything using ASP.NET. Changed some requests to use JSON instead of FORM data. Some work towards deleting the FIXMEGlobal instance. Auth is missing, XSRF does not work correctly.
354 lines
13 KiB
JavaScript
354 lines
13 KiB
JavaScript
backupApp.directive('restoreFilePicker', function() {
|
|
return {
|
|
restrict: 'E',
|
|
require: ['ngSources', 'ngFilters', 'ngBackupId', 'ngTimestamp', 'ngSelected'],
|
|
scope: {
|
|
ngSources: '=',
|
|
ngFilters: '=',
|
|
ngBackupId: '=',
|
|
ngTimestamp: '=',
|
|
treedata: '=',
|
|
ngSelected: '=',
|
|
ngSearchFilter: '=',
|
|
ngSearchMode: '='
|
|
},
|
|
templateUrl: 'templates/restorefilepicker.html',
|
|
|
|
controller: function($scope, $timeout, SystemInfo, AppService, AppUtils) {
|
|
|
|
var scope = $scope;
|
|
var dirsep = '/';
|
|
|
|
$scope.systeminfo = SystemInfo.watch($scope);
|
|
$scope.treedata = [];
|
|
$scope.AppUtils = AppUtils;
|
|
|
|
function compareablePath(path) {
|
|
return scope.systeminfo.CaseSensitiveFilesystem ? path : path.toLowerCase();
|
|
};
|
|
|
|
$scope.toggleExpanded = function(node) {
|
|
node.expanded = !node.expanded;
|
|
|
|
if (node.root || node.iconCls == 'x-tree-icon-leaf' || node.iconCls == 'x-tree-icon-locked')
|
|
return;
|
|
|
|
if (!node.children && !node.loading) {
|
|
node.loading = true;
|
|
|
|
// Prefix filter with "@" to prevent Duplicati from
|
|
// mistaking literal '*' and '?' characters in paths
|
|
// for glob wildcard characters
|
|
AppService.get('/backup/' + $scope.ngBackupId + '/files?prefix-only=false&folder-contents=true&time=' + encodeURIComponent($scope.ngTimestamp) + '&filter=' + encodeURIComponent('@' + node.id)).then(function(data) {
|
|
var children = []
|
|
|
|
for(var n in data.data.Files)
|
|
{
|
|
var disp = data.data.Files[n].Path.substr(node.id.length);
|
|
var leaf = true;
|
|
if (disp.substr(disp.length - 1, 1) == dirsep) {
|
|
disp = disp.substr(0, disp.length - 1);
|
|
leaf = false;
|
|
}
|
|
|
|
children.push({
|
|
text: disp,
|
|
id: data.data.Files[n].Path,
|
|
size: data.data.Files[n].Sizes[0],
|
|
iconCls: leaf ? 'x-tree-icon-leaf' : '',
|
|
entrytype: AppUtils.getEntryTypeFromIconCls(leaf ? 'x-tree-icon-leaf' : ''),
|
|
leaf: leaf
|
|
});
|
|
}
|
|
|
|
|
|
node.children = children;
|
|
node.loading = false;
|
|
|
|
updateCheckState(children);
|
|
|
|
}, function() {
|
|
node.loading = false;
|
|
node.expanded = false;
|
|
AppUtils.connectionError.apply(AppUtils, arguments);
|
|
});
|
|
}
|
|
};
|
|
|
|
$scope.toggleSelected = function(node) {
|
|
if (scope.selectednode != null)
|
|
scope.selectednode.selected = false;
|
|
|
|
scope.selectednode = node;
|
|
scope.selectednode.selected = true;
|
|
};
|
|
|
|
function forAllChildren(node, callback) {
|
|
var q = [node];
|
|
while(q.length > 0) {
|
|
var x = q.pop();
|
|
if (x.children != null)
|
|
for(var c in x.children) {
|
|
q.push(x.children[c]);
|
|
callback(x.children[c]);
|
|
}
|
|
}
|
|
};
|
|
|
|
function propagateCheckDown(node) {
|
|
forAllChildren(node, function(n) { n.include = node.include; });
|
|
};
|
|
|
|
function findParent(node) {
|
|
var e = [];
|
|
e.push.apply(e, $scope.treedata.children);
|
|
var p = compareablePath(node.id);
|
|
|
|
while(e.length > 0) {
|
|
var el = e.pop();
|
|
if (p.length > el.id.length && el.id.substr(el.id.length - 1, 1) == dirsep && el.children != null) {
|
|
if (p.indexOf(compareablePath(el.id)) == 0) {
|
|
for(var n in el.children) {
|
|
if (el.children[n] == node)
|
|
return el;
|
|
else
|
|
e.push(el.children[n]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
function propagateCheckUp(node) {
|
|
var p = findParent(node);
|
|
while(p != null) {
|
|
var all = true;
|
|
var any = false;
|
|
for(var i in p.children) {
|
|
if (p.children[i].include != '+')
|
|
all = false;
|
|
if (p.children[i].include == '+' || p.children[i].include == ' ')
|
|
any = true;
|
|
|
|
if (all == false && any == true)
|
|
break;
|
|
}
|
|
|
|
if (all) {
|
|
p.include = '+';
|
|
} else if (any) {
|
|
p.include = ' ';
|
|
} else {
|
|
p.include = '';
|
|
}
|
|
|
|
p = findParent(p);
|
|
}
|
|
};
|
|
|
|
function buildSelectedMap() {
|
|
var map = {};
|
|
for (var i = $scope.ngSelected.length - 1; i >= 0; i--)
|
|
map[compareablePath($scope.ngSelected[i])] = true;
|
|
return map;
|
|
};
|
|
|
|
function buildPartialMap() {
|
|
var map = {};
|
|
for (var i = $scope.ngSelected.length - 1; i >= 0; i--) {
|
|
var is_dir = $scope.ngSelected[i].substr($scope.ngSelected[i].length - 1, 1) == dirsep;
|
|
var parts = $scope.ngSelected[i].split(dirsep);
|
|
var cur = '';
|
|
for (var j = 0; j < parts.length; j++) {
|
|
cur += parts[j];
|
|
if (j != parts.length - 1 || is_dir)
|
|
cur += dirsep;
|
|
|
|
map[compareablePath(cur)] = true;
|
|
};
|
|
}
|
|
return map;
|
|
};
|
|
|
|
function removePathFromArray(arr, path) {
|
|
path = compareablePath(path);
|
|
|
|
for (var i = arr.length - 1; i >= 0; i--) {
|
|
var n = compareablePath(arr[i]);
|
|
if (n == path)
|
|
arr.splice(i, 1);
|
|
};
|
|
};
|
|
|
|
$scope.toggleCheck = function(node) {
|
|
|
|
if (node.include != '+') {
|
|
var p = findParent(node) || node;
|
|
|
|
var cur = node;
|
|
while(p != null) {
|
|
// Remove path and all sub-paths
|
|
|
|
var c = compareablePath(cur.id);
|
|
var is_dir = c.substr(c.length - 1, 1) == dirsep;
|
|
|
|
for (var i = $scope.ngSelected.length - 1; i >= 0; i--) {
|
|
var n = compareablePath($scope.ngSelected[i]);
|
|
if (n == c || (n.indexOf(c) == 0 && is_dir))
|
|
$scope.ngSelected.splice(i, 1);
|
|
};
|
|
|
|
var all = true;
|
|
var pp = compareablePath(p.id);
|
|
var map = buildSelectedMap();
|
|
map[c] = true;
|
|
|
|
for (var i = p.children.length - 1; i >= 0; i--)
|
|
if (!map[compareablePath(p.children[i].id)]) {
|
|
all = false;
|
|
break;
|
|
}
|
|
|
|
if (!all || p == node || $scope.ngSearchMode) {
|
|
$scope.ngSelected.push(cur.id);
|
|
break;
|
|
}
|
|
|
|
cur = p;
|
|
p = findParent(p);
|
|
|
|
if (p == null && all && !$scope.ngSearchMode)
|
|
$scope.ngSelected.push(cur.id);
|
|
}
|
|
|
|
} else {
|
|
|
|
var map = buildSelectedMap();
|
|
|
|
// This item is not included, include parents children
|
|
|
|
var backtrace = [];
|
|
|
|
var p = node;
|
|
while(p != null && !map[compareablePath(p.id)]) {
|
|
backtrace.push(p);
|
|
p = findParent(p);
|
|
}
|
|
|
|
removePathFromArray($scope.ngSelected, p.id);
|
|
|
|
while(backtrace.length > 0) {
|
|
var t = backtrace.pop();
|
|
for(var n in p.children)
|
|
if (t != p.children[n]) {
|
|
$scope.ngSelected.push(p.children[n].id);
|
|
}
|
|
|
|
p = t;
|
|
}
|
|
}
|
|
|
|
//updateCheckState();
|
|
};
|
|
|
|
var updateCheckState = function(nodes) {
|
|
var map = buildSelectedMap();
|
|
var partialmap = buildPartialMap();
|
|
|
|
nodes = nodes || $scope.treedata.children;
|
|
|
|
var w = [];
|
|
w.push.apply(w, $scope.treedata.children);
|
|
|
|
while(w.length > 0) {
|
|
var n = w.pop();
|
|
var cp = compareablePath(n.id);
|
|
|
|
if (map[cp]) {
|
|
n.include = '+';
|
|
propagateCheckDown(n);
|
|
} else if (partialmap[cp]) {
|
|
n.include = ' ';
|
|
if (n.children != null)
|
|
w.push.apply(w, n.children);
|
|
}
|
|
else {
|
|
n.include = '';
|
|
propagateCheckDown(n);
|
|
}
|
|
}
|
|
};
|
|
|
|
var buildnodes = function(items, parentpath) {
|
|
var res = [];
|
|
|
|
parentpath = parentpath || '';
|
|
|
|
for(var n in items) {
|
|
var txt = items[n].Path.substr(parentpath.length);
|
|
if (txt[txt.length] == dirsep)
|
|
txt.length--;
|
|
|
|
var rootnode = {
|
|
text: txt,
|
|
expanded: items[n].expanded,
|
|
iconCls: items[n].iconCls,
|
|
leaf: items[n].leaf,
|
|
id: items[n].Path,
|
|
entrytype: AppUtils.getEntryTypeFromIconCls(items[n].iconCls)
|
|
};
|
|
|
|
if (items[n].Children) {
|
|
rootnode.children = buildnodes(items[n].Children, items[n].Path);
|
|
delete items[n].Children;
|
|
}
|
|
|
|
res.push(rootnode);
|
|
}
|
|
|
|
return res;
|
|
};
|
|
|
|
var updateRoots = function()
|
|
{
|
|
if ($scope.ngSources == null || $scope.ngSources.length == 0)
|
|
dirsep = scope.systeminfo.DirectorySeparator || '/';
|
|
else
|
|
dirsep = $scope.ngSources[0].Path[0] == '/' ? '/' : '\\';
|
|
|
|
var roots = buildnodes($scope.ngSources);
|
|
|
|
$scope.treedata = $scope.treedata || {};
|
|
|
|
$scope.treedata.children = roots;
|
|
$scope.treedata.forAll = function(callback) { forAllChildren(this, callback); };
|
|
$scope.treedata.allSelected = function() {
|
|
var q = [];
|
|
var r = [];
|
|
q.push.apply(q, this.children);
|
|
while(q.length > 0) {
|
|
var n = q.pop();
|
|
if (n.include == '+')
|
|
r.push(n.id);
|
|
else if (n.include == ' ')
|
|
q.push.apply(q, n.children);
|
|
}
|
|
|
|
return r;
|
|
};
|
|
if (roots.length == 1)
|
|
$scope.toggleExpanded(roots[0]);
|
|
|
|
updateCheckState();
|
|
};
|
|
|
|
$scope.$watchCollection('ngSources', updateRoots);
|
|
updateRoots();
|
|
|
|
$scope.$watchCollection('ngSelected', updateCheckState)
|
|
|
|
}
|
|
};
|
|
});
|