Files
speckle-sharp-connectors/Connectors/Autocad/Speckle.Connectors.AutocadShared/Bindings/AutocadSendBinding.cs
T
2024-07-04 11:56:34 +01:00

197 lines
6.7 KiB
C#

using Autodesk.AutoCAD.DatabaseServices;
using Speckle.Connectors.Autocad.HostApp;
using Speckle.Connectors.Autocad.HostApp.Extensions;
using Speckle.Connectors.DUI.Bindings;
using Speckle.Connectors.DUI.Bridge;
using Speckle.Connectors.DUI.Models;
using Speckle.Connectors.DUI.Models.Card;
using Speckle.Connectors.Utils.Cancellation;
using Speckle.Autofac.DependencyInjection;
using Speckle.Connectors.Autocad.Operations.Send;
using Speckle.Connectors.DUI.Exceptions;
using Speckle.Connectors.Utils.Operations;
using Speckle.Connectors.DUI.Models.Card.SendFilter;
using Speckle.Connectors.Utils;
using Speckle.Connectors.Utils.Caching;
namespace Speckle.Connectors.Autocad.Bindings;
public sealed class AutocadSendBinding : ISendBinding
{
public string Name => "sendBinding";
public SendBindingUICommands Commands { get; }
public IBridge Parent { get; }
private readonly DocumentModelStore _store;
private readonly AutocadIdleManager _idleManager;
private readonly List<ISendFilter> _sendFilters;
private readonly CancellationManager _cancellationManager;
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
private readonly AutocadSettings _autocadSettings;
private readonly ISendConversionCache _sendConversionCache;
private readonly ITopLevelExceptionHandler _topLevelExceptionHandler;
/// <summary>
/// Used internally to aggregate the changed objects' id.
/// </summary>
private HashSet<string> ChangedObjectIds { get; set; } = new();
public AutocadSendBinding(
DocumentModelStore store,
AutocadIdleManager idleManager,
IBridge parent,
IEnumerable<ISendFilter> sendFilters,
CancellationManager cancellationManager,
AutocadSettings autocadSettings,
IUnitOfWorkFactory unitOfWorkFactory,
ISendConversionCache sendConversionCache,
ITopLevelExceptionHandler topLevelExceptionHandler
)
{
_store = store;
_idleManager = idleManager;
_unitOfWorkFactory = unitOfWorkFactory;
_autocadSettings = autocadSettings;
_cancellationManager = cancellationManager;
_sendFilters = sendFilters.ToList();
_sendConversionCache = sendConversionCache;
_topLevelExceptionHandler = topLevelExceptionHandler;
Parent = parent;
Commands = new SendBindingUICommands(parent);
Application.DocumentManager.DocumentActivated += (_, args) =>
topLevelExceptionHandler.CatchUnhandled(() => SubscribeToObjectChanges(args.Document));
if (Application.DocumentManager.CurrentDocument != null)
{
// catches the case when autocad just opens up with a blank new doc
SubscribeToObjectChanges(Application.DocumentManager.CurrentDocument);
}
}
private readonly List<string> _docSubsTracker = new();
private void SubscribeToObjectChanges(Document doc)
{
if (doc == null || doc.Database == null || _docSubsTracker.Contains(doc.Name))
{
return;
}
_docSubsTracker.Add(doc.Name);
doc.Database.ObjectAppended += (_, e) => OnObjectChanged(e.DBObject);
doc.Database.ObjectErased += (_, e) => OnObjectChanged(e.DBObject);
doc.Database.ObjectModified += (_, e) => OnObjectChanged(e.DBObject);
}
void OnObjectChanged(DBObject dbObject)
{
_topLevelExceptionHandler.CatchUnhandled(() => OnChangeChangedObjectIds(dbObject));
}
private void OnChangeChangedObjectIds(DBObject dBObject)
{
ChangedObjectIds.Add(dBObject.Handle.Value.ToString());
_idleManager.SubscribeToIdle(RunExpirationChecks);
}
private void RunExpirationChecks()
{
var senders = _store.GetSenders();
string[] objectIdsList = ChangedObjectIds.ToArray();
List<string> expiredSenderIds = new();
_sendConversionCache.EvictObjects(objectIdsList);
foreach (SenderModelCard modelCard in senders)
{
var intersection = modelCard.SendFilter.NotNull().GetObjectIds().Intersect(objectIdsList).ToList();
bool isExpired = intersection.Count != 0;
if (isExpired)
{
expiredSenderIds.Add(modelCard.ModelCardId.NotNull());
}
}
Commands.SetModelsExpired(expiredSenderIds);
ChangedObjectIds = new HashSet<string>();
}
public List<ISendFilter> GetSendFilters() => _sendFilters;
public Task Send(string modelCardId)
{
Parent.RunOnMainThread(async () => await SendInternal(modelCardId).ConfigureAwait(false));
return Task.CompletedTask;
}
private async Task SendInternal(string modelCardId)
{
try
{
if (_store.GetModelById(modelCardId) is not SenderModelCard modelCard)
{
// Handle as GLOBAL ERROR at BrowserBridge
throw new InvalidOperationException("No publish model card was found.");
}
using var uow = _unitOfWorkFactory.Resolve<SendOperation<AutocadRootObject>>();
// Init cancellation token source -> Manager also cancel it if exist before
CancellationTokenSource cts = _cancellationManager.InitCancellationTokenSource(modelCardId);
// Disable document activation (document creation and document switch)
// Not disabling results in DUI model card being out of sync with the active document
// The DocumentActivated event isn't usable probably because it is pushed to back of main thread queue
Application.DocumentManager.DocumentActivationEnabled = false;
// Get elements to convert
List<AutocadRootObject> autocadObjects = Application.DocumentManager.CurrentDocument.GetObjects(
modelCard.SendFilter.NotNull().GetObjectIds()
);
if (autocadObjects.Count == 0)
{
// Handle as CARD ERROR in this function
throw new SpeckleSendFilterException("No objects were found to convert. Please update your publish filter!");
}
var sendInfo = new SendInfo(
modelCard.AccountId.NotNull(),
modelCard.ProjectId.NotNull(),
modelCard.ModelId.NotNull(),
_autocadSettings.HostAppInfo.Name
);
var sendResult = await uow.Service
.Execute(
autocadObjects,
sendInfo,
(status, progress) =>
Commands.SetModelProgress(modelCardId, new ModelCardProgress(modelCardId, status, progress), cts),
cts.Token
)
.ConfigureAwait(false);
Commands.SetModelSendResult(modelCardId, sendResult.RootObjId, sendResult.ConversionResults);
}
// Catch here specific exceptions if they related to model card.
catch (OperationCanceledException)
{
// SWALLOW -> UI handles it immediately, so we do not need to handle anything
return;
}
catch (SpeckleSendFilterException e)
{
Commands.SetModelError(modelCardId, e);
}
finally
{
// renable document activation
Application.DocumentManager.DocumentActivationEnabled = true;
}
}
public void CancelSend(string modelCardId) => _cancellationManager.CancelOperation(modelCardId);
}