Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancel Dashboard loading when navigating away #3587

Merged
merged 2 commits into from
Aug 13, 2024
Merged
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
90 changes: 74 additions & 16 deletions tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public partial class DashboardView : ToolPage, IDisposable
private static DispatcherQueue _dispatcherQueue;
private readonly ILocalSettingsService _localSettingsService;
private readonly IWidgetExtensionService _widgetExtensionService;
private CancellationTokenSource _initWidgetsCancellationTokenSource;
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
private bool _disposedValue;

private const string DraggedWidget = "DraggedWidget";
Expand Down Expand Up @@ -143,23 +144,26 @@ private async Task OnLoadedAsync()
[RelayCommand]
private async Task OnUnloadedAsync()
{
_log.Debug($"Unloading Dashboard, cancel any loading.");
_initWidgetsCancellationTokenSource.Cancel();
ViewModel.PinnedWidgets.CollectionChanged -= OnPinnedWidgetsCollectionChangedAsync;
Bindings.StopTracking();

Application.Current.GetService<WidgetAdaptiveCardRenderingService>().RendererUpdated -= HandleRendererUpdated;

_log.Debug($"Leaving Dashboard, deactivating widgets.");

await _pinnedWidgetsLock.WaitAsync();
try
{
await Task.Run(() => UnsubscribeFromWidgets());
}
catch (Exception ex)
finally
{
_log.Error(ex, "Exception in UnsubscribeFromWidgets:");
ViewModel.PinnedWidgets.Clear();
_pinnedWidgetsLock.Release();
}

ViewModel.PinnedWidgets.Clear();
await UnsubscribeFromWidgetCatalogEventsAsync();
}

Expand Down Expand Up @@ -200,7 +204,16 @@ private async Task InitializeDashboard()
await _localSettingsService.SaveSettingAsync(WellKnownSettingsKeys.IsNotFirstDashboardRun, true);
}

await InitializePinnedWidgetListAsync(isFirstDashboardRun);
_initWidgetsCancellationTokenSource = new();
try
{
await InitializePinnedWidgetListAsync(isFirstDashboardRun, _initWidgetsCancellationTokenSource.Token);
}
catch (OperationCanceledException ex)
{
_log.Information(ex, "InitializePinnedWidgetListAsync operation was cancelled.");
return;
}
}
else
{
Expand Down Expand Up @@ -229,19 +242,48 @@ private async Task InitializeDashboard()
ViewModel.IsLoading = false;
}

private async Task InitializePinnedWidgetListAsync(bool isFirstDashboardRun)
private async Task InitializePinnedWidgetListAsync(bool isFirstDashboardRun, CancellationToken cancellationToken)
{
var hostWidgets = await GetPreviouslyPinnedWidgets();
if ((hostWidgets.Length == 0) && isFirstDashboardRun)
{
// If it's the first time the Dashboard has been displayed and we have no other widgets pinned to a
// different version of Dev Home, pin some default widgets.
_log.Information($"Pin default widgets");
await PinDefaultWidgetsAsync();
await _pinnedWidgetsLock.WaitAsync(CancellationToken.None);
try
{
await PinDefaultWidgetsAsync(cancellationToken);
}
catch (OperationCanceledException ex)
{
// If the operation is cancelled, delete any default widgets that were already pinned and reset the IsNotFirstDashboardRun setting.
// Next time the user opens the Dashboard, treat it as the first run again.
_log.Information(ex, "PinDefaultWidgetsAsync operation was cancelled, delete any widgets already pinned");
foreach (var widget in ViewModel.PinnedWidgets)
{
await widget.Widget.DeleteAsync();
}

await _localSettingsService.SaveSettingAsync(WellKnownSettingsKeys.IsNotFirstDashboardRun, false);
}
finally
{
_pinnedWidgetsLock.Release();
}
}
else
{
await RestorePinnedWidgetsAsync(hostWidgets);
await _pinnedWidgetsLock.WaitAsync(CancellationToken.None);
try
{
await RestorePinnedWidgetsAsync(hostWidgets, cancellationToken);
}
finally
{
// No cleanup to do if the operation is cancelled.
_pinnedWidgetsLock.Release();
}
}
}

Expand Down Expand Up @@ -274,7 +316,7 @@ private async Task<ComSafeWidget[]> GetPreviouslyPinnedWidgets()
return [.. comSafeHostWidgets];
}

