diff --git a/Duplicati/Library/Backend/S3/IS3Client.cs b/Duplicati/Library/Backend/S3/IS3Client.cs index fee68361b..cfd10f2a9 100644 --- a/Duplicati/Library/Backend/S3/IS3Client.cs +++ b/Duplicati/Library/Backend/S3/IS3Client.cs @@ -22,13 +22,13 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Duplicati.Library.Interface; +using Duplicati.Library.Common.IO; namespace Duplicati.Library.Backend { - public interface IS3Client : IDisposable + internal interface IS3Client : IDisposable { - IEnumerable ListBucket(string bucketName, string prefix); + IEnumerable ListBucket(string bucketName, string prefix); Task AddBucketAsync(string bucketName, CancellationToken cancelToken); diff --git a/Duplicati/Library/Backend/S3/S3AwsClient.cs b/Duplicati/Library/Backend/S3/S3AwsClient.cs index e3a480159..3afa4217a 100644 --- a/Duplicati/Library/Backend/S3/S3AwsClient.cs +++ b/Duplicati/Library/Backend/S3/S3AwsClient.cs @@ -1,28 +1,28 @@ -// Copyright (C) 2025, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Amazon.S3; using Amazon.S3.Model; -using Duplicati.Library.Backend.Strings; +using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; using System; @@ -192,7 +192,7 @@ namespace Duplicati.Library.Backend return m_client.DeleteObjectAsync(objectDeleteRequest, cancellationToken); } - public virtual IEnumerable ListBucket(string bucketName, string prefix) + public virtual IEnumerable ListBucket(string bucketName, string prefix) { bool isTruncated = true; string filename = null; @@ -239,12 +239,22 @@ namespace Duplicati.Library.Backend obj.Key, obj.Size, obj.LastModified, - obj.LastModified + obj.LastModified, + false, + IsStorageClassArchive(obj.StorageClass) ); } } } + private bool IsStorageClassArchive(S3StorageClass storageClass) + { + return storageClass == S3StorageClass.DeepArchive + || storageClass == S3StorageClass.Glacier + || storageClass == S3StorageClass.GlacierInstantRetrieval + || storageClass == S3StorageClass.Snow; + } + public async Task RenameFileAsync(string bucketName, string source, string target, CancellationToken cancelToken) { var copyObjectRequest = new CopyObjectRequest diff --git a/Duplicati/Library/Backend/S3/S3Backend.cs b/Duplicati/Library/Backend/S3/S3Backend.cs index 8df4c8355..cffae9bb2 100644 --- a/Duplicati/Library/Backend/S3/S3Backend.cs +++ b/Duplicati/Library/Backend/S3/S3Backend.cs @@ -1,27 +1,26 @@ -// Copyright (C) 2025, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using Duplicati.Library.Utility; using System; using System.Collections.Generic; using System.IO; @@ -272,13 +271,13 @@ namespace Duplicati.Library.Backend public IEnumerable List() { - foreach (IFileEntry file in Connection.ListBucket(m_bucket, m_prefix)) + foreach (var file in Connection.ListBucket(m_bucket, m_prefix)) { - ((FileEntry)file).Name = file.Name.Substring(m_prefix.Length); + file.Name = file.Name.Substring(m_prefix.Length); //Fix for a bug in Duplicati 1.0 beta 3 and earlier, where filenames are incorrectly prefixed with a slash if (file.Name.StartsWith("/", StringComparison.Ordinal) && !m_prefix.StartsWith("/", StringComparison.Ordinal)) - ((FileEntry)file).Name = file.Name.Substring(1); + file.Name = file.Name.Substring(1); yield return file; } diff --git a/Duplicati/Library/Backend/S3/S3MinioClient.cs b/Duplicati/Library/Backend/S3/S3MinioClient.cs index 09d2e5d52..317a9e036 100644 --- a/Duplicati/Library/Backend/S3/S3MinioClient.cs +++ b/Duplicati/Library/Backend/S3/S3MinioClient.cs @@ -24,6 +24,7 @@ using System.IO; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; +using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; using Minio; @@ -58,7 +59,7 @@ namespace Duplicati.Library.Backend m_dnsHost = servername; } - public IEnumerable ListBucket(string bucketName, string prefix) + public IEnumerable ListBucket(string bucketName, string prefix) { ThrowExceptionIfBucketDoesNotExist(bucketName); diff --git a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs index 188826783..388ce0df9 100644 --- a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs +++ b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs @@ -507,9 +507,9 @@ namespace Duplicati.Library.Backend else { return remoteFolder.Folders.Where(ff => ff.Exists) - .Select(f => new FileEntry(f.Name, -1, f.TimeLastModified, f.TimeLastModified) { IsFolder = true }) + .Select(f => new FileEntry(f.Name, -1, f.TimeLastModified, f.TimeLastModified, true, false)) .Concat(remoteFolder.Files.Where(ff => ff.Exists) - .Select(f => new FileEntry(f.Name, f.Length, f.TimeLastModified, f.TimeLastModified) { IsFolder = false })) + .Select(f => new FileEntry(f.Name, f.Length, f.TimeLastModified, f.TimeLastModified, false, false))) .ToArray(); } } diff --git a/Duplicati/Library/Common/IO/FileEntry.cs b/Duplicati/Library/Common/IO/FileEntry.cs index fc1b18b49..9811e7b35 100644 --- a/Duplicati/Library/Common/IO/FileEntry.cs +++ b/Duplicati/Library/Common/IO/FileEntry.cs @@ -1,23 +1,23 @@ -// Copyright (C) 2025, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using Duplicati.Library.Interface; @@ -29,91 +29,40 @@ namespace Duplicati.Library.Common.IO /// public class FileEntry : IFileEntry { - private string m_name; - private DateTime m_lastAccess; - private DateTime m_lastModification; - private long m_size; - private bool m_isFolder; - /// /// Gets or sets the file or folder name /// - public string Name - { - get { return m_name; } - set { m_name = value; } - } + public string Name { get; set; } = null; /// /// Gets or sets the time the file or folder was last accessed /// - public DateTime LastAccess - { - get { return m_lastAccess; } - set { m_lastAccess = value; } - } + public DateTime LastAccess { get; set; } = new DateTime(); /// /// Gets or sets the time the file or folder was last modified /// - public DateTime LastModification - { - get { return m_lastModification; } - set { m_lastModification = value; } - } + public DateTime LastModification { get; set; } = new DateTime(); /// /// Gets or sets the size of the file or folder /// - public long Size - { - get { return m_size; } - set { m_size = value; } - } + public long Size { get; set; } = -1; /// /// Gets or sets a value indicating if the entry is a folder /// - public bool IsFolder - { - get { return m_isFolder; } - set { m_isFolder = value; } - } + public bool IsFolder { get; set; } = false; + + /// + /// Gets or sets a value indicating if the entry is archived (not readable) + /// + public bool IsArchived { get; set; } = false; /// /// Helper function to initialize the instance to default values /// - private FileEntry() - { - m_name = null; - m_lastAccess = new DateTime(); - m_lastModification = new DateTime(); - m_size = -1; - m_isFolder = false; - } - - /// - /// Constructs an entry using only the name. - /// The entry is assumed to be a file. - /// - /// The name of the file - public FileEntry(string filename) - : this() - { - m_name = filename; - } - - /// - /// Constructs an entry using only the name and size. - /// The entry is assumed to be a file. - /// - /// The name of the file - /// The size of the file - public FileEntry(string filename, long size) - : this(filename) - { - m_size = size; - } + private FileEntry() { } /// /// Constructs an entry supplying all information @@ -122,11 +71,16 @@ namespace Duplicati.Library.Common.IO /// The size of the file or folder /// The time the file or folder was last accessed /// The time the file or folder was last modified - public FileEntry(string filename, long size, DateTime lastAccess, DateTime lastModified) - : this(filename, size) + /// A value indicating if the entry is a folder + /// A value indicating if the entry is archived + public FileEntry(string filename, long size = -1, DateTime lastAccess = default, DateTime lastModified = default, bool isFolder = false, bool isArchived = false) { - m_lastModification = lastModified; - m_lastAccess = lastAccess; + Name = filename; + Size = size; + LastAccess = lastAccess; + LastModification = lastModified; + IsFolder = isFolder; + IsArchived = isArchived; } } }