Merge branch 'master' into experiment/quickfix_concurrent
This commit is contained in:
@@ -254,33 +254,75 @@ namespace Duplicati.Library.Backend
|
||||
{
|
||||
int offset = 0;
|
||||
int count = FILE_LIST_PAGE_SIZE;
|
||||
|
||||
int numFiles = 0;
|
||||
int filesOk = 0;
|
||||
int filesRepeated = 0;
|
||||
int iteration = 0;
|
||||
|
||||
var files = new List<IFileEntry>();
|
||||
|
||||
m_fileidCache.Clear();
|
||||
|
||||
while(count == FILE_LIST_PAGE_SIZE)
|
||||
do
|
||||
{
|
||||
var url = string.Format("{0}/{1}?access_token={2}&limit={3}&offset={4}", WLID_SERVER, string.Format(FOLDER_TEMPLATE, FolderID), Library.Utility.Uri.UrlEncode(m_oauth.AccessToken), FILE_LIST_PAGE_SIZE, offset);
|
||||
var res = m_oauth.GetJSONData<WLID_DataItem>(url, x => x.UserAgent = USER_AGENT);
|
||||
|
||||
if (res != null && res.data != null)
|
||||
while (count == FILE_LIST_PAGE_SIZE)
|
||||
{
|
||||
count = res.data.Length;
|
||||
foreach(var r in res.data)
|
||||
var url = string.Format("{0}/{1}?access_token={2}&limit={3}&offset={4}", WLID_SERVER, string.Format(FOLDER_TEMPLATE, FolderID), Library.Utility.Uri.UrlEncode(m_oauth.AccessToken), FILE_LIST_PAGE_SIZE, offset);
|
||||
var res = m_oauth.GetJSONData<WLID_DataItem>(url);
|
||||
|
||||
if (res != null && res.data != null)
|
||||
{
|
||||
m_fileidCache.Add(r.name, r.id);
|
||||
count = res.data.Length;
|
||||
|
||||
var fe = new FileEntry(r.name, r.size.Value, r.updated_time.Value, r.updated_time.Value);
|
||||
fe.IsFolder = string.Equals(r.type, "folder", StringComparison.OrdinalIgnoreCase);
|
||||
yield return fe;
|
||||
// log
|
||||
Console.WriteLine("Iteration: {0:D} Offset: {1:D} Count: {2:D} TotalOK: {3:D} TotalRep: {4:D} TotalFiles: {5:D}", iteration, offset, count, filesOk, filesRepeated, numFiles);
|
||||
|
||||
foreach (var r in res.data)
|
||||
{
|
||||
|
||||
if (m_fileidCache.ContainsKey(r.name))
|
||||
{
|
||||
filesRepeated++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fileidCache.Add(r.name, r.id);
|
||||
|
||||
var fe = new FileEntry(r.name, r.size.Value, r.updated_time.Value, r.updated_time.Value);
|
||||
fe.IsFolder = string.Equals(r.type, "folder", StringComparison.OrdinalIgnoreCase);
|
||||
files.Add(fe);
|
||||
|
||||
filesOk++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
count = 0;
|
||||
else
|
||||
{
|
||||
count = 0;
|
||||
}
|
||||
|
||||
if (iteration != 0 && filesOk == numFiles) return files;
|
||||
|
||||
offset += count;
|
||||
|
||||
}
|
||||
|
||||
offset += count;
|
||||
// Save total number of files in the first iteration
|
||||
if (iteration == 0)
|
||||
{
|
||||
numFiles = offset;
|
||||
}
|
||||
|
||||
filesRepeated = 0;
|
||||
iteration++;
|
||||
|
||||
offset = 0;
|
||||
count = FILE_LIST_PAGE_SIZE;
|
||||
}
|
||||
while (filesOk != numFiles);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public void Put(string remotename, string filename)
|
||||
|
||||
@@ -144,40 +144,46 @@ namespace Duplicati.Library.Main.Operation
|
||||
/// Gets the filesets selected for deletion
|
||||
/// </summary>
|
||||
/// <returns>The filesets to delete</returns>
|
||||
/// <param name="backups">The list of backups that can be deleted</param>
|
||||
private DateTime[] GetFilesetsToDelete(DateTime[] backups)
|
||||
/// <param name="allBackups">The list of backups that can be deleted</param>
|
||||
private DateTime[] GetFilesetsToDelete(DateTime[] allBackups)
|
||||
{
|
||||
if (backups.Length == 0)
|
||||
return backups;
|
||||
if (allBackups.Length == 0)
|
||||
return allBackups;
|
||||
|
||||
if (backups.Distinct().Count() != backups.Length)
|
||||
throw new Exception(string.Format("List of backup timestamps contains duplicates: {0}", string.Join(", ", backups.Select(x => x.ToString()))));
|
||||
if (allBackups.Distinct().Count() != allBackups.Length)
|
||||
throw new Exception(string.Format("List of backup timestamps contains duplicates: {0}", string.Join(", ", allBackups.Select(x => x.ToString()))));
|
||||
|
||||
List<DateTime> res = new List<DateTime>();
|
||||
List<DateTime> toDelete = new List<DateTime>();
|
||||
|
||||
// Remove backups explicitely specified via option
|
||||
var versions = m_options.Version;
|
||||
if (versions != null && versions.Length > 0)
|
||||
foreach (var ix in versions.Distinct())
|
||||
if (ix >= 0 && ix < backups.Length)
|
||||
res.Add(backups[ix]);
|
||||
|
||||
var keepVersions = m_options.KeepVersions;
|
||||
if (keepVersions > 0 && keepVersions < backups.Length)
|
||||
res.AddRange(backups.Skip(keepVersions));
|
||||
if (ix >= 0 && ix < allBackups.Length)
|
||||
toDelete.Add(allBackups[ix]);
|
||||
|
||||
// Remove backups that are older than date specified via option
|
||||
var keepTime = m_options.KeepTime;
|
||||
if (keepTime.Ticks > 0)
|
||||
res.AddRange(backups.SkipWhile(x => x >= keepTime));
|
||||
toDelete.AddRange(allBackups.SkipWhile(x => x >= keepTime));
|
||||
|
||||
res.AddRange(ApplyRetentionPolicy(backups));
|
||||
// Remove backups via rentention policy option
|
||||
toDelete.AddRange(ApplyRetentionPolicy(allBackups));
|
||||
|
||||
var filtered = res.Distinct().OrderByDescending(x => x).AsEnumerable();
|
||||
// Check how many backups will be remaining after the previous steps
|
||||
// and remove oldest backups while there are still more backups than should be kept as specified via option
|
||||
var backupsRemaining = allBackups.Except(toDelete).ToList();
|
||||
var keepVersions = m_options.KeepVersions;
|
||||
if (keepVersions > 0 && keepVersions < backupsRemaining.Count())
|
||||
toDelete.AddRange(backupsRemaining.Skip(keepVersions));
|
||||
|
||||
var removeCount = filtered.Count();
|
||||
if (removeCount > backups.Length)
|
||||
throw new Exception(string.Format("Too many entries {0} vs {1}, lists: {2} vs {3}", removeCount, backups.Length, string.Join(", ", filtered.Select(x => x.ToString())), string.Join(", ", backups.Select(x => x.ToString()))));
|
||||
var toDeleteDistinct = toDelete.Distinct().OrderByDescending(x => x).AsEnumerable();
|
||||
|
||||
return filtered.ToArray();
|
||||
var removeCount = toDeleteDistinct.Count();
|
||||
if (removeCount > allBackups.Length)
|
||||
throw new Exception(string.Format("Too many entries {0} vs {1}, lists: {2} vs {3}", removeCount, allBackups.Length, string.Join(", ", toDeleteDistinct.Select(x => x.ToString())), string.Join(", ", allBackups.Select(x => x.ToString()))));
|
||||
|
||||
return toDeleteDistinct.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Duplicati.Library.Main.Volumes
|
||||
: base(options)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(options.AsynchronousUploadFolder))
|
||||
m_localfile = Library.Utility.TempFile.CreateInFolder(options.AsynchronousUploadFolder);
|
||||
m_localfile = Library.Utility.TempFile.CreateInFolder(options.AsynchronousUploadFolder, true);
|
||||
else
|
||||
m_localfile = new Library.Utility.TempFile();
|
||||
|
||||
|
||||
@@ -161,8 +161,11 @@ namespace Duplicati.Library.Utility
|
||||
return new TempFile(path);
|
||||
}
|
||||
|
||||
public static TempFile CreateInFolder(string path)
|
||||
public static TempFile CreateInFolder(string path, bool autocreatefolder)
|
||||
{
|
||||
if (autocreatefolder && !System.IO.Directory.Exists(path))
|
||||
System.IO.Directory.CreateDirectory(path);
|
||||
|
||||
return new TempFile(System.IO.Path.Combine(path, GenerateUniqueName()));
|
||||
}
|
||||
|
||||
|
||||
+121
-5
@@ -153,7 +153,7 @@ namespace Duplicati.Server
|
||||
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args, Duplicati.Library.AutoUpdater.AutoUpdateStrategy.Never);
|
||||
}
|
||||
|
||||
public static int RealMain(string[] args)
|
||||
public static int RealMain(string[] _args)
|
||||
{
|
||||
//If we are on Windows, append the bundled "win-tools" programs to the search path
|
||||
//We add it last, to allow the user to override with other versions
|
||||
@@ -170,11 +170,14 @@ namespace Duplicati.Server
|
||||
|
||||
//If this executable is invoked directly, write to console, otherwise throw exceptions
|
||||
var writeConsole = System.Reflection.Assembly.GetEntryAssembly() == System.Reflection.Assembly.GetExecutingAssembly();
|
||||
|
||||
//Find commandline options here for handling special startup cases
|
||||
var commandlineOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(new List<string>(args));
|
||||
|
||||
foreach(var s in args)
|
||||
//Find commandline options here for handling special startup cases
|
||||
var args = new List<string>(_args);
|
||||
var argtuple = Library.Utility.FilterCollector.ExtractOptions(new List<string>(args));
|
||||
var commandlineOptions = argtuple.Item1;
|
||||
var filter = argtuple.Item2;
|
||||
|
||||
foreach(var s in _args)
|
||||
if (
|
||||
s.Equals("help", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Equals("/help", StringComparison.OrdinalIgnoreCase) ||
|
||||
@@ -182,6 +185,33 @@ namespace Duplicati.Server
|
||||
s.Equals("/usage", StringComparison.OrdinalIgnoreCase))
|
||||
commandlineOptions["help"] = "";
|
||||
|
||||
// Check if a parameters-file was provided. Skip if help was already specified
|
||||
if (!commandlineOptions.ContainsKey("help"))
|
||||
{
|
||||
string filename = null;
|
||||
if (commandlineOptions.ContainsKey("parameters-file"))
|
||||
{
|
||||
filename = commandlineOptions["parameters-file"];
|
||||
commandlineOptions.Remove("parameters-file");
|
||||
}
|
||||
else if (commandlineOptions.ContainsKey("parameter-file"))
|
||||
{
|
||||
filename = commandlineOptions["parameter-file"];
|
||||
commandlineOptions.Remove("parameter-file");
|
||||
}
|
||||
else if (commandlineOptions.ContainsKey("parameterfile"))
|
||||
{
|
||||
filename = commandlineOptions["parameterfile"];
|
||||
commandlineOptions.Remove("parameterfile");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
// Check the parameters-file and add provided arguments (overriding commandline arguments)
|
||||
ReadOptionsFromFile(filename, ref filter, args, commandlineOptions);
|
||||
}
|
||||
}
|
||||
|
||||
//If the commandline issues --help, just stop here
|
||||
if (commandlineOptions.ContainsKey("help"))
|
||||
{
|
||||
@@ -759,6 +789,7 @@ namespace Duplicati.Server
|
||||
{
|
||||
var lst = new List<Duplicati.Library.Interface.ICommandLineArgument> (new Duplicati.Library.Interface.ICommandLineArgument[] {
|
||||
new Duplicati.Library.Interface.CommandLineArgument("help", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.HelpCommandDescription, Strings.Program.HelpCommandDescription),
|
||||
new Duplicati.Library.Interface.CommandLineArgument("parameters-file", Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong2, "", new string[] {"parameter-file", "parameterfile"}),
|
||||
new Duplicati.Library.Interface.CommandLineArgument("unencrypted-database", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.UnencrypteddatabaseCommandDescription, Strings.Program.UnencrypteddatabaseCommandDescription),
|
||||
new Duplicati.Library.Interface.CommandLineArgument("portable-mode", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortablemodeCommandDescription, Strings.Program.PortablemodeCommandDescription),
|
||||
new Duplicati.Library.Interface.CommandLineArgument("log-file", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.LogfileCommandDescription, Strings.Program.LogfileCommandDescription),
|
||||
@@ -780,5 +811,90 @@ namespace Duplicati.Server
|
||||
return lst.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ReadOptionsFromFile(string filename, ref Library.Utility.IFilter filter, List<string> cargs, Dictionary<string, string> options)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> fargs = new List<string>(Library.Utility.Utility.ReadFileWithDefaultEncoding(Library.Utility.Utility.ExpandEnvironmentVariables(filename)).Replace("\r\n", "\n").Replace("\r", "\n").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()));
|
||||
var newsource = new List<string>();
|
||||
string newtarget = null;
|
||||
string prependfilter = null;
|
||||
string appendfilter = null;
|
||||
string replacefilter = null;
|
||||
|
||||
var tmpparsed = Library.Utility.FilterCollector.ExtractOptions(fargs, (key, value) => {
|
||||
if (key.Equals("source", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
newsource.Add(value);
|
||||
return false;
|
||||
}
|
||||
else if (key.Equals("target", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
newtarget = value;
|
||||
return false;
|
||||
}
|
||||
else if (key.Equals("append-filter", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
appendfilter = value;
|
||||
return false;
|
||||
}
|
||||
else if (key.Equals("prepend-filter", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
prependfilter = value;
|
||||
return false;
|
||||
}
|
||||
else if (key.Equals("replace-filter", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
replacefilter = value;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
var opt = tmpparsed.Item1;
|
||||
var newfilter = tmpparsed.Item2;
|
||||
|
||||
// If the user specifies parameters-file, all filters must be in the file.
|
||||
// Allowing to specify some filters on the command line could result in wrong filter ordering
|
||||
if (!filter.Empty && !newfilter.Empty)
|
||||
throw new Duplicati.Library.Interface.UserInformationException(Strings.Program.FiltersCannotBeUsedWithFileError2);
|
||||
|
||||
if (!newfilter.Empty)
|
||||
filter = newfilter;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(prependfilter))
|
||||
filter = Library.Utility.FilterExpression.Combine(Library.Utility.FilterExpression.Deserialize(prependfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)), filter);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(appendfilter))
|
||||
filter = Library.Utility.FilterExpression.Combine(filter, Library.Utility.FilterExpression.Deserialize(appendfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(replacefilter))
|
||||
filter = Library.Utility.FilterExpression.Deserialize(replacefilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
foreach (KeyValuePair<String, String> keyvalue in opt)
|
||||
options[keyvalue.Key] = keyvalue.Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(newtarget))
|
||||
{
|
||||
if (cargs.Count <= 1)
|
||||
cargs.Add(newtarget);
|
||||
else
|
||||
cargs[1] = newtarget;
|
||||
}
|
||||
|
||||
if (cargs.Count >= 1 && cargs[0].Equals("backup", StringComparison.OrdinalIgnoreCase))
|
||||
cargs.AddRange(newsource);
|
||||
else if (newsource.Count > 0 && Library.Utility.Utility.ParseBoolOption(options, "verbose"))
|
||||
Console.WriteLine(Strings.Program.SkippingSourceArgumentsOnNonBackupOperation);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception(Strings.Program.FailedToParseParametersFileError(filename, e.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +245,12 @@ namespace Duplicati.Server
|
||||
|
||||
try
|
||||
{
|
||||
// Recover from timedrift issues by overriding the dates if the last run date is in the future.
|
||||
if (last > DateTime.UtcNow)
|
||||
{
|
||||
start = DateTime.UtcNow;
|
||||
last = DateTime.UtcNow;
|
||||
}
|
||||
start = GetNextValidTime(start, last, sc.Repeat, sc.AllowedDays);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -12,6 +12,11 @@ Error message: {0}", message); }
|
||||
|
||||
"); } }
|
||||
public static string HelpDisplayFormat(string optionname, string optiontext) { return LC.L(@"--{0}: {1}", optionname, optiontext); }
|
||||
public static string ParametersFileOptionLong2 { get { return LC.L(@"This option can be used to store some or all of the options given to the commandline client. The file must be a plain text file, UTF-8 encoding is preferred. Each line in the file should be of the format --option=value. The special options --{0} and --{1} can be used to override the localpath and the remote destination uri, respectively. The options in this file take precedence over the options provided on the commandline. You cannot specify filters in both the file and on the commandline. Instead, you can use the special --{2}, --{3}, or --{4} options to specify filters inside the parameter file. Each filter must be prefixed with either a + or a -, and multiple filters must be joined with {5} ", "source", "target", "replace-filter", "append-filter", "prepend-filter", System.IO.Path.PathSeparator); } }
|
||||
public static string ParametersFileOptionShort { get { return LC.L(@"Path to a file with parameters"); } }
|
||||
public static string FiltersCannotBeUsedWithFileError2 { get { return LC.L(@"Filters cannot be specified on the commandline if filters are also present in the parameter file. Use the special --{0}, --{1}, or --{2} options to specify filters inside the parameter file. Each filter must be prefixed with either a + or a -, and multiple filters must be joined with {3}", "replace-filter", "append-filter", "prepend-filter", System.IO.Path.PathSeparator); } }
|
||||
public static string FailedToParseParametersFileError(string path, string message) { return LC.L(@"Unable to read the parameters file ""{0}"", reason: {1}", path, message); }
|
||||
public static string SkippingSourceArgumentsOnNonBackupOperation { get { return @"The --source argument was specified in the parameter file, but the current operation is not a backup operation, so the argument is ignored"; } }
|
||||
public static string LogfileCommandDescription { get { return LC.L(@"Outputs log information to the file given"); } }
|
||||
public static string LoglevelCommandDescription { get { return LC.L(@"Determines the amount of information written in the log file"); } }
|
||||
public static string PortablemodeCommandDescription { get { return LC.L(@"Activates portable mode where the database is placed below the program executable"); } }
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Duplicati.Server.WebServer.RESTMethods
|
||||
{
|
||||
var input = info.Request.Form;
|
||||
var cmdline = Library.Utility.Utility.ParseBool(input["cmdline"].Value, false);
|
||||
var import_metadata = Library.Utility.Utility.ParseBool(input["import_metadata"].Value, false);
|
||||
var direct = Library.Utility.Utility.ParseBool(input["direct"].Value, false);
|
||||
output_template = output_template.Replace("CBM", input["callback"].Value);
|
||||
if (cmdline)
|
||||
@@ -90,6 +91,9 @@ namespace Duplicati.Server.WebServer.RESTMethods
|
||||
ipx = Serializer.Deserialize<Serializable.ImportExportStructure>(sr);
|
||||
}
|
||||
}
|
||||
if (!import_metadata) {
|
||||
ipx.Backup.Metadata.Clear();
|
||||
}
|
||||
|
||||
ipx.Backup.ID = null;
|
||||
((Database.Backup)ipx.Backup).DBPath = null;
|
||||
|
||||
@@ -212,7 +212,6 @@ ul.tabs {
|
||||
.entries.livedata {
|
||||
li {
|
||||
height: 1.2em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
li.expanded {
|
||||
@@ -1492,7 +1491,7 @@ body {
|
||||
|
||||
ul.entries {
|
||||
li {
|
||||
padding-top: 15px;
|
||||
padding-top: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1820,6 +1819,15 @@ div.modal-dialog {
|
||||
}
|
||||
}
|
||||
|
||||
.importpage {
|
||||
form.styled {
|
||||
input {
|
||||
margin-top: 11px;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.restorewizard,
|
||||
.addwizard {
|
||||
form.styled {
|
||||
|
||||
+13
-13
File diff suppressed because one or more lines are too long
@@ -119,6 +119,14 @@ backupApp.directive('advancedOptionsEditor', function() {
|
||||
return item.LongDescription;
|
||||
};
|
||||
|
||||
$scope.getDefaultValue = function(item) {
|
||||
var item = $scope.getEntry(item);
|
||||
if (item == null)
|
||||
return null;
|
||||
|
||||
return item.DefaultValue;
|
||||
};
|
||||
|
||||
$scope.getEnumerations = function(item) {
|
||||
var item = $scope.getEntry(item);
|
||||
if (item == null)
|
||||
|
||||
@@ -201,26 +201,17 @@ backupApp.service('EditUriBuiltins', function(AppService, AppUtils, SystemInfo,
|
||||
if (scope.openstack_providers == null) {
|
||||
AppService.post('/webmodule/openstack-getconfig', {'openstack-config': 'Providers'}).then(function(data) {
|
||||
scope.openstack_providers = data.data.Result;
|
||||
if (scope.openstack_providers != null && scope.openstack_server == undefined && scope.openstack_server_custom == undefined)
|
||||
var first = null;
|
||||
var rs = null;
|
||||
|
||||
for (var k in scope.openstack_providers) {
|
||||
if (first == null)
|
||||
first = scope.openstack_providers[k];
|
||||
if (scope.openstack_providers[k] != null && scope.openstack_providers[k].indexOf('https://identity.api.rackspacecloud.com/') == 0)
|
||||
rs = scope.openstack_providers[k];
|
||||
}
|
||||
|
||||
if (rs != null)
|
||||
scope.openstack_server = rs;
|
||||
else if (first != null)
|
||||
scope.openstack_server = first;
|
||||
if (scope.openstack_server == undefined && scope.openstack_server_custom == undefined)
|
||||
scope.openstack_server = 'https://identity.api.rackspacecloud.com/';
|
||||
|
||||
}, AppUtils.connectionError);
|
||||
} else {
|
||||
if (scope.openstack_server == undefined && scope.openstack_server_custom == undefined)
|
||||
scope.openstack_server = 'https://identity.api.rackspacecloud.com/';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
EditUriBackendConfig.loaders['gcs'] = function(scope) {
|
||||
if (scope.gcs_locations == null) {
|
||||
AppService.post('/webmodule/gcs-getconfig', {'gcs-config': 'Locations'}).then(function(data) {
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
<ul class="advancedoptions">
|
||||
<li ng-repeat="item in ngModel" class="advancedentry">
|
||||
<li ng-repeat="item in ngModel | orderBy: item as nn track by $index" class="advancedentry">
|
||||
<div>
|
||||
<label class="shortname">{{getShortName(item)}}</label>
|
||||
|
||||
<select ng-if="getInputType(item) == 'enum'" ng-options="n for n in getEnumerations(item)" ng-model="$parent.ngModel[$index]" parse-advanced-option="getEnumerations(item)"></select>
|
||||
<select ng-if="getInputType(item) == 'flags'" ng-options="n for n in getEnumerations(item)" ng-model="$parent.ngModel[$index]" parse-advanced-option-flags="getEnumerations(item)" multiple="multiple"></select>
|
||||
<input ng-if="getInputType(item) == 'bool'" type="checkbox" ng-model="$parent.ngModel[$index]" parse-advanced-option-bool ></input>
|
||||
<input ng-if="getInputType(item) == 'password'" type="password" ng-model="$parent.ngModel[$index]" parse-advanced-option ></input>
|
||||
<input ng-if="getInputType(item) == 'text'" type="text" ng-model="$parent.ngModel[$index]" parse-advanced-option ></input>
|
||||
<input ng-if="getInputType(item) == 'int'" type="number" ng-model="$parent.ngModel[$index]" parse-advanced-option-integer ></input>
|
||||
<select ng-if="getInputType(item) == 'enum'" ng-options="n for n in getEnumerations(item)" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" parse-advanced-option="getEnumerations(item)"></select>
|
||||
<select ng-if="getInputType(item) == 'flags'" ng-options="n for n in getEnumerations(item)" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" parse-advanced-option-flags="getEnumerations(item)" multiple="multiple"></select>
|
||||
<input ng-if="getInputType(item) == 'bool'" type="checkbox" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" parse-advanced-option-bool ></input>
|
||||
<input ng-if="getInputType(item) == 'password'" type="password" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" parse-advanced-option ></input>
|
||||
<input ng-if="getInputType(item) == 'text'" type="text" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" parse-advanced-option ></input>
|
||||
<input ng-if="getInputType(item) == 'int'" type="number" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" parse-advanced-option-integer ></input>
|
||||
|
||||
<div ng-if="getInputType(item) == 'size'" class="input type multiple">
|
||||
<input type="number" parse-advanced-option-size-number="uppercase" ng-model="$parent.ngModel[$index]" />
|
||||
<select parse-advanced-option-size-multiplier="uppercase" ng-model="$parent.ngModel[$index]" ng-options="item.value as item.name for item in fileSizeMultipliers">
|
||||
<input type="number" parse-advanced-option-size-number="uppercase" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" />
|
||||
<select parse-advanced-option-size-multiplier="uppercase" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" ng-options="item.value as item.name for item in fileSizeMultipliers">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div ng-if="getInputType(item) == 'speed'" class="input type multiple">
|
||||
<input type="number" parse-advanced-option-size-number="uppercase" ng-model="$parent.ngModel[$index]" />
|
||||
<select parse-advanced-option-size-multiplier="uppercase" ng-model="$parent.ngModel[$index]" ng-options="item.value as item.name for item in speedMultipliers">
|
||||
<input type="number" parse-advanced-option-size-number="uppercase" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" />
|
||||
<select parse-advanced-option-size-multiplier="uppercase" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" ng-options="item.value as item.name for item in speedMultipliers">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div ng-if="getInputType(item) == 'timespan'" class="input type multiple">
|
||||
<input parse-advanced-option-size-number type="number" ng-model="$parent.ngModel[$index]" />
|
||||
<select parse-advanced-option-size-multiplier ng-model="$parent.ngModel[$index]" ng-options="item.value as item.name for item in timerangeMultipliers">
|
||||
<input parse-advanced-option-size-number type="number" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" />
|
||||
<select parse-advanced-option-size-multiplier ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" ng-options="item.value as item.name for item in timerangeMultipliers">
|
||||
<option value="" translate>custom</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div ng-if="getInputType(item) == 'shorttimespan'" class="input type multiple">
|
||||
<input parse-advanced-option-size-number type="number" ng-model="$parent.ngModel[$index]" />
|
||||
<select parse-advanced-option-size-multiplier ng-model="$parent.ngModel[$index]" ng-options="item.value as item.name for item in shorttimerangeMultipliers">
|
||||
<input parse-advanced-option-size-number type="number" ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" />
|
||||
<select parse-advanced-option-size-multiplier ng-model="$parent.ngModel[$parent.ngModel.indexOf(item)]" ng-options="item.value as item.name for item in shorttimerangeMultipliers">
|
||||
<option value="" translate>custom</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
<a href ng-click="deleteItem(item)" title="{{'Remove option' | translate}}">x</a>
|
||||
|
||||
<div class="longdescription">{{getLongDescription(item)}}</div>
|
||||
<div class="longdescription">{{getLongDescription(item)}}<br>Default value: "{{getDefaultValue(item)}}"</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div ng-controller="ImportController" class="importpage">
|
||||
|
||||
|
||||
<h1 ng-hide="restoremode" translate>Import backup configuration</h1>
|
||||
<h1 ng-show="restoremode" translate>Restore from backup configuration</h1>
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
<input type="password" name="passphrase" id="passphrase" placeholder="{{'Enter encryption passphrase' | translate}}" />
|
||||
</div>
|
||||
|
||||
<div class="input checkbox" ng-hide="restoremode">
|
||||
<label for="import_metadata" translate>Import metadata</label>
|
||||
<input type="checkbox" name="import_metadata" id="import_metadata" value="true" />
|
||||
</div>
|
||||
|
||||
<div class="input checkbox" ng-hide="restoremode">
|
||||
<label for="direct" translate>Save immediately</label>
|
||||
<input type="checkbox" name="direct" id="direct" value="true" />
|
||||
@@ -25,7 +30,7 @@
|
||||
<div class="buttons" ng-hide="Connecting">
|
||||
<a href ng-click="doSubmit()" translate>Import</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="buttons" ng-show="Connecting">
|
||||
<a href translate>Importing ...</a>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,6 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
[TestFixture]
|
||||
public class BorderTests : BasicSetupHelper
|
||||
{
|
||||
public override void PrepareSourceData()
|
||||
|
||||
@@ -23,7 +23,6 @@ using System.Reflection;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
[TestFixture]
|
||||
public class CommandLineOperationsTests : BasicSetupHelper
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -22,7 +22,6 @@ using NUnit.Framework;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
[TestFixture]
|
||||
[Category("Compression")]
|
||||
public class CompressionTests
|
||||
{
|
||||
|
||||
@@ -23,7 +23,6 @@ using System.Reflection;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
[TestFixture]
|
||||
public class GeneralBlackBoxTesting
|
||||
{
|
||||
private static readonly string SOURCE_FOLDERS =
|
||||
|
||||
@@ -22,7 +22,6 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
[TestFixture]
|
||||
public class Issue1410 : BasicSetupHelper
|
||||
{
|
||||
[OneTimeSetUp]
|
||||
|
||||
@@ -22,7 +22,6 @@ using NUnit.Framework;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
[TestFixture]
|
||||
public class PurgeTesting : BasicSetupHelper
|
||||
{
|
||||
public override void PrepareSourceData()
|
||||
|
||||
@@ -21,7 +21,6 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Duplicati.UnitTest
|
||||
{
|
||||
[TestFixture]
|
||||
public class UtilityTests
|
||||
{
|
||||
[Test]
|
||||
|
||||
@@ -100,7 +100,7 @@ git checkout INFO
|
||||
echo "version=\"${VERSION}\"" >> "INFO"
|
||||
MD5=`md5 "package.tgz" | awk -F ' ' '{print $NF}'`
|
||||
echo "checksum=\"${MD5}\"" >> "INFO"
|
||||
echo "extractsize=\"${DIRSIZE}\"" >> "INFO"
|
||||
echo "extractsize=\"${DIRSIZE_KB}\"" >> "INFO"
|
||||
echo "package_icon=\"${ICON_72}\"" >> "INFO"
|
||||
echo "package_icon_256=\"${ICON_256}\"" >> "INFO"
|
||||
|
||||
@@ -145,4 +145,4 @@ if [ "z${GPGID}" != "z" ]; then
|
||||
tar cf "${BASE_FILE_NAME}.spk" -C "${TMPDIRNAME}" `ls -1 ${TMPDIRNAME}`
|
||||
|
||||
rm -rf "${TMPDIRNAME}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1 +1 @@
|
||||
16
|
||||
17
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
2018-01-23 - 2.0.2.17_canary_2018-01-23
|
||||
==========
|
||||
- Fixed a bug with the update packages having an invalid hash
|
||||
- Updated external links in the UI to be clearly marked
|
||||
- Experimental support for Google Team Drives
|
||||
- Fixed WEBDAV Test function, thanks @inat3k
|
||||
- Added an rclone backend, thanks @wjansenw
|
||||
- Fixed some broken error messages, thanks @JonMikeIV
|
||||
- Added a simple UI for exposing the `--retention-policy` option
|
||||
|
||||
2018-01-18 - 2.0.2.16_canary_2018-01-18
|
||||
==========
|
||||
* Reworked some internal compression code, thanks @vnau
|
||||
|
||||
Reference in New Issue
Block a user