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;
///
/// Encapsulates the state Speckle needs to persist in the host app's document.
///
[GenerateAutoInterface]
public abstract class DocumentModelStore(ILogger logger, IJsonSerializer serializer)
: IDocumentModelStore
{
private readonly List _models = new();
///
/// This event is triggered by each specific host app implementation of the document model store.
///
// 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 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 models)
{
lock (_models)
{
var listForMissingModelCards = new List();
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 GetSenders()
{
lock (_models)
{
return _models
.Where(model => model.TypeDiscriminator == nameof(SenderModelCard))
.Cast()
.ToList();
}
}
protected string Serialize() => serializer.Serialize(Models.ToList());
// POC: this seemms more like a IModelsDeserializer?, seems disconnected from this class
protected List Deserialize(string models) => serializer.Deserialize>(models).NotNull();
protected void SaveState()
{
lock (_models)
{
var state = Serialize();
HostAppSaveState(state);
}
}
///
/// Implement this method according to the host app's specific ways of reading custom data from its file.
///
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
}
}
}