Fixed UI and added some verbose logging
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -269,13 +270,13 @@ public class FilenClient : IDisposable
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
var result = JsonSerializer.Deserialize<FilenResponseEnvelope<T>>(json);
|
||||
//var result = await response.Content.ReadFromJsonAsync<FilenResponse<T>>(cancellationToken).ConfigureAwait(false);
|
||||
// var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
// var result = JsonSerializer.Deserialize<FilenResponseEnvelope<T>>(json);
|
||||
var result = await response.Content.ReadFromJsonAsync<FilenResponseEnvelope<T>>(cancellationToken).ConfigureAwait(false);
|
||||
if (result is null)
|
||||
throw new Exception("Failed to read response");
|
||||
if (!result.Status || !string.IsNullOrWhiteSpace(result.Error))
|
||||
throw new Exception($"Server returned error: {result.Error ?? result.Message ?? "Unknown"}");
|
||||
throw new UserInformationException($"{result.Error ?? result.Message ?? "Unknown"}", "FilenAPIError");
|
||||
|
||||
return result.Data;
|
||||
}
|
||||
@@ -456,6 +457,8 @@ public class FilenClient : IDisposable
|
||||
bytesRead += read;
|
||||
}
|
||||
|
||||
Logging.Log.WriteVerboseMessage(LOGTAG, "UploadingChunk", "Uploading chunk {0}, size {1}", chunk, bytesRead);
|
||||
|
||||
// Stream is exhausted
|
||||
if (bytesRead == 0) break;
|
||||
|
||||
@@ -482,6 +485,8 @@ public class FilenClient : IDisposable
|
||||
chunk++;
|
||||
}
|
||||
|
||||
Logging.Log.WriteVerboseMessage(LOGTAG, "UploadingChunk", "Finished uploading {0} chunks", chunk);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@@ -554,6 +559,10 @@ public class FilenClient : IDisposable
|
||||
var totalSize = file.Size;
|
||||
while (downloaded < totalSize)
|
||||
{
|
||||
if (chunk >= file.Chunks)
|
||||
throw new Exception($"Attempted to download more chunks than available, expected {file.Chunks}, got {chunk}. File: {file.Name}, total size: {totalSize}, downloaded: {downloaded}");
|
||||
|
||||
Logging.Log.WriteVerboseMessage(LOGTAG, "DownloadingChunk", "Downloading chunk {0}, size {1}", chunk, totalSize - downloaded);
|
||||
var url = $"{EgestUrls[Random.Shared.Next(0, EgestUrls.Count)]}/{file.Region}/{file.Bucket}/{file.Uuid}/{chunk}";
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey);
|
||||
|
||||
@@ -233,13 +233,13 @@ public static class FilenCrypto
|
||||
}
|
||||
else if (version == "003")
|
||||
{
|
||||
Logging.Log.WriteWarningMessage(LOGTAG, "EncV3NotTested", null, "Encryption version 3 is not tested.");
|
||||
Logging.Log.WriteVerboseMessage(LOGTAG, "EncV3NotTested", "Encryption version 3 encoutered for metadata, but this is not tested.");
|
||||
if (key.Key.Length != 64)
|
||||
throw new ArgumentException($"Invalid key length {key.Key.Length}. Expected 64 (hex).", nameof(key));
|
||||
|
||||
var keyBuffer = Convert.FromHexString(key.Key);
|
||||
var ivBuffer = Convert.FromHexString(metadata.Substring(3, 24)); // Sliced from 3, length 24
|
||||
var encrypted = Convert.FromBase64String(metadata.Substring(27));
|
||||
var ivBuffer = Convert.FromHexString(metadata.Substring(3, 12)); // Sliced from 3, length 24
|
||||
var encrypted = Convert.FromBase64String(metadata.Substring(15));
|
||||
|
||||
return Encoding.UTF8.GetString(AesGcmDecrypt(keyBuffer, ivBuffer, encrypted));
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ namespace Duplicati.Library.Backend.Strings
|
||||
{
|
||||
internal static class FilenBackend
|
||||
{
|
||||
public static string Description => LC.L(@"This backend can read and write data to Filen.io using its REST protocol. Allowed format is ""filen://folder/subfolder"".");
|
||||
public static string DisplayName => LC.L(@"Local folder or drive");
|
||||
public static string Description => LC.L(@"This backend can read and write data to Filen.io using its REST protocol. Supported format is ""filen://folder/subfolder"".");
|
||||
public static string DisplayName => LC.L(@"Filen.io");
|
||||
public static string TwoFactorShort => LC.L(@"Optional 2-factor code");
|
||||
public static string TwoFactorLong => LC.L(@"The 2-factor code to use for authentication, leave empty if the account is not MFA protected. Not that a new code must be provided by the user for each authentication attempt.");
|
||||
public static string MoveToTrashShort => LC.L(@"Move to trash");
|
||||
|
||||
@@ -35,8 +35,9 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
EditUriBackendConfig.templates['aliyunoss'] = 'templates/backends/aliyunoss.html';
|
||||
EditUriBackendConfig.templates['e2'] = 'templates/backends/e2.html';
|
||||
EditUriBackendConfig.templates['pcloud'] = 'templates/backends/pcloud.html';
|
||||
EditUriBackendConfig.templates['smb'] = 'templates/backends/smb.html';
|
||||
EditUriBackendConfig.templates['smb'] = 'templates/backends/smb.html';
|
||||
EditUriBackendConfig.templates['cifs'] = 'templates/backends/smb.html';
|
||||
EditUriBackendConfig.templates['filen'] = 'templates/backends/filen.html';
|
||||
|
||||
EditUriBackendConfig.testers['s3'] = function(scope, callback) {
|
||||
|
||||
@@ -637,6 +638,15 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
|
||||
EditUriBackendConfig.parsers['cifs'] = EditUriBackendConfig.parsers['smb'];
|
||||
|
||||
EditUriBackendConfig.parsers['filen'] = function (scope, module, server, path, port, options) {
|
||||
console.log("filen parser", scope, module, server, path, port, options);
|
||||
var p1 = server ?? '';
|
||||
var p2 = path ?? '';
|
||||
var combined = p1;
|
||||
if (p1.length > 0 && p2.length > 0)
|
||||
combined += '/' + p2;
|
||||
scope.Path = combined;
|
||||
};
|
||||
|
||||
// Builders take the scope and produce the uri output
|
||||
EditUriBackendConfig.builders['s3'] = function (scope) {
|
||||
@@ -1007,6 +1017,22 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
|
||||
EditUriBackendConfig.builders['cifs'] = EditUriBackendConfig.builders['smb'];
|
||||
|
||||
EditUriBackendConfig.builders['filen'] = function (scope) {
|
||||
var opts = {};
|
||||
EditUriBackendConfig.merge_in_advanced_options(scope, opts, true);
|
||||
// Remove trailing and leading slashes
|
||||
if (scope.Path)
|
||||
scope.Path = scope.Path.replace(/^[/\\]+/, '').replace(/[/\\]+$/, '');
|
||||
else
|
||||
scope.Path = '';
|
||||
|
||||
return AppUtils.format('{0}://{1}{2}',
|
||||
scope.Backend.Key,
|
||||
scope.Path,
|
||||
AppUtils.encodeDictAsUrl(opts)
|
||||
);
|
||||
}
|
||||
|
||||
EditUriBackendConfig.validaters['smb'] = function (scope, continuation) {
|
||||
if (EditUriBackendConfig.require_server(scope)
|
||||
&& EditUriBackendConfig.require_username(scope)
|
||||
@@ -1016,6 +1042,11 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
|
||||
EditUriBackendConfig.validaters['smb'] = EditUriBackendConfig.validaters['cifs'];
|
||||
|
||||
EditUriBackendConfig.validaters['filen'] = function (scope, continuation) {
|
||||
if (EditUriBackendConfig.require_username_and_password(scope))
|
||||
EditUriBackendConfig.recommend_path(scope, continuation);
|
||||
};
|
||||
|
||||
EditUriBackendConfig.validaters['file'] = function (scope, continuation) {
|
||||
if (EditUriBackendConfig.require_path(scope))
|
||||
continuation();
|
||||
|
||||
@@ -78,7 +78,8 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ
|
||||
'rclone': null,
|
||||
'cos': null,
|
||||
'aliyunoss': null,
|
||||
'pcloud': null
|
||||
'pcloud': null,
|
||||
'filen': null,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="input text">
|
||||
<label for="generic_path" translate>Path on server</label>
|
||||
<input type="text" name="generic_path" id="generic_path" ng-model="$parent.Path" placeholder="{{'Destination path' | translate}}" />
|
||||
</div>
|
||||
|
||||
<div class="input text">
|
||||
<label for="generic_username" translate>Username</label>
|
||||
<input type="text" name="generic_username" id="generic_username" ng-model="$parent.Username" placeholder="{{'Authentication username' | translate}}" />
|
||||
</div>
|
||||
<div class="input password">
|
||||
<label for="generic_password" translate>Password</label>
|
||||
<input autocomplete="new-password" type="password" name="generic_password" id="generic_password" ng-model="$parent.Password" placeholder="{{'Authentication password' | translate}}" />
|
||||
</div>
|
||||
Reference in New Issue
Block a user