Front End Rework & Deployment Cleanup
- Restructured references to service projects instead of ASP.NET Core web projects - Terminal.Gui front end for easy track management from the shell
This commit is contained in:
@@ -60,6 +60,18 @@ public async Task<TrackEntity?> AddTrackFromWavAsync(
|
||||
|
||||
### Available Commands
|
||||
|
||||
#### GUI Mode (Interactive Terminal Interface)
|
||||
```bash
|
||||
DeepDrftCli gui
|
||||
```
|
||||
Launches the interactive Terminal.Gui interface with:
|
||||
- **DeepDrft brand color theme** (Magenta/Purple/Pink)
|
||||
- **Color-coded track list** with navigation
|
||||
- **Persistent hotkey legend** showing shortcuts
|
||||
- **Interactive add track dialog** with file browser
|
||||
- **Real-time status updates** and feedback
|
||||
- **Full keyboard shortcuts** for all operations
|
||||
|
||||
#### Add Track
|
||||
```bash
|
||||
DeepDrftCli add <wav-file-path> <track-name> <artist> [album] [genre] [release-date]
|
||||
|
||||
@@ -8,24 +8,39 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.8" />
|
||||
<PackageReference Include="Terminal.Gui" Version="1.17.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DeepDrftWeb\DeepDrftWeb.csproj" />
|
||||
<ProjectReference Include="..\DeepDrftContent\DeepDrftContent.csproj" />
|
||||
<ProjectReference Include="..\DeepDrftModels\DeepDrftModels.csproj" />
|
||||
<ProjectReference Include="..\DeepDrftWeb.Services\DeepDrftWeb.Services.csproj" />
|
||||
<ProjectReference Include="..\DeepDrftContent.Services\DeepDrftContent.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<None Update="environment\config.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="environment\connections.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Enable single file publish -->
|
||||
<PropertyGroup>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<!-- <SelfContained>true</SelfContained>-->
|
||||
<!-- <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>-->
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Environment config (copied manually by build script) -->
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace DeepDrftCli.Models
|
||||
{
|
||||
public class CliSettings
|
||||
{
|
||||
public string ConnectionString { get; set; } = string.Empty;
|
||||
public string VaultPath { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
+25
-23
@@ -3,43 +3,34 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using DeepDrftWeb.Data;
|
||||
using DeepDrftWeb.Data.Repositories;
|
||||
using DeepDrftContent.FileDatabase.Services;
|
||||
using DeepDrftContent.Processors;
|
||||
using DeepDrftContent.Services;
|
||||
using DeepDrftWeb.Services.Data;
|
||||
using DeepDrftWeb.Services.Repositories;
|
||||
using DeepDrftContent.Services.FileDatabase.Services;
|
||||
using DeepDrftContent.Services.Processors;
|
||||
using DeepDrftCli.Services;
|
||||
using DeepDrftCli.Models;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
|
||||
// Add configuration
|
||||
var appDirectory = AppContext.BaseDirectory;
|
||||
var configPath = Path.Combine(appDirectory, "appsettings.json");
|
||||
builder.Configuration.AddJsonFile(configPath, optional: false, reloadOnChange: true);
|
||||
// Load configuration from environment/config.json
|
||||
builder.Configuration.AddJsonFile($"{AppDomain.CurrentDomain.BaseDirectory}environment/connections.json", optional: false, reloadOnChange: true);
|
||||
var cliSettings = builder.Configuration.GetSection(nameof(CliSettings)).Get<CliSettings>();
|
||||
if (cliSettings is null) { throw new Exception("CLI settings are not configured"); }
|
||||
|
||||
// Add logging
|
||||
builder.Services.AddLogging(configure => configure.AddConsole());
|
||||
|
||||
// Add database context
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
throw new InvalidOperationException("DefaultConnection not found in configuration");
|
||||
|
||||
builder.Services.AddDbContext<DeepDrftContext>(options =>
|
||||
options.UseSqlite(connectionString));
|
||||
options.UseSqlite(cliSettings.ConnectionString));
|
||||
|
||||
// Add FileDatabase
|
||||
builder.Services.AddSingleton<FileDatabase>(provider =>
|
||||
{
|
||||
var logger = provider.GetRequiredService<ILogger<Program>>();
|
||||
var configuration = provider.GetRequiredService<IConfiguration>();
|
||||
try
|
||||
{
|
||||
var vaultPath = configuration["FileDatabaseSettings:VaultPath"];
|
||||
if (string.IsNullOrEmpty(vaultPath))
|
||||
throw new InvalidOperationException("FileDatabaseSettings:VaultPath not found in configuration");
|
||||
|
||||
var fileDatabase = FileDatabase.FromAsync(vaultPath).GetAwaiter().GetResult();
|
||||
var fileDatabase = FileDatabase.FromAsync(cliSettings.VaultPath).GetAwaiter().GetResult();
|
||||
if (fileDatabase == null)
|
||||
{
|
||||
logger.LogError("Failed to initialize FileDatabase");
|
||||
@@ -60,10 +51,21 @@ builder.Services.AddScoped<DeepDrftWeb.Services.TrackService>();
|
||||
builder.Services.AddScoped<AudioProcessor>();
|
||||
builder.Services.AddScoped<DeepDrftContent.Services.TrackService>();
|
||||
builder.Services.AddScoped<CliService>();
|
||||
builder.Services.AddScoped<GuiService>();
|
||||
|
||||
// Build and run
|
||||
var app = builder.Build();
|
||||
|
||||
// Get the CLI service and run
|
||||
var cliService = app.Services.GetRequiredService<CliService>();
|
||||
await cliService.RunAsync(args);
|
||||
// Check if GUI mode is requested
|
||||
if (args.Length > 0 && (args[0].ToLowerInvariant() == "gui" || args[0].ToLowerInvariant() == "--gui"))
|
||||
{
|
||||
// Run GUI mode
|
||||
var guiService = app.Services.GetRequiredService<GuiService>();
|
||||
await guiService.RunAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Run traditional CLI mode
|
||||
var cliService = app.Services.GetRequiredService<CliService>();
|
||||
await cliService.RunAsync(args);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using DeepDrftWeb.Data.Repositories;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using DeepDrftWeb.Services.Repositories;
|
||||
using DeepDrftContent.Services;
|
||||
using DeepDrftModels.Entities;
|
||||
using NetBlocks.Models;
|
||||
@@ -50,6 +51,10 @@ public class CliService
|
||||
case "list":
|
||||
await HandleListCommand();
|
||||
break;
|
||||
case "gui":
|
||||
case "--gui":
|
||||
Console.WriteLine("Error: GUI mode should be launched directly. Use: DeepDrftCli gui");
|
||||
break;
|
||||
case "help":
|
||||
case "--help":
|
||||
case "-h":
|
||||
@@ -244,6 +249,10 @@ public class CliService
|
||||
{
|
||||
Console.WriteLine("DeepDrft CLI - Audio Track Management Tool");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" DeepDrftCli gui - Launch interactive GUI mode");
|
||||
Console.WriteLine(" DeepDrftCli [command] [options] - Run command-line mode");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Commands:");
|
||||
Console.WriteLine(" add <wav-file> <track-name> <artist> [album] [genre] [release-date]");
|
||||
Console.WriteLine(" - Adds a WAV file to both SQL and FileDatabase");
|
||||
|
||||
@@ -0,0 +1,913 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Terminal.Gui;
|
||||
using DeepDrftWeb.Services.Repositories;
|
||||
using DeepDrftContent.Services;
|
||||
using DeepDrftModels.Entities;
|
||||
using NetBlocks.Models;
|
||||
|
||||
namespace DeepDrftCli.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Terminal.Gui based interactive interface for DeepDrft CLI operations
|
||||
/// </summary>
|
||||
public class GuiService
|
||||
{
|
||||
private readonly ILogger<GuiService> _logger;
|
||||
private readonly TrackRepository _trackRepository;
|
||||
private readonly DeepDrftWeb.Services.TrackService _webTrackService;
|
||||
private readonly DeepDrftContent.Services.TrackService _contentTrackService;
|
||||
|
||||
// GUI Components
|
||||
private Window? _mainWindow;
|
||||
private MenuBar? _menuBar;
|
||||
private ListView? _trackListView;
|
||||
private TextView? _statusView;
|
||||
private FrameView? _legendFrame;
|
||||
private List<TrackEntity> _tracks = new();
|
||||
|
||||
public GuiService(
|
||||
ILogger<GuiService> logger,
|
||||
TrackRepository trackRepository,
|
||||
DeepDrftWeb.Services.TrackService webTrackService,
|
||||
DeepDrftContent.Services.TrackService contentTrackService)
|
||||
{
|
||||
_logger = logger;
|
||||
_trackRepository = trackRepository;
|
||||
_webTrackService = webTrackService;
|
||||
_contentTrackService = contentTrackService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize and run the GUI application
|
||||
/// </summary>
|
||||
public async Task RunAsync()
|
||||
{
|
||||
Application.Init();
|
||||
|
||||
try
|
||||
{
|
||||
await SetupMainWindowAsync();
|
||||
Application.Run(_mainWindow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "GUI application failed");
|
||||
MessageBox.ErrorQuery(50, 7, "Error", $"Application failed: {ex.Message}", "OK");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Application.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the main application window with all components
|
||||
/// </summary>
|
||||
private async Task SetupMainWindowAsync()
|
||||
{
|
||||
// Create main window with DeepDrft theme
|
||||
_mainWindow = new Window("DeepDrft CLI - Interactive Mode")
|
||||
{
|
||||
X = 0,
|
||||
Y = 1, // Leave room for menu bar
|
||||
Width = Dim.Fill(),
|
||||
Height = Dim.Fill()
|
||||
};
|
||||
|
||||
// Apply DeepDrft theme to main window with improved contrast
|
||||
_mainWindow.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.White, Color.Black),
|
||||
Focus = new Terminal.Gui.Attribute(Color.White, Color.DarkGray),
|
||||
HotNormal = new Terminal.Gui.Attribute(Color.BrightCyan, Color.Black),
|
||||
HotFocus = new Terminal.Gui.Attribute(Color.BrightYellow, Color.DarkGray)
|
||||
};
|
||||
|
||||
// Setup menu bar
|
||||
SetupMenuBar();
|
||||
|
||||
// Setup track list view
|
||||
SetupTrackListView();
|
||||
|
||||
// Setup status view
|
||||
SetupStatusView();
|
||||
|
||||
// Setup legend panel
|
||||
SetupLegendPanel();
|
||||
|
||||
// Setup key bindings
|
||||
SetupKeyBindings();
|
||||
|
||||
// Add components to main window
|
||||
_mainWindow.Add(_trackListView!, _statusView!, _legendFrame!);
|
||||
|
||||
// Load initial data
|
||||
await RefreshTrackListAsync();
|
||||
|
||||
// Set initial status
|
||||
UpdateStatus("Ready - Use keyboard shortcuts shown below or press F1 for detailed help");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the menu bar with color-coded options
|
||||
/// </summary>
|
||||
private void SetupMenuBar()
|
||||
{
|
||||
_menuBar = new MenuBar(new MenuBarItem[] {
|
||||
new MenuBarItem("_File", new MenuItem[] {
|
||||
new MenuItem("_Add Track (Ctrl+A)", "", () => ShowAddTrackDialog()),
|
||||
new MenuItem("_Refresh (F5)", "", async () => await RefreshTrackListAsync()),
|
||||
null, // Separator
|
||||
new MenuItem("_Quit (Ctrl+Q)", "", () => Application.RequestStop())
|
||||
}),
|
||||
new MenuBarItem("_Edit", new MenuItem[] {
|
||||
new MenuItem("_Edit Track (Ctrl+E)", "", () => ShowEditTrackDialog()),
|
||||
new MenuItem("_Delete Track (Delete)", "", () => ShowDeleteTrackDialog()),
|
||||
null, // Separator
|
||||
new MenuItem("_Track Details (Enter)", "", () => ShowTrackDetails()),
|
||||
}),
|
||||
new MenuBarItem("_View", new MenuItem[] {
|
||||
new MenuItem("_Clear Status (Ctrl+L)", "", () => ClearStatus()),
|
||||
}),
|
||||
new MenuBarItem("_Help", new MenuItem[] {
|
||||
new MenuItem("_Shortcuts (F1)", "", () => ShowHelp()),
|
||||
new MenuItem("_About", "", () => ShowAbout())
|
||||
})
|
||||
});
|
||||
|
||||
Application.Top.Add(_menuBar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the track list view with color coding
|
||||
/// </summary>
|
||||
private void SetupTrackListView()
|
||||
{
|
||||
_trackListView = new ListView()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill(),
|
||||
Height = Dim.Fill(6), // Leave room for status and legend at bottom
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
// Set up high-contrast colors for track list visibility
|
||||
_trackListView.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.White, Color.Black),
|
||||
Focus = new Terminal.Gui.Attribute(Color.BrightYellow, Color.Blue), // High contrast selection
|
||||
HotNormal = new Terminal.Gui.Attribute(Color.BrightCyan, Color.Black),
|
||||
HotFocus = new Terminal.Gui.Attribute(Color.White, Color.Blue) // Clear cursor visibility
|
||||
};
|
||||
|
||||
// Handle selection events
|
||||
_trackListView.SelectedItemChanged += OnTrackSelectionChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the status view at the bottom
|
||||
/// </summary>
|
||||
private void SetupStatusView()
|
||||
{
|
||||
_statusView = new TextView()
|
||||
{
|
||||
X = 0,
|
||||
Y = Pos.AnchorEnd(5), // Position above legend panel
|
||||
Width = Dim.Fill(),
|
||||
Height = 2,
|
||||
ReadOnly = true,
|
||||
WordWrap = true
|
||||
};
|
||||
|
||||
// Status view with high contrast colors for better readability
|
||||
_statusView.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.BrightCyan, Color.Black), // High contrast status
|
||||
Focus = new Terminal.Gui.Attribute(Color.BrightCyan, Color.DarkGray)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup the hotkey legend panel at the bottom
|
||||
/// </summary>
|
||||
private void SetupLegendPanel()
|
||||
{
|
||||
_legendFrame = new FrameView("Keyboard Shortcuts")
|
||||
{
|
||||
X = 0,
|
||||
Y = Pos.AnchorEnd(3),
|
||||
Width = Dim.Fill(),
|
||||
Height = 3
|
||||
};
|
||||
|
||||
// Apply high contrast theme to legend frame for better visibility
|
||||
_legendFrame.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.White, Color.Black), // Clear border
|
||||
Focus = new Terminal.Gui.Attribute(Color.White, Color.DarkGray),
|
||||
HotNormal = new Terminal.Gui.Attribute(Color.BrightYellow, Color.Black),
|
||||
HotFocus = new Terminal.Gui.Attribute(Color.BrightYellow, Color.DarkGray)
|
||||
};
|
||||
|
||||
// Create legend content with color coding
|
||||
var legendView = new TextView()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = Dim.Fill(),
|
||||
Height = Dim.Fill(),
|
||||
ReadOnly = true,
|
||||
WordWrap = false,
|
||||
Text = CreateLegendText()
|
||||
};
|
||||
|
||||
// Legend with high contrast colors for easy reading
|
||||
legendView.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.BrightGreen, Color.Black), // High contrast legend text
|
||||
Focus = new Terminal.Gui.Attribute(Color.BrightGreen, Color.DarkGray)
|
||||
};
|
||||
|
||||
_legendFrame.Add(legendView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the formatted legend text with shortcuts
|
||||
/// </summary>
|
||||
private string CreateLegendText()
|
||||
{
|
||||
return "Ctrl+A: Add │ Ctrl+E: Edit │ Del: Delete │ F5: Refresh │ Enter: Details │ F1: Help │ Ctrl+Q: Quit";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup keyboard shortcuts
|
||||
/// </summary>
|
||||
private void SetupKeyBindings()
|
||||
{
|
||||
// Global key bindings using KeyDown event which is more reliable
|
||||
_mainWindow.KeyDown += async (e) =>
|
||||
{
|
||||
var key = e.KeyEvent.Key;
|
||||
|
||||
// Debug logging for key presses
|
||||
UpdateStatus($"Key pressed: {key}");
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case Key.CtrlMask | Key.Q:
|
||||
case Key.CtrlMask | Key.q:
|
||||
Application.RequestStop();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.CtrlMask | Key.A:
|
||||
case Key.CtrlMask | Key.a:
|
||||
UpdateStatus("Opening Add Track dialog...");
|
||||
ShowAddTrackDialog();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.CtrlMask | Key.E:
|
||||
case Key.CtrlMask | Key.e:
|
||||
UpdateStatus("Opening Edit Track dialog...");
|
||||
ShowEditTrackDialog();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.DeleteChar:
|
||||
UpdateStatus("Opening Delete Track dialog...");
|
||||
ShowDeleteTrackDialog();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.F5:
|
||||
UpdateStatus("Refreshing track list...");
|
||||
await RefreshTrackListAsync();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.F1:
|
||||
ShowHelp();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.CtrlMask | Key.L:
|
||||
case Key.CtrlMask | Key.l:
|
||||
ClearStatus();
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
case Key.Enter:
|
||||
if (_trackListView?.HasFocus == true)
|
||||
{
|
||||
ShowTrackDetails();
|
||||
e.Handled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Also add global application-level key bindings as backup
|
||||
Application.Top.KeyDown += async (e) =>
|
||||
{
|
||||
var key = e.KeyEvent.Key;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case Key.CtrlMask | Key.A:
|
||||
case Key.CtrlMask | Key.a:
|
||||
if (!e.Handled)
|
||||
{
|
||||
UpdateStatus("Global: Opening Add Track dialog...");
|
||||
ShowAddTrackDialog();
|
||||
e.Handled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the Add Track dialog with form validation
|
||||
/// </summary>
|
||||
private void ShowAddTrackDialog()
|
||||
{
|
||||
var dialog = new Dialog("Add New Track", 80, 18);
|
||||
|
||||
// File path field
|
||||
var filePathLabel = new Label("WAV File Path:") { X = 1, Y = 1 };
|
||||
var filePathField = new TextField("") { X = 1, Y = 2, Width = Dim.Fill(2) };
|
||||
var browseButton = new Button("Browse...") { X = Pos.AnchorEnd(12), Y = 2 };
|
||||
|
||||
// Track metadata fields
|
||||
var trackNameLabel = new Label("Track Name: *") { X = 1, Y = 4 };
|
||||
var trackNameField = new TextField("") { X = 1, Y = 5, Width = Dim.Fill(2) };
|
||||
|
||||
var artistLabel = new Label("Artist: *") { X = 1, Y = 6 };
|
||||
var artistField = new TextField("") { X = 1, Y = 7, Width = Dim.Fill(2) };
|
||||
|
||||
var albumLabel = new Label("Album:") { X = 1, Y = 8 };
|
||||
var albumField = new TextField("") { X = 1, Y = 9, Width = Dim.Fill(2) };
|
||||
|
||||
var genreLabel = new Label("Genre:") { X = 1, Y = 10 };
|
||||
var genreField = new TextField("") { X = 1, Y = 11, Width = Dim.Fill(2) };
|
||||
|
||||
var releaseDateLabel = new Label("Release Date (YYYY-MM-DD):") { X = 1, Y = 12 };
|
||||
var releaseDateField = new TextField("") { X = 1, Y = 13, Width = Dim.Fill(2) };
|
||||
|
||||
// Buttons
|
||||
var addButton = new Button("Add Track") { X = 1, Y = 15 };
|
||||
var cancelButton = new Button("Cancel") { X = 15, Y = 15 };
|
||||
|
||||
// Color coding for required fields with high contrast
|
||||
trackNameLabel.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.BrightRed, Color.Black) // High contrast for required fields
|
||||
};
|
||||
artistLabel.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.BrightRed, Color.Black) // High contrast for required fields
|
||||
};
|
||||
|
||||
// Browse button click handler
|
||||
browseButton.Clicked += () =>
|
||||
{
|
||||
var openDialog = new OpenDialog("Select WAV File", "Select a WAV audio file to add");
|
||||
openDialog.AllowedFileTypes = new[] { ".wav" };
|
||||
|
||||
Application.Run(openDialog);
|
||||
|
||||
if (!openDialog.Canceled && openDialog.FilePath != null)
|
||||
{
|
||||
filePathField.Text = openDialog.FilePath.ToString();
|
||||
|
||||
// Try to extract metadata from filename
|
||||
var fileName = Path.GetFileNameWithoutExtension(openDialog.FilePath.ToString());
|
||||
if (string.IsNullOrEmpty(trackNameField.Text.ToString()))
|
||||
{
|
||||
trackNameField.Text = fileName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add button click handler
|
||||
addButton.Clicked += async () =>
|
||||
{
|
||||
if (await ValidateAndAddTrackAsync(
|
||||
filePathField.Text.ToString(),
|
||||
trackNameField.Text.ToString(),
|
||||
artistField.Text.ToString(),
|
||||
albumField.Text.ToString(),
|
||||
genreField.Text.ToString(),
|
||||
releaseDateField.Text.ToString()))
|
||||
{
|
||||
Application.RequestStop();
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel button click handler
|
||||
cancelButton.Clicked += () => Application.RequestStop();
|
||||
|
||||
// Add all components to dialog
|
||||
dialog.Add(filePathLabel, filePathField, browseButton,
|
||||
trackNameLabel, trackNameField,
|
||||
artistLabel, artistField,
|
||||
albumLabel, albumField,
|
||||
genreLabel, genreField,
|
||||
releaseDateLabel, releaseDateField,
|
||||
addButton, cancelButton);
|
||||
|
||||
// Set focus to file path field
|
||||
filePathField.SetFocus();
|
||||
|
||||
Application.Run(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the Edit Track dialog for the selected track
|
||||
/// </summary>
|
||||
private void ShowEditTrackDialog()
|
||||
{
|
||||
if (_trackListView?.SelectedItem < 0 || _trackListView?.SelectedItem >= _tracks.Count)
|
||||
{
|
||||
UpdateStatus("No track selected for editing. Select a track first.");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedTrack = _tracks[_trackListView!.SelectedItem];
|
||||
|
||||
var dialog = new Dialog("Edit Track", 80, 18);
|
||||
|
||||
// Track metadata fields pre-filled with current values
|
||||
var trackNameLabel = new Label("Track Name: *") { X = 1, Y = 1 };
|
||||
var trackNameField = new TextField(selectedTrack.TrackName) { X = 1, Y = 2, Width = Dim.Fill(2) };
|
||||
|
||||
var artistLabel = new Label("Artist: *") { X = 1, Y = 3 };
|
||||
var artistField = new TextField(selectedTrack.Artist) { X = 1, Y = 4, Width = Dim.Fill(2) };
|
||||
|
||||
var albumLabel = new Label("Album:") { X = 1, Y = 5 };
|
||||
var albumField = new TextField(selectedTrack.Album ?? "") { X = 1, Y = 6, Width = Dim.Fill(2) };
|
||||
|
||||
var genreLabel = new Label("Genre:") { X = 1, Y = 7 };
|
||||
var genreField = new TextField(selectedTrack.Genre ?? "") { X = 1, Y = 8, Width = Dim.Fill(2) };
|
||||
|
||||
var releaseDateLabel = new Label("Release Date (YYYY-MM-DD):") { X = 1, Y = 9 };
|
||||
var releaseDateField = new TextField(selectedTrack.ReleaseDate?.ToString() ?? "") { X = 1, Y = 10, Width = Dim.Fill(2) };
|
||||
|
||||
// Info label showing current track ID
|
||||
var infoLabel = new Label($"Editing Track ID: {selectedTrack.Id} - Entry Key: {selectedTrack.EntryKey}") { X = 1, Y = 12 };
|
||||
|
||||
// Buttons
|
||||
var saveButton = new Button("Save Changes") { X = 1, Y = 14 };
|
||||
var cancelButton = new Button("Cancel") { X = 18, Y = 14 };
|
||||
|
||||
// Color coding for required fields with high contrast
|
||||
trackNameLabel.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.BrightRed, Color.Black)
|
||||
};
|
||||
artistLabel.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.BrightRed, Color.Black)
|
||||
};
|
||||
|
||||
// Info label styling with better contrast
|
||||
infoLabel.ColorScheme = new ColorScheme()
|
||||
{
|
||||
Normal = new Terminal.Gui.Attribute(Color.BrightCyan, Color.Black)
|
||||
};
|
||||
|
||||
// Save button click handler
|
||||
saveButton.Clicked += async () =>
|
||||
{
|
||||
if (await ValidateAndUpdateTrackAsync(selectedTrack,
|
||||
trackNameField.Text.ToString(),
|
||||
artistField.Text.ToString(),
|
||||
albumField.Text.ToString(),
|
||||
genreField.Text.ToString(),
|
||||
releaseDateField.Text.ToString()))
|
||||
{
|
||||
Application.RequestStop();
|
||||
}
|
||||
};
|
||||
|
||||
// Cancel button click handler
|
||||
cancelButton.Clicked += () => Application.RequestStop();
|
||||
|
||||
// Add all components to dialog
|
||||
dialog.Add(trackNameLabel, trackNameField,
|
||||
artistLabel, artistField,
|
||||
albumLabel, albumField,
|
||||
genreLabel, genreField,
|
||||
releaseDateLabel, releaseDateField,
|
||||
infoLabel,
|
||||
saveButton, cancelButton);
|
||||
|
||||
// Set focus to track name field
|
||||
trackNameField.SetFocus();
|
||||
|
||||
Application.Run(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the Delete Track confirmation dialog for the selected track
|
||||
/// </summary>
|
||||
private void ShowDeleteTrackDialog()
|
||||
{
|
||||
if (_trackListView?.SelectedItem < 0 || _trackListView?.SelectedItem >= _tracks.Count)
|
||||
{
|
||||
UpdateStatus("No track selected for deletion. Select a track first.");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedTrack = _tracks[_trackListView!.SelectedItem];
|
||||
|
||||
var message = $"Are you sure you want to delete this track?\n\n" +
|
||||
$"Track: {selectedTrack.TrackName}\n" +
|
||||
$"Artist: {selectedTrack.Artist}\n" +
|
||||
$"Album: {selectedTrack.Album ?? "N/A"}\n" +
|
||||
$"Genre: {selectedTrack.Genre ?? "N/A"}\n" +
|
||||
$"ID: {selectedTrack.Id}\n\n" +
|
||||
$"WARNING: This action cannot be undone!\n" +
|
||||
$"The track metadata will be removed from the database.";
|
||||
|
||||
var result = MessageBox.Query(70, 14, "Confirm Delete Track", message, "Delete", "Cancel");
|
||||
|
||||
if (result == 0) // Delete button clicked
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await DeleteTrackAsync(selectedTrack);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the specified track from the database
|
||||
/// </summary>
|
||||
private async Task DeleteTrackAsync(TrackEntity trackToDelete)
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateStatus($"Deleting track '{trackToDelete.TrackName}'...");
|
||||
|
||||
// Delete from SQL database
|
||||
var result = await _webTrackService.Delete(trackToDelete.Id);
|
||||
if (result.Success)
|
||||
{
|
||||
UpdateStatus($"✓ Track '{trackToDelete.TrackName}' by {trackToDelete.Artist} deleted successfully!");
|
||||
await RefreshTrackListAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
UpdateStatus($"Failed to delete track: {errorMessage}");
|
||||
|
||||
// Show error dialog on UI thread
|
||||
Application.MainLoop.Invoke(() =>
|
||||
{
|
||||
MessageBox.ErrorQuery(60, 8, "Database Error", $"Failed to delete track: {errorMessage}", "OK");
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete track via GUI");
|
||||
UpdateStatus($"Error deleting track: {ex.Message}");
|
||||
|
||||
// Show error dialog on UI thread
|
||||
Application.MainLoop.Invoke(() =>
|
||||
{
|
||||
MessageBox.ErrorQuery(60, 8, "Error", $"An error occurred: {ex.Message}", "OK");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate input and add track to database
|
||||
/// </summary>
|
||||
private async Task<bool> ValidateAndAddTrackAsync(string filePath, string trackName,
|
||||
string artist, string album, string genre, string releaseDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate required fields
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Validation Error", "File path is required.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trackName))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Validation Error", "Track name is required.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artist))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Validation Error", "Artist is required.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate file exists and has .wav extension
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "File Error", "The specified file does not exist.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Path.GetExtension(filePath).Equals(".wav", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "File Error", "Only WAV files are supported.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate release date if provided
|
||||
DateOnly? parsedReleaseDate = null;
|
||||
if (!string.IsNullOrWhiteSpace(releaseDate))
|
||||
{
|
||||
if (!DateOnly.TryParse(releaseDate, out var date))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Date Error", "Release date must be in YYYY-MM-DD format.", "OK");
|
||||
return false;
|
||||
}
|
||||
parsedReleaseDate = date;
|
||||
}
|
||||
|
||||
// Show progress dialog
|
||||
UpdateStatus("Processing audio file...");
|
||||
|
||||
// Initialize tracks vault
|
||||
await _contentTrackService.InitializeTracksVaultAsync();
|
||||
|
||||
// Process and add track
|
||||
var trackEntity = await _contentTrackService.AddTrackFromWavAsync(
|
||||
filePath, trackName, artist,
|
||||
string.IsNullOrWhiteSpace(album) ? null : album,
|
||||
string.IsNullOrWhiteSpace(genre) ? null : genre,
|
||||
parsedReleaseDate);
|
||||
|
||||
if (trackEntity == null)
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Processing Error", "Failed to process audio file.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to SQL database
|
||||
var result = await _webTrackService.Create(trackEntity);
|
||||
if (result.Success && result.Value != null)
|
||||
{
|
||||
UpdateStatus($"✓ Track '{trackName}' by {artist} added successfully!");
|
||||
await RefreshTrackListAsync();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
MessageBox.ErrorQuery(60, 8, "Database Error", $"Failed to save track: {errorMessage}", "OK");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to add track via GUI");
|
||||
MessageBox.ErrorQuery(60, 8, "Error", $"An error occurred: {ex.Message}", "OK");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate input and update existing track in database
|
||||
/// </summary>
|
||||
private async Task<bool> ValidateAndUpdateTrackAsync(TrackEntity originalTrack, string trackName,
|
||||
string artist, string album, string genre, string releaseDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate required fields
|
||||
if (string.IsNullOrWhiteSpace(trackName))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Validation Error", "Track name is required.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(artist))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Validation Error", "Artist is required.", "OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate release date if provided
|
||||
DateOnly? parsedReleaseDate = null;
|
||||
if (!string.IsNullOrWhiteSpace(releaseDate))
|
||||
{
|
||||
if (!DateOnly.TryParse(releaseDate, out var date))
|
||||
{
|
||||
MessageBox.ErrorQuery(50, 7, "Date Error", "Release date must be in YYYY-MM-DD format.", "OK");
|
||||
return false;
|
||||
}
|
||||
parsedReleaseDate = date;
|
||||
}
|
||||
|
||||
UpdateStatus("Updating track...");
|
||||
|
||||
// Create updated track entity
|
||||
var updatedTrack = new TrackEntity
|
||||
{
|
||||
Id = originalTrack.Id,
|
||||
EntryKey = originalTrack.EntryKey, // Keep original entry key
|
||||
TrackName = trackName,
|
||||
Artist = artist,
|
||||
Album = string.IsNullOrWhiteSpace(album) ? null : album,
|
||||
Genre = string.IsNullOrWhiteSpace(genre) ? null : genre,
|
||||
ReleaseDate = parsedReleaseDate,
|
||||
ImagePath = originalTrack.ImagePath // Keep original image path
|
||||
};
|
||||
|
||||
// Update in SQL database
|
||||
var result = await _webTrackService.Update(updatedTrack);
|
||||
if (result.Success && result.Value != null)
|
||||
{
|
||||
UpdateStatus($"✓ Track '{trackName}' by {artist} updated successfully!");
|
||||
await RefreshTrackListAsync();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
MessageBox.ErrorQuery(60, 8, "Database Error", $"Failed to update track: {errorMessage}", "OK");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to update track via GUI");
|
||||
MessageBox.ErrorQuery(60, 8, "Error", $"An error occurred: {ex.Message}", "OK");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show detailed information about the selected track
|
||||
/// </summary>
|
||||
private void ShowTrackDetails()
|
||||
{
|
||||
if (_trackListView?.SelectedItem < 0 || _trackListView?.SelectedItem >= _tracks.Count)
|
||||
{
|
||||
UpdateStatus("No track selected.");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedTrack = _tracks[_trackListView!.SelectedItem];
|
||||
|
||||
var details = $"Track Details:\n\n" +
|
||||
$"ID: {selectedTrack.Id}\n" +
|
||||
$"Name: {selectedTrack.TrackName}\n" +
|
||||
$"Artist: {selectedTrack.Artist}\n" +
|
||||
$"Album: {selectedTrack.Album ?? "N/A"}\n" +
|
||||
$"Genre: {selectedTrack.Genre ?? "N/A"}\n" +
|
||||
$"Release Date: {selectedTrack.ReleaseDate?.ToString() ?? "N/A"}\n" +
|
||||
$"Entry Key: {selectedTrack.EntryKey}\n" +
|
||||
$"Image Path: {selectedTrack.ImagePath ?? "N/A"}";
|
||||
|
||||
MessageBox.Query(70, 12, "Track Details", details, "OK");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh the track list from database
|
||||
/// </summary>
|
||||
private async Task RefreshTrackListAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateStatus("Loading tracks...");
|
||||
|
||||
var result = await _webTrackService.GetAll();
|
||||
if (result.Success && result.Value != null)
|
||||
{
|
||||
_tracks = result.Value.ToList();
|
||||
|
||||
// Create display items for the list view
|
||||
var displayItems = _tracks.Select(t =>
|
||||
$"{t.Id,4} │ {TruncateString(t.TrackName, 25),25} │ {TruncateString(t.Artist, 20),20} │ {TruncateString(t.Album ?? "", 15),15} │ {TruncateString(t.Genre ?? "", 10),10}"
|
||||
).ToArray();
|
||||
|
||||
_trackListView?.SetSource(displayItems);
|
||||
|
||||
UpdateStatus($"Loaded {_tracks.Count} tracks. Use shortcuts below or navigate with ↑/↓ arrows.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorMessage = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
UpdateStatus($"Failed to load tracks: {errorMessage}");
|
||||
MessageBox.ErrorQuery(50, 7, "Database Error", $"Failed to load tracks: {errorMessage}", "OK");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to refresh track list");
|
||||
UpdateStatus($"Error loading tracks: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle track selection changes
|
||||
/// </summary>
|
||||
private void OnTrackSelectionChanged(ListViewItemEventArgs args)
|
||||
{
|
||||
if (args.Item >= 0 && args.Item < _tracks.Count)
|
||||
{
|
||||
var selectedTrack = _tracks[args.Item];
|
||||
UpdateStatus($"Selected: {selectedTrack.TrackName} by {selectedTrack.Artist} - Press Enter for full details");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show help dialog with keyboard shortcuts
|
||||
/// </summary>
|
||||
private void ShowHelp()
|
||||
{
|
||||
var helpText =
|
||||
"DeepDrft CLI - Interactive Mode Help\n\n" +
|
||||
"KEYBOARD SHORTCUTS (also shown in legend at bottom):\n" +
|
||||
"Ctrl+A - Add new track\n" +
|
||||
"Ctrl+E - Edit selected track\n" +
|
||||
"Delete - Delete selected track\n" +
|
||||
"F5 - Refresh track list\n" +
|
||||
"Enter - Show track details\n" +
|
||||
"Ctrl+L - Clear status\n" +
|
||||
"F1 - Show this help\n" +
|
||||
"Ctrl+Q - Quit application\n\n" +
|
||||
"NAVIGATION:\n" +
|
||||
"↑/↓ - Navigate track list\n" +
|
||||
"Tab - Switch between controls\n" +
|
||||
"Space - Select/activate control\n\n" +
|
||||
"USER INTERFACE:\n" +
|
||||
"• Track list shows: ID │ Name │ Artist │ Album │ Genre\n" +
|
||||
"• Status bar provides real-time feedback\n" +
|
||||
"• Legend bar shows common shortcuts\n" +
|
||||
"• Menu bar accessible via Alt or mouse\n\n" +
|
||||
"HIGH CONTRAST COLOR SCHEME:\n" +
|
||||
"Bright Red - Required fields (*)\n" +
|
||||
"Bright Yellow - Selected/focused items\n" +
|
||||
"Blue Background - Selection highlight\n" +
|
||||
"Bright Cyan - Status messages & info\n" +
|
||||
"Bright Green - Legend shortcuts\n" +
|
||||
"Bright White - Normal text & borders";
|
||||
|
||||
MessageBox.Query(70, 22, "Help - Interactive Mode Guide", helpText, "OK");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show about dialog
|
||||
/// </summary>
|
||||
private void ShowAbout()
|
||||
{
|
||||
var aboutText =
|
||||
"DeepDrft CLI - Interactive Mode\n\n" +
|
||||
"Version: 1.0.0\n" +
|
||||
"Built with Terminal.Gui\n\n" +
|
||||
"Features:\n" +
|
||||
"• Interactive track management\n" +
|
||||
"• Dual database architecture\n" +
|
||||
"• WAV file processing\n" +
|
||||
"• Color-coded interface\n" +
|
||||
"• Keyboard shortcuts\n\n" +
|
||||
"© 2025 DeepDrft Project";
|
||||
|
||||
MessageBox.Query(50, 12, "About DeepDrft CLI", aboutText, "OK");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the status display
|
||||
/// </summary>
|
||||
private void UpdateStatus(string message)
|
||||
{
|
||||
if (_statusView != null)
|
||||
{
|
||||
_statusView.Text = $"Status: {message}";
|
||||
_statusView.SetNeedsDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the status display
|
||||
/// </summary>
|
||||
private void ClearStatus()
|
||||
{
|
||||
UpdateStatus("Ready");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncate string to fit display width
|
||||
/// </summary>
|
||||
private string TruncateString(string input, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
|
||||
return input.Length <= maxLength ? input : input.Substring(0, maxLength - 3) + "...";
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"System": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Data Source=F:\\Development\\DeepDrftHome\\Database\\deepdrft.db"
|
||||
},
|
||||
"FileDatabaseSettings": {
|
||||
"VaultPath": "F:\\Development\\DeepDrftHome\\Database\\Vaults"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🚀 Starting CLI deployment process..."
|
||||
|
||||
# start SSH agent and add key
|
||||
echo "🔑 Starting SSH agent and adding deployment key..."
|
||||
eval $(ssh-agent -s)
|
||||
ssh-add /c/.ssh/deepdrft_ed25519
|
||||
echo "✅ SSH agent configured"
|
||||
|
||||
CLI_PROJ="DeepDrftCli"
|
||||
CLI_APP="deepdrft-cli.tar.gz"
|
||||
|
||||
# Publish CLI with framework-dependent single file
|
||||
echo "🔨 Publishing CLI project for linux-x64..."
|
||||
dotnet publish $CLI_PROJ -c Release -f net9.0 -o $CLI_PROJ/publish -r linux-x64 \
|
||||
--self-contained false \
|
||||
-p:PublishSingleFile=true \
|
||||
-p:Platform="Any CPU" \
|
||||
--verbosity normal
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ CLI project published successfully"
|
||||
else
|
||||
echo "❌ Failed to publish CLI project"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Eliminate local environment from package
|
||||
echo "🧹 Removing local environment from package..."
|
||||
rm -rf $CLI_PROJ/publish/environment
|
||||
echo "✅ Local environment removed"
|
||||
|
||||
# Compress published files
|
||||
echo "📦 Compressing published files..."
|
||||
tar -czf $CLI_APP -C $CLI_PROJ/publish .
|
||||
echo "✅ Package created: $CLI_APP"
|
||||
|
||||
# Deploy
|
||||
REMOTE="deepdrft@dch5.snailbird.net"
|
||||
CLI_APPROOT="/deepdrft/cli"
|
||||
|
||||
echo "🌐 Deploying to remote server: $REMOTE"
|
||||
echo "📁 Target directory: $CLI_APPROOT"
|
||||
|
||||
echo "🗑️ Cleaning existing deployment..."
|
||||
ssh $REMOTE "rm -rf $CLI_APPROOT/bin/*"
|
||||
echo "✅ Remote directory cleaned"
|
||||
|
||||
echo "📤 Uploading package to remote server..."
|
||||
scp $CLI_APP $REMOTE:$CLI_APPROOT/$CLI_APP
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Package uploaded successfully"
|
||||
else
|
||||
echo "❌ Failed to upload package"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📦 Extracting and setting up CLI on remote server..."
|
||||
ssh $REMOTE "tar -xzf $CLI_APPROOT/$CLI_APP -C $CLI_APPROOT/bin && \
|
||||
chmod +x $CLI_APPROOT/bin/DeepDrftCli && \
|
||||
rm $CLI_APPROOT/$CLI_APP"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ CLI extracted and configured on remote server"
|
||||
else
|
||||
echo "❌ Failed to extract CLI on remote server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Apply Local Environment (if exists)
|
||||
echo "🔧 Checking for local environment configuration..."
|
||||
if ssh $REMOTE "[ -d $CLI_APPROOT/environment ]"; then
|
||||
echo "📋 Local environment found, applying configuration..."
|
||||
|
||||
# Ensure environment directory exists in the binary location
|
||||
ssh $REMOTE "mkdir -p $CLI_APPROOT/bin/environment"
|
||||
|
||||
# Copy environment files with better error handling
|
||||
if ssh $REMOTE "cp $CLI_APPROOT/environment/* $CLI_APPROOT/bin/environment/ 2>/dev/null"; then
|
||||
echo "✅ Local environment configuration applied successfully"
|
||||
else
|
||||
echo "⚠️ Warning: Some environment files may not have been copied"
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ No local environment configuration found - skipping"
|
||||
fi
|
||||
|
||||
echo "🔗 Setting up user-accessible command symlink..."
|
||||
# Create user-accessible symlink without sudo
|
||||
ssh $REMOTE "mkdir -p ~/bin && ln -sf $CLI_APPROOT/bin/DeepDrftCli ~/bin/deepdrft"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Symlink created successfully"
|
||||
else
|
||||
echo "❌ Failed to create symlink"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🛣️ Ensuring ~/bin is in PATH..."
|
||||
# Ensure ~/bin is in PATH (add to .bashrc if not present)
|
||||
ssh $REMOTE "grep -q '~/bin' ~/.bashrc || echo 'export PATH=\"\$HOME/bin:\$PATH\"' >> ~/.bashrc"
|
||||
echo "✅ PATH configuration updated"
|
||||
|
||||
echo "🧹 Cleaning up local files..."
|
||||
# Clean up
|
||||
rm -rf ./$CLI_PROJ/publish
|
||||
rm -f ./$CLI_APP
|
||||
ssh-agent -k
|
||||
echo "✅ Local cleanup completed"
|
||||
|
||||
echo ""
|
||||
echo "🎉 CLI deployment complete!"
|
||||
echo "📝 Note: Run 'source ~/.bashrc' or start a new shell session to activate the deepdrft command in PATH"
|
||||
Reference in New Issue
Block a user