private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets)
private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets, CancellationToken cancellationToken)
{
var restoredWidgetsWithPosition = new SortedDictionary<int, ComSafeWidget>();
var restoredWidgetsWithoutPosition = new SortedDictionary<int, ComSafeWidget>();
Expand All @@ -289,6 +331,8 @@ private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets)
// append it at the end. If a position is missing, just show the next widget in order.
foreach (var widget in hostWidgets)
{
cancellationToken.ThrowIfCancellationRequested();

try
{
var stateStr = await widget.GetCustomStateAsync();
Expand Down Expand Up @@ -381,7 +425,9 @@ private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets)
{
var comSafeWidget = orderedWidget.Value;
var size = await comSafeWidget.GetSizeAsync();
await InsertWidgetInPinnedWidgetsAsync(comSafeWidget, size, finalPlace++);
cancellationToken.ThrowIfCancellationRequested();

await InsertWidgetInPinnedWidgetsAsync(comSafeWidget, size, finalPlace++, cancellationToken);
}

// Go through the newly created list of pinned widgets and update any positions that may have changed.
Expand All @@ -390,6 +436,8 @@ private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets)
var updatedPlace = 0;
foreach (var widget in ViewModel.PinnedWidgets)
{
cancellationToken.ThrowIfCancellationRequested();

await WidgetHelpers.SetPositionCustomStateAsync(widget.Widget, updatedPlace++);
}

Expand All @@ -410,21 +458,23 @@ private async Task DeleteAbandonedWidgetAsync(ComSafeWidget widget)
_log.Information($"After delete, {length} widgets for this host");
}

private async Task PinDefaultWidgetsAsync()
private async Task PinDefaultWidgetsAsync(CancellationToken cancellationToken)
{
var comSafeWidgetDefinitions = await ComSafeHelpers.GetAllOrderedComSafeWidgetDefinitions(ViewModel.WidgetHostingService);
foreach (var comSafeWidgetDefinition in comSafeWidgetDefinitions)
{
cancellationToken.ThrowIfCancellationRequested();

var id = comSafeWidgetDefinition.Id;
if (WidgetHelpers.DefaultWidgetDefinitionIds.Contains(id))
{
_log.Information($"Found default widget {id}");
await PinDefaultWidgetAsync(comSafeWidgetDefinition);
await PinDefaultWidgetAsync(comSafeWidgetDefinition, cancellationToken);
}
}
}

private async Task PinDefaultWidgetAsync(ComSafeWidgetDefinition defaultWidgetDefinition)
private async Task PinDefaultWidgetAsync(ComSafeWidgetDefinition defaultWidgetDefinition, CancellationToken cancellationToken)
{
try
{
Expand Down Expand Up @@ -467,9 +517,13 @@ private async Task PinDefaultWidgetAsync(ComSafeWidgetDefinition defaultWidgetDe
await comSafeWidget.SetCustomStateAsync(newCustomState);

// Put new widget on the Dashboard.
await InsertWidgetInPinnedWidgetsAsync(comSafeWidget, size, position);
await InsertWidgetInPinnedWidgetsAsync(comSafeWidget, size, position, cancellationToken);
_log.Information($"Inserted default widget {unsafeWidgetId} at position {position}");
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
// We can fail silently since this isn't in response to user action.
Expand Down Expand Up @@ -580,9 +634,10 @@ await mainWindow.ShowErrorMessageDialogAsync(
buttonText: stringResource.GetLocalized("CloseButtonText"));
}

private async Task InsertWidgetInPinnedWidgetsAsync(ComSafeWidget widget, WidgetSize size, int index)
private async Task InsertWidgetInPinnedWidgetsAsync(ComSafeWidget widget, WidgetSize size, int index, CancellationToken cancellationToken = default)
{
await Task.Run(async () =>
await Task.Run(
async () =>
{
var widgetDefinitionId = widget.DefinitionId;
var widgetId = widget.Id;
Expand Down Expand Up @@ -613,6 +668,8 @@ await Task.Run(async () =>
new ReportPinnedWidgetEvent(comSafeWidgetDefinition.ProviderDefinitionId, widgetDefinitionId));

var wvm = _widgetViewModelFactory(widget, size, comSafeWidgetDefinition);
cancellationToken.ThrowIfCancellationRequested();

_dispatcherQueue.TryEnqueue(() =>
{
try
Expand All @@ -631,7 +688,8 @@ await Task.Run(async () =>
{
await DeleteWidgetWithNoDefinition(widget, widgetDefinitionId);
}
});
},
cancellationToken);
}

private async Task DeleteWidgetWithNoDefinition(ComSafeWidget widget, string widgetDefinitionId)
Expand Down
Loading