Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions uSync.BackOffice/Services/ISyncFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ public interface ISyncFileService
/// <returns></returns>
Task<XElement> LoadXElementAsync(string file);

/// <summary>
/// load just the item key (the Key attribute on the root element) from a file.
/// </summary>
/// <remarks>
/// This streams the file and stops at the root element, so we don't pay the cost
/// of parsing the whole document when all we need is the key (e.g. when working out
/// which items live in a folder for a 'clean' operation).
/// </remarks>
Task<Guid> LoadKeyFromFileAsync(string file);

/// <summary>
/// merge all the files in the given folders into a single xml node, that can be bulk imported
/// </summary>
Expand Down
45 changes: 45 additions & 0 deletions uSync.BackOffice/Services/SyncFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,51 @@ public async Task<XElement> LoadXElementAsync(string file)
}
}

private static readonly XmlReaderSettings _keyReaderSettings = new()
{
CheckCharacters = false,
Async = true,
IgnoreWhitespace = true,
IgnoreComments = true,
IgnoreProcessingInstructions = true,
DtdProcessing = DtdProcessing.Prohibit,
};

/// <inheritdoc/>
public async Task<Guid> LoadKeyFromFileAsync(string file)
{
EnsureFileExists(file);

try
{
using (var stream = OpenRead(file))
{
if (stream is null)
throw new FileNotFoundException($"Cannot create stream for {file}");

using (var reader = XmlReader.Create(stream, _keyReaderSettings.Clone()))
{
// move to the first (root) element and read its Key attribute,
// we don't need to read any further into the document.
while (await reader.ReadAsync())
{
if (reader.NodeType != XmlNodeType.Element) continue;

var key = reader.GetAttribute(global::uSync.Core.uSyncConstants.Xml.Key);
return Guid.TryParse(key, out var guid) ? guid : Guid.Empty;
}
}
}
}
catch (Exception ex)
{
_logger.LogWarning("Error while reading key from {file} {message}", file, ex.Message);
throw new Exception($"Error while reading key from {file}", ex);
}

return Guid.Empty;
}

/// <inheritdoc/>
public async Task SaveFileAsync(string filename, Stream stream)
{
Expand Down
8 changes: 5 additions & 3 deletions uSync.BackOffice/SyncHandlers/SyncHandlerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ protected override async Task<IEnumerable<uSyncAction>> CleanFolderAsync(string

private async Task<Guid?> GetCleanParentKeyAsync(string cleanFile)
{
var node = await syncFileService.LoadXElementAsync(cleanFile);
if (node.GetKey() == Guid.Empty) return Guid.Empty;
return (await GetCleanParentAsync(cleanFile))?.Key;
// stream the key rather than parsing the whole file, and reuse it for the
// parent lookup so we don't read the clean file a second time.
var key = await syncFileService.LoadKeyFromFileAsync(cleanFile);
if (key == Guid.Empty) return Guid.Empty;
return (await GetFromServiceAsync(key))?.Key;
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,8 @@ protected async Task<IList<Guid>> GetFolderKeysAsync(string folder, bool flat)

foreach (var file in files)
{
var node = await syncFileService.LoadXElementAsync(file);
var key = node.GetKey();
// we only need the key here, so stream it rather than parsing the whole file.
var key = await syncFileService.LoadKeyFromFileAsync(file);
if (key != Guid.Empty)
{
keySet.Add(key);
Expand All @@ -719,8 +719,8 @@ protected async Task<IList<Guid>> GetFolderKeysAsync(string folder, bool flat)
/// </summary>
protected async Task<TObject?> GetCleanParentAsync(string file)
{
var node = await syncFileService.LoadXElementAsync(file);
var key = node.GetKey();
// we only need the key to find the parent, so stream it rather than parsing the whole file.
var key = await syncFileService.LoadKeyFromFileAsync(file);
if (key == Guid.Empty) return default;
return await GetFromServiceAsync(key);
}
Expand Down
Loading