using System.Diagnostics; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using TaskbarLauncher.Models; namespace TaskbarLauncher.Services; public static class ConfigService { private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, PropertyNameCaseInsensitive = true }; static ConfigService() { JsonOptions.Converters.Add(new JsonStringEnumConverter()); } public static string AppFolder => AppContext.BaseDirectory; public static string ConfigPath => Path.Combine(AppFolder, "config.json"); public static string BackupFolder => Path.Combine(AppFolder, "backups"); public static LauncherConfig Load() { if (!File.Exists(ConfigPath)) { LauncherConfig defaults = CreateDefaultConfig(); Save(defaults); return defaults; } string json = File.ReadAllText(ConfigPath); LauncherConfig config = JsonSerializer.Deserialize(json, JsonOptions) ?? CreateDefaultConfig(); NormalizeConfig(config); Save(config); return config; } public static void Save(LauncherConfig config) { string json = JsonSerializer.Serialize(config, JsonOptions); File.WriteAllText(ConfigPath, json); } public static string Backup() { Directory.CreateDirectory(BackupFolder); if (!File.Exists(ConfigPath)) { Save(CreateDefaultConfig()); } string timestamp = DateTime.Now.ToString("yyyyMMdd-HHmmss"); string backupPath = Path.Combine(BackupFolder, $"config-{timestamp}.json"); File.Copy(ConfigPath, backupPath, overwrite: false); return backupPath; } public static string ExportTo(string destinationPath, LauncherConfig config) { if (string.IsNullOrWhiteSpace(destinationPath)) { throw new ArgumentException("Choose a destination path for the exported config.", nameof(destinationPath)); } string? destinationFolder = Path.GetDirectoryName(destinationPath); if (!string.IsNullOrWhiteSpace(destinationFolder)) { Directory.CreateDirectory(destinationFolder); } NormalizeConfig(config); string json = JsonSerializer.Serialize(config, JsonOptions); File.WriteAllText(destinationPath, json); return destinationPath; } public static LauncherConfig ImportFrom(string sourcePath) { LauncherConfig config = ReadConfigFile(sourcePath); Save(config); return config; } public static LauncherConfig ResetToDefaults() { LauncherConfig config = CreateDefaultConfig(); Save(config); return config; } public static LauncherConfig Restore(string sourcePath) { LauncherConfig config = ReadConfigFile(sourcePath); Save(config); return config; } public static void OpenConfigFolder() { Directory.CreateDirectory(AppFolder); Process.Start(new ProcessStartInfo { FileName = AppFolder, UseShellExecute = true }); } private static LauncherConfig CreateDefaultConfig() { LauncherConfig config = new() { Items = [ new LauncherItem { Title = "Folders", Type = LauncherItemType.Menu, Children = [ new LauncherItem { Title = "Documents", Type = LauncherItemType.Folder, Target = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) }, new LauncherItem { Title = "Downloads", Type = LauncherItemType.Folder, Target = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads") } ] }, new LauncherItem { Title = "Apps", Type = LauncherItemType.Menu, Children = [ new LauncherItem { Title = "Notepad", Type = LauncherItemType.App, Target = "notepad.exe", IconPath = "notepad.exe" } ] } ] }; NormalizeConfig(config); return config; } private static LauncherConfig ReadConfigFile(string sourcePath) { if (!File.Exists(sourcePath)) { throw new FileNotFoundException("The selected config file was not found.", sourcePath); } string json = File.ReadAllText(sourcePath); LauncherConfig config; try { config = JsonSerializer.Deserialize(json, JsonOptions) ?? throw new InvalidDataException("The selected config file could not be read."); } catch (JsonException ex) { throw new InvalidDataException("The selected file is not a valid Taskbar Launcher config.", ex); } ValidateConfig(config); NormalizeConfig(config); return config; } private static void ValidateConfig(LauncherConfig config) { if (config.Items is null) { throw new InvalidDataException("The selected config is missing launcher items."); } if (config.RecentItems is null) { config.RecentItems = []; } foreach (LauncherItem item in config.Items) { ValidateItem(item); } foreach (LauncherItem item in config.RecentItems) { ValidateItem(item); } } private static void ValidateItem(LauncherItem item) { if (item.Children is null) { throw new InvalidDataException($"The item '{item.Title}' has an invalid children list."); } if (!Enum.IsDefined(item.Type)) { throw new InvalidDataException($"The item '{item.Title}' has an invalid type."); } foreach (LauncherItem child in item.Children) { ValidateItem(child); } } private static void NormalizeConfig(LauncherConfig config) { if (!Enum.IsDefined(config.DisplayMode)) { config.DisplayMode = LauncherDisplayMode.CompactList; } if (!Enum.IsDefined(config.ThemeScheme)) { config.ThemeScheme = LauncherThemeScheme.MarkHaven; } config.RecentItems ??= []; config.RecentItems.RemoveAll(item => item.Type == LauncherItemType.Menu || string.IsNullOrWhiteSpace(item.Target)); foreach (LauncherItem item in config.RecentItems) { item.Children = []; NormalizeDisplayModes(item); } foreach (LauncherItem item in config.Items) { NormalizeDisplayModes(item); } } private static void NormalizeDisplayModes(LauncherItem item) { if (!Enum.IsDefined(item.DisplayMode)) { item.DisplayMode = LauncherDisplayMode.CompactList; } foreach (LauncherItem child in item.Children) { NormalizeDisplayModes(child); } } }