diff --git a/src/DynamoCore/Configuration/PreferenceSettings.cs b/src/DynamoCore/Configuration/PreferenceSettings.cs index 08fe38e99c2..e9cd35e5334 100644 --- a/src/DynamoCore/Configuration/PreferenceSettings.cs +++ b/src/DynamoCore/Configuration/PreferenceSettings.cs @@ -73,6 +73,7 @@ private readonly static Lazy private double defaultScaleFactor; private bool disableTrustWarnings = false; private bool isNotificationCenterEnabled; + private bool isAutoSaveEnabled; private bool isEnablePersistExtensionsEnabled; private bool isAutoSyncDocumentBrowser = true; private bool isStaticSplashScreenEnabled; @@ -778,6 +779,23 @@ public bool EnableNotificationCenter } } + /// + /// This defines if user wants to enable AutoSave: automatically save edited graphs + /// to their original file on disk after a period of inactivity. The default value is false. + /// + public bool EnableAutoSave + { + get + { + return isAutoSaveEnabled; + } + set + { + isAutoSaveEnabled = value; + RaisePropertyChanged(nameof(EnableAutoSave)); + } + } + /// /// This defines if user wants the Extensions settings to persist across sessions. /// @@ -1099,6 +1117,7 @@ public PreferenceSettings() MLRecommendationNumberOfResults = 10; HideAutocompleteMethodOptions = false; EnableNotificationCenter = true; + EnableAutoSave = false; isStaticSplashScreenEnabled = true; isTimeStampIncludedInExportFilePath = true; DefaultPythonEngine = string.Empty; diff --git a/src/DynamoCore/Graph/ModelBase.cs b/src/DynamoCore/Graph/ModelBase.cs index eec9bb7202a..c8f21e943b8 100644 --- a/src/DynamoCore/Graph/ModelBase.cs +++ b/src/DynamoCore/Graph/ModelBase.cs @@ -13,7 +13,7 @@ namespace Dynamo.Graph /// /// SaveContext represents several contexts, in which node can be serialized/deserialized. /// - public enum SaveContext { [Obsolete("Use Save or SaveAs, instead of File")] File, Copy, Undo, Preset, None, Save, SaveAs }; + public enum SaveContext { [Obsolete("Use Save or SaveAs, instead of File")] File, Copy, Undo, Preset, None, Save, SaveAs, AutoSave }; /// /// This class encapsulates the input parameters that need to be passed into nodes diff --git a/src/DynamoCore/PublicAPI.Unshipped.txt b/src/DynamoCore/PublicAPI.Unshipped.txt index fd19c94b62b..cb39c80054c 100644 --- a/src/DynamoCore/PublicAPI.Unshipped.txt +++ b/src/DynamoCore/PublicAPI.Unshipped.txt @@ -4,3 +4,6 @@ Dynamo.Graph.Nodes.IValueSchemaProvider.ValueTypeId.get -> string Dynamo.Models.DynamoModel.DefaultStartConfiguration.EnableUnTrustedLocationsNotifications.get -> bool Dynamo.Models.DynamoModel.DefaultStartConfiguration.EnableUnTrustedLocationsNotifications.set -> void Dynamo.Models.DynamoModel.IStartConfiguration.EnableUnTrustedLocationsNotifications.get -> bool +Dynamo.Graph.SaveContext.AutoSave = 7 -> Dynamo.Graph.SaveContext +Dynamo.Configuration.PreferenceSettings.EnableAutoSave.get -> bool +Dynamo.Configuration.PreferenceSettings.EnableAutoSave.set -> void diff --git a/src/DynamoCoreWpf/Properties/Resources.cs-CZ.resx b/src/DynamoCoreWpf/Properties/Resources.cs-CZ.resx index d252315885a..172ecbede75 100644 --- a/src/DynamoCoreWpf/Properties/Resources.cs-CZ.resx +++ b/src/DynamoCoreWpf/Properties/Resources.cs-CZ.resx @@ -3235,6 +3235,12 @@ Tato umístění můžete spravovat v části Předvolby -> Zabezpečení. Přijímat oznámení + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Centrum upozornění diff --git a/src/DynamoCoreWpf/Properties/Resources.de-DE.resx b/src/DynamoCoreWpf/Properties/Resources.de-DE.resx index 55b2b9be18f..222e1679b03 100644 --- a/src/DynamoCoreWpf/Properties/Resources.de-DE.resx +++ b/src/DynamoCoreWpf/Properties/Resources.de-DE.resx @@ -3234,6 +3234,12 @@ Sie können dies unter Voreinstellungen -> Sicherheit verwalten. Benachrichtigungen erhalten + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Benachrichtigungscenter diff --git a/src/DynamoCoreWpf/Properties/Resources.en-GB.resx b/src/DynamoCoreWpf/Properties/Resources.en-GB.resx index f7dc64a70df..187231fcd1f 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-GB.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-GB.resx @@ -3236,6 +3236,12 @@ You can manage this in Preferences -> Security. Receive notification + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Notification Center diff --git a/src/DynamoCoreWpf/Properties/Resources.en-US.resx b/src/DynamoCoreWpf/Properties/Resources.en-US.resx index 5f45b505dd9..51e85eba9ce 100644 --- a/src/DynamoCoreWpf/Properties/Resources.en-US.resx +++ b/src/DynamoCoreWpf/Properties/Resources.en-US.resx @@ -3585,6 +3585,14 @@ You can manage this in Preferences -> Security. Receive notification Preferences | Features | Notification Center | Receive notification + + Enable AutoSave + Preferences | General | Backup | Enable AutoSave toggle label + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Preferences | General | Backup | Enable AutoSave toggle tooltip + Notification Center Preferences | Features | Notification Center diff --git a/src/DynamoCoreWpf/Properties/Resources.es-ES.resx b/src/DynamoCoreWpf/Properties/Resources.es-ES.resx index 3a2e87bf8e2..14d0222bf71 100644 --- a/src/DynamoCoreWpf/Properties/Resources.es-ES.resx +++ b/src/DynamoCoreWpf/Properties/Resources.es-ES.resx @@ -3236,6 +3236,12 @@ Puede administrar esto en Preferencias - > Seguridad. Recibir notificación + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Centro de notificaciones diff --git a/src/DynamoCoreWpf/Properties/Resources.fr-FR.resx b/src/DynamoCoreWpf/Properties/Resources.fr-FR.resx index 312ca03fa51..4994c18e6e5 100644 --- a/src/DynamoCoreWpf/Properties/Resources.fr-FR.resx +++ b/src/DynamoCoreWpf/Properties/Resources.fr-FR.resx @@ -3234,6 +3234,12 @@ Vous pouvez gérer ce paramètre dans Préférences -> Sécurité. Recevoir une notification + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Centre de notification diff --git a/src/DynamoCoreWpf/Properties/Resources.it-IT.resx b/src/DynamoCoreWpf/Properties/Resources.it-IT.resx index cecf314a0c4..ff55907f74d 100644 --- a/src/DynamoCoreWpf/Properties/Resources.it-IT.resx +++ b/src/DynamoCoreWpf/Properties/Resources.it-IT.resx @@ -3218,6 +3218,12 @@ Provare a posizionare il nodo **ByOrigin** evidenziato. Ricevi notifica + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Centro notifiche diff --git a/src/DynamoCoreWpf/Properties/Resources.ja-JP.resx b/src/DynamoCoreWpf/Properties/Resources.ja-JP.resx index 8196203d8cb..860acb69ff4 100644 --- a/src/DynamoCoreWpf/Properties/Resources.ja-JP.resx +++ b/src/DynamoCoreWpf/Properties/Resources.ja-JP.resx @@ -3236,6 +3236,12 @@ Dynamo を再起動してアンインストールを完了します。 通知を受信 + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + 通知センター diff --git a/src/DynamoCoreWpf/Properties/Resources.ko-KR.resx b/src/DynamoCoreWpf/Properties/Resources.ko-KR.resx index ace48776029..86a6cf36bae 100644 --- a/src/DynamoCoreWpf/Properties/Resources.ko-KR.resx +++ b/src/DynamoCoreWpf/Properties/Resources.ko-KR.resx @@ -3234,6 +3234,12 @@ 알림 수신 + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + 알림 센터 diff --git a/src/DynamoCoreWpf/Properties/Resources.pl-PL.resx b/src/DynamoCoreWpf/Properties/Resources.pl-PL.resx index 8013bc75664..da6fdde747f 100644 --- a/src/DynamoCoreWpf/Properties/Resources.pl-PL.resx +++ b/src/DynamoCoreWpf/Properties/Resources.pl-PL.resx @@ -3236,6 +3236,12 @@ Można tym zarządzać w obszarze Preferencje -> Zabezpieczenia. Odbierz powiadomienie + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Centrum powiadomień diff --git a/src/DynamoCoreWpf/Properties/Resources.pt-BR.resx b/src/DynamoCoreWpf/Properties/Resources.pt-BR.resx index b53c3479607..c3391126128 100644 --- a/src/DynamoCoreWpf/Properties/Resources.pt-BR.resx +++ b/src/DynamoCoreWpf/Properties/Resources.pt-BR.resx @@ -3236,6 +3236,12 @@ Tente colocar o nó **ByOrigin** realçado. Receber notificações + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Centro de notificações diff --git a/src/DynamoCoreWpf/Properties/Resources.resx b/src/DynamoCoreWpf/Properties/Resources.resx index 29ddf57ce0e..d2163f45f77 100644 --- a/src/DynamoCoreWpf/Properties/Resources.resx +++ b/src/DynamoCoreWpf/Properties/Resources.resx @@ -3579,6 +3579,14 @@ You can manage this in Preferences -> Security. Receive notification Preferences | Features | Notification Center | Receive notification + + Enable AutoSave + Preferences | General | Backup | Enable AutoSave toggle label + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Preferences | General | Backup | Enable AutoSave toggle tooltip + Notification Center Preferences | Features | Notification Center diff --git a/src/DynamoCoreWpf/Properties/Resources.ru-RU.resx b/src/DynamoCoreWpf/Properties/Resources.ru-RU.resx index 10175173a7c..e35bb27ac5a 100644 --- a/src/DynamoCoreWpf/Properties/Resources.ru-RU.resx +++ b/src/DynamoCoreWpf/Properties/Resources.ru-RU.resx @@ -3236,6 +3236,12 @@ Уведомление о получении + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + Центр уведомлений diff --git a/src/DynamoCoreWpf/Properties/Resources.zh-CN.resx b/src/DynamoCoreWpf/Properties/Resources.zh-CN.resx index abf5e3c849f..5f59cd43539 100644 --- a/src/DynamoCoreWpf/Properties/Resources.zh-CN.resx +++ b/src/DynamoCoreWpf/Properties/Resources.zh-CN.resx @@ -3234,6 +3234,12 @@ 接收通知 + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + 通知中心 diff --git a/src/DynamoCoreWpf/Properties/Resources.zh-TW.resx b/src/DynamoCoreWpf/Properties/Resources.zh-TW.resx index 8a24cbcc6bb..1daacdaa63d 100644 --- a/src/DynamoCoreWpf/Properties/Resources.zh-TW.resx +++ b/src/DynamoCoreWpf/Properties/Resources.zh-TW.resx @@ -3235,6 +3235,12 @@ 接收通知 + + Enable AutoSave + + + Automatically save your graph to its original file after a period of inactivity. Only applies to graphs that have already been saved to disk. + 通知中心 diff --git a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt index f4ba45d8860..452703db520 100644 --- a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt +++ b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt @@ -26,6 +26,8 @@ Dynamo.ViewModels.DynamoViewModel.PythonEngineUpgradeToastRequested -> System.Ac Dynamo.ViewModels.DynamoViewModel.ToastManager.get -> Dynamo.Wpf.UI.ToastManager Dynamo.ViewModels.DynamoViewModel.ToastManager.set -> void Dynamo.ViewModels.DynamoViewModel.ShowPythonEngineUpgradeCanvasToast(string message, bool stayOpen = true, string filePath = null) -> void +Dynamo.ViewModels.PreferencesViewModel.AutoSaveIsChecked.get -> bool +Dynamo.ViewModels.PreferencesViewModel.AutoSaveIsChecked.set -> void Dynamo.ViewModels.PreferencesViewModel.AutoSyncDocumentBrowserIsChecked.get -> bool Dynamo.ViewModels.PreferencesViewModel.AutoSyncDocumentBrowserIsChecked.set -> void Dynamo.ViewModels.PreferencesViewModel.ShowPythonAutoMigrationNotificationIsChecked.get -> bool diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs index 2fbe44f1901..35420f270c5 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs @@ -75,6 +75,10 @@ public partial class DynamoViewModel : ViewModelBase, IDynamoViewModel private string dynamoMLDataPath = string.Empty; private const string dynamoMLDataFileName = "DynamoMLDataPipeline.json"; + internal const int AutoSaveDebounceMs = 30_000; + internal readonly Dictionary autoSaveDebouncers = new Dictionary(); + internal readonly Dictionary autoSaveHandlers = new Dictionary(); + private bool onlineAccess = true; //2px tolerance range for node filtering during Home and End key press private readonly int tolerance = 2; @@ -878,6 +882,8 @@ protected DynamoViewModel(StartConfiguration startConfiguration) workspaces.Add(homespaceViewModel); currentWorkspaceViewModel = homespaceViewModel; + SubscribeAutoSaveForWorkspace(model.CurrentWorkspace); + model.WorkspaceAdded += WorkspaceAdded; model.WorkspaceRemoved += WorkspaceRemoved; if (model.LinterManager != null) @@ -1882,6 +1888,8 @@ internal void AddGroupToGroup(object hostGroupGuid) private void WorkspaceAdded(WorkspaceModel item) { + SubscribeAutoSaveForWorkspace(item); + if (item is HomeWorkspaceModel) { var newVm = new HomeWorkspaceViewModel(item as HomeWorkspaceModel, this); @@ -1915,6 +1923,8 @@ private void WorkspaceAdded(WorkspaceModel item) private void WorkspaceRemoved(WorkspaceModel item) { + UnsubscribeAutoSaveForWorkspace(item); + var viewModel = workspaces.First(x => x.Model == item); if (currentWorkspaceViewModel == viewModel) if(currentWorkspaceViewModel != null) @@ -1925,6 +1935,106 @@ private void WorkspaceRemoved(WorkspaceModel item) workspaces.Remove(viewModel); } + /// + /// Subscribes to the workspace's event so that + /// AutoSave can be triggered when HasUnsavedChanges becomes true. A per-workspace + /// coalesces rapid edits into a single save after a period of inactivity. + /// + private void SubscribeAutoSaveForWorkspace(WorkspaceModel workspace) + { + if (workspace == null || autoSaveHandlers.ContainsKey(workspace.Guid)) + { + return; + } + + var debouncer = new ActionDebouncer(Model.Logger); + autoSaveDebouncers[workspace.Guid] = debouncer; + + var workspaceGuid = workspace.Guid; + PropertyChangedEventHandler handler = (sender, e) => + { + if (e.PropertyName != nameof(WorkspaceModel.HasUnsavedChanges)) + { + return; + } + + if (sender is not WorkspaceModel ws) + { + return; + } + + if (!ws.HasUnsavedChanges) + { + return; + } + + if (string.IsNullOrEmpty(ws.FileName)) + { + return; + } + + if (!PreferenceSettings.EnableAutoSave) + { + return; + } + + debouncer.Debounce(AutoSaveDebounceMs, () => TriggerAutoSave(workspaceGuid)); + }; + + autoSaveHandlers[workspace.Guid] = handler; + workspace.PropertyChanged += handler; + } + + /// + /// Cancels and disposes the per-workspace AutoSave debouncer and detaches the property-changed handler. + /// Called when a workspace is removed so that no stale save fires after close. + /// + private void UnsubscribeAutoSaveForWorkspace(WorkspaceModel workspace) + { + if (workspace == null) + { + return; + } + + if (autoSaveHandlers.TryGetValue(workspace.Guid, out var handler)) + { + workspace.PropertyChanged -= handler; + autoSaveHandlers.Remove(workspace.Guid); + } + + if (autoSaveDebouncers.TryGetValue(workspace.Guid, out var debouncer)) + { + debouncer.Dispose(); + autoSaveDebouncers.Remove(workspace.Guid); + } + } + + /// + /// Resolves the workspace by GUID and writes it to its current + /// using . Re-reads FileName at trigger time so that a Save As + /// performed during the debounce window writes to the new path. + /// + internal void TriggerAutoSave(Guid workspaceGuid) + { + var workspace = Model.Workspaces.FirstOrDefault(w => w.Guid == workspaceGuid); + if (workspace == null) + { + return; + } + + if (!PreferenceSettings.EnableAutoSave) + { + return; + } + + if (string.IsNullOrEmpty(workspace.FileName)) + { + return; + } + + SaveAs(workspaceGuid, workspace.FileName, isBackup: false, SaveContext.AutoSave); + } + private void OnRuleEvaluationResultsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { RaisePropertyChanged(nameof(LinterIssuesCount)); @@ -4662,6 +4772,14 @@ public bool PerformShutdownSequence(ShutdownParams shutdownParams) { wsvm.Dispose(); } + + foreach (var debouncer in autoSaveDebouncers.Values) + { + debouncer.Dispose(); + } + autoSaveDebouncers.Clear(); + autoSaveHandlers.Clear(); + ToastManager?.CloseRealTimeInfoWindow(); model.ShutDown(shutdownParams.ShutdownHost); diff --git a/src/DynamoCoreWpf/ViewModels/Menu/PreferencesViewModel.cs b/src/DynamoCoreWpf/ViewModels/Menu/PreferencesViewModel.cs index d5f50cac3b0..ff7656e42dc 100644 --- a/src/DynamoCoreWpf/ViewModels/Menu/PreferencesViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Menu/PreferencesViewModel.cs @@ -1116,6 +1116,23 @@ public bool NotificationCenterIsChecked } } + /// + /// Controls the IsChecked property in the "Enable AutoSave" toggle button + /// in the Preferences > Backup section. + /// + public bool AutoSaveIsChecked + { + get + { + return preferenceSettings.EnableAutoSave; + } + set + { + preferenceSettings.EnableAutoSave = value; + RaisePropertyChanged(nameof(AutoSaveIsChecked)); + } + } + /// /// Controls the IsChecked property in the "Extensions" toggle button, to enable persisted extensions, that will remember /// extensions setting as per the last session. diff --git a/src/DynamoCoreWpf/Views/Menu/PreferencesView.xaml b/src/DynamoCoreWpf/Views/Menu/PreferencesView.xaml index 246c0329346..9096e166a7c 100644 --- a/src/DynamoCoreWpf/Views/Menu/PreferencesView.xaml +++ b/src/DynamoCoreWpf/Views/Menu/PreferencesView.xaml @@ -579,6 +579,23 @@ IsExpanded="{Binding PreferencesTabs[General].ExpanderActive, Converter={StaticResource ExpandersBindingConverter}, ConverterParameter=BackupSettingsExpander}" Header="{x:Static p:Resources.PreferencesViewGeneralSettingsBackup}"> + + + + + + + + diff --git a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs index ab659c59255..b367e99df50 100644 --- a/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs +++ b/test/DynamoCoreTests/Configuration/PreferenceSettingsTests.cs @@ -185,6 +185,29 @@ public void TestSettingsSerialization() Assert.AreEqual(settings.Locale, "zh-CN"); } + [Test] + [Category("UnitTests")] + public void WhenDefaultSettingsThenAutoSaveIsDisabled() + { + var settings = new PreferenceSettings(); + + Assert.IsFalse(settings.EnableAutoSave); + } + + [Test] + [Category("UnitTests")] + public void WhenSettingsSavedAndLoadedThenAutoSaveRoundTrips() + { + var tempPath = GetNewFileNameOnTempPath(".xml"); + + var settings = new PreferenceSettings { EnableAutoSave = true }; + settings.Save(tempPath); + + var loaded = PreferenceSettings.Load(tempPath); + + Assert.IsTrue(loaded.EnableAutoSave); + } + [Test] [Category("UnitTests")] public void TestMigrateStdLibTokenToBuiltInToken() diff --git a/test/DynamoCoreWpf3Tests/WorkspaceSaving.cs b/test/DynamoCoreWpf3Tests/WorkspaceSaving.cs index b40c45328bb..c086f72d6c0 100644 --- a/test/DynamoCoreWpf3Tests/WorkspaceSaving.cs +++ b/test/DynamoCoreWpf3Tests/WorkspaceSaving.cs @@ -1740,5 +1740,135 @@ public void WorkapceChecksumTest() Assert.AreEqual("65b395b9874b9d82e088093f30234c496704006030ecf35471404f62b62a6442", checksumString); } #endregion + + #region AutoSave + + [Test] + [Category("UnitTests")] + public void WhenAutoSaveEnabledAndWorkspaceIsDirtyAndHasFileNameThenSaveIsCalledAfterDebounce() + { + ViewModel.Model.PreferenceSettings.EnableAutoSave = true; + + var workspace = ViewModel.Model.CurrentWorkspace; + var savePath = GetNewFileNameOnTempPath("dyn"); + workspace.Save(savePath); + Assert.IsTrue(File.Exists(savePath)); + + var originalTimestamp = File.GetLastWriteTimeUtc(savePath); + // Make the on-disk timestamp distinct from the autosave write. + File.SetLastWriteTimeUtc(savePath, originalTimestamp.AddSeconds(-5)); + + workspace.HasUnsavedChanges = true; + Assert.IsTrue(workspace.HasUnsavedChanges); + + ViewModel.TriggerAutoSave(workspace.Guid); + + Assert.IsTrue(File.Exists(savePath)); + Assert.IsFalse(workspace.HasUnsavedChanges); + Assert.Greater(File.GetLastWriteTimeUtc(savePath), originalTimestamp.AddSeconds(-5)); + } + + [Test] + [Category("UnitTests")] + public void WhenAutoSaveEnabledAndWorkspaceHasNoFileNameThenSaveIsNotCalled() + { + ViewModel.Model.PreferenceSettings.EnableAutoSave = true; + + var workspace = ViewModel.Model.CurrentWorkspace; + Assert.IsTrue(string.IsNullOrEmpty(workspace.FileName)); + + var savingFired = false; + Action handler = _ => savingFired = true; + workspace.WorkspaceSaving += handler; + try + { + workspace.HasUnsavedChanges = true; + ViewModel.TriggerAutoSave(workspace.Guid); + + Assert.IsFalse(savingFired); + } + finally + { + workspace.WorkspaceSaving -= handler; + } + } + + [Test] + [Category("UnitTests")] + public void WhenAutoSaveEnabledAndSaveFiredThenSaveContextIsAutoSave() + { + ViewModel.Model.PreferenceSettings.EnableAutoSave = true; + + var workspace = ViewModel.Model.CurrentWorkspace; + var savePath = GetNewFileNameOnTempPath("dyn"); + workspace.Save(savePath); + + SaveContext? capturedContext = null; + Action handler = ctx => capturedContext = ctx; + workspace.WorkspaceSaving += handler; + try + { + workspace.HasUnsavedChanges = true; + ViewModel.TriggerAutoSave(workspace.Guid); + + Assert.IsTrue(capturedContext.HasValue); + Assert.AreEqual(SaveContext.AutoSave, capturedContext.Value); + } + finally + { + workspace.WorkspaceSaving -= handler; + } + } + + [Test] + [Category("UnitTests")] + public void WhenAutoSaveDisabledAndWorkspaceIsDirtyThenSaveIsNotCalled() + { + ViewModel.Model.PreferenceSettings.EnableAutoSave = false; + + var workspace = ViewModel.Model.CurrentWorkspace; + var savePath = GetNewFileNameOnTempPath("dyn"); + workspace.Save(savePath); + + workspace.HasUnsavedChanges = true; + + var savingFired = false; + Action handler = _ => savingFired = true; + workspace.WorkspaceSaving += handler; + try + { + ViewModel.TriggerAutoSave(workspace.Guid); + + Assert.IsFalse(savingFired); + Assert.IsTrue(workspace.HasUnsavedChanges); + } + finally + { + workspace.WorkspaceSaving -= handler; + } + } + + [Test] + [Category("UnitTests")] + public void WhenWorkspaceRemovedThenAutoSaveDebouncerIsDisposed() + { + var funcguid = GuidUtility.Create(GuidUtility.UrlNamespace, "AutoSaveDebouncerDisposedTest"); + ViewModel.ExecuteCommand(new DynamoModel.CreateCustomNodeCommand(funcguid, "AutoSaveNode", "Custom Nodes", "", true)); + ViewModel.FocusCustomNodeWorkspace(funcguid); + + var customNodeWorkspace = ViewModel.CurrentSpace; + Assert.IsTrue(customNodeWorkspace is CustomNodeWorkspaceModel); + var workspaceGuid = customNodeWorkspace.Guid; + + Assert.IsTrue(ViewModel.autoSaveDebouncers.ContainsKey(workspaceGuid)); + Assert.IsTrue(ViewModel.autoSaveHandlers.ContainsKey(workspaceGuid)); + + ViewModel.Model.RemoveWorkspace(customNodeWorkspace); + + Assert.IsFalse(ViewModel.autoSaveDebouncers.ContainsKey(workspaceGuid)); + Assert.IsFalse(ViewModel.autoSaveHandlers.ContainsKey(workspaceGuid)); + } + + #endregion } } diff --git a/test/settings/DynamoSettings-NewSettings.xml b/test/settings/DynamoSettings-NewSettings.xml index 6d0b1938a2e..32403650f4b 100644 --- a/test/settings/DynamoSettings-NewSettings.xml +++ b/test/settings/DynamoSettings-NewSettings.xml @@ -86,6 +86,7 @@ true false false + true false false false