using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using RobotNet10.ScriptEngine; using RobotNet10.ScriptEngine.HubContexts; using RobotNet10.ScriptEngine.Shared; namespace RobotNet10.ScriptEngine.Hubs; /// /// SignalR Hub for managing script files. /// [Authorize] public class FileManagerHub(FileManager _fileManager, ConsoleHubContext _consoleHubContext) : Hub { /// /// Called when a client disconnects. /// public override Task OnDisconnectedAsync(Exception? exception) { // Revoke edit permission if this connection had it _fileManager.RevokeEditPermission(Context.ConnectionId); return base.OnDisconnectedAsync(exception); } /// /// Gets the root folder structure with all files and folders. /// public async Task GetRootFolder() { return await _fileManager.GetRootFolderAsync(); } /// /// Gets the current state of the FileManager. /// public Task GetState() { return Task.FromResult(_fileManager.State); } /// /// Requests edit permission. If another connection has permission, it will be revoked and notified. /// public Task RequestEditPermission() { var previousConnectionId = _fileManager.RequestEditPermission(Context.ConnectionId); if (previousConnectionId != null && previousConnectionId != Context.ConnectionId) { // Notify the previous connection that their permission was revoked // Only notify if it's a different connection (not the same one reconnecting) try { _ = Clients.Client(previousConnectionId).SendAsync("EditPermissionRevoked", Context.UserIdentifier); } catch { // Ignore if connection is already disconnected } } return Task.FromResult(true); } /// /// Revokes edit permission for the current connection. /// public Task RevokeEditPermission() { _fileManager.RevokeEditPermission(Context.ConnectionId); return Task.CompletedTask; } /// /// Checks if the current connection has edit permission. /// public Task HasEditPermission() { return Task.FromResult(_fileManager.HasEditPermission(Context.ConnectionId)); } /// /// Saves file content. /// public async Task SaveFile(string relativePath, string content) { try { await _fileManager.SaveFileAsync(relativePath, content, Context.ConnectionId); _consoleHubContext.LogInfo($"File saved successfully: {relativePath}"); // Notify other clients that file was saved (fire-and-forget) _ = Clients.Others.SendAsync("FileSaved", relativePath, Context.UserIdentifier); } catch (UnauthorizedAccessException ex) { _consoleHubContext.LogError($"Failed to save file '{relativePath}': {ex.Message}"); throw; } catch (Exception ex) { _consoleHubContext.LogError($"Error saving file '{relativePath}': {ex.Message}"); throw; } } /// /// Creates a new file. /// public async Task CreateFile(string relativePath, string content) { try { await _fileManager.CreateFileAsync(relativePath, content, Context.ConnectionId); _consoleHubContext.LogInfo($"File created successfully: {relativePath}"); // Notify other clients that file was created (fire-and-forget) _ = Clients.Others.SendAsync("FileCreated", relativePath, Context.UserIdentifier); } catch (UnauthorizedAccessException ex) { _consoleHubContext.LogError($"Failed to create file '{relativePath}': {ex.Message}"); throw; } catch (IOException ex) { _consoleHubContext.LogWarning($"Failed to create file '{relativePath}': {ex.Message}"); throw; } catch (Exception ex) { _consoleHubContext.LogError($"Error creating file '{relativePath}': {ex.Message}"); throw; } } /// /// Creates a new folder. /// public Task CreateFolder(string relativePath) { try { _fileManager.CreateFolder(relativePath, Context.ConnectionId); _consoleHubContext.LogInfo($"Folder created successfully: {relativePath}"); // Notify other clients that folder was created (fire-and-forget) _ = Clients.Others.SendAsync("FolderCreated", relativePath, Context.UserIdentifier); } catch (UnauthorizedAccessException ex) { _consoleHubContext.LogError($"Failed to create folder '{relativePath}': {ex.Message}"); throw; } catch (IOException ex) { _consoleHubContext.LogWarning($"Failed to create folder '{relativePath}': {ex.Message}"); throw; } catch (Exception ex) { _consoleHubContext.LogError($"Error creating folder '{relativePath}': {ex.Message}"); throw; } return Task.CompletedTask; } /// /// Deletes a file. /// public Task DeleteFile(string relativePath) { try { _fileManager.DeleteFile(relativePath, Context.ConnectionId); _consoleHubContext.LogInfo($"File deleted successfully: {relativePath}"); // Notify other clients that file was deleted (fire-and-forget) _ = Clients.Others.SendAsync("FileDeleted", relativePath, Context.UserIdentifier); } catch (UnauthorizedAccessException ex) { _consoleHubContext.LogError($"Failed to delete file '{relativePath}': {ex.Message}"); throw; } catch (FileNotFoundException ex) { _consoleHubContext.LogWarning($"Failed to delete file '{relativePath}': {ex.Message}"); throw; } catch (Exception ex) { _consoleHubContext.LogError($"Error deleting file '{relativePath}': {ex.Message}"); throw; } return Task.CompletedTask; } /// /// Deletes a folder. /// public Task DeleteFolder(string relativePath) { try { _fileManager.DeleteFolder(relativePath, Context.ConnectionId); _consoleHubContext.LogInfo($"Folder deleted successfully: {relativePath}"); // Notify other clients that folder was deleted (fire-and-forget) _ = Clients.Others.SendAsync("FolderDeleted", relativePath, Context.UserIdentifier); } catch (UnauthorizedAccessException ex) { _consoleHubContext.LogError($"Failed to delete folder '{relativePath}': {ex.Message}"); throw; } catch (DirectoryNotFoundException ex) { _consoleHubContext.LogWarning($"Failed to delete folder '{relativePath}': {ex.Message}"); throw; } catch (Exception ex) { _consoleHubContext.LogError($"Error deleting folder '{relativePath}': {ex.Message}"); throw; } return Task.CompletedTask; } /// /// Creates a backup of all scripts. /// public async Task CreateBackup(string? backupName = null) { try { var backupFileName = await _fileManager.CreateBackupAsync(backupName); _consoleHubContext.LogInfo($"Backup created successfully: {backupFileName}"); // Notify all clients that backup was created (fire-and-forget) _ = Clients.All.SendAsync("BackupCreated", backupFileName, Context.UserIdentifier); return backupFileName; } catch (Exception ex) { _consoleHubContext.LogError($"Error creating backup: {ex.Message}"); throw; } } /// /// Lists available backup files. /// public Task ListBackups() { var backups = _fileManager.ListBackups(); return Task.FromResult(backups); } /// /// Restores scripts from a backup. /// public async Task RestoreBackup(string backupFileName, bool replaceExisting = true) { try { await _fileManager.RestoreBackupAsync(backupFileName, replaceExisting); _consoleHubContext.LogInfo($"Backup restored successfully: {backupFileName}"); // Notify all clients that backup was restored (fire-and-forget) _ = Clients.All.SendAsync("BackupRestored", backupFileName, Context.UserIdentifier); } catch (Exception ex) { _consoleHubContext.LogError($"Error restoring backup '{backupFileName}': {ex.Message}"); throw; } } /// /// Deletes a backup file. /// public Task DeleteBackup(string backupFileName) { try { _fileManager.DeleteBackup(backupFileName); _consoleHubContext.LogInfo($"Backup deleted successfully: {backupFileName}"); // Notify all clients that backup was deleted (fire-and-forget) _ = Clients.All.SendAsync("BackupDeleted", backupFileName, Context.UserIdentifier); } catch (Exception ex) { _consoleHubContext.LogError($"Error deleting backup '{backupFileName}': {ex.Message}"); throw; } return Task.CompletedTask; } }