using System; using System.Collections.Generic; using System.Drawing; using System.IO; using TweetDuck.Core.Controls; using TweetDuck.Core.Notification; using TweetDuck.Core.Other; using TweetDuck.Core.Utils; using TweetDuck.Data; using TweetDuck.Data.Serialization; namespace TweetDuck.Configuration{ sealed class UserConfig{ private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>(); static UserConfig(){ Serializer.RegisterTypeConverter(typeof(WindowState), WindowState.Converter); Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{ ConvertToString = value => $"{value.X} {value.Y}", ConvertToObject = value => { int[] elements = StringUtils.ParseInts(value, ' '); return new Point(elements[0], elements[1]); } }); Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{ ConvertToString = value => $"{value.Width} {value.Height}", ConvertToObject = value => { int[] elements = StringUtils.ParseInts(value, ' '); return new Size(elements[0], elements[1]); } }); } // CONFIGURATION DATA public bool FirstRun { get; set; } = true; public bool AllowDataCollection { get; set; } = false; public WindowState BrowserWindow { get; set; } = new WindowState(); public Size PluginsWindowSize { get; set; } = Size.Empty; public bool ExpandLinksOnHover { get; set; } = true; public bool OpenSearchInFirstColumn { get; set; } = true; public bool KeepLikeFollowDialogsOpen { get; set; } = true; public bool BestImageQuality { get; set; } = true; public bool EnableAnimatedImages { get; set; } = true; public bool IgnoreTrackingUrlWarning { get; set; } = false; public bool EnableSmoothScrolling { get; set; } = true; public string BrowserPath { get; set; } = null; public string SearchEngineUrl { get; set; } = null; private int _zoomLevel = 100; private bool _muteNotifications; public int VideoPlayerVolume { get; set; } = 50; public bool EnableSpellCheck { get; set; } = false; public string SpellCheckLanguage { get; set; } = "en-US"; public string TranslationTarget { get; set; } = "en"; private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled; public bool EnableTrayHighlight { get; set; } = true; public bool EnableUpdateCheck { get; set; } = true; public string DismissedUpdate { get; set; } = null; public bool DisplayNotificationColumn { get; set; } = false; public bool NotificationMediaPreviews { get; set; } = true; public bool NotificationSkipOnLinkClick { get; set; } = false; public bool NotificationNonIntrusiveMode { get; set; } = true; public int NotificationIdlePauseSeconds { get; set; } = 0; public bool DisplayNotificationTimer { get; set; } = true; public bool NotificationTimerCountDown { get; set; } = false; public int NotificationDurationValue { get; set; } = 25; public TweetNotification.Position NotificationPosition { get; set; } = TweetNotification.Position.TopRight; public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation; public int NotificationDisplay { get; set; } = 0; public int NotificationEdgeDistance { get; set; } = 8; public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto; public Size CustomNotificationSize { get; set; } = Size.Empty; public int NotificationScrollSpeed { get; set; } = 100; private string _notificationSoundPath; private int _notificationSoundVolume = 100; public string CustomCefArgs { get; set; } = null; public string CustomBrowserCSS { get; set; } = null; public string CustomNotificationCSS { get; set; } = null; // SPECIAL PROPERTIES public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty; public bool IsCustomSoundNotificationSet => NotificationSoundPath != string.Empty; public TwitterUtils.ImageQuality TwitterImageQuality => BestImageQuality ? TwitterUtils.ImageQuality.Orig : TwitterUtils.ImageQuality.Default; public string NotificationSoundPath{ get => _notificationSoundPath ?? string.Empty; set => UpdatePropertyWithEvent(ref _notificationSoundPath, value, SoundNotificationChanged); } public int NotificationSoundVolume{ get => _notificationSoundVolume; set => UpdatePropertyWithEvent(ref _notificationSoundVolume, value, SoundNotificationChanged); } public bool MuteNotifications{ get => _muteNotifications; set => UpdatePropertyWithEvent(ref _muteNotifications, value, MuteToggled); } public int ZoomLevel{ get => _zoomLevel; set => UpdatePropertyWithEvent(ref _zoomLevel, value, ZoomLevelChanged); } public TrayIcon.Behavior TrayBehavior{ get => _trayBehavior; set => UpdatePropertyWithEvent(ref _trayBehavior, value, TrayBehaviorChanged); } // EVENTS public event EventHandler MuteToggled; public event EventHandler ZoomLevelChanged; public event EventHandler TrayBehaviorChanged; public event EventHandler SoundNotificationChanged; // END OF CONFIG private readonly string file; private UserConfig(string file){ this.file = file; } private void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){ if (!EqualityComparer<T>.Default.Equals(field, value)){ field = value; eventHandler?.Invoke(this, EventArgs.Empty); } } public void Save(){ try{ if (File.Exists(file)){ string backupFile = GetBackupFile(file); File.Delete(backupFile); File.Move(file, backupFile); } Serializer.Write(file, this); }catch(Exception e){ Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e); } } public void Reload(){ try{ LoadInternal(false); }catch(FileNotFoundException){ try{ Serializer.Write(file, new UserConfig(file)); LoadInternal(false); }catch(Exception e){ Program.Reporter.HandleException("Configuration Error", "Could not regenerate configuration file.", true, e); } }catch(Exception e){ Program.Reporter.HandleException("Configuration Error", "Could not reload configuration file.", true, e); } } public void Reset(){ try{ File.Delete(file); File.Delete(GetBackupFile(file)); }catch(Exception e){ Program.Reporter.HandleException("Configuration Error", "Could not delete configuration files to reset the options.", true, e); return; } Reload(); } private void LoadInternal(bool backup){ Serializer.Read(backup ? GetBackupFile(file) : file, this); if (NotificationScrollSpeed == 10){ // incorrect initial value NotificationScrollSpeed = 100; Save(); } } public static UserConfig Load(string file){ Exception firstException = null; for(int attempt = 0; attempt < 2; attempt++){ try{ UserConfig config = new UserConfig(file); config.LoadInternal(attempt > 0); return config; }catch(FileNotFoundException){ }catch(DirectoryNotFoundException){ break; }catch(Exception e){ if (attempt == 0){ firstException = e; Program.Reporter.Log(e.ToString()); } else if (firstException is FormatException){ Program.Reporter.HandleException("Configuration Error", "The configuration file is outdated or corrupted. If you continue, your program options will be reset.", true, e); return new UserConfig(file); } else if (firstException != null){ Program.Reporter.HandleException("Configuration Error", "Could not open the backup configuration file. If you continue, your program options will be reset.", true, e); return new UserConfig(file); } } } if (firstException != null){ Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException); } return new UserConfig(file); } public static string GetBackupFile(string file){ return file+".bak"; } } }