supervisor-simulator/addons/autosaver_editor/Services/AutoSaveManager.cs
2024-11-19 00:32:10 +08:00

261 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AutoSaverPlugin.Contracts;
using AutoSaverPlugin.Services.GDComponent;
using AutoSaverPlugin.Shared;
using Godot;
using static AutoSaverPlugin.Shared.CommonUtils;
namespace AutoSaverPlugin.Services;
internal sealed class AutoSaveManager : IAutoSaveManager
{
private readonly EditorInterface _editorInterface = EditorInterface.Singleton;
private ScriptEditor _scriptEditor => _editorInterface.GetScriptEditor();
private readonly ISceneStatusReporter _sceneReporter;
private readonly IGDScriptStatusReporter _gdScriptReporter;
private readonly ITimerService _timerAutoSaver;
private readonly ITimerService _timerActivityUserCheck;
private readonly IConfigurationManager _configManager;
private readonly ILoggerService _logger;
private AutoSaverEditorPlugin _plugin;
private UserActivityMonitorNode _activityMonitor;
public AutoSaveManager(ISceneStatusReporter sceneStatusReporter, IGDScriptStatusReporter scriptStatusReporter, IConfigurationManager configManager,
ILoggerService loggerService, ITimerService timerAutoSaver, ITimerService timerActivity)
{
_sceneReporter = sceneStatusReporter ?? throw new ArgumentNullException(nameof(sceneStatusReporter));
_gdScriptReporter = scriptStatusReporter ?? throw new ArgumentNullException(nameof(scriptStatusReporter));
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
_logger = loggerService ?? throw new ArgumentNullException(nameof(loggerService));
_timerAutoSaver = timerAutoSaver ?? throw new ArgumentNullException(nameof(timerAutoSaver));
_timerActivityUserCheck = timerActivity ?? throw new ArgumentNullException(nameof(timerActivity));
}
public void Initialize(AutoSaverEditorPlugin plugin)
{
LoadingConfiguration();
_plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
Activate();
}
private void LoadingConfiguration()
{
_configManager.LoadSettings();
}
public void Activate() => SetupAutoSave(restart: false);
public void Reactivate() => SetupAutoSave(restart: true);
public void Deactivate()
{
_logger.LogDiagnostic("Stopping autosaver service..");
_timerAutoSaver.End();
_timerActivityUserCheck.End();
RemoveActivityMonitor();
PrintStatus();
}
private void SetupAutoSave(bool restart)
{
_logger.LogDiagnostic($"{(restart ? "Restarting" : "Initializing")} autosaver service..");
_configManager.LoadSettings();
SetTimers();
ManageActivityMonitor(add: true);
PrintStatus();
}
private void SetTimers()
{
_logger.LogDebug("Setting timers..");
int intervalSec = _configManager.AutoSaverIntervalSetting;
int timeToStartCheck = Math.Clamp(intervalSec - _configManager.ActivityCheckWindowSetting, 1, intervalSec);
_timerAutoSaver.End().AttachTo(_plugin).OnTimeout(OnAutosaveTimerTimeout);
_timerActivityUserCheck.End().AttachTo(_plugin).OnTimeout(StartMonitoringUserActivity, oneShot: true);
if (_configManager.IsAutoSaverEnabled)
{
_timerAutoSaver.Begin(intervalSec);
_timerActivityUserCheck.Begin(timeToStartCheck);
}
}
private void ManageActivityMonitor(bool add)
{
if (add)
{
_activityMonitor = new UserActivityMonitorNode();
_activityMonitor.UserActivityDetected += OnUserActivityDetected;
_plugin.AddChild(_activityMonitor);
}
else
{
if (_activityMonitor != null)
{
_activityMonitor.UserActivityDetected -= OnUserActivityDetected;
if (_activityMonitor.IsInsideTree())
_plugin?.RemoveChild(_activityMonitor);
if (!_activityMonitor.IsQueuedForDeletion())
_activityMonitor.QueueFree();
_activityMonitor = null;
}
}
}
private void RemoveActivityMonitor() => ManageActivityMonitor(add: false);
private void OnUserActivityDetected(string eventName, float lastActivityTimeSec) =>
_logger.LogDebug($"User activity detected: {eventName} at last activity time: {lastActivityTimeSec}ms");
private void StartMonitoringUserActivity()
{
_logger.LogDebug($"Starting user activity monitoring..@{GetCurrentTimestamp()}");
_activityMonitor.StartMonitoring();
}
private void OnAutosaveTimerTimeout()
{
_logger.LogDiagnostic($"Running autosaver @{GetCurrentTimestamp()}");
PerformAutoSaveIfNeeded();
}
private void PerformAutoSaveIfNeeded()
{
const float inTheLastMilliSecs = 500f; // 0.5 sec
if (!_activityMonitor.IsMonitoring || _activityMonitor.NoActivityTriggered(thresholdMillisec: inTheLastMilliSecs))
{
PerformAutoSave();
}
else
{
PostponeAutoSave();
}
}
private void PerformAutoSave()
{
_activityMonitor.StopMonitoring();
var modifiedScenes = _configManager.IsOptionSaveScenesEnabled ? GetModifiedItems(_sceneReporter.FetchModifiedItems()) : new List<string>();
var modifiedScripts = _configManager.IsOptionSaveScriptsEnabled ? GetModifiedItems(_gdScriptReporter.FetchModifiedItems()) : new List<string>();
if (modifiedScenes.Count == 0 && modifiedScripts.Count == 0)
{
_logger.LogDiagnostic("No modified items. Skipping autosave.");
return;
}
// save all scenes (also save all scripts)
bool savedAll = SaveScenes(modifiedScenes);
if (!savedAll && !_configManager.HasGDEditorAutosaveEnabled)
SaveFiles(modifiedScripts);
LogAutosaveResult(modifiedScenes.Count + modifiedScripts.Count, modifiedScenes.Concat(modifiedScripts).ToList());
SetTimers(); // Restart timers
}
private bool SaveScenes(List<string> modifiedScenes)
{
List<string> savedFiles = new();
var openScenes = _editorInterface.GetOpenScenes();
var editedScene = _editorInterface.GetEditedSceneRoot();
var fnSceneRoot = Path.GetFileName(editedScene.SceneFilePath).Split('.')[0];
bool saveAllAtOnce = false;
int numFilesSaved = 0;
foreach (string scenePath in openScenes)
{
var fileNameScenePath = Path.GetFileName(scenePath).Split('.')[0];
if (modifiedScenes.Contains(fileNameScenePath))
{
numFilesSaved++;
savedFiles.Add(scenePath);
saveAllAtOnce = saveAllAtOnce || fnSceneRoot != fileNameScenePath;
}
}
if (saveAllAtOnce)
{
_editorInterface.SaveAllScenes();
}
else if (numFilesSaved == 1)
{
var err = _editorInterface.SaveScene();
if (err != Error.Ok)
{
_logger.LogError($"Failed to autoSave scene: {fnSceneRoot}. Error: {err}");
}
}
return saveAllAtOnce;
}
private void SaveFiles(List<string> modifiedFiles)
{
if (modifiedFiles.Count > 0)
_editorInterface.SaveAllScenes();
}
private void PostponeAutoSave()
{
_logger.LogDebug($"Postponing autoSave for {_configManager.PostponeTimeSetting}sec..");
_timerAutoSaver.End().OnTimeout(OnAutosaveTimerTimeout).Begin(_configManager.PostponeTimeSetting);
}
private static List<string> GetModifiedItems(List<string> items)
{
var modifiedScripts = new List<string>();
foreach (var i in items)
{
if (i.Contains("*"))
{
modifiedScripts.Add(i.Replace("(*)", ""));
}
}
return modifiedScripts;
}
private void LogAutosaveResult(int numFilesSaved, List<string> savedFiles)
{
string currentTimestamp = GetCurrentTimestamp();
if (numFilesSaved > 0)
{
_logger.LogInfo($"Autosave executed at {currentTimestamp}. {numFilesSaved} file(s) saved:");
foreach (var file in savedFiles)
{
_logger.LogInfo($"- {file}");
}
}
else
{
_logger.LogDiagnostic($"Autosave completed at {currentTimestamp}: No files saved.");
}
}
private void PrintStatus()
{
string timestamp = GetCurrentTimestamp();
string pluginSetTimestamp = $"Plugin set @{timestamp}.";
string autosaveScene = $"scenes ({(_configManager.IsOptionSaveScenesEnabled ? "ON" : "OFF")})";
string autosaveScript = $"GDScript files ({(_configManager.IsOptionSaveScriptsEnabled ? "ON" : "OFF")})";
string verboseLevelMessage = $"Verbose level: {_configManager.VerboseLevelSetting}.";
string editorMessage = $"[Editor] Autosave Interval: {_configManager.GDEditor_autosave_interval_secs}sec, [Editor] Save on focus loss: {_configManager.GDEditor_save_on_focus_loss}";
string statusAutosaving = _configManager.IsAutoSaverEnabled ? "Autosaving every {_autoSaveConfig.AutoSaverIntervalSetting} seconds: {autosaveScene} and {autosaveScript}." : "Autosaving disabled.";
_logger.LogInfo($"{pluginSetTimestamp} {statusAutosaving}");
_logger.LogDiagnostic($"{verboseLevelMessage} {editorMessage}");
}
}