From bb87e94ffab18df2f422cd2a8813e91e059fd657 Mon Sep 17 00:00:00 2001 From: Carl Johnsen Date: Tue, 24 Jun 2025 14:38:58 +0200 Subject: [PATCH] Post merge master fixes --- .../Main/Database/LocalListDatabase.cs | 54 +++++++++++-------- .../Main/Operation/ListFolderHandler.cs | 5 +- .../Library/Main/Operation/TestHandler.cs | 4 +- Duplicati/UnitTest/DirectListHandlerTests.cs | 23 +++++--- 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/Duplicati/Library/Main/Database/LocalListDatabase.cs b/Duplicati/Library/Main/Database/LocalListDatabase.cs index d1d852c57..95fb130ac 100644 --- a/Duplicati/Library/Main/Database/LocalListDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListDatabase.cs @@ -1099,45 +1099,59 @@ namespace Duplicati.Library.Main.Database /// Gets the minimal unique prefix entries for a given fileset ID. /// This returns the roots of all unique paths in the fileset. /// - /// The fileset id - /// The entries that are unique roots - public IList GetMinimalUniquePrefixEntries(long filesetId) + /// The fileset id. + /// A cancellation token to cancel the operation. + /// A task that when awaited returns a list of the entries that are unique roots. + public async IAsyncEnumerable GetMinimalUniquePrefixEntries(long filesetId, [EnumeratorCancellation] CancellationToken token) { - var results = new List(); - - using var cmd = m_connection.CreateCommand(); + await using var cmd = m_connection.CreateCommand(); cmd.SetCommandAndParameters(@" WITH AllPaths AS ( - SELECT fl.""ID"" AS ""FileID"", + SELECT + fl.""ID"" AS ""FileID"", pp.""Prefix"" || fl.""Path"" AS ""FullPath"", fl.""BlocksetID"", b.""Length"", fe.""Lastmodified"" FROM ""FilesetEntry"" fe - JOIN ""FileLookup"" fl ON fe.""FileID"" = fl.""ID"" - JOIN ""PathPrefix"" pp ON fl.""PrefixID"" = pp.""ID"" - LEFT JOIN ""Blockset"" b ON fl.""BlocksetID"" = b.""ID"" + JOIN ""FileLookup"" fl + ON fe.""FileID"" = fl.""ID"" + JOIN ""PathPrefix"" pp + ON fl.""PrefixID"" = pp.""ID"" + LEFT JOIN ""Blockset"" b + ON fl.""BlocksetID"" = b.""ID"" WHERE fe.""FilesetID"" = @FilesetId ), RootCandidates AS ( SELECT a1.* FROM AllPaths a1 WHERE NOT EXISTS ( - SELECT 1 FROM AllPaths a2 - WHERE a2.""FullPath"" != a1.""FullPath"" - AND a1.""FullPath"" LIKE a2.""FullPath"" || '%' + SELECT 1 + FROM AllPaths a2 + WHERE + a2.""FullPath"" != a1.""FullPath"" + AND a1.""FullPath"" LIKE a2.""FullPath"" || '%' ) ) SELECT ""FullPath"", ""Length"", - CASE WHEN ""BlocksetID"" = -100 THEN 1 ELSE 0 END AS ""IsDirectory"", - CASE WHEN ""BlocksetID"" = -200 THEN 1 ELSE 0 END AS ""IsSymlink"", + CASE + WHEN ""BlocksetID"" = -100 + THEN 1 + ELSE 0 + END AS ""IsDirectory"", + CASE + WHEN ""BlocksetID"" = -200 + THEN 1 + ELSE 0 + END AS ""IsSymlink"", ""Lastmodified"" FROM RootCandidates ORDER BY ""FullPath"" - "); - cmd.SetParameterValue("@FilesetId", filesetId); + ") + .SetTransaction(m_rtr) + .SetParameterValue("@FilesetId", filesetId); - foreach (var rd in cmd.ExecuteReaderEnumerable()) + await foreach (var rd in cmd.ExecuteReaderEnumerableAsync(token)) { var path = rd.ConvertValueToString(0) ?? string.Empty; var size = rd.ConvertValueToInt64(1, -1); @@ -1145,10 +1159,8 @@ namespace Duplicati.Library.Main.Database var isSymlink = rd.GetInt32(3) != 0; var lastModified = new DateTime(rd.ConvertValueToInt64(4, 0), DateTimeKind.Utc); - results.Add(new FolderEntry(path, size, isDir, isSymlink, lastModified)); + yield return new FolderEntry(path, size, isDir, isSymlink, lastModified); } - - return results; } /// diff --git a/Duplicati/Library/Main/Operation/ListFolderHandler.cs b/Duplicati/Library/Main/Operation/ListFolderHandler.cs index 8c7aaf569..0305812f2 100644 --- a/Duplicati/Library/Main/Operation/ListFolderHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFolderHandler.cs @@ -63,7 +63,10 @@ internal static class ListFolderHandler { if (folders != null && folders.Length > 1) throw new UserInformationException("When no folder is specified, only one folder can be listed", "MultipleFoldersFound"); - var rootFolders = db.GetMinimalUniquePrefixEntries(filesetIds[0]); + var rootFolders = await db + .GetMinimalUniquePrefixEntries(filesetIds[0], result.TaskControl.ProgressToken) + .ToListAsync(cancellationToken: result.TaskControl.ProgressToken) + .ConfigureAwait(false); result.Entries = new PaginatedResults(0, rootFolders.Count, 1, rootFolders.Count, rootFolders); } else diff --git a/Duplicati/Library/Main/Operation/TestHandler.cs b/Duplicati/Library/Main/Operation/TestHandler.cs index 033bc459f..b8b48581d 100644 --- a/Duplicati/Library/Main/Operation/TestHandler.cs +++ b/Duplicati/Library/Main/Operation/TestHandler.cs @@ -433,9 +433,9 @@ namespace Duplicati.Library.Main.Operation await backendManager.DeleteAsync(vol.Name, vol.Size, true, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await backendManager.WaitForEmptyAsync(repairdb, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await repairdb.Transaction - .CommitAsync("ReplaceFaultyIndexFileCommit") + .CommitAsync("ReplaceFaultyIndexFileCommit", token: cancellationToken) .ConfigureAwait(false); - m_results.RemoveResult(vol.Name); + m_result.RemoveResult(vol.Name); } } catch (Exception ex) diff --git a/Duplicati/UnitTest/DirectListHandlerTests.cs b/Duplicati/UnitTest/DirectListHandlerTests.cs index 39994f8c9..4da12acab 100644 --- a/Duplicati/UnitTest/DirectListHandlerTests.cs +++ b/Duplicati/UnitTest/DirectListHandlerTests.cs @@ -337,7 +337,8 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1).Select(e => e.Path, CancellationToken.None) + .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -363,7 +364,8 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1).Select(e => e.Path, CancellationToken.None) + .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -387,7 +389,8 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1).Select(e => e.Path, CancellationToken.None) + .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -414,7 +417,8 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1).Select(e => e.Path, CancellationToken.None) + .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -429,7 +433,7 @@ namespace Duplicati.UnitTest } [Test] - public void GetMinimalUniquePrefixEntries_ShouldReturnExpectedMinimalRoots() + public async Task GetMinimalUniquePrefixEntries_ShouldReturnExpectedMinimalRoots() { // Arrange: Prepare prefixes (minimal unique) and contents var testPrefixes = new[] @@ -448,10 +452,15 @@ namespace Duplicati.UnitTest // Act using var tempFile = new TempFile(); - using var db = new LocalListDatabase(tempFile, 1); + await using var db = await LocalListDatabase.CreateAsync(tempFile, null, CancellationToken.None) + .ConfigureAwait(false); SeedTestData(db, testPrefixes); - var resultItems = db.GetMinimalUniquePrefixEntries(1).ToList(); + var resultItems = await db + .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .ToListAsync() + .ConfigureAwait(false); + var result = resultItems.Select(e => e.Path).ToList(); Assert.That(result, Does.Contain(@"C:\Downloads\testsource\AA\"));