using System.Runtime.InteropServices; using Catalog.Communication.DependencyInjection; using ImageCatalog; using ImageCatalog_2.Services; using MaddoShared; using Microsoft.Extensions.DependencyInjection; using AutoMapper; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Console; using System.IO; using Microsoft.Extensions.Options; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; namespace ImageCatalog_2; static class Program { #if WINDOWS [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AllocConsole(); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int nStdHandle); private const int STD_OUTPUT_HANDLE = -11; private const int STD_ERROR_HANDLE = -12; [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetStdHandle(int nStdHandle, IntPtr handle); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetConsoleWindow(); [DllImport("kernel32.dll", SetLastError = true)] static extern bool AttachConsole(int dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); private const uint GENERIC_WRITE = 0x40000000; private const uint OPEN_EXISTING = 3; private static void RedirectConsoleOutput() { var stdOutHandle = CreateFile("CONOUT$", GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); var safeFileHandle = new Microsoft.Win32.SafeHandles.SafeFileHandle(stdOutHandle, true); var fileStream = new FileStream(safeFileHandle, FileAccess.Write); var standardOutput = new StreamWriter(fileStream) { AutoFlush = true }; Console.SetOut(standardOutput); Console.SetError(standardOutput); } #endif public static IServiceProvider ServiceProvider { get; private set; } = default!; public static Avalonia.AppBuilder BuildAvaloniaApp() => Avalonia.AppBuilder.Configure() .UsePlatformDetect() .LogToTrace(); [STAThread] static void Main(string[] args) { #if WINDOWS System.Windows.Forms.Application.SetHighDpiMode(System.Windows.Forms.HighDpiMode.SystemAware); System.Windows.Forms.Application.EnableVisualStyles(); System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false); AllocConsole(); RedirectConsoleOutput(); #endif var serviceCollection = new ServiceCollection(); ConfigureServices(serviceCollection); ServiceProvider = serviceCollection.BuildServiceProvider(); var serviceProvider = ServiceProvider; // Determine UI based on command line. Default: WinForms. Use --wpf for WPF, --avalonia for Avalonia. bool useWpf = args is not null && Array.Exists(args, a => string.Equals(a, "--wpf", StringComparison.OrdinalIgnoreCase)); bool useAvalonia = args is not null && Array.Exists(args, a => string.Equals(a, "--avalonia", StringComparison.OrdinalIgnoreCase)); if (useAvalonia) { BuildAvaloniaApp().StartWithClassicDesktopLifetime(args ?? Array.Empty()); return; } #if WINDOWS if (useWpf) { var wpfApp = new System.Windows.Application(); try { wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml") }); wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml") }); wpfApp.Resources.MergedDictionaries.Add(new System.Windows.ResourceDictionary { Source = new Uri("pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml") }); try { ControlzEx.Theming.ThemeManager.Current.ChangeTheme(wpfApp, "Light.Blue"); } catch { // ignore if ThemeManager API isn't present } } catch { // If resources fail to load, continue silently } var wpfMain = serviceProvider.GetService(typeof(ImageCatalog_2.MainWindow)) as ImageCatalog_2.MainWindow; if (wpfMain is not null) { wpfApp.Run(wpfMain); return; } // If WPF was requested but not available, fall through to WinForms. } // Default / fallback to WinForms UI var mainForm = serviceProvider.GetRequiredService(); System.Windows.Forms.Application.Run(mainForm); #else // On non-Windows, Avalonia is the only available UI BuildAvaloniaApp().StartWithClassicDesktopLifetime(args ?? Array.Empty()); #endif } private static void ConfigureServices(ServiceCollection services) { services.AddAutoMapper(cfg => { }, typeof(Program)); services.AddTransient(); services.AddTransient(); services.AddTransient(sp => { var testService = sp.GetRequiredService(); var settingsService = sp.GetRequiredService(); var imageCreation = sp.GetRequiredService(); var aiExtractionService = sp.GetRequiredService(); var imageProcessingCoordinator = sp.GetRequiredService(); var picSettings = sp.GetRequiredService(); var mapper = sp.GetRequiredService(); var logger = sp.GetRequiredService>(); var versionProvider = sp.GetService(); return new DataModel(testService, settingsService, imageCreation, aiExtractionService, imageProcessingCoordinator, picSettings, mapper, logger, versionProvider); }); services.AddTransient(); services.AddTransient(); services.AddTransient(); #if WINDOWS services.AddTransient(); #endif services.AddTransient(); services.AddTransient(); services.AddTransient(sp => sp.GetRequiredService()); var userPrefsPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ImageCatalog", "userprefs.xml"); services.AddSingleton(new ParametriSetup(userPrefsPath)); services.AddSingleton(); services.AddCatalogCommunication(options => { options.BaseUri = new Uri("https://www.regalamiunsorriso.it/"); options.AdminPageBasePath = "admin/pg_RUS"; options.ReceiveFilePath = "ReceiveFile.abl"; options.RequestTimeout = TimeSpan.FromSeconds(30); options.RetryCount = 2; options.RetryBaseDelay = TimeSpan.FromMilliseconds(250); }); services.AddTransient(); #if WINDOWS services.AddTransient(); services.AddTransient(); #endif services.AddSingleton(); services.AddLogging(configure => { configure.AddCustomFormatter(); configure.AddConsole(); configure.SetMinimumLevel(LogLevel.Debug); }); } } public static class ConsoleLoggerExtensions { public static ILoggingBuilder AddCustomFormatter( this ILoggingBuilder builder) => builder .AddConsole(options => options.FormatterName = nameof(CustomLoggingFormatter)) .AddConsoleFormatter() .AddFilter("LuckyPennySoftware.AutoMapper.License", LogLevel.None); } public sealed class CustomLoggingFormatter : ConsoleFormatter, IDisposable { private readonly IDisposable? _optionsReloadToken; private ConsoleFormatterOptions _formatterOptions; public CustomLoggingFormatter(IOptionsMonitor options) // Case insensitive : base(nameof(CustomLoggingFormatter)) => (_optionsReloadToken, _formatterOptions) = (options.OnChange(ReloadLoggerOptions), options.CurrentValue); private void ReloadLoggerOptions(ConsoleFormatterOptions options) => _formatterOptions = options; public override void Write( in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter? textWriter) { if (textWriter is null) { return; } string? message = logEntry.Formatter?.Invoke( logEntry.State, logEntry.Exception); if (message is null) { return; } var timestamp = DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); var level = logEntry.LogLevel.ToString().ToUpperInvariant(); var category = logEntry.Category ?? "App"; var line = $"{timestamp} [{level}] {category}: {message}"; textWriter.WriteLine(line); System.Diagnostics.Debug.WriteLine(line); if (logEntry.Exception is not null) { var exceptionText = logEntry.Exception.ToString(); textWriter.WriteLine(exceptionText); System.Diagnostics.Debug.WriteLine(exceptionText); } } public void Dispose() => _optionsReloadToken?.Dispose(); }