From 475f846ff7a9f16bc2145bddbc08b60ad671f0cb Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Wed, 13 May 2026 08:45:23 +0200 Subject: [PATCH] Fixed rename to send correct fields --- .../Library/Backend/Filen/FilenBackend.cs | 2 +- .../Library/Backend/Filen/FilenClient.cs | 50 ++++++++++++++++--- .../Library/Backend/Filen/FilenFileEntry.cs | 8 +++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Duplicati/Library/Backend/Filen/FilenBackend.cs b/Duplicati/Library/Backend/Filen/FilenBackend.cs index 7a7915c34..3f953a754 100644 --- a/Duplicati/Library/Backend/Filen/FilenBackend.cs +++ b/Duplicati/Library/Backend/Filen/FilenBackend.cs @@ -282,6 +282,6 @@ public class FilenBackend : IStreamingBackend, IRenameEnabledBackend if (fileEntry == null) throw new FileMissingException($"File '{oldname}' not found."); - await Utility.Utility.WithTimeout(_timeout.ShortTimeout, cancellationToken, ct => client.RenameFileAsync(fileEntry.Uuid, newname, ct)).ConfigureAwait(false); + await Utility.Utility.WithTimeout(_timeout.ShortTimeout, cancellationToken, ct => client.RenameFileAsync(fileEntry, newname, ct)).ConfigureAwait(false); } } diff --git a/Duplicati/Library/Backend/Filen/FilenClient.cs b/Duplicati/Library/Backend/Filen/FilenClient.cs index 28de2c4db..21b21e636 100644 --- a/Duplicati/Library/Backend/Filen/FilenClient.cs +++ b/Duplicati/Library/Backend/Filen/FilenClient.cs @@ -22,7 +22,6 @@ using System.Net.Http.Headers; using System.Net.Http.Json; using System.Runtime.CompilerServices; -using System.Text; using System.Text.Json; using Duplicati.Library.Interface; @@ -415,12 +414,14 @@ public class FilenClient : IDisposable Name = decryptedName.Name, IsFolder = true, LastModified = DateTimeOffset.FromUnixTimeMilliseconds(folder.LastModified).UtcDateTime, + CreatedTimestamp = new DateTime(0), Size = 0, Region = string.Empty, Bucket = string.Empty, Chunks = 0, FileKey = string.Empty, - Version = 0 + Version = 0, + MimeType = string.Empty, }; } @@ -447,11 +448,13 @@ public class FilenClient : IDisposable IsFolder = false, Size = file.Size, LastModified = DateTimeOffset.FromUnixTimeMilliseconds(fileInfo.LastModified).UtcDateTime, + CreatedTimestamp = DateTimeOffset.FromUnixTimeMilliseconds(fileInfo.Create).UtcDateTime, Region = file.Region, Bucket = file.Bucket, Chunks = file.Chunks, FileKey = fileInfo.Key, - Version = file.Version + Version = file.Version, + MimeType = fileInfo.Mime, }; } } @@ -736,27 +739,58 @@ public class FilenClient : IDisposable _httpClient.Dispose(); } + /// + /// Helper for mapping DateTime to epoch milliseconds + /// + /// The date time to map + /// The epoch milliseconds + private static long MapDateTimeToEpochMs(DateTime dateTime) + { + if (dateTime.Ticks == 0) + return 0; + return (long)(dateTime - DateTime.UnixEpoch).TotalMilliseconds; + } + /// /// Renames a file in the Filen API /// - /// The UUID of the file to rename + /// The file entry to rename /// The new name of the file /// The cancellation token to use for the operation /// A task that completes when the file is renamed - public async Task RenameFileAsync(string fileUuid, string newName, CancellationToken cancellationToken) + public async Task RenameFileAsync(FilenFileEntry fileEntry, string newName, CancellationToken cancellationToken) { - var encryptedName = _authResult.EncryptMetadata(JsonSerializer.Serialize(new NameEntry() { Name = newName })); + var fileKey = DerivedKey.Create(fileEntry.FileKey); + var encryptedName = fileKey.EncryptMetadata(newName); + var nameHashed = FilenCrypto.HashFn(newName); + + var metadata = _authResult.EncryptMetadata(JsonSerializer.Serialize(new FileInfoMetadata + { + Name = newName, + Size = fileEntry.Size, + Mime = fileEntry.MimeType, + Key = fileEntry.FileKey, + LastModified = MapDateTimeToEpochMs(fileEntry.LastModified), + Create = MapDateTimeToEpochMs(fileEntry.CreatedTimestamp), + })); + var url = $"{_baseUrl}/v3/file/rename"; using var request = new HttpRequestMessage(HttpMethod.Post, url); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); - request.Content = JsonContent.Create(new { uuid = fileUuid, name = encryptedName }); + request.Content = JsonContent.Create(new + { + uuid = fileEntry.Uuid, + name = encryptedName, + nameHashed, + metadata + }); var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); // Update cache - var key = _cachedFiles.FirstOrDefault(kv => kv.Value.Uuid == fileUuid).Key; + var key = _cachedFiles.FirstOrDefault(kv => kv.Value.Uuid == fileEntry.Uuid).Key; if (!string.IsNullOrWhiteSpace(key)) _cachedFiles.Remove(key); } diff --git a/Duplicati/Library/Backend/Filen/FilenFileEntry.cs b/Duplicati/Library/Backend/Filen/FilenFileEntry.cs index 32dd2aff3..b1ab2dbd1 100644 --- a/Duplicati/Library/Backend/Filen/FilenFileEntry.cs +++ b/Duplicati/Library/Backend/Filen/FilenFileEntry.cs @@ -47,6 +47,10 @@ public sealed record FilenFileEntry /// public required DateTime LastModified { get; set; } /// + /// The creation date of the file + /// + public required DateTime CreatedTimestamp { get; set; } + /// /// The region of the file /// public required string Region { get; set; } @@ -66,5 +70,9 @@ public sealed record FilenFileEntry /// The encryption version /// public required int Version; + /// + /// The MIME type of the file + /// + public required string MimeType { get; set; } }