@@ -54,11 +54,18 @@ backupApp.controller('CommandlineController', function($scope, $routeParams, $lo
|
||||
|
||||
combined.unshift($scope.Command);
|
||||
|
||||
for(n in opts) {
|
||||
if (opts[n] == null)
|
||||
for (n in opts) {
|
||||
var value = opts[n];
|
||||
if (n == 'include' || n == 'exclude') {
|
||||
// Handle filters that appear multiple times
|
||||
for (var i = 0; i < value.length; ++i) {
|
||||
combined.push('--' + n + '=' + value[i]);
|
||||
}
|
||||
} else if (value == null) {
|
||||
combined.push(n);
|
||||
else
|
||||
combined.push(n + '=' + opts[n]);
|
||||
} else {
|
||||
combined.push(n + '=' + value);
|
||||
}
|
||||
}
|
||||
|
||||
options = {
|
||||
|
||||
@@ -3,11 +3,13 @@ backupApp.directive('parseFilterType', function(AppUtils, SystemInfo) {
|
||||
restrict: 'A',
|
||||
require: ['ngModel'],
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
var body = null;
|
||||
|
||||
ctrl[0].$parsers.push(function(txt) {
|
||||
var dirsep = SystemInfo.state.DirectorySeparator || '/';
|
||||
return AppUtils.buildFilter(txt, body, dirsep);
|
||||
// Store type in scope for parseFilterContent directive
|
||||
// This works because ng-repeat creates a child scope for each iteration
|
||||
// It is still glitchy in some edge cases, but should be more consistent
|
||||
scope.filterType = txt;
|
||||
return AppUtils.buildFilter(txt, scope.filterBody, dirsep);
|
||||
});
|
||||
|
||||
ctrl[0].$formatters.push(function(src) {
|
||||
@@ -18,8 +20,7 @@ backupApp.directive('parseFilterType', function(AppUtils, SystemInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
body = parts[1];
|
||||
|
||||
scope.filterType = parts[0];
|
||||
return parts[0];
|
||||
});
|
||||
}
|
||||
@@ -31,23 +32,23 @@ backupApp.directive('parseFilterContent', function(AppUtils, SystemInfo) {
|
||||
restrict: 'A',
|
||||
require: ['ngModel'],
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
|
||||
var type = null;
|
||||
|
||||
ctrl[0].$parsers.push(function(txt) {
|
||||
var dirsep = SystemInfo.state.DirectorySeparator || '/';
|
||||
return AppUtils.buildFilter(type, txt, dirsep);
|
||||
// Store type in scope for parseFilterType directive
|
||||
scope.filterBody = txt;
|
||||
return AppUtils.buildFilter(scope.filterType, txt, dirsep);
|
||||
});
|
||||
|
||||
ctrl[0].$formatters.push(function(src) {
|
||||
var dirsep = SystemInfo.state.DirectorySeparator || '/';
|
||||
// Remove double trailing slashes
|
||||
var parts = AppUtils.splitFilterIntoTypeAndBody(src, dirsep);
|
||||
if (parts == null) {
|
||||
type = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
type = parts[0];
|
||||
scope.filterBody = parts[1];
|
||||
return parts[1];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ backupApp.directive('restoreFilePicker', function() {
|
||||
|
||||
$scope.systeminfo = SystemInfo.watch($scope);
|
||||
$scope.treedata = [];
|
||||
$scope.AppUtils = AppUtils;
|
||||
|
||||
function compareablePath(path) {
|
||||
return scope.systeminfo.CaseSensitiveFilesystem ? path : path.toLowerCase();
|
||||
|
||||
@@ -178,7 +178,9 @@ backupApp.directive('sourceFolderPicker', function() {
|
||||
for(var i = 0; i < (scope.ngFilters || []).length; i++) {
|
||||
var f = AppUtils.splitFilterIntoTypeAndBody(scope.ngFilters[i], dirsep);
|
||||
if (f != null) {
|
||||
if (f[0] == '-path')
|
||||
if (f[1].indexOf('?') != -1 || f[1].indexOf('*') != -1)
|
||||
anySpecials = true;
|
||||
else if (f[0] == '-path')
|
||||
excludemap[compareablePath(f[1])] = true;
|
||||
else if (f[0] == '-folder')
|
||||
excludemap[compareablePath(f[1] + dirsep)] = true;
|
||||
|
||||
@@ -138,11 +138,13 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
key: '-folder',
|
||||
prefix: '-',
|
||||
suffix: '!',
|
||||
stripSep: true,
|
||||
rx: '\\-(.*)\\!'
|
||||
}, {
|
||||
name: gettextCatalog.getString('Exclude file'),
|
||||
key: '-path',
|
||||
prefix: '-',
|
||||
stripSep: true,
|
||||
exclude: ['*', '?', '{'],
|
||||
rx: '\\-([^\\[\\{\\*\\?]+)'
|
||||
}, {
|
||||
@@ -257,12 +259,14 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
return new Date(dt);
|
||||
};
|
||||
|
||||
this.parseOptionStrings = function(val, dict, validateCallback) {
|
||||
this.parseOptionStrings = function (val, dict, validateCallback) {
|
||||
// Parse options and return a dict with dict['--key']=value options
|
||||
// Include and exclude filters are returned as arrays in dict['include'], dict['exclude'] if present
|
||||
dict = dict || {};
|
||||
|
||||
var lines = null;
|
||||
|
||||
if (val != null && typeof(val) == typeof([]))
|
||||
if (val != null && Array.isArray(val))
|
||||
lines = val;
|
||||
else
|
||||
lines = this.replace_all(val || '', '\r', '\n').split('\n');
|
||||
@@ -287,8 +291,18 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
if (validateCallback)
|
||||
if (!validateCallback(dict, key, value))
|
||||
return null;
|
||||
|
||||
dict['--' + key] = value;
|
||||
// Special handing for --include and --exclude, which are the only options that can appear multiple times (compare FilterCollector.DoExtractOptions)
|
||||
// In all other cases, the last value overrides the earlier values
|
||||
var lower = key.toLowerCase();
|
||||
if (lower == 'include' || lower == 'exclude') {
|
||||
if (!Array.isArray(dict[lower])) {
|
||||
dict[lower] = [value];
|
||||
} else {
|
||||
dict[lower].push(value);
|
||||
}
|
||||
} else {
|
||||
dict['--' + key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +314,10 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
if (d['--' + k] !== undefined) {
|
||||
DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Duplicate option {{opt}}', { opt: k }));
|
||||
return false;
|
||||
} else if (k == 'include' || k == 'exclude') {
|
||||
// Cannot specify filters in extra options
|
||||
DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Cannot specify filter include or excludes in extra options'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -422,6 +440,13 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
return str.replace( new RegExp( "(" + this.preg_quote(pattern) + ")" , 'g' ), replacement );
|
||||
};
|
||||
|
||||
this.globToRegexp = function (str) {
|
||||
// Escape special chars, except ? and *
|
||||
str = (str + '').replace(/([\\\.\+\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
|
||||
// Replace ? and * with .? and .*
|
||||
return str.replace(/(\?|\*)/g, ".$1");
|
||||
};
|
||||
|
||||
this.format = function() {
|
||||
if (arguments == null || arguments.length < 1)
|
||||
return null;
|
||||
@@ -568,13 +593,25 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
return [type, body];
|
||||
}
|
||||
|
||||
for(var i in this.filterClasses) {
|
||||
var m = [];
|
||||
for (var i in this.filterClasses) {
|
||||
var n = matches(src, this.filterClasses[i]);
|
||||
if (n != null)
|
||||
return n;
|
||||
m.push(n);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (m.length == 0) {
|
||||
return null;
|
||||
}
|
||||
// Select match with shortest body, meaning longest prefix and suffix
|
||||
var shortestIdx = 0;
|
||||
var shortestLen = src.length;
|
||||
for (i = 0; i < m.length; ++i) {
|
||||
if (m[i][1].length < shortestLen) {
|
||||
shortestIdx = i;
|
||||
shortestLen = m[i][1].length;
|
||||
}
|
||||
}
|
||||
return m[shortestIdx];
|
||||
};
|
||||
|
||||
this.buildFilter = function(type, body, dirsep) {
|
||||
@@ -590,7 +627,10 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
if (pre.length >= 2 && pre[1] == '[') {
|
||||
//Regexp encode body ....
|
||||
}
|
||||
|
||||
if (f.stripSep == true && body[body.length - 1] == dirsep) {
|
||||
// Remove trailing dirsep
|
||||
body = body.slice(0, -1);
|
||||
}
|
||||
return pre + body + suf;
|
||||
};
|
||||
|
||||
@@ -609,7 +649,7 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
if (rx)
|
||||
filter = filter.substr(1, filter.length - 2);
|
||||
else
|
||||
filter = this.replace_all(this.replace_all(this.preg_quote(filter), '*', '.*'), '?', '.');
|
||||
filter = this.globToRegexp(filter);
|
||||
|
||||
try {
|
||||
res.push([flag == '+', new RegExp(filter, caseSensitive ? 'g' : 'gi')]);
|
||||
@@ -621,9 +661,11 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, DialogSer
|
||||
};
|
||||
|
||||
this.evalFilter = function(path, filters, include) {
|
||||
for(var i = 0; i < filters.length; i++) {
|
||||
for (var i = 0; i < filters.length; i++) {
|
||||
var m = path.match(filters[i][1]);
|
||||
if (m && m.length == 1 && m[0].length == path.length)
|
||||
// Regex such as .* might match empty string at the end which is unwanted
|
||||
// Check that the first match covers the full string
|
||||
if (m && m.length >= 1 && m[0].length == path.length)
|
||||
return filters[i][0];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<a class="check" ng-click="toggleCheck(node)" ng-class="{include: node.include == '+', exclude: node.include == '-', partial: node.include == ' ', root: node.root}" alt="{{node.include == ' ' ? 'partially included' : (node.include == '-' ? 'excluded' : (node.include == '+' ? 'included' : 'not checked'))}}"></a>
|
||||
<a class="type {{node.iconCls}}" alt="{{loading ? 'loading' : node.entrytype}}" ng-class="{invisible: node.hidden, loading: node.loading}"></a>
|
||||
|
||||
<span ng-click="toggleSelected(node)" ng-bind-html="node.text | highlight:ngSearchFilter"></span>
|
||||
<span ng-click="toggleSelected(node)" ng-bind-html="node.text | highlight:AppUtils.globToRegexp(ngSearchFilter)"></span>
|
||||
</div>
|
||||
<ul ng-class="{expanded: node.expanded}" dx-connect="node" alt="{{node.expanded ? 'expanded' : 'contracted'}}"/>
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user