mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Implement startup state management and enhance configuration loading
- Introduced a new `StartupState` class to encapsulate the state collected before the console backend initialization. - Updated the configuration loading process to handle legacy upgrades and provide detailed feedback on configuration status. - Enhanced the TUI backend to utilize the new startup state for improved initialization flow. - Refactored the `LoadFromFile` method in `Settings` to return a structured result, improving error handling and clarity.
This commit is contained in:
parent
b33ee7e4a1
commit
fe7ab9f373
3 changed files with 219 additions and 129 deletions
|
|
@ -58,6 +58,17 @@ namespace MinecraftClient
|
|||
// Setting this string to an empty string will disable Sentry
|
||||
private const string SentryDSN = "";
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot of all state collected before the console backend is initialized.
|
||||
/// Passed to <see cref="ProcessStartupState"/> once the backend is ready.
|
||||
/// </summary>
|
||||
internal sealed class StartupState
|
||||
{
|
||||
public Settings.ConfigLoadResult ConfigResult { get; init; }
|
||||
public bool NewlyGenerated { get; init; }
|
||||
public bool SentryEnabled { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point of Minecraft Console Client
|
||||
/// </summary>
|
||||
|
|
@ -103,7 +114,6 @@ namespace MinecraftClient
|
|||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
});
|
||||
|
||||
//Setup ConsoleIO
|
||||
ConsoleIO.LogPrefix = "§8[MCC] ";
|
||||
if (args.Length >= 1 && args[^1] == "BasicIO" || args.Length >= 1 && args[^1] == "BasicIO-NoColor")
|
||||
{
|
||||
|
|
@ -115,133 +125,178 @@ namespace MinecraftClient
|
|||
args = args.Where(o => !Object.ReferenceEquals(o, args[^1])).ToArray();
|
||||
}
|
||||
|
||||
//Debug input ?
|
||||
if (args.Length == 1 && args[0] == "--keyboard-debug")
|
||||
{
|
||||
if (!ConsoleIO.BasicIO)
|
||||
{
|
||||
ConsoleIO.Backend = new ClassicConsoleBackend();
|
||||
ConsoleIO.Backend.Init();
|
||||
}
|
||||
ConsoleIO.WriteLine("Keyboard debug mode: Press any key to display info");
|
||||
ConsoleIO.DebugReadInput();
|
||||
}
|
||||
|
||||
// --- Load config as early as possible (no printing yet) ---
|
||||
Settings.ConfigLoadResult configResult;
|
||||
bool newlyGenerated = false;
|
||||
|
||||
if (args.Length >= 1 && File.Exists(args[0]) && Settings.ToLowerIfNeed(Path.GetExtension(args[0])) == ".ini")
|
||||
{
|
||||
configResult = Settings.LoadFromFile(args[0]);
|
||||
settingsIniPath = args[0];
|
||||
|
||||
List<string> args_tmp = args.ToList<string>();
|
||||
args_tmp.RemoveAt(0);
|
||||
args = args_tmp.ToArray();
|
||||
}
|
||||
else if (File.Exists("MinecraftClient.ini"))
|
||||
{
|
||||
configResult = Settings.LoadFromFile("MinecraftClient.ini");
|
||||
}
|
||||
else
|
||||
{
|
||||
configResult = new Settings.ConfigLoadResult { Success = true, NeedWriteDefault = true };
|
||||
newlyGenerated = true;
|
||||
}
|
||||
|
||||
if (configResult.NeedWriteDefault)
|
||||
{
|
||||
Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
|
||||
WriteBackSettings(false);
|
||||
}
|
||||
else if (configResult.Success)
|
||||
{
|
||||
WriteBackSettings(true);
|
||||
}
|
||||
|
||||
if (!Config.Main.Advanced.EnableSentry)
|
||||
_sentrySdk?.Dispose();
|
||||
|
||||
var startupState = new StartupState
|
||||
{
|
||||
ConfigResult = configResult,
|
||||
NewlyGenerated = newlyGenerated,
|
||||
SentryEnabled = SentryDSN != string.Empty,
|
||||
};
|
||||
|
||||
// --- Determine console mode and initialize backend ---
|
||||
if (!ConsoleIO.BasicIO && Config.Console.General.ConsoleMode == ConsoleModeType.tui)
|
||||
{
|
||||
ConsoleIO.Backend?.Shutdown();
|
||||
var tuiBackend = new Tui.TuiConsoleBackend();
|
||||
ConsoleIO.Backend = tuiBackend;
|
||||
tuiBackend.RunTuiMainLoop(args, startupState);
|
||||
return;
|
||||
}
|
||||
|
||||
// Classic mode: init backend, then print and process startup state.
|
||||
if (!ConsoleIO.BasicIO)
|
||||
{
|
||||
ConsoleIO.Backend = new ClassicConsoleBackend();
|
||||
ConsoleIO.Backend.Init();
|
||||
}
|
||||
|
||||
ConsoleIO.WriteLine($"Minecraft Console Client v{Version} - for MC {MCLowestVersion} to {MCHighestVersion} - Github.com/MCCTeam");
|
||||
|
||||
//Build information to facilitate processing of bug reports
|
||||
if (BuildInfo is not null)
|
||||
ConsoleIO.WriteLineFormatted("§8" + BuildInfo);
|
||||
|
||||
//Debug input ?
|
||||
if (args.Length == 1 && args[0] == "--keyboard-debug")
|
||||
{
|
||||
ConsoleIO.WriteLine("Keyboard debug mode: Press any key to display info");
|
||||
ConsoleIO.DebugReadInput();
|
||||
}
|
||||
|
||||
//Process ini configuration file
|
||||
{
|
||||
bool loadSucceed, needWriteDefaultSetting, newlyGenerated = false;
|
||||
if (args.Length >= 1 && File.Exists(args[0]) && Settings.ToLowerIfNeed(Path.GetExtension(args[0])) == ".ini")
|
||||
{
|
||||
(loadSucceed, needWriteDefaultSetting) = Settings.LoadFromFile(args[0]);
|
||||
settingsIniPath = args[0];
|
||||
|
||||
//remove ini configuration file from arguments array
|
||||
List<string> args_tmp = args.ToList<string>();
|
||||
args_tmp.RemoveAt(0);
|
||||
args = args_tmp.ToArray();
|
||||
}
|
||||
else if (File.Exists("MinecraftClient.ini"))
|
||||
{
|
||||
(loadSucceed, needWriteDefaultSetting) = Settings.LoadFromFile("MinecraftClient.ini");
|
||||
}
|
||||
else
|
||||
{
|
||||
loadSucceed = true;
|
||||
needWriteDefaultSetting = true;
|
||||
newlyGenerated = true;
|
||||
}
|
||||
|
||||
if (needWriteDefaultSetting)
|
||||
{
|
||||
Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
|
||||
WriteBackSettings(false);
|
||||
if (newlyGenerated)
|
||||
ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_settings_generated);
|
||||
ConsoleIO.WriteLine(Translations.mcc_run_with_default_settings);
|
||||
|
||||
// Only show the Sentry message if the DSN is not empty
|
||||
// as Sentry will not be initialized if the DSN is empty
|
||||
if (SentryDSN != string.Empty)
|
||||
{
|
||||
ConsoleIO.WriteLine(Translations.mcc_sentry_logging);
|
||||
}
|
||||
}
|
||||
else if (!loadSucceed)
|
||||
{
|
||||
ConsoleIO.Backend?.StopReadThread();
|
||||
string command = " ";
|
||||
while (command.Length > 0)
|
||||
{
|
||||
ConsoleIO.WriteLine(string.Empty);
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_invaild_config, Config.Main.Advanced.InternalCmdChar.ToLogString()));
|
||||
if (ConsoleIO.Backend is Tui.TuiConsoleBackend)
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_use_quit_to_exit, Config.Main.Advanced.InternalCmdChar.ToLogString()));
|
||||
else
|
||||
ConsoleIO.WriteLineFormatted(Translations.mcc_press_exit, acceptnewlines: true);
|
||||
command = ConsoleIO.ReadLine().Trim();
|
||||
if (command.Length > 0)
|
||||
{
|
||||
if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
|
||||
&& command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
|
||||
command = command[1..];
|
||||
|
||||
if (command.StartsWith("exit") || command.StartsWith("quit"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (command.StartsWith("new"))
|
||||
{
|
||||
Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
|
||||
WriteBackSettings(true);
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_gen_new_config, settingsIniPath));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Load external translation file. Should be called AFTER settings loaded
|
||||
if (!Config.Main.Advanced.Language.StartsWith("en"))
|
||||
ConsoleIO.WriteLine(string.Format(Translations.mcc_help_us_translate, Settings.TranslationProjectUrl));
|
||||
WriteBackSettings(true); // format
|
||||
}
|
||||
|
||||
if (!Config.Main.Advanced.EnableSentry)
|
||||
_sentrySdk?.Dispose();
|
||||
}
|
||||
|
||||
// Switch to TUI mode if configured (must happen after config load)
|
||||
if (!ConsoleIO.BasicIO && Config.Console.General.ConsoleMode == ConsoleModeType.tui)
|
||||
{
|
||||
ConsoleIO.Backend?.Shutdown();
|
||||
var tuiBackend = new Tui.TuiConsoleBackend();
|
||||
ConsoleIO.Backend = tuiBackend;
|
||||
tuiBackend.RunTuiMainLoop(args);
|
||||
if (!ProcessStartupState(startupState))
|
||||
return;
|
||||
}
|
||||
|
||||
ContinueAfterTuiInit(args);
|
||||
RunStartupSequence(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continues MCC startup after console mode has been determined.
|
||||
/// Called directly from Main for classic/basic mode, or from a background
|
||||
/// thread for TUI mode (after the Avalonia UI loop has started).
|
||||
/// Prints the application banner and processes the startup state collected before
|
||||
/// the console backend was ready. Called once from classic mode or from TUI after
|
||||
/// the view is initialized.
|
||||
/// </summary>
|
||||
internal static void ContinueAfterTuiInit(string[] args)
|
||||
/// <returns>True if startup can continue; false if config load failed and user chose to exit.</returns>
|
||||
internal static bool ProcessStartupState(StartupState state)
|
||||
{
|
||||
ConsoleIO.WriteLine($"Minecraft Console Client v{Version} - for MC {MCLowestVersion} to {MCHighestVersion} - Github.com/MCCTeam");
|
||||
if (BuildInfo is not null)
|
||||
ConsoleIO.WriteLineFormatted("§8" + BuildInfo);
|
||||
|
||||
var cfg = state.ConfigResult;
|
||||
|
||||
if (cfg.NeedWriteDefault)
|
||||
{
|
||||
if (cfg.IsLegacyUpgrade)
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_use_new_config);
|
||||
ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.mcc_backup_old_config, cfg.LegacyBackupPath));
|
||||
}
|
||||
|
||||
if (state.NewlyGenerated)
|
||||
ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_settings_generated);
|
||||
|
||||
ConsoleIO.WriteLine(Translations.mcc_run_with_default_settings);
|
||||
|
||||
if (state.SentryEnabled)
|
||||
ConsoleIO.WriteLine(Translations.mcc_sentry_logging);
|
||||
}
|
||||
else if (!cfg.Success)
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§c" + Translations.config_load_fail);
|
||||
if (cfg.ErrorMessage is not null)
|
||||
ConsoleIO.WriteLine(cfg.ErrorMessage);
|
||||
HandleConfigLoadFailure();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Config.Main.Advanced.Language.StartsWith("en"))
|
||||
ConsoleIO.WriteLine(string.Format(Translations.mcc_help_us_translate, Settings.TranslationProjectUrl));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a failed config load by prompting the user to fix or regenerate the config file.
|
||||
/// </summary>
|
||||
internal static void HandleConfigLoadFailure()
|
||||
{
|
||||
ConsoleIO.Backend?.StopReadThread();
|
||||
string command = " ";
|
||||
while (command.Length > 0)
|
||||
{
|
||||
ConsoleIO.WriteLine(string.Empty);
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_invaild_config, Config.Main.Advanced.InternalCmdChar.ToLogString()));
|
||||
if (ConsoleIO.Backend is Tui.TuiConsoleBackend)
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_use_quit_to_exit, Config.Main.Advanced.InternalCmdChar.ToLogString()));
|
||||
else
|
||||
ConsoleIO.WriteLineFormatted(Translations.mcc_press_exit, acceptnewlines: true);
|
||||
command = ConsoleIO.ReadLine().Trim();
|
||||
if (command.Length > 0)
|
||||
{
|
||||
if (Config.Main.Advanced.InternalCmdChar.ToChar() != ' '
|
||||
&& command[0] == Config.Main.Advanced.InternalCmdChar.ToChar())
|
||||
command = command[1..];
|
||||
|
||||
if (command.StartsWith("exit") || command.StartsWith("quit"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (command.StartsWith("new"))
|
||||
{
|
||||
Config.Main.Advanced.Language = Settings.GetDefaultGameLanguage();
|
||||
WriteBackSettings(true);
|
||||
ConsoleIO.WriteLineFormatted(string.Format(Translations.mcc_gen_new_config, settingsIniPath));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the main startup sequence: CLI argument processing, auth, and connection.
|
||||
/// Called from Main() for classic/basic mode, or from TuiConsoleBackend on a
|
||||
/// background thread after the Avalonia UI loop has started.
|
||||
/// </summary>
|
||||
internal static void RunStartupSequence(string[] args)
|
||||
{
|
||||
//Other command-line arguments
|
||||
if (args.Length >= 1)
|
||||
|
|
@ -732,7 +787,8 @@ namespace MinecraftClient
|
|||
/// </summary>
|
||||
public static void ReloadSettings(bool keepAccountAndServerSettings = false)
|
||||
{
|
||||
if (Settings.LoadFromFile(settingsIniPath, keepAccountAndServerSettings).Item1)
|
||||
var result = Settings.LoadFromFile(settingsIniPath, keepAccountAndServerSettings);
|
||||
if (result.Success)
|
||||
ConsoleIO.WriteLine(string.Format(Translations.config_load, settingsIniPath));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,22 @@ namespace MinecraftClient
|
|||
|
||||
}
|
||||
|
||||
public static Tuple<bool, bool> LoadFromFile(string filepath, bool keepAccountAndServerSettings = false)
|
||||
/// <summary>
|
||||
/// Structured result returned by <see cref="LoadFromFile(string, bool)"/>.
|
||||
/// </summary>
|
||||
public readonly struct ConfigLoadResult
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
public bool NeedWriteDefault { get; init; }
|
||||
/// <summary>True when a pre-TOML legacy config was detected, backed up, and a fresh default is needed.</summary>
|
||||
public bool IsLegacyUpgrade { get; init; }
|
||||
/// <summary>Non-null when the load failed due to a parse/IO error (not a legacy upgrade).</summary>
|
||||
public string? ErrorMessage { get; init; }
|
||||
/// <summary>Path where the old config was backed up (legacy upgrade case).</summary>
|
||||
public string? LegacyBackupPath { get; init; }
|
||||
}
|
||||
|
||||
public static ConfigLoadResult LoadFromFile(string filepath, bool keepAccountAndServerSettings = false)
|
||||
{
|
||||
bool keepAccountSettings = InternalConfig.KeepAccountSettings;
|
||||
bool keepServerSettings = InternalConfig.KeepServerSettings;
|
||||
|
|
@ -157,21 +172,27 @@ namespace MinecraftClient
|
|||
Thread.CurrentThread.CurrentCulture = Program.ActualCulture;
|
||||
try
|
||||
{
|
||||
// The old configuration file has been backed up as A.
|
||||
string configString = File.ReadAllText(filepath);
|
||||
if (configString.Contains("Some settings missing here after an upgrade?"))
|
||||
{
|
||||
string newFilePath = Path.ChangeExtension(filepath, ".old.ini");
|
||||
File.Copy(filepath, newFilePath, true);
|
||||
ConsoleIO.WriteLineFormatted("§c" + Translations.mcc_use_new_config);
|
||||
ConsoleIO.WriteLineFormatted("§c" + string.Format(Translations.mcc_backup_old_config, newFilePath));
|
||||
return new(false, true);
|
||||
return new ConfigLoadResult
|
||||
{
|
||||
Success = false,
|
||||
NeedWriteDefault = true,
|
||||
IsLegacyUpgrade = true,
|
||||
LegacyBackupPath = newFilePath
|
||||
};
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
ConsoleIO.WriteLineFormatted("§c" + Translations.config_load_fail);
|
||||
ConsoleIO.WriteLine(ex.GetFullMessage());
|
||||
return new(false, false);
|
||||
return new ConfigLoadResult
|
||||
{
|
||||
Success = false,
|
||||
NeedWriteDefault = false,
|
||||
ErrorMessage = ex.GetFullMessage()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -180,7 +201,7 @@ namespace MinecraftClient
|
|||
if (!keepServerSettings)
|
||||
InternalConfig.KeepServerSettings = false;
|
||||
}
|
||||
return new(true, false);
|
||||
return new ConfigLoadResult { Success = true, NeedWriteDefault = false };
|
||||
}
|
||||
|
||||
public static void WriteToFile(string filepath, bool backupOldFile)
|
||||
|
|
|
|||
|
|
@ -25,14 +25,18 @@ namespace MinecraftClient.Tui
|
|||
|
||||
internal static TuiConsoleBackend? Instance { get; private set; }
|
||||
|
||||
private Program.StartupState? _pendingStartupState;
|
||||
private readonly ManualResetEventSlim _viewReady = new(false);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the Avalonia app and starts the main UI loop.
|
||||
/// This blocks the calling thread until the TUI exits.
|
||||
/// Before blocking, it starts MCC's remaining initialization on a background thread.
|
||||
/// </summary>
|
||||
public void RunTuiMainLoop(string[] args)
|
||||
internal void RunTuiMainLoop(string[] args, Program.StartupState startupState)
|
||||
{
|
||||
Instance = this;
|
||||
_pendingStartupState = startupState;
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => RestoreTerminalState();
|
||||
|
||||
|
|
@ -46,7 +50,7 @@ namespace MinecraftClient.Tui
|
|||
|
||||
new Thread(() =>
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
_viewReady.Wait();
|
||||
ContinueMccStartup(args);
|
||||
})
|
||||
{ Name = "MCC-Main", IsBackground = true }.Start();
|
||||
|
|
@ -113,7 +117,15 @@ namespace MinecraftClient.Tui
|
|||
{
|
||||
try
|
||||
{
|
||||
Program.ContinueAfterTuiInit(args);
|
||||
var instance = Instance;
|
||||
if (instance?._pendingStartupState is { } state)
|
||||
{
|
||||
instance._pendingStartupState = null;
|
||||
if (!Program.ProcessStartupState(state))
|
||||
return;
|
||||
}
|
||||
|
||||
Program.RunStartupSequence(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -124,6 +136,7 @@ namespace MinecraftClient.Tui
|
|||
internal void SetView(MainTuiView view)
|
||||
{
|
||||
_view = view;
|
||||
_viewReady.Set();
|
||||
}
|
||||
|
||||
internal MainTuiView? GetView() => _view;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue