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
9 changes: 8 additions & 1 deletion uSync.BackOffice/Services/ISyncActionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,12 @@ public interface ISyncActionService
/// <summary>
/// unpacks a zip archive (stream) to disk, checks it and copies it over the existing uSync folder.
/// </summary>
UploadImportResult UnpackImportFromStream(Stream stream);
[Obsolete("Use UnpackImportFromStreamAsync(Stream stream) will be removed in v19")]
UploadImportResult UnpackImportFromStream(Stream stream)
=> UnpackImportFromStreamAsync(stream).GetAwaiter().GetResult();

/// <summary>
/// unpacks a zip archive (stream) to disk, checks it and copies it over the existing uSync folder.
/// </summary>
Task<UploadImportResult> UnpackImportFromStreamAsync(Stream stream);
}
12 changes: 10 additions & 2 deletions uSync.BackOffice/Services/ISyncFileService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Xml.Linq;
Expand Down Expand Up @@ -200,5 +201,12 @@ public interface ISyncFileService
/// <summary>
/// run some basic checks on a folder to see if it looks ok.
/// </summary>
List<string> VerifyFolder(string folder, string extension);
[Obsolete("Use VerifyFolderAsync instead will be removed in v19")]
List<string> VerifyFolder(string folder, string extension)
=> VerifyFolderAsync(folder, extension).GetAwaiter().GetResult();

/// <summary>
/// run some basic checks on a folder to see if it looks ok.
/// </summary>
Task<List<string>> VerifyFolderAsync(string folder, string extension);
}
4 changes: 2 additions & 2 deletions uSync.BackOffice/Services/SyncActionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public Stream GetExportFolderAsStream()
return _uSyncService.CompressFolder(_uSyncConfig.GetWorkingFolder());
}

public UploadImportResult UnpackImportFromStream(Stream stream)
public async Task<UploadImportResult> UnpackImportFromStreamAsync(Stream stream)
{
var tempFolder = Path.Combine(_uSyncTempPath, Path.GetFileNameWithoutExtension(Path.GetRandomFileName()))
?? $"{_uSyncTempPath}{Path.DirectorySeparatorChar}{Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) ?? Guid.NewGuid().ToString()}";
Expand All @@ -242,7 +242,7 @@ public UploadImportResult UnpackImportFromStream(Stream stream)
{
_uSyncService.DeCompressFile(stream, tempFolder);

var errors = _syncFileService.VerifyFolder(tempFolder,
var errors = await _syncFileService.VerifyFolderAsync(tempFolder,
_uSyncConfig.Settings.DefaultExtension);

if (errors.Count > 0)
Expand Down
49 changes: 27 additions & 22 deletions uSync.BackOffice/Services/SyncFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.Logging;

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -197,7 +198,7 @@ public async Task<XElement> LoadXElementAsync(string file)
if (stream is null)
throw new FileNotFoundException($"Cannot create stream for {file}"); ;

using (var xmlReader = XmlReader.Create(stream, _readerSettings))
using (var xmlReader = XmlReader.Create(stream, _readerSettings.Clone()))
{
return await XElement.LoadAsync(xmlReader, LoadOptions.PreserveWhitespace, CancellationToken.None);
}
Expand Down Expand Up @@ -348,7 +349,7 @@ public async Task<int> MakeSingleExportFromFolders(string[] folders, string item
}

/// <inheritdoc/>
public List<string> VerifyFolder(string folder, string extension)
public async Task<List<string>> VerifyFolderAsync(string folder, string extension)
{
var resolvedFolder = GetAbsPath(folder);
if (!DirectoryExists(resolvedFolder))
Expand All @@ -371,7 +372,7 @@ public List<string> VerifyFolder(string folder, string extension)
{
try
{
var node = LoadXElementAsync(file).Result;
var node = await LoadXElementAsync(file);

if (!node.IsEmptyItem())
{
Expand Down Expand Up @@ -489,29 +490,33 @@ public async Task<IEnumerable<OrderedNodeInfo>> MergeFoldersAsync(string[] folde
private static XElement MergeNodes(XElement source, XElement target, ISyncTrackerBase? trackerBase)
=> trackerBase is null ? target : trackerBase.MergeFiles(source, target) ?? target;

private async Task<IEnumerable<KeyValuePair<string, OrderedNodeInfo>>> GetFolderItemsAsync(string folder, string extension)
private async Task<IEnumerable<KeyValuePair<string, OrderedNodeInfo>>> GetFolderItemsAsync(
string folder, string extension)
{
var items = new List<KeyValuePair<string, OrderedNodeInfo>>();
var filePaths = GetFilePaths(folder, extension);
var results = new ConcurrentBag<KeyValuePair<string, OrderedNodeInfo>>();

foreach (var file in GetFilePaths(folder, extension))
{
var element = await LoadXElementSafeAsync(file);
if (element != null)
await Parallel.ForEachAsync(
filePaths,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
async (file, _) =>
{
var path = file.Substring(folder.Length);

items.Add(new KeyValuePair<string, OrderedNodeInfo>(
key: path,
value: new OrderedNodeInfo(
filename: file,
node: element,
level: (element.GetLevel() * 1000) + element.GetItemSortOrder(),
path: path,
isRoot: true)));
}
}
var element = await LoadXElementSafeAsync(file);
if (element is not null)
{
var path = file.Substring(folder.Length);
results.Add(new KeyValuePair<string, OrderedNodeInfo>(
key: path,
value: new OrderedNodeInfo(
filename: file,
node: element,
level: (element.GetLevel() * 1000) + element.GetItemSortOrder(),
path: path,
isRoot: true)));
}
});
Comment thread
KevinJump marked this conversation as resolved.

return items;
return results;
}

/// <inheritdoc/>
Expand Down
2 changes: 1 addition & 1 deletion uSync.BackOffice/SyncHandlers/SyncHandlerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected override async Task<IEnumerable<uSyncAction>> CleanFolderAsync(string
// be a little slower (not much though)

// we cache this, (it is cleared on an ImportAll)
var keys = GetFolderKeys(folder, flat);
var keys = await GetFolderKeysAsync(folder, flat);
if (keys.Count > 0)
{
// move parent to here, we only need to check it if there are files.
Expand Down
80 changes: 47 additions & 33 deletions uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ protected virtual async Task<IEnumerable<uSyncAction>> CleanFolderAsync(string c
// be a little slower (not much though)

// we cache this, (it is cleared on an ImportAll)
var keys = GetFolderKeys(folder, flat);
var keys = await GetFolderKeysAsync(folder, flat);
if (keys.Count > 0)
{
// move parent to here, we only need to check it if there are files.
Expand Down Expand Up @@ -664,39 +664,52 @@ public Task PreCacheFolderKeysAsync(string folder, IList<Guid> folderKeys)
/// so we cache it, and if we are using the flat folder structure, then
/// we only do it once, so its quicker.
/// </remarks>
[Obsolete("Use GetFolderKeysAsync instead, will be removed in v19")]
protected IList<Guid> GetFolderKeys(string folder, bool flat)
=> GetFolderKeysAsync(folder, flat).GetAwaiter().GetResult();

/// <summary>
/// Get the GUIDs for all items in a folder
/// </summary>
/// <remarks>
/// This is disk intensive, (checking the .config files all the time)
/// so we cache it, and if we are using the flat folder structure, then
/// we only do it once, so its quicker.
/// </remarks>
protected async Task<IList<Guid>> GetFolderKeysAsync(string folder, bool flat)
{
// We only need to load all the keys once per handler (if all items are in a folder that key will be used).
var folderKey = folder.GetHashCode();

var cacheKey = $"{GetCacheKeyBase()}_{folderKey}";

var cached = runtimeCache.GetCacheItem<IList<Guid>>(cacheKey);
if (cached is not null) return cached;

return runtimeCache.GetCacheItem(cacheKey, () =>
{
if (logger.IsEnabled(LogLevel.Debug))
logger.LogDebug("Getting Folder Keys : {cacheKey}", cacheKey);
if (logger.IsEnabled(LogLevel.Debug))
logger.LogDebug("Getting Folder Keys : {cacheKey}", cacheKey);

// when it's not flat structure we also get the sub folders. (extra defensive get them all)
var keys = new List<Guid>();
var files = syncFileService.GetFiles(folder, $"*.{this.uSyncConfig.Settings.DefaultExtension}", !flat).ToList();
// when it's not flat structure we also get the sub folders. (extra defensive get them all)
var keySet = new HashSet<Guid>();
var files = syncFileService.GetFiles(folder, $"*.{this.uSyncConfig.Settings.DefaultExtension}", !flat);

foreach (var file in files)
foreach (var file in files)
{
var node = await syncFileService.LoadXElementAsync(file);
var key = node.GetKey();
if (key != Guid.Empty)
{
var node = syncFileService.LoadXElementAsync(file).Result;
var key = node.GetKey();
if (key != Guid.Empty && !keys.Contains(key))
{
keys.Add(key);
}
keySet.Add(key);
}
Comment thread
KevinJump marked this conversation as resolved.
}

if (logger.IsEnabled(LogLevel.Debug))
logger.LogDebug("Loaded {count} keys from {folder} [{cacheKey}]", keys.Count, folder, cacheKey);
var keys = keySet.ToList();

return keys;
if (logger.IsEnabled(LogLevel.Debug))
logger.LogDebug("Loaded {count} keys from {folder} [{cacheKey}]", keys.Count, folder, cacheKey);

}, null) ?? [];
runtimeCache.GetCacheItem(cacheKey, () => keys);
return keys;
}

/// <summary>
Expand Down Expand Up @@ -872,7 +885,7 @@ public async Task<IEnumerable<uSyncAction>> ExportAsync(Udi udi, string[] folder

return [uSyncAction.Fail(nameof(udi), this.handlerType, this.ItemType, ChangeType.Fail, $"Item not found {udi}",
new KeyNotFoundException(nameof(udi)))];

}

/// <summary>
Expand Down Expand Up @@ -1325,7 +1338,7 @@ public virtual async Task<IEnumerable<uSyncAction>> ReportElementSingleAsync(XEl
{
return [uSyncActionHelper<TObject>
.ReportActionFail(Path.GetFileName(node.GetAlias()), $"format error {fex.Message}")];

}
}

Expand Down Expand Up @@ -1501,17 +1514,16 @@ protected virtual async Task ExportDeletedItemAsync(TObject item, string[] folde
if (item == null) return;

var targetFolder = folders.Last();

var filename = (await GetPathAsync(targetFolder, item, config.GuidNames, config.UseFlatStructure))
.ToAppSafeFileName();

if (IsLockedAtRoot(folders, filename.Substring(targetFolder.Length + 1)))
{
// don't do anything this thing exists at a higher level. !
return;
}

if (await ShouldExportDeletedFileAsync(item, config) is false) return;
// Only perform the expensive full-serialize check if this handler
// actually overrides ShouldExportAsync (i.e. has filtering logic).
if (HandlerHasShouldExportOverride() && await ShouldExportDeletedFileAsync(item, config) is false)
return;

var attempt = await serializer.SerializeEmptyAsync(item, SyncActionType.Delete, string.Empty);
if (attempt.Item is not null && await ShouldExportAsync(attempt.Item, config) is true)
Expand All @@ -1520,17 +1532,20 @@ protected virtual async Task ExportDeletedItemAsync(TObject item, string[] folde
{
await syncFileService.SaveXElementAsync(attempt.Item, filename);

// so check - it shouldn't (under normal operation)
// be possible for a clash to exist at delete, because nothing else
// will have changed (like name or location)

// we only then do this if we are not using flat structure.
if (!DefaultConfig.UseFlatStructure)
await this.CleanUpAsync(item, filename, Path.Combine(folders.Last(), this.DefaultFolder));
}
}
}

// Cached reflection result — only computed once per handler type.
private bool? _hasShouldExportOverride;
private bool HandlerHasShouldExportOverride()
=> _hasShouldExportOverride ??= GetType()
.GetMethod(nameof(ShouldExportAsync),
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
?.DeclaringType != typeof(SyncHandlerRoot<TObject, TContainer>);

private async Task<bool> ShouldExportDeletedFileAsync(TObject item, HandlerSettings config)
{
try
Expand All @@ -1541,7 +1556,7 @@ private async Task<bool> ShouldExportDeletedFileAsync(TObject item, HandlerSetti
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to calculate if this item should be exported when deleted, the common option is yes, so we will");
logger.LogWarning(ex, "Error while checking if should export deleted file.");
return true;
}
}
Expand Down Expand Up @@ -1608,7 +1623,6 @@ protected virtual async Task CleanUpAsync(TObject item, string newFile, string f
await CleanUpAsync(item, newFile, children);
}
}

#endregion

// 98% of the time the serializer can do all these calls for us,
Expand Down
21 changes: 14 additions & 7 deletions uSync.Core/Serialization/SyncContainerSerializerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,19 +194,22 @@ public virtual async Task<IEnumerable<EntityContainer>> GetContainersAsync(TObje
{
if (entityTypeContainerTypeService is null) return [];

var parent = await entityTypeContainerTypeService.GetParentAsync(item);
if (parent is null) return [];
if (_containersCache.TryGetValue(item.ParentId, out var cached) && cached is not null)
return cached;

var containers = new List<EntityContainer>();

var containers = new List<EntityContainer>() { parent };
var parent = await entityTypeContainerTypeService.GetParentAsync(item);

while (parent is not null)
{
containers.Add(parent);
parent = await entityTypeContainerTypeService.GetParentAsync(parent);
if (parent is not null)
containers.Add(parent);
}

return containers;
var containersArray = containers.ToArray();
_containersCache.TryAdd(item.ParentId, containersArray);
return containersArray;
}


Expand Down Expand Up @@ -302,9 +305,13 @@ public virtual async Task SaveContainerAsync(Guid parent, EntityContainer contai
/// only used on serialization, allows us to only build the folder path for a set of containers once.
/// </remarks>
private ConcurrentDictionary<int, XElement> _folderCache = [];
private ConcurrentDictionary<int, EntityContainer[]> _containersCache = [];

private void ClearFolderCache()
=> _folderCache = [];
{
_folderCache = [];
_containersCache = [];
}

public void InitializeCache()
{
Expand Down
Loading