Files
speckle-sharp-connectors/DUI3/Speckle.Connectors.DUI/Models/DocumentModelStore.cs
T
Adam Hathcock 0361e5ea10 Introduce receive manager (#895)
* Don't log conversion errors to seq?

* add cancellation

* Make a generic handler for receive conversions

* Use for all receives?

* for cancellations, rethrow

* add vibe tests

* fix up receive cancellation

* fmt

* made ReceiveOperationManager with revit

* everything compiles

* fmt

* add tests

* This check shouldn't report to seq
2025-06-09 12:21:52 +00:00

184 lines
4.8 KiB
C#

using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.DUI.Utils;
using Speckle.InterfaceGenerator;
using Speckle.Sdk;
using Speckle.Sdk.Common;
namespace Speckle.Connectors.DUI.Models;
/// <summary>
/// Encapsulates the state Speckle needs to persist in the host app's document.
/// </summary>
[GenerateAutoInterface]
public abstract class DocumentModelStore(ILogger<DocumentModelStore> logger, IJsonSerializer serializer)
: IDocumentModelStore
{
private readonly List<ModelCard> _models = new();
/// <summary>
/// This event is triggered by each specific host app implementation of the document model store.
/// </summary>
// POC: unsure about the PublicAPI annotation, unsure if this changed handle should live here on the store... :/
public event EventHandler? DocumentChanged;
//needed for javascript UI
public IReadOnlyList<ModelCard> Models
{
get
{
lock (_models)
{
return _models.AsReadOnly();
}
}
}
protected void OnDocumentChanged() => DocumentChanged?.Invoke(this, EventArgs.Empty);
public virtual Task OnDocumentStoreInitialized() => Task.CompletedTask;
public virtual bool IsDocumentInit { get; set; }
// TODO: not sure about this, throwing an exception, needs some thought...
// Further note (dim): If we reach to the stage of throwing an exception here because a model is not found, there's a huge misalignment between the UI's list of model cards and the host app's.
// In theory this should never really happen, (Adam) but it does because of threading so don't throw (as said above)
public ModelCard? GetModelById(string id)
{
lock (_models)
{
var model = _models.FirstOrDefault(model => model.ModelCardId == id);
if (model is null)
{
logger.LogWarning($"Model with id {id} not found.");
return null;
}
return model;
}
}
public void AddModel(ModelCard model)
{
lock (_models)
{
_models.Add(model);
SaveState();
}
}
public void ClearAndSave()
{
lock (_models)
{
_models.Clear();
SaveState();
}
}
public void UpdateModel(ModelCard model)
{
lock (_models)
{
var index = _models.FindIndex(m => m.ModelCardId == model.ModelCardId);
if (index == -1)
{
logger.LogWarning($"Model card not found to update. Model card ID: {model.ModelCardId}");
return;
}
_models[index] = model;
SaveState();
}
}
public void RemoveModel(ModelCard model)
{
lock (_models)
{
var index = _models.FindIndex(m => m.ModelCardId == model.ModelCardId);
if (index == -1)
{
logger.LogWarning($"Model card not found to update. Model card ID: {model.ModelCardId}");
return;
}
_models.RemoveAt(index);
SaveState();
}
}
public void RemoveModels(List<ModelCard> models)
{
lock (_models)
{
var listForMissingModelCards = new List<string>();
foreach (var model in models)
{
var index = _models.FindIndex(m => m.ModelCardId == model.ModelCardId);
if (index == -1)
{
listForMissingModelCards.Add(model.ModelCardId.NotNull());
}
_models.RemoveAt(index);
}
SaveState();
if (listForMissingModelCards.Count > 0)
{
logger.LogWarning($"Model cards with IDs {listForMissingModelCards} not found to remove.");
}
}
}
public IEnumerable<SenderModelCard> GetSenders()
{
lock (_models)
{
return _models
.Where(model => model.TypeDiscriminator == nameof(SenderModelCard))
.Cast<SenderModelCard>()
.ToList();
}
}
protected string Serialize() => serializer.Serialize(Models.ToList());
// POC: this seemms more like a IModelsDeserializer?, seems disconnected from this class
protected List<ModelCard> Deserialize(string models) => serializer.Deserialize<List<ModelCard>>(models).NotNull();
protected void SaveState()
{
lock (_models)
{
var state = Serialize();
HostAppSaveState(state);
}
}
/// <summary>
/// Implement this method according to the host app's specific ways of reading custom data from its file.
/// </summary>
protected abstract void HostAppSaveState(string modelCardState);
protected abstract void LoadState();
protected void LoadFromString(string? models)
{
try
{
lock (_models)
{
_models.Clear();
if (string.IsNullOrEmpty(models))
{
return;
}
_models.AddRange(Deserialize(models.NotNull()).NotNull());
}
}
catch (Exception ex) when (!ex.IsFatal())
{
ClearAndSave();
Debug.WriteLine(ex.Message); // POC: Log here error and notify UI that cards not read succesfully
}
}
}