diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js
index 0f8c2654b..72c0588b4 100644
--- a/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js
+++ b/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js
@@ -29,7 +29,7 @@ backupApp.controller('StateController', function($scope, $timeout, ServerStatus,
text = ServerStatus.progress_state_text[$scope.state.lastPgEvent.Phase || ''] || $scope.state.lastPgEvent.Phase;
- if ($scope.state.lastPgEvent.Phase == 'Backup_ProcessingFiles') {
+ if ($scope.state.lastPgEvent.Phase == 'Backup_ProcessingFiles' || $scope.state.lastPgEvent.Phase == 'Restore_DownloadingRemoteFiles') {
if ($scope.state.lastPgEvent.StillCounting) {
text = gettextCatalog.getString('Counting ({{files}} files found, {{size}})', { files: $scope.state.lastPgEvent.TotalFileCount, size: AppUtils.formatSizeString($scope.state.lastPgEvent.TotalFileSize) });
pg = 0;
@@ -47,8 +47,10 @@ backupApp.controller('StateController', function($scope, $timeout, ServerStatus,
// If we have a speed append it
var speed_txt = ($scope.state.lastPgEvent.BackendSpeed < 0) ? "" : " at "+AppUtils.formatSizeString($scope.state.lastPgEvent.BackendSpeed)+"/s";
+ var restoring_text = $scope.state.lastPgEvent.Phase == 'Restore_DownloadingRemoteFiles' ? 'Restoring: ' : '';
+
// Finally construct the whole text
- text = gettextCatalog.getString('{{files}} files ({{size}}) to go {{speed_txt}}', { files: filesleft, size: AppUtils.formatSizeString(sizeleft), speed_txt: speed_txt});
+ text = gettextCatalog.getString(restoring_text + '{{files}} files ({{size}}) to go {{speed_txt}}', { files: filesleft, size: AppUtils.formatSizeString(sizeleft), speed_txt: speed_txt});
}
}
else if ($scope.state.lastPgEvent.Phase == 'Backup_Finalize' || $scope.state.lastPgEvent.Phase == 'Backup_WaitForUpload')
diff --git a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js
index 63a94b64d..7ec3a522c 100644
--- a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js
+++ b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js
@@ -25,14 +25,16 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.templates['gcs'] = 'templates/backends/gcs.html';
EditUriBackendConfig.templates['b2'] = 'templates/backends/b2.html';
EditUriBackendConfig.templates['mega'] = 'templates/backends/mega.html';
- EditUriBackendConfig.templates['jottacloud'] = 'templates/backends/jottacloud.html';
+ EditUriBackendConfig.templates['jottacloud'] = 'templates/backends/oauth.html';
+ EditUriBackendConfig.templates['idrive'] = 'templates/backends/idrive.html';
EditUriBackendConfig.templates['box'] = 'templates/backends/oauth.html';
- EditUriBackendConfig.templates['dropbox'] = 'templates/backends/oauth.html';
- EditUriBackendConfig.templates['sia'] = 'templates/backends/sia.html';
- EditUriBackendConfig.templates['storj'] = 'templates/backends/storj.html';
+ EditUriBackendConfig.templates['dropbox'] = 'templates/backends/oauth.html';
+ EditUriBackendConfig.templates['sia'] = 'templates/backends/sia.html';
+ EditUriBackendConfig.templates['storj'] = 'templates/backends/storj.html';
EditUriBackendConfig.templates['tardigrade'] = 'templates/backends/tardigrade.html';
EditUriBackendConfig.templates['rclone'] = 'templates/backends/rclone.html';
EditUriBackendConfig.templates['cos'] = 'templates/backends/cos.html';
+ EditUriBackendConfig.templates['e2'] = 'templates/backends/e2.html';
EditUriBackendConfig.testers['s3'] = function(scope, callback) {
@@ -267,6 +269,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.loaders['onedrivev2'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.loaders['sharepoint'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.loaders['msgroup'] = function() { return this['oauth-base'].apply(this, arguments); };
+ EditUriBackendConfig.loaders['jottacloud'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.loaders['box'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.loaders['dropbox'] = function() { return this['oauth-base'].apply(this, arguments); };
@@ -421,6 +424,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.parsers['onedrive'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.parsers['onedrivev2'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.parsers['sharepoint'] = function() { return this['oauth-base'].apply(this, arguments); };
+ EditUriBackendConfig.parsers['jottacloud'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.parsers['box'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.parsers['dropbox'] = function() { return this['oauth-base'].apply(this, arguments); };
@@ -478,11 +482,18 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
delete options[nukeopts[x]];
};
- EditUriBackendConfig.parsers['mega'] = function (scope, module, server, port, path, options) {
- EditUriBackendConfig.mergeServerAndPath(scope);
+ EditUriBackendConfig.parsers['e2'] = function (scope, module, server, port, path, options) {
+ if (options['--access-key-id'])
+ scope.Username = options['--access-key-id'];
+ if (options['--access-key-secret'])
+ scope.Password = options['--access-key-secret'];
+
+ var nukeopts = ['--access-key-id', '--access-key-secret'];
+ for (var x in nukeopts)
+ delete options[nukeopts[x]];
};
- EditUriBackendConfig.parsers['jottacloud'] = function (scope, module, server, port, path, options) {
+ EditUriBackendConfig.parsers['mega'] = function (scope, module, server, port, path, options) {
EditUriBackendConfig.mergeServerAndPath(scope);
};
@@ -643,6 +654,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.builders['onedrivev2'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.builders['sharepoint'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.builders['msgroup'] = function() { return this['oauth-base'].apply(this, arguments); };
+ EditUriBackendConfig.builders['jottacloud'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.builders['box'] = function() { return this['oauth-base'].apply(this, arguments); };
EditUriBackendConfig.builders['dropbox'] = function() { return this['oauth-base'].apply(this, arguments); };
@@ -759,6 +771,24 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
return url;
};
+ EditUriBackendConfig.builders['e2'] = function (scope) {
+ var opts = {};
+
+ EditUriBackendConfig.merge_in_advanced_options(scope, opts);
+
+ // Slightly better error message
+ scope.Folder = scope.Server;
+
+ var url = AppUtils.format('{0}://{1}/{2}{3}',
+ scope.Backend.Key,
+ scope.Server || '',
+ scope.Path || '',
+ AppUtils.encodeDictAsUrl(opts)
+ );
+
+ return url;
+ };
+
EditUriBackendConfig.builders['mega'] = function (scope) {
var opts = {};
@@ -865,23 +895,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
}
- EditUriBackendConfig.builders['jottacloud'] = function (scope) {
- var opts = {};
-
- EditUriBackendConfig.merge_in_advanced_options(scope, opts);
-
- // Slightly better error message
- scope.Folder = scope.Path;
-
- var url = AppUtils.format('{0}://{1}{2}',
- scope.Backend.Key,
- scope.Path,
- AppUtils.encodeDictAsUrl(opts)
- );
-
- return url;
- };
-
EditUriBackendConfig.builders['cos'] = function (scope) {
var opts = {
@@ -948,6 +961,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
EditUriBackendConfig.validaters['googledrive'] = EditUriBackendConfig.validaters['authid-base'];
EditUriBackendConfig.validaters['gcs'] = EditUriBackendConfig.validaters['authid-base'];
+ EditUriBackendConfig.validaters['jottacloud'] = EditUriBackendConfig.validaters['authid-base'];
EditUriBackendConfig.validaters['box'] = EditUriBackendConfig.validaters['authid-base'];
EditUriBackendConfig.validaters['dropbox'] = EditUriBackendConfig.validaters['authid-base'];
EditUriBackendConfig.validaters['onedrive'] = EditUriBackendConfig.validaters['authid-base'];
@@ -1157,16 +1171,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
continuation();
};
- EditUriBackendConfig.validaters['jottacloud'] = function (scope, continuation) {
- scope.Path = scope.Path || '';
- var res =
- EditUriBackendConfig.require_field(scope, 'Username', gettextCatalog.getString('Username')) &&
- EditUriBackendConfig.require_field(scope, 'Password', gettextCatalog.getString('Password'));
-
- if (res)
- continuation();
- };
-
EditUriBackendConfig.validaters['sia'] = function (scope, continuation) {
var res =
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Server'));
@@ -1272,4 +1276,48 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
if (res)
continuation();
};
+
+ EditUriBackendConfig.validaters['e2'] = function (scope, continuation) {
+ var res =
+ EditUriBackendConfig.require_field(scope, 'Username', gettextCatalog.getString('Idrivee2 Access Key Id')) &&
+ EditUriBackendConfig.require_field(scope, 'Password', gettextCatalog.getString('Idrivee2 Access Key Secret')) &&
+ EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket Name'));
+
+ if (res) {
+ var re = new RegExp('[^A-Za-z0-9-]');
+ var bucketname = scope['Server'] || '';
+ var ix = bucketname.search(/[^A-Za-z0-9-]/g);
+
+ if (ix >= 0) {
+ EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('The \'{{fieldname}}\' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})', {
+ value: bucketname[ix].charCodeAt(),
+ pos: ix,
+ character: bucketname[ix],
+ fieldname: gettextCatalog.getString('Bucket Name')
+ }));
+ res = false;
+ }
+ }
+
+ if (res) {
+ var pathname = scope['Path'] || '';
+ for (var i = pathname.length - 1; i >= 0; i--) {
+ var char = pathname.charCodeAt(i);
+
+ if (char == '\\'.charCodeAt(0) || char == 127 || char < 32) {
+ EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('The \'{{fieldname}}\' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})', {
+ value: char,
+ pos: i,
+ character: pathname[i],
+ fieldname: gettextCatalog.getString('Path')
+ }));
+ res = false;
+ break;
+ }
+ }
+ }
+
+ if (res)
+ continuation();
+ };
});
diff --git a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js
index b6b4e4e52..deef0c821 100644
--- a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js
+++ b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js
@@ -53,6 +53,7 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ
},
local: {'file': null},
prop: {
+ 'e2':null,
's3': null,
'azure': null,
'googledrive': null,
@@ -66,6 +67,7 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ
'hubic': null,
'b2': null,
'mega': null,
+ 'idrive': null,
'box': null,
'od4b': null,
'mssp': null,
diff --git a/Duplicati/Server/webroot/ngax/templates/backends/e2.html b/Duplicati/Server/webroot/ngax/templates/backends/e2.html
new file mode 100644
index 000000000..86af8a762
--- /dev/null
+++ b/Duplicati/Server/webroot/ngax/templates/backends/e2.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Duplicati/Server/webroot/ngax/templates/backends/idrive.html b/Duplicati/Server/webroot/ngax/templates/backends/idrive.html
new file mode 100644
index 000000000..1a5973341
--- /dev/null
+++ b/Duplicati/Server/webroot/ngax/templates/backends/idrive.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Duplicati/Server/webroot/ngax/templates/backends/jottacloud.html b/Duplicati/Server/webroot/ngax/templates/backends/jottacloud.html
index 04efe6d28..dcff7dab5 100644
--- a/Duplicati/Server/webroot/ngax/templates/backends/jottacloud.html
+++ b/Duplicati/Server/webroot/ngax/templates/backends/jottacloud.html
@@ -1,13 +1,12 @@
-
+
-
-
-
-
diff --git a/Duplicati/Service/Runner.cs b/Duplicati/Service/Runner.cs
index 4f2ae0f76..3922f7910 100644
--- a/Duplicati/Service/Runner.cs
+++ b/Duplicati/Service/Runner.cs
@@ -79,13 +79,13 @@ namespace Duplicati.Service
m_reportMessage(string.Format("Starting process {0} with cmd args {1}", exec, cmdargs), false);
- var pr = new System.Diagnostics.ProcessStartInfo(exec, cmdargs)
- {
- UseShellExecute = false,
- RedirectStandardInput = true,
+ var pr = new System.Diagnostics.ProcessStartInfo(exec, cmdargs)
+ {
+ UseShellExecute = false,
+ RedirectStandardInput = true,
RedirectStandardOutput = true,
- RedirectStandardError = false,
- WorkingDirectory = path
+ RedirectStandardError = false,
+ WorkingDirectory = path
};
if (!m_terminate)
diff --git a/Duplicati/UnitTest/BackendToolTests.cs b/Duplicati/UnitTest/BackendToolTests.cs
index d617b5c71..d74564287 100755
--- a/Duplicati/UnitTest/BackendToolTests.cs
+++ b/Duplicati/UnitTest/BackendToolTests.cs
@@ -31,6 +31,10 @@ namespace Duplicati.UnitTest
using (Controller c = new Controller(backendURL, options, null))
{
var backupResults = c.Backup(new[] {DATAFOLDER});
+ foreach (var backupResultsWarning in backupResults.Warnings)
+ {
+ TestContext.WriteLine("Backend result warning:" + backupResultsWarning);
+ }
Assert.AreEqual(0, backupResults.Errors.Count(), $"Errors: {String.Join(", ", backupResults.Errors)}");
Assert.AreEqual(0, backupResults.Warnings.Count(), $"Warnings: {String.Join(", ", backupResults.Warnings)}");
}
@@ -71,4 +75,4 @@ namespace Duplicati.UnitTest
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Duplicati/UnitTest/Duplicati.UnitTest.csproj b/Duplicati/UnitTest/Duplicati.UnitTest.csproj
index 03c5c7b59..d83485a2a 100644
--- a/Duplicati/UnitTest/Duplicati.UnitTest.csproj
+++ b/Duplicati/UnitTest/Duplicati.UnitTest.csproj
@@ -29,6 +29,8 @@
+
+
diff --git a/Installer/Docker/README.md b/Installer/Docker/README.md
index 3928c17d5..e905ed620 100644
--- a/Installer/Docker/README.md
+++ b/Installer/Docker/README.md
@@ -1,7 +1,7 @@
# [Duplicati](https://www.duplicati.com)
Duplicati is a free, open source, backup client that securely stores encrypted, incremental, compressed backups on cloud storage services and remote file servers. It works with:
-*Amazon S3, OneDrive, Google Drive, Rackspace Cloud Files, HubiC, Backblaze (B2), Swift / OpenStack, WebDAV, SSH (SFTP), FTP, and more!*
+*Amazon S3, IDrive e2, OneDrive, Google Drive, Rackspace Cloud Files, HubiC, Backblaze (B2), Swift / OpenStack, WebDAV, SSH (SFTP), FTP, and more!*
Duplicati is licensed under LGPL and available for Windows, OSX and Linux (.NET 4.7.1+ or Mono 4.8.0+ required).
diff --git a/README.md b/README.md
index 4200e63b8..a0eec245a 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Removed Gitter
Duplicati is a free, open source, backup client that securely stores encrypted, incremental, compressed backups on cloud storage services and remote file servers. It works with:
- *Amazon S3, [Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage"), Box, Dropbox, FTP, Google Cloud and Drive, HubiC, MEGA, Microsoft Azure and OneDrive, Rackspace Cloud Files, OpenStack Storage (Swift), Sia, Storj DCS, SSH (SFTP), WebDAV, Tencent Cloud Object Storage (COS), [and more!](https://duplicati.readthedocs.io/en/latest/01-introduction/#supported-backends)*
+ *Amazon S3, [IDrive e2](https://www.idrive.com/e2/duplicati "Using Duplicati with IDrive e2"), [Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage"), Box, Dropbox, FTP, Google Cloud and Drive, HubiC, MEGA, Microsoft Azure and OneDrive, Rackspace Cloud Files, OpenStack Storage (Swift), Sia, Storj DCS, SSH (SFTP), WebDAV, Tencent Cloud Object Storage (COS), [and more!](https://duplicati.readthedocs.io/en/latest/01-introduction/#supported-backends)*
Duplicati is licensed under LGPL and available for Windows, OSX and Linux (.NET 4.7.1+ or Mono 5.10.0+ required).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..4f39f5969
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,3 @@
+Any security issues can be reported to the main author at kenneth@duplicati.com.
+
+If the issue is sensitive, the [PGP signing key](https://pgp.mit.edu/pks/lookup?op=get&search=0xC20E90473DAC703D) can be used for encryption.
diff --git a/Tools/Verification/DuplicatiVerify.py b/Tools/Verification/DuplicatiVerify.py
index f0372cdf4..0b88b1693 100644
--- a/Tools/Verification/DuplicatiVerify.py
+++ b/Tools/Verification/DuplicatiVerify.py
@@ -46,11 +46,13 @@ def verifyHashes(filename, quiet = True, buffer_size=8192):
filename = file["Name"]
hash = file["Hash"]
size = file["Size"]
+ state = file["State"]
fullpath = os.path.join(folder, filename)
if not os.path.exists(fullpath):
- print("File missing:", fullpath)
- errorCount += 1
+ if state != 5:
+ print("File missing:", fullpath)
+ errorCount += 1
else:
checked += 1
if not quiet:
@@ -82,22 +84,142 @@ if __name__ == "__main__":
parser.add_argument("path", type=str, nargs='?',
help="""path to the verification file or folder containing the verification file.\
Defaulf is curent path""")
+ parser.add_argument("--stats_only", action='store_true', help="display statistics from json only, no verification")
args = parser.parse_args()
- if args.path is None:
- args.path = os.getcwd()
+ if not args.stats_only: # if stats_only arg present skip file verification
+ if args.path is None:
+ args.path = os.getcwd()
- if not os.path.exists(args.path):
- print("No such file or directory: ", args.path)
- else:
- if os.path.isfile(args.path):
- verifyHashes(args.path, args.quiet, args.buffer_size * 1024)
+ if not os.path.exists(args.path):
+ print("No such file or directory: ", args.path)
else:
- files = 0
- for f in os.listdir(args.path):
- if (f.endswith("-verification.json")):
- print("Verifying file:", f)
- files += 1
- verifyHashes(os.path.join(args.path, f), args.quiet, args.buffer_size *1024)
- if files == 0:
- print("No verification files in folder:", args.path)
+ if os.path.isfile(args.path):
+ verifyHashes(args.path, args.quiet, args.buffer_size * 1024)
+ else:
+ files = 0
+ for f in os.listdir(args.path):
+ if (f.endswith("-verification.json")):
+ print("Verifying file:", f)
+ files += 1
+ verifyHashes(os.path.join(args.path, f), args.quiet, args.buffer_size *1024)
+ if files == 0:
+ print("No verification files in folder:", args.path)
+ else:
+ print("Verify skipped, displaying statistics only ")
+
+
+""" Calculate statistics """
+class Statistics:
+
+ def get_json_for_statistics(): #copied from main
+ files = 0
+ for f in os.listdir(args.path):
+ if (f.endswith("-verification.json")):
+ print("Statistics for file", f)
+ files += 1
+ statistics_source = Statistics.load_json_for_statistics(os.path.join(args.path, f), args.quiet, args.buffer_size *1024)
+ return statistics_source
+ if files == 0:
+ print("No verification files in folder:", args.path)
+
+ def load_json_for_statistics(filename, quiet = True, buffer_size=8192): #copied from verifyHashes
+ if (not os.path.exists(filename)):
+ print("Specified file does not exist:", filename)
+ return -1
+ folder = os.path.dirname(filename)
+ with codecs.open(filename, "r", "utf-8-sig") as f:
+ doc = json.load(f)
+ statistics_source = doc
+ return statistics_source
+
+ """ Statistics legend """
+ RemoteVolumeState = [
+ # Indicates that the remote volume is being created
+ "Temporary",
+ # Indicates that the remote volume is being uploaded
+ "Uploading",
+ # Indicates that the remote volume has been uploaded
+ "Uploaded",
+ # Indicates that the remote volume has been uploaded, and seen by a list operation
+ "Verified",
+ # Indicattes that the remote volume should be deleted
+ "Deleting",
+ # Indicates that the remote volume was successfully deleted from the remote location
+ "Deleted"]
+ RemoteVolumeType = [
+ # Contains data blocks
+ "Blocks",
+ # Contains file lists
+ "Files",
+ # Contains redundant lookup information
+ "Index"]
+
+ """ Utility convert bytes to readable units for statistics """
+ def convert_bytes_to_readable(size):
+ if size is None:
+ size = 0
+ return size
+ if size < 0:
+ size = 0 #because for "RemoteVolumeState 5" (Deleted) is size in json "-1"
+ return size
+ for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
+ if size < 1024.0:
+ return "%3.1f %s" % (size, x)
+ size /= 1024.0
+ return size
+
+
+ def compute_statistics():
+ index_main_loop = 0
+ index_sub_loop = 0
+ list_of_all_types = []
+ list_of_all_states = []
+ dict_with_sizes_of_all_items= {}
+ total_size = 0
+
+ #main statistics loop
+ statistics_source = Statistics.get_json_for_statistics() # call load data from json
+ for file in statistics_source:
+ #loop for geting size for each RemoteVolumeType to dict
+ for each_type in Statistics.RemoteVolumeType: #for each json file for each type ID
+ current_type = (statistics_source[index_main_loop]["Type"])
+ if current_type == index_sub_loop:
+ dict_with_sizes_of_all_items[each_type] = dict_with_sizes_of_all_items.get(each_type,0) + (statistics_source[index_main_loop]["Size"])
+ index_sub_loop += 1
+ if index_sub_loop >2:
+ index_sub_loop = 0
+ #loop for geting size for each RemoteVolumeState to dict
+ for each_state in Statistics.RemoteVolumeState:
+ current_type = (statistics_source[index_main_loop]["State"])
+ if current_type == index_sub_loop:
+ dict_with_sizes_of_all_items[each_state] = dict_with_sizes_of_all_items.get(each_state,0) + (statistics_source[index_main_loop]["Size"])
+ index_sub_loop += 1
+ if index_sub_loop >5:
+ index_sub_loop = 0
+ #store Type and State to list
+ list_of_all_types.append(statistics_source[index_main_loop]["Type"])
+ list_of_all_states.append(statistics_source[index_main_loop]["State"])
+ index_main_loop += 1
+
+ print("\n")
+ index_main_loop = 0
+ #Print number of files and size from dict
+ for state in Statistics.RemoteVolumeState:
+ print(state,"count",list_of_all_states.count(Statistics.RemoteVolumeState.index(state)),"Size ",Statistics.convert_bytes_to_readable(dict_with_sizes_of_all_items.get(state)))
+ index_main_loop += 1
+ print("\n")
+
+ #Print number of files and size from dict
+ for type in Statistics.RemoteVolumeType:
+ print(type,"count",list_of_all_types.count(Statistics.RemoteVolumeType.index(type)),"size:",Statistics.convert_bytes_to_readable(dict_with_sizes_of_all_items.get(type)))
+
+ #Print total size
+ for key in Statistics.RemoteVolumeType:
+ total_size += dict_with_sizes_of_all_items.get(key)
+ print("\n""Total size:",Statistics.convert_bytes_to_readable(total_size) )
+
+#to run statistics
+Statistics.compute_statistics()
+
+
diff --git a/Updates/build_version.txt b/Updates/build_version.txt
index 29d6383b5..b16e5f75e 100644
--- a/Updates/build_version.txt
+++ b/Updates/build_version.txt
@@ -1 +1 @@
-100
+104
diff --git a/changelog.txt b/changelog.txt
index d0fb0e6d0..390c895f6 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,64 @@
+2022-06-15 - 2.0.6.104_canary_2022-06-15
+==========
+* Updated compressed files exclusion list, thanks @martengooz
+* Updated list of Infomaniak servers, thanks @linitio
+* Fixed irregular parsing of `--patch-with-local-blocks`, thanks @warwickmm
+* Added `IDrivee2` backend, thanks @MuhammadAbrar1984
+* Fixed `--use-move-for-put` which broken in previous build, thanks @kenkendk
+
+2022-06-12 - 2.0.6.103_canary_2022-06-12
+==========
+* Avoid creating empty data folders, thanks @albertony
+* Disable updates for debug builds, thanks @AxeOfMen
+* Fixed issue with empty popups, thanks @AxeOfMen
+* Improved Python verification script, thanks @mr-flibble
+* Fixed issue where invalid timestamps would prevent files from being backed up, thanks @albertony
+* Improved restore progress information, thanks @flawiddsouza
+* Python verification script now has statistics, thanks @mr-flibble
+* Added Swiss Backup to list of OpenStack and S3 providers, thanks @PAPAMICA
+* Fixed errors on updated Hyper-V versions, thanks @awerghcpc
+* Added check to detect partially written files on network drives, thanks @dkrahmer
+* Added IDrive as a backup destination, thanks @dkrahmer
+* Removed insecure TLS versions, thanks @mnaiman
+* Changed Jottacloud backend to use OAuth, thanks @albertony
+
+2022-04-06 - 2.0.6.102_canary_2022-04-06
+==========
+**NOTE**: This is a rebuild of 2.0.6.101, now signed.
+
+- Fixed duplicity documentation Url, thanks @beppo-dd
+- Added option to set Tencent storage class, thanks @moesoha
+- Added Poli Systems as S3 provider, thanks @IIPoliII
+- Improved automated unittests, thanks @samuel-w
+- Improved contrast ratios in themes, thanks @samuel-w
+- Improved handling of relative paths and extended device paths, thanks @dferreyra
+- UI performance improvements, thanks @ltGuillaume
+- Added exponential backup for retries, thanks @vmsh0
+- Improved filter-text-box and made log-view resizable, thanks @idotj
+- Improved error messages, thanks @warwickmm
+- Added support for old and new Dropbox AuthIDs, thanks @kenkendk
+- Improved database query performance, thanks @jedthe3rd
+
+2022-03-13 - 2.0.6.101_canary_2022-03-13
+==========
+**NOTE**: This is a preliminary build that is not signed with AuthentiCode.
+
+It is primarily intended for users that need access to Dropbox which stopped working after an update.
+A signed version will be published asap.
+
+- Fixed duplicity documentation Url, thanks @beppo-dd
+- Added option to set Tencent storage class, thanks @moesoha
+- Added Poli Systems as S3 provider, thanks @IIPoliII
+- Improved automated unittests, thanks @samuel-w
+- Improved contrast ratios in themes, thanks @samuel-w
+- Improved handling of relative paths and extended device paths, thanks @dferreyra
+- UI performance improvements, thanks @ltGuillaume
+- Added exponential backup for retries, thanks @vmsh0
+- Improved filter-text-box and made log-view resizable, thanks @idotj
+- Improved error messages, thanks @warwickmm
+- Added support for old and new Dropbox AuthIDs, thanks @kenkendk
+- Improved database query performance, thanks @jedthe3rd
+
2021-08-11 - 2.0.6.100_canary_2021-08-11
==========
* Switched to improved CI model, thanks @warwickmm
diff --git a/guiTests/guiTest.py b/guiTests/guiTest.py
index 8290e64e1..99cc56999 100644
--- a/guiTests/guiTest.py
+++ b/guiTests/guiTest.py
@@ -120,14 +120,14 @@ wait_for_load(10, By.ID, "save").click()
# Run the backup job and wait for finish
wait_for_load(10, By.LINK_TEXT, BACKUP_NAME).click()
-[n for n in driver.find_elements_by_xpath("//dl[@class='taskmenu']/dd/p/span[contains(text(),'Run now')]") if n.is_displayed()][0].click()
+[n for n in driver.find_elements("xpath", "//dl[@class='taskmenu']/dd/p/span[contains(text(),'Run now')]") if n.is_displayed()][0].click()
wait_for_text(60, "//div[@class='task ng-scope']/dl[2]/dd[1]", "(took ")
# Restore
-if len([n for n in driver.find_elements_by_xpath("//span[contains(text(),'Restore files \u2026')]") if n.is_displayed()]) == 0:
+if len([n for n in driver.find_elements("xpath", u"//span[contains(text(),'Restore files \u2026')]") if n.is_displayed()]) == 0:
wait_for_load(10, By.LINK_TEXT, BACKUP_NAME).click()
-[n for n in driver.find_elements_by_xpath("//span[contains(text(),'Restore files \u2026')]") if n.is_displayed()][0].click()
+[n for n in driver.find_elements("xpath", u"//span[contains(text(),'Restore files \u2026')]") if n.is_displayed()][0].click()
wait_for_load(10, By.XPATH, "//span[contains(text(),'" + SOURCE_FOLDER + "')]") # wait for filelist
time.sleep(1) # Delay so page has time to load
wait_for_load(10, By.XPATH, "//restore-file-picker/ul/li/div/a[2]").click() # select root folder checkbox