using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using Avalonia.Collections; using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { public class Preference : ObservableObject { [JsonIgnore] public static Preference Instance { get { if (_instance == null) { if (!File.Exists(_savePath)) { _instance = new Preference(); } else { try { _instance = JsonSerializer.Deserialize(File.ReadAllText(_savePath), JsonSerializationCodeGen.Default.Preference); } catch { _instance = new Preference(); } } } _instance.Repositories.RemoveAll(x => !Directory.Exists(x.FullPath)); if (!_instance.IsGitConfigured) { _instance.GitInstallPath = Native.OS.FindGitExecutable(); } return _instance; } } public string Locale { get => _locale; set { if (SetProperty(ref _locale, value)) { App.SetLocale(value); } } } public string Theme { get => _theme; set { if (SetProperty(ref _theme, value)) { App.SetTheme(value); } } } public string AvatarServer { get => Models.AvatarManager.SelectedServer; set { if (Models.AvatarManager.SelectedServer != value) { Models.AvatarManager.SelectedServer = value; OnPropertyChanged(nameof(AvatarServer)); } } } public int MaxHistoryCommits { get => _maxHistoryCommits; set => SetProperty(ref _maxHistoryCommits, value); } public bool RestoreTabs { get => _restoreTabs; set => SetProperty(ref _restoreTabs, value); } public bool UseFixedTabWidth { get => _useFixedTabWidth; set => SetProperty(ref _useFixedTabWidth, value); } public bool UseTwoColumnsLayoutInHistories { get => _useTwoColumnsLayoutInHistories; set => SetProperty(ref _useTwoColumnsLayoutInHistories, value); } public bool UseSideBySideDiff { get => _useSideBySideDiff; set => SetProperty(ref _useSideBySideDiff, value); } public Models.ChangeViewMode UnstagedChangeViewMode { get => _unstagedChangeViewMode; set => SetProperty(ref _unstagedChangeViewMode, value); } public Models.ChangeViewMode StagedChangeViewMode { get => _stagedChangeViewMode; set => SetProperty(ref _stagedChangeViewMode, value); } public Models.ChangeViewMode CommitChangeViewMode { get => _commitChangeViewMode; set => SetProperty(ref _commitChangeViewMode, value); } [JsonIgnore] public bool IsGitConfigured { get => !string.IsNullOrEmpty(GitInstallPath) && File.Exists(GitInstallPath); } public string GitInstallPath { get => Native.OS.GitInstallPath; set { if (Native.OS.GitInstallPath != value) { Native.OS.GitInstallPath = value; OnPropertyChanged(nameof(GitInstallPath)); } } } public string GitDefaultCloneDir { get => _gitDefaultCloneDir; set => SetProperty(ref _gitDefaultCloneDir, value); } public bool GitAutoFetch { get => Commands.AutoFetch.IsEnabled; set { if (Commands.AutoFetch.IsEnabled != value) { Commands.AutoFetch.IsEnabled = value; OnPropertyChanged(nameof(GitAutoFetch)); } } } public int ExternalMergeToolType { get => _externalMergeToolType; set { var changed = SetProperty(ref _externalMergeToolType, value); if (changed && !OperatingSystem.IsWindows() && value > 0 && value < Models.ExternalMergeTools.Supported.Count) { var tool = Models.ExternalMergeTools.Supported[value]; if (File.Exists(tool.Exec)) ExternalMergeToolPath = tool.Exec; else ExternalMergeToolPath = string.Empty; } } } public string ExternalMergeToolPath { get => _externalMergeToolPath; set => SetProperty(ref _externalMergeToolPath, value); } public string ExternalMergeToolCmd { get => _externalMergeToolCmd; set => SetProperty(ref _externalMergeToolCmd, value); } public string ExternalMergeToolDiffCmd { get => _externalMergeToolDiffCmd; set => SetProperty(ref _externalMergeToolDiffCmd, value); } public List Repositories { get; set; } = new List(); public AvaloniaList RepositoryNodes { get => _repositoryNodes; set => SetProperty(ref _repositoryNodes, value); } public List OpenedTabs { get; set; } = new List(); public int LastActiveTabIdx { get; set; } = 0; public static void AddNode(RepositoryNode node, RepositoryNode to = null) { var collection = to == null ? _instance._repositoryNodes : to.SubNodes; var list = new List(); list.AddRange(collection); list.Add(node); list.Sort((l, r) => { if (l.IsRepository != r.IsRepository) { return l.IsRepository ? 1 : -1; } else { return l.Name.CompareTo(r.Name); } }); collection.Clear(); foreach (var one in list) { collection.Add(one); } } public static RepositoryNode FindNode(string id) { return FindNodeRecursive(id, _instance.RepositoryNodes); } public static void MoveNode(RepositoryNode node, RepositoryNode to = null) { if (to == null && _instance._repositoryNodes.Contains(node)) return; if (to != null && to.SubNodes.Contains(node)) return; RemoveNode(node); AddNode(node, to); } public static void RemoveNode(RepositoryNode node) { RemoveNodeRecursive(node, _instance._repositoryNodes); } public static Repository FindRepository(string path) { foreach (var repo in _instance.Repositories) { if (repo.FullPath == path) return repo; } return null; } public static Repository AddRepository(string rootDir, string gitDir) { var normalized = rootDir.Replace('\\', '/'); var repo = FindRepository(normalized); if (repo != null) { repo.GitDir = gitDir; return repo; } repo = new Repository() { FullPath = normalized, GitDir = gitDir }; _instance.Repositories.Add(repo); return repo; } public static void Save() { var dir = Path.GetDirectoryName(_savePath); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); var data = JsonSerializer.Serialize(_instance, JsonSerializationCodeGen.Default.Preference); File.WriteAllText(_savePath, data); } private static RepositoryNode FindNodeRecursive(string id, AvaloniaList collection) { foreach (var node in collection) { if (node.Id == id) return node; var sub = FindNodeRecursive(id, node.SubNodes); if (sub != null) return sub; } return null; } private static bool RemoveNodeRecursive(RepositoryNode node, AvaloniaList collection) { if (collection.Contains(node)) { collection.Remove(node); return true; } foreach (RepositoryNode one in collection) { if (RemoveNodeRecursive(node, one.SubNodes)) return true; } return false; } private static Preference _instance = null; private static readonly string _savePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SourceGit", "preference.json"); private string _locale = "en_US"; private string _theme = "Default"; private int _maxHistoryCommits = 20000; private bool _restoreTabs = false; private bool _useFixedTabWidth = true; private bool _useTwoColumnsLayoutInHistories = false; private bool _useSideBySideDiff = false; private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _commitChangeViewMode = Models.ChangeViewMode.List; private string _gitDefaultCloneDir = string.Empty; private int _externalMergeToolType = 0; private string _externalMergeToolPath = string.Empty; private string _externalMergeToolCmd = string.Empty; private string _externalMergeToolDiffCmd = string.Empty; private AvaloniaList _repositoryNodes = new AvaloniaList(); } [JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)] [JsonSerializable(typeof(Preference))] internal partial class JsonSerializationCodeGen : JsonSerializerContext { } }