diff --git a/Configuration/Arguments.cs b/Configuration/Arguments.cs index 34c52491..85ed6beb 100644 --- a/Configuration/Arguments.cs +++ b/Configuration/Arguments.cs @@ -1,5 +1,5 @@ using System; -using TweetDuck.Core.Utils; +using TweetDuck.Data; namespace TweetDuck.Configuration{ static class Arguments{ diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index 4e108ac9..b7408d8e 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -1,217 +1,140 @@ using System; using System.Drawing; using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; using TweetDuck.Core; using TweetDuck.Core.Controls; using TweetDuck.Core.Notification; using TweetDuck.Core.Utils; +using TweetDuck.Data; +using TweetDuck.Data.Serialization; namespace TweetDuck.Configuration{ - [Serializable] - sealed class UserConfig{ - private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() }; + sealed class UserConfig : ISerializedObject{ + private static readonly FileSerializer<UserConfig> Serializer = new FileSerializer<UserConfig>(); - private class LegacyBinder : SerializationBinder{ - public override Type BindToType(string assemblyName, string typeName){ - return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck"), assemblyName.Replace("TweetDck", "TweetDuck"))); - } + 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 - private const int CurrentFileVersion = 12; + public WindowState BrowserWindow { get; set; } = new WindowState(); + public WindowState PluginsWindow { get; set; } = new WindowState(); - // START OF CONFIGURATION + public bool ExpandLinksOnHover { get; set; } = true; + public bool SwitchAccountSelectors { get; set; } = true; + public bool EnableSpellCheck { get; set; } = false; + private int _zoomLevel = 100; + private bool _muteNotifications; + + private TrayIcon.Behavior _trayBehavior = TrayIcon.Behavior.Disabled; + public bool EnableTrayHighlight { get; set; } = true; - public WindowState BrowserWindow { get; set; } - public WindowState PluginsWindow { get; set; } + public bool EnableUpdateCheck { get; set; } = true; + public string DismissedUpdate { get; set; } = null; - public bool DisplayNotificationColumn { get; set; } - public bool DisplayNotificationTimer { get; set; } - public bool NotificationTimerCountDown { get; set; } - public bool NotificationSkipOnLinkClick { get; set; } - public bool NotificationNonIntrusiveMode { get; set; } + public bool DisplayNotificationColumn { get; set; } = false; + public bool NotificationSkipOnLinkClick { get; set; } = false; + public bool NotificationNonIntrusiveMode { get; set; } = true; + public int NotificationIdlePauseSeconds { get; set; } = 0; - public int NotificationIdlePauseSeconds { get; set; } - public int NotificationDurationValue { get; set; } - public int NotificationScrollSpeed { get; set; } + public bool DisplayNotificationTimer { get; set; } = true; + public bool NotificationTimerCountDown { get; set; } = false; + public int NotificationDurationValue { get; set; } = 25; - public TweetNotification.Position NotificationPosition { get; set; } - public Point CustomNotificationPosition { get; set; } - public int NotificationEdgeDistance { get; set; } - public int NotificationDisplay { get; set; } + 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; } - public Size CustomNotificationSize { get; set; } + public TweetNotification.Size NotificationSize { get; set; } = TweetNotification.Size.Auto; + public Size CustomNotificationSize { get; set; } = Size.Empty; + public int NotificationScrollSpeed { get; set; } = 10; - public bool EnableSpellCheck { get; set; } - public bool ExpandLinksOnHover { get; set; } - public bool SwitchAccountSelectors { get; set; } - public bool EnableTrayHighlight { get; set; } + private string _notificationSoundPath; - public bool EnableUpdateCheck { get; set; } - public string DismissedUpdate { get; set; } + public string CustomCefArgs { get; set; } = null; + public string CustomBrowserCSS { get; set; } = null; + public string CustomNotificationCSS { get; set; } = null; - public string CustomCefArgs { get; set; } - public string CustomBrowserCSS { get; set; } - public string CustomNotificationCSS { get; set; } - - public bool EnableBrowserGCReload { get; set; } - public int BrowserMemoryThreshold { get; set; } + public bool EnableBrowserGCReload { get; set; } = false; + public int BrowserMemoryThreshold { get; set; } = 350; + + // SPECIAL PROPERTIES public bool IsCustomNotificationPositionSet => CustomNotificationPosition != ControlExtensions.InvisibleLocation; public bool IsCustomNotificationSizeSet => CustomNotificationSize != Size.Empty; public string NotificationSoundPath{ - get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath; - set => notificationSoundPath = value; + get => string.IsNullOrEmpty(_notificationSoundPath) ? string.Empty : _notificationSoundPath; + set => _notificationSoundPath = value; } public bool MuteNotifications{ - get => muteNotifications; + get => _muteNotifications; set{ - if (muteNotifications != value){ - muteNotifications = value; + if (_muteNotifications != value){ + _muteNotifications = value; MuteToggled?.Invoke(this, new EventArgs()); } } } public int ZoomLevel{ - get => zoomLevel; + get => _zoomLevel; set{ - if (zoomLevel != value){ - zoomLevel = value; + if (_zoomLevel != value){ + _zoomLevel = value; ZoomLevelChanged?.Invoke(this, new EventArgs()); } } } - public double ZoomMultiplier => zoomLevel/100.0; + public double ZoomMultiplier => _zoomLevel/100.0; public TrayIcon.Behavior TrayBehavior{ - get => trayBehavior; + get => _trayBehavior; set{ - if (trayBehavior != value){ - trayBehavior = value; + if (_trayBehavior != value){ + _trayBehavior = value; TrayBehaviorChanged?.Invoke(this, new EventArgs()); } } } - // END OF CONFIGURATION + // EVENTS - [field:NonSerialized] public event EventHandler MuteToggled; - - [field:NonSerialized] public event EventHandler ZoomLevelChanged; - - [field:NonSerialized] public event EventHandler TrayBehaviorChanged; + + private readonly string file; - [NonSerialized] - private string file; - - private int fileVersion; - private bool muteNotifications; - private int zoomLevel; - private string notificationSoundPath; - private TrayIcon.Behavior trayBehavior; - - private UserConfig(string file){ + public UserConfig(string file){ // TODO make private after removing UserConfigLegacy this.file = file; - - BrowserWindow = new WindowState(); - ZoomLevel = 100; - DisplayNotificationTimer = true; - NotificationNonIntrusiveMode = true; - NotificationPosition = TweetNotification.Position.TopRight; - CustomNotificationPosition = ControlExtensions.InvisibleLocation; - NotificationSize = TweetNotification.Size.Auto; - NotificationEdgeDistance = 8; - NotificationDurationValue = 25; - NotificationScrollSpeed = 100; - BrowserMemoryThreshold = 350; - EnableUpdateCheck = true; - ExpandLinksOnHover = true; - SwitchAccountSelectors = true; - EnableTrayHighlight = true; - PluginsWindow = new WindowState(); } - private void UpgradeFile(){ - if (fileVersion == CurrentFileVersion){ - return; - } - - // if outdated, cycle through all versions - if (fileVersion == 0){ - DisplayNotificationTimer = true; - EnableUpdateCheck = true; - ++fileVersion; - } - - if (fileVersion == 1){ - ExpandLinksOnHover = true; - ++fileVersion; - } - - if (fileVersion == 2){ - BrowserWindow = new WindowState(); - PluginsWindow = new WindowState(); - ++fileVersion; - } - - if (fileVersion == 3){ - EnableTrayHighlight = true; - NotificationDurationValue = 25; - ++fileVersion; - } - - if (fileVersion == 4){ - ++fileVersion; - } - - if (fileVersion == 5){ - ++fileVersion; - } - - if (fileVersion == 6){ - NotificationNonIntrusiveMode = true; - ++fileVersion; - } - - if (fileVersion == 7){ - ZoomLevel = 100; - ++fileVersion; - } - - if (fileVersion == 8){ - SwitchAccountSelectors = true; - ++fileVersion; - } - - if (fileVersion == 9){ - NotificationScrollSpeed = 100; - ++fileVersion; - } - - if (fileVersion == 10){ - NotificationSize = TweetNotification.Size.Auto; - } - - if (fileVersion == 11){ - BrowserMemoryThreshold = 350; - ++fileVersion; - } - - // update the version - fileVersion = CurrentFileVersion; - Save(); + bool ISerializedObject.OnReadUnknownProperty(string property, string value){ + return false; } public bool Save(){ @@ -227,10 +150,7 @@ public bool Save(){ File.Move(file, backupFile); } - using(Stream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)){ - Formatter.Serialize(stream, this); - } - + Serializer.Write(file, this); return true; }catch(Exception e){ Program.Reporter.HandleException("Configuration Error", "Could not save the configuration file.", true, e); @@ -239,22 +159,20 @@ public bool Save(){ } public static UserConfig Load(string file){ - UserConfig config = null; Exception firstException = null; for(int attempt = 0; attempt < 2; attempt++){ try{ - using(Stream stream = new FileStream(attempt == 0 ? file : GetBackupFile(file), FileMode.Open, FileAccess.Read, FileShare.Read)){ - if ((config = Formatter.Deserialize(stream) as UserConfig) != null){ - config.file = file; - } - } - - config?.UpgradeFile(); - break; + UserConfig config = new UserConfig(file); + Serializer.Read(attempt == 0 ? file : GetBackupFile(file), config); + return config; }catch(FileNotFoundException){ }catch(DirectoryNotFoundException){ break; + }catch(FormatException){ + UserConfig config = UserConfigLegacy.Load(file); + config.Save(); + return config; }catch(Exception e){ if (attempt == 0){ firstException = e; @@ -266,11 +184,11 @@ public static UserConfig Load(string file){ } } - if (firstException != null && config == null){ + if (firstException != null){ Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, firstException); } - return config ?? new UserConfig(file); + return new UserConfig(file); } public static string GetBackupFile(string file){ diff --git a/Configuration/UserConfigLegacy.cs b/Configuration/UserConfigLegacy.cs new file mode 100644 index 00000000..ec8218db --- /dev/null +++ b/Configuration/UserConfigLegacy.cs @@ -0,0 +1,210 @@ +using System; +using System.Drawing; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using TweetDuck.Core; +using TweetDuck.Core.Controls; +using TweetDuck.Core.Notification; +using TweetDuck.Data; + +namespace TweetDuck.Configuration{ + [Serializable] + sealed class UserConfigLegacy{ // TODO remove eventually + private static readonly IFormatter Formatter = new BinaryFormatter{ Binder = new LegacyBinder() }; + + private class LegacyBinder : SerializationBinder{ + public override Type BindToType(string assemblyName, string typeName){ + return Type.GetType(string.Format("{0}, {1}", typeName.Replace("TweetDck", "TweetDuck").Replace(".UserConfig", ".UserConfigLegacy").Replace("Core.Utils.WindowState", "Data.WindowState"), assemblyName.Replace("TweetDck", "TweetDuck"))); + } + } + + private const int CurrentFileVersion = 11; + + // START OF CONFIGURATION + + public WindowState BrowserWindow { get; set; } + public WindowState PluginsWindow { get; set; } + + public bool DisplayNotificationColumn { get; set; } + public bool DisplayNotificationTimer { get; set; } + public bool NotificationTimerCountDown { get; set; } + public bool NotificationSkipOnLinkClick { get; set; } + public bool NotificationNonIntrusiveMode { get; set; } + + public int NotificationIdlePauseSeconds { get; set; } + public int NotificationDurationValue { get; set; } + public int NotificationScrollSpeed { get; set; } + + public TweetNotification.Position NotificationPosition { get; set; } + public Point CustomNotificationPosition { get; set; } + public int NotificationEdgeDistance { get; set; } + public int NotificationDisplay { get; set; } + + public TweetNotification.Size NotificationSize { get; set; } + public Size CustomNotificationSize { get; set; } + + public bool EnableSpellCheck { get; set; } + public bool ExpandLinksOnHover { get; set; } + public bool SwitchAccountSelectors { get; set; } + public bool EnableTrayHighlight { get; set; } + + public bool EnableUpdateCheck { get; set; } + public string DismissedUpdate { get; set; } + + public string CustomCefArgs { get; set; } + public string CustomBrowserCSS { get; set; } + public string CustomNotificationCSS { get; set; } + + public string NotificationSoundPath{ + get => string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath; + set => notificationSoundPath = value; + } + + public bool MuteNotifications{ + get => muteNotifications; + set => muteNotifications = value; + } + + public int ZoomLevel{ + get => zoomLevel; + set => zoomLevel = value; + } + + public TrayIcon.Behavior TrayBehavior{ + get => trayBehavior; + set => trayBehavior = value; + } + + // END OF CONFIGURATION + + [NonSerialized] + private string file; + + private int fileVersion; + private bool muteNotifications; + private int zoomLevel; + private string notificationSoundPath; + private TrayIcon.Behavior trayBehavior; + + private UserConfigLegacy(string file){ + this.file = file; + + BrowserWindow = new WindowState(); + ZoomLevel = 100; + DisplayNotificationTimer = true; + NotificationNonIntrusiveMode = true; + NotificationPosition = TweetNotification.Position.TopRight; + CustomNotificationPosition = ControlExtensions.InvisibleLocation; + NotificationSize = TweetNotification.Size.Auto; + NotificationEdgeDistance = 8; + NotificationDurationValue = 25; + NotificationScrollSpeed = 100; + EnableUpdateCheck = true; + ExpandLinksOnHover = true; + SwitchAccountSelectors = true; + EnableTrayHighlight = true; + PluginsWindow = new WindowState(); + } + + private void UpgradeFile(){ + if (fileVersion == CurrentFileVersion){ + return; + } + + // if outdated, cycle through all versions + if (fileVersion <= 5){ + DisplayNotificationTimer = true; + EnableUpdateCheck = true; + ExpandLinksOnHover = true; + BrowserWindow = new WindowState(); + PluginsWindow = new WindowState(); + EnableTrayHighlight = true; + NotificationDurationValue = 25; + fileVersion = 6; + } + + if (fileVersion == 6){ + NotificationNonIntrusiveMode = true; + ++fileVersion; + } + + if (fileVersion == 7){ + ZoomLevel = 100; + ++fileVersion; + } + + if (fileVersion == 8){ + SwitchAccountSelectors = true; + ++fileVersion; + } + + if (fileVersion == 9){ + NotificationScrollSpeed = 100; + ++fileVersion; + } + + if (fileVersion == 10){ + NotificationSize = TweetNotification.Size.Auto; + ++fileVersion; + } + + // update the version + fileVersion = CurrentFileVersion; + } + + public UserConfig ConvertLegacy(){ + return new UserConfig(file){ + BrowserWindow = BrowserWindow, + PluginsWindow = PluginsWindow, + DisplayNotificationColumn = DisplayNotificationColumn, + DisplayNotificationTimer = DisplayNotificationTimer, + NotificationTimerCountDown = NotificationTimerCountDown, + NotificationSkipOnLinkClick = NotificationSkipOnLinkClick, + NotificationNonIntrusiveMode = NotificationNonIntrusiveMode, + NotificationIdlePauseSeconds = NotificationIdlePauseSeconds, + NotificationDurationValue = NotificationDurationValue, + NotificationScrollSpeed = NotificationScrollSpeed, + NotificationPosition = NotificationPosition, + CustomNotificationPosition = CustomNotificationPosition, + NotificationEdgeDistance = NotificationEdgeDistance, + NotificationDisplay = NotificationDisplay, + NotificationSize = NotificationSize, + CustomNotificationSize = CustomNotificationSize, + EnableSpellCheck = EnableSpellCheck, + ExpandLinksOnHover = ExpandLinksOnHover, + SwitchAccountSelectors = SwitchAccountSelectors, + EnableTrayHighlight = EnableTrayHighlight, + EnableUpdateCheck = EnableUpdateCheck, + DismissedUpdate = DismissedUpdate, + CustomCefArgs = CustomCefArgs, + CustomBrowserCSS = CustomBrowserCSS, + CustomNotificationCSS = CustomNotificationCSS, + NotificationSoundPath = NotificationSoundPath, + MuteNotifications = MuteNotifications, + ZoomLevel = ZoomLevel, + TrayBehavior = TrayBehavior + }; + } + + public static UserConfig Load(string file){ + UserConfigLegacy config = null; + + try{ + using(Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)){ + if ((config = Formatter.Deserialize(stream) as UserConfigLegacy) != null){ + config.file = file; + } + } + + config?.UpgradeFile(); + }catch(FileNotFoundException){ + }catch(DirectoryNotFoundException){ + }catch(Exception e){ + Program.Reporter.HandleException("Configuration Error", "Could not open the configuration file.", true, e); + } + + return (config ?? new UserConfigLegacy(file)).ConvertLegacy(); + } + } +} diff --git a/Core/Controls/ControlExtensions.cs b/Core/Controls/ControlExtensions.cs index 337c8dd5..c8b02846 100644 --- a/Core/Controls/ControlExtensions.cs +++ b/Core/Controls/ControlExtensions.cs @@ -70,7 +70,7 @@ public static bool AlignValueToTick(this TrackBar trackBar){ public static void SetElevated(this Button button){ button.Text = " "+button.Text; button.FlatStyle = FlatStyle.System; - NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, new IntPtr(1)); + NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, new UIntPtr(0), new IntPtr(1)); } public static void EnableMultilineShortcuts(this TextBox textBox){ diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index f89e4353..f4e81cae 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -19,7 +19,7 @@ using TweetDuck.Resources; using TweetDuck.Updates; using TweetDuck.Updates.Events; -using TweetLib.Audio.Utils; +using TweetLib.Audio; namespace TweetDuck.Core{ sealed partial class FormBrowser : Form{ @@ -208,7 +208,7 @@ private void browser_LoadError(object sender, LoadErrorEventArgs e){ return; } - if (!e.FailedUrl.StartsWith("http://td/")){ + if (!e.FailedUrl.StartsWith("http://td/", StringComparison.Ordinal)){ string errorPage = ScriptLoader.LoadResource("pages/error.html", true); if (errorPage != null){ @@ -406,7 +406,7 @@ public void UpdateProperties(PropertyBridge.Properties properties){ } public void ReloadToTweetDeck(){ - browser.ExecuteScriptAsync("gc&&gc();window.location.href='https://tweetdeck.twitter.com'"); + browser.ExecuteScriptAsync($"gc&&gc();window.location.href='{BrowserUtils.TweetDeckURL}'"); } // callback handlers @@ -485,7 +485,7 @@ public void PlayNotificationSound(){ public void OnTweetScreenshotReady(string html, int width, int height){ if (notificationScreenshotManager == null){ - notificationScreenshotManager = new TweetScreenshotManager(this); + notificationScreenshotManager = new TweetScreenshotManager(this, plugins); } notificationScreenshotManager.Trigger(html, width, height); diff --git a/Core/Handling/ContextMenuBase.cs b/Core/Handling/ContextMenuBase.cs index 05325c07..cc915a4e 100644 --- a/Core/Handling/ContextMenuBase.cs +++ b/Core/Handling/ContextMenuBase.cs @@ -113,7 +113,7 @@ protected void SetClipboardText(string text){ form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText)); } - protected void AddDebugMenuItems(IMenuModel model){ + protected static void AddDebugMenuItems(IMenuModel model){ model.AddItem((CefMenuCommand)MenuOpenDevTools, "Open dev tools"); } @@ -134,11 +134,7 @@ private static string GetImageFileName(string url){ int dot = url.LastIndexOf('.'); if (dot != -1){ - int colon = url.IndexOf(':', dot); - - if (colon != -1){ - url = url.Substring(0, colon); - } + url = StringUtils.ExtractBefore(url, ':', dot); } // return file name diff --git a/Core/Notification/FormNotificationBase.cs b/Core/Notification/FormNotificationBase.cs index 8606d1b1..45216bb1 100644 --- a/Core/Notification/FormNotificationBase.cs +++ b/Core/Notification/FormNotificationBase.cs @@ -111,7 +111,7 @@ public FormNotificationBase(Form owner, bool enableContextMenu){ this.dpiScale = this.GetDPIScale(); DefaultResourceHandlerFactory handlerFactory = (DefaultResourceHandlerFactory)browser.ResourceHandlerFactory; - handlerFactory.RegisterHandler("https://tweetdeck.twitter.com", this.resourceHandler); + handlerFactory.RegisterHandler(BrowserUtils.TweetDeckURL, this.resourceHandler); Controls.Add(browser); @@ -183,7 +183,7 @@ protected virtual void LoadTweet(TweetNotification tweet){ currentColumn = tweet.Column; resourceHandler.SetHTML(GetTweetHTML(tweet)); - browser.Load("https://tweetdeck.twitter.com"); + browser.Load(BrowserUtils.TweetDeckURL); } protected virtual void SetNotificationSize(int width, int height){ diff --git a/Core/Notification/FormNotificationMain.cs b/Core/Notification/FormNotificationMain.cs index a230c6b2..7e6e9a41 100644 --- a/Core/Notification/FormNotificationMain.cs +++ b/Core/Notification/FormNotificationMain.cs @@ -5,6 +5,7 @@ using TweetDuck.Core.Bridge; using TweetDuck.Core.Controls; using TweetDuck.Core.Utils; +using TweetDuck.Data; using TweetDuck.Plugins; using TweetDuck.Plugins.Enums; using TweetDuck.Resources; @@ -17,12 +18,8 @@ partial class FormNotificationMain : FormNotificationBase{ private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile); - private static readonly string NotificationJS, PluginJS; - - static FormNotificationMain(){ - NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile); - PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile); - } + private static readonly string NotificationJS = ScriptLoader.LoadResource(NotificationScriptFile); + private static readonly string PluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile); private readonly PluginManager plugins; diff --git a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs index e1e427b8..e308aa4f 100644 --- a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs +++ b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs @@ -4,11 +4,17 @@ using System.Windows.Forms; using TweetDuck.Core.Bridge; using TweetDuck.Core.Utils; +using TweetDuck.Data; +using TweetDuck.Plugins; using TweetDuck.Resources; namespace TweetDuck.Core.Notification.Screenshot{ sealed class FormNotificationScreenshotable : FormNotificationBase{ - public FormNotificationScreenshotable(Action callback, Form owner) : base(owner, false){ + private readonly PluginManager plugins; + + public FormNotificationScreenshotable(Action callback, Form owner, PluginManager pluginManager) : base(owner, false){ + this.plugins = pluginManager; + browser.RegisterAsyncJsObject("$TD_NotificationScreenshot", new CallbackBridge(this, callback)); browser.FrameLoadEnd += (sender, args) => { @@ -17,9 +23,15 @@ public FormNotificationScreenshotable(Action callback, Form owner) : base(owner, } }; } - + protected override string GetTweetHTML(TweetNotification tweet){ - return tweet.GenerateHtml(enableCustomCSS: false); + string html = tweet.GenerateHtml("td-screenshot", false); + + foreach(InjectedHTML injection in plugins.Bridge.NotificationInjections){ + html = injection.Inject(html); + } + + return html; } public void LoadNotificationForScreenshot(TweetNotification tweet, int width, int height){ diff --git a/Core/Notification/Screenshot/TweetScreenshotManager.cs b/Core/Notification/Screenshot/TweetScreenshotManager.cs index 27e4885f..ddae302a 100644 --- a/Core/Notification/Screenshot/TweetScreenshotManager.cs +++ b/Core/Notification/Screenshot/TweetScreenshotManager.cs @@ -4,17 +4,20 @@ using System; using System.Windows.Forms; using TweetDuck.Core.Controls; +using TweetDuck.Plugins; namespace TweetDuck.Core.Notification.Screenshot{ sealed class TweetScreenshotManager : IDisposable{ private readonly Form owner; + private readonly PluginManager plugins; private readonly Timer timeout; private readonly Timer disposer; private FormNotificationScreenshotable screenshot; - public TweetScreenshotManager(Form owner){ + public TweetScreenshotManager(Form owner, PluginManager pluginManager){ this.owner = owner; + this.plugins = pluginManager; this.timeout = new Timer{ Interval = 8000 }; this.timeout.Tick += timeout_Tick; @@ -40,7 +43,7 @@ public void Trigger(string html, int width, int height){ return; } - screenshot = new FormNotificationScreenshotable(Callback, owner){ + screenshot = new FormNotificationScreenshotable(Callback, owner, plugins){ CanMoveWindow = () => false }; diff --git a/Core/Notification/SoundNotification.cs b/Core/Notification/SoundNotification.cs index c0f05f42..645e0262 100644 --- a/Core/Notification/SoundNotification.cs +++ b/Core/Notification/SoundNotification.cs @@ -1,6 +1,5 @@ using System; using TweetLib.Audio; -using TweetLib.Audio.Utils; namespace TweetDuck.Core.Notification{ sealed class SoundNotification : IDisposable{ diff --git a/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs b/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs index a719d84c..e362b6b3 100644 --- a/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs +++ b/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Windows.Forms; using TweetDuck.Configuration; -using TweetDuck.Core.Utils; +using TweetDuck.Data; namespace TweetDuck.Core.Other.Settings.Dialogs{ sealed partial class DialogSettingsRestart : Form{ diff --git a/Core/Other/Settings/Export/ExportManager.cs b/Core/Other/Settings/Export/ExportManager.cs index e3f2c3a2..cf42ab4b 100644 --- a/Core/Other/Settings/Export/ExportManager.cs +++ b/Core/Other/Settings/Export/ExportManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Windows.Forms; using TweetDuck.Configuration; +using TweetDuck.Data; using TweetDuck.Plugins; using TweetDuck.Plugins.Enums; diff --git a/Core/Other/Settings/TabSettingsNotifications.cs b/Core/Other/Settings/TabSettingsNotifications.cs index b2a9c2f5..315b2b2c 100644 --- a/Core/Other/Settings/TabSettingsNotifications.cs +++ b/Core/Other/Settings/TabSettingsNotifications.cs @@ -68,10 +68,10 @@ public TabSettingsNotifications(FormNotificationMain notification){ checkNonIntrusive.Checked = Config.NotificationNonIntrusiveMode; trackBarScrollSpeed.SetValueSafe(Config.NotificationScrollSpeed); - labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%"; + labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%"; trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance); - labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px"; + labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px"; this.notification.CanMoveWindow = () => radioLocCustom.Checked; this.notification.CanResizeWindow = radioSizeCustom.Checked; @@ -233,7 +233,7 @@ private void comboBoxIdlePause_SelectedValueChanged(object sender, EventArgs e){ private void trackBarScrollSpeed_ValueChanged(object sender, EventArgs e){ if (trackBarScrollSpeed.AlignValueToTick()){ - labelScrollSpeedValue.Text = trackBarScrollSpeed.Value.ToString(CultureInfo.InvariantCulture)+"%"; + labelScrollSpeedValue.Text = trackBarScrollSpeed.Value+"%"; Config.NotificationScrollSpeed = trackBarScrollSpeed.Value; } } @@ -244,7 +244,7 @@ private void comboBoxDisplay_SelectedValueChanged(object sender, EventArgs e){ } private void trackBarEdgeDistance_ValueChanged(object sender, EventArgs e){ - labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px"; + labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value+" px"; Config.NotificationEdgeDistance = trackBarEdgeDistance.Value; notification.ShowNotificationForSettings(false); } diff --git a/Core/Other/Settings/TabSettingsSounds.Designer.cs b/Core/Other/Settings/TabSettingsSounds.Designer.cs index f75e43a0..04c3acbe 100644 --- a/Core/Other/Settings/TabSettingsSounds.Designer.cs +++ b/Core/Other/Settings/TabSettingsSounds.Designer.cs @@ -77,6 +77,7 @@ private void InitializeComponent() { this.tbCustomSound.Name = "tbCustomSound"; this.tbCustomSound.Size = new System.Drawing.Size(316, 20); this.tbCustomSound.TabIndex = 0; + this.toolTip.SetToolTip(this.tbCustomSound, "When empty, the default TweetDeck sound notification is used."); // // labelSoundNotification // diff --git a/Core/Other/Settings/TabSettingsSounds.cs b/Core/Other/Settings/TabSettingsSounds.cs index a2947eef..fe320588 100644 --- a/Core/Other/Settings/TabSettingsSounds.cs +++ b/Core/Other/Settings/TabSettingsSounds.cs @@ -3,7 +3,7 @@ using System.IO; using System.Windows.Forms; using TweetDuck.Core.Notification; -using TweetLib.Audio.Utils; +using TweetLib.Audio; namespace TweetDuck.Core.Other.Settings{ partial class TabSettingsSounds : BaseTabSettings{ diff --git a/Core/Utils/BrowserUtils.cs b/Core/Utils/BrowserUtils.cs index e2e73b69..c56d00ba 100644 --- a/Core/Utils/BrowserUtils.cs +++ b/Core/Utils/BrowserUtils.cs @@ -3,23 +3,23 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; -using System.Globalization; using System.IO; using System.Net; -using System.Text.RegularExpressions; using System.Windows.Forms; namespace TweetDuck.Core.Utils{ static class BrowserUtils{ + public const string TweetDeckURL = "https://tweetdeck.twitter.com"; + public static string HeaderAcceptLanguage{ get{ - string culture = CultureInfo.CurrentCulture.Name; + string culture = Program.Culture.Name; if (culture == "en"){ return "en-us,en"; } else{ - return culture.ToLowerInvariant()+",en;q=0.9"; + return culture.ToLower()+",en;q=0.9"; } } } @@ -80,12 +80,8 @@ public static string GetFileNameFromUrl(string url){ return string.IsNullOrEmpty(file) ? null : file; } - public static string ConvertPascalCaseToScreamingSnakeCase(string str){ - return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpperInvariant(); - } - public static string GetErrorName(CefErrorCode code){ - return ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); + return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); } public static WebClient DownloadFileAsync(string url, string target, Action onSuccess, Action<Exception> onFailure){ diff --git a/Core/Utils/CommandLineArgsParser.cs b/Core/Utils/CommandLineArgsParser.cs index 7c3397bf..538b8a73 100644 --- a/Core/Utils/CommandLineArgsParser.cs +++ b/Core/Utils/CommandLineArgsParser.cs @@ -1,5 +1,6 @@ using System; using System.Text.RegularExpressions; +using TweetDuck.Data; namespace TweetDuck.Core.Utils{ static class CommandLineArgsParser{ diff --git a/Core/Utils/NativeMethods.cs b/Core/Utils/NativeMethods.cs index 8fe77c76..b1ab111d 100644 --- a/Core/Utils/NativeMethods.cs +++ b/Core/Utils/NativeMethods.cs @@ -67,10 +67,10 @@ private struct MSLLHOOKSTRUCT{ private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop); [DllImport("user32.dll")] - public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam); + public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] - public static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam); + public static extern bool PostMessage(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] public static extern uint RegisterWindowMessage(string messageName); diff --git a/Core/Utils/StringUtils.cs b/Core/Utils/StringUtils.cs new file mode 100644 index 00000000..13e1f36c --- /dev/null +++ b/Core/Utils/StringUtils.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace TweetDuck.Core.Utils{ + static class StringUtils{ + public static string ExtractBefore(string str, char search, int startIndex = 0){ + int index = str.IndexOf(search, startIndex); + return index == -1 ? str : str.Substring(0, index); + } + + public static int[] ParseInts(string str, char separator){ + return str.Split(new char[]{ separator }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); + } + + public static string ConvertPascalCaseToScreamingSnakeCase(string str){ + return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper(); + } + } +} diff --git a/Core/Other/Settings/Export/CombinedFileStream.cs b/Data/CombinedFileStream.cs similarity index 90% rename from Core/Other/Settings/Export/CombinedFileStream.cs rename to Data/CombinedFileStream.cs index 792a28f0..223bded6 100644 --- a/Core/Other/Settings/Export/CombinedFileStream.cs +++ b/Data/CombinedFileStream.cs @@ -1,9 +1,10 @@ using System; using System.IO; using System.Text; +using TweetDuck.Core.Utils; -namespace TweetDuck.Core.Other.Settings.Export{ - class CombinedFileStream : IDisposable{ +namespace TweetDuck.Data{ + sealed class CombinedFileStream : IDisposable{ public const char KeySeparator = '|'; private readonly Stream stream; @@ -79,8 +80,7 @@ public string SkipFile(){ stream.Position += BitConverter.ToInt32(contentLength, 0); string keyName = Encoding.UTF8.GetString(name); - int separatorIndex = keyName.IndexOf(KeySeparator); - return separatorIndex == -1 ? keyName : keyName.Substring(0, separatorIndex); + return StringUtils.ExtractBefore(keyName, KeySeparator); } public void Flush(){ @@ -96,8 +96,7 @@ public class Entry{ public string KeyName{ get{ - int index = Identifier.IndexOf(KeySeparator); - return index == -1 ? Identifier : Identifier.Substring(0, index); + return StringUtils.ExtractBefore(Identifier, KeySeparator); } } diff --git a/Core/Utils/CommandLineArgs.cs b/Data/CommandLineArgs.cs similarity index 85% rename from Core/Utils/CommandLineArgs.cs rename to Data/CommandLineArgs.cs index 295233ec..0caa437f 100644 --- a/Core/Utils/CommandLineArgs.cs +++ b/Data/CommandLineArgs.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Text; -namespace TweetDuck.Core.Utils{ - class CommandLineArgs{ +namespace TweetDuck.Data{ + sealed class CommandLineArgs{ public static CommandLineArgs FromStringArray(char entryChar, string[] array){ CommandLineArgs args = new CommandLineArgs(); ReadStringArray(entryChar, array, args); @@ -38,31 +38,31 @@ public static void ReadStringArray(char entryChar, string[] array, CommandLineAr public int Count => flags.Count+values.Count; public void AddFlag(string flag){ - flags.Add(flag.ToLowerInvariant()); + flags.Add(flag.ToLower()); } public bool HasFlag(string flag){ - return flags.Contains(flag.ToLowerInvariant()); + return flags.Contains(flag.ToLower()); } public void RemoveFlag(string flag){ - flags.Remove(flag.ToLowerInvariant()); + flags.Remove(flag.ToLower()); } public void SetValue(string key, string value){ - values[key.ToLowerInvariant()] = value; + values[key.ToLower()] = value; } public bool HasValue(string key){ - return values.ContainsKey(key.ToLowerInvariant()); + return values.ContainsKey(key.ToLower()); } public string GetValue(string key, string defaultValue){ - return values.TryGetValue(key.ToLowerInvariant(), out string val) ? val : defaultValue; + return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue; } public void RemoveValue(string key){ - values.Remove(key.ToLowerInvariant()); + values.Remove(key.ToLower()); } public CommandLineArgs Clone(){ diff --git a/Core/Utils/InjectedHTML.cs b/Data/InjectedHTML.cs similarity index 94% rename from Core/Utils/InjectedHTML.cs rename to Data/InjectedHTML.cs index 7982ff36..6b797b48 100644 --- a/Core/Utils/InjectedHTML.cs +++ b/Data/InjectedHTML.cs @@ -1,7 +1,7 @@ using System; -namespace TweetDuck.Core.Utils{ - class InjectedHTML{ +namespace TweetDuck.Data{ + sealed class InjectedHTML{ public enum Position{ Before, After } diff --git a/Data/Serialization/FileSerializer.cs b/Data/Serialization/FileSerializer.cs new file mode 100644 index 00000000..052ad227 --- /dev/null +++ b/Data/Serialization/FileSerializer.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; + +namespace TweetDuck.Data.Serialization{ + sealed class FileSerializer<T> where T : ISerializedObject{ + private const string NewLineReal = "\r\n"; + private const string NewLineCustom = "\r~\n"; + + private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); + + private readonly Dictionary<string, PropertyInfo> props; + private readonly Dictionary<Type, ITypeConverter> converters; + + public FileSerializer(){ + this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.CanWrite).ToDictionary(prop => prop.Name); + this.converters = new Dictionary<Type, ITypeConverter>(); + } + + public void RegisterTypeConverter(Type type, ITypeConverter converter){ + converters[type] = converter; + } + + public void Write(string file, T obj){ + using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ + foreach(KeyValuePair<string, PropertyInfo> prop in props){ + Type type = prop.Value.PropertyType; + object value = prop.Value.GetValue(obj); + + if (!converters.TryGetValue(type, out ITypeConverter serializer)) { + serializer = BasicSerializerObj; + } + + if (serializer.TryWriteType(type, value, out string converted)){ + if (converted != null){ + writer.Write($"{prop.Key} {converted.Replace(Environment.NewLine, NewLineCustom)}"); + writer.Write(NewLineReal); + } + } + else{ + throw new SerializationException($"Invalid serialization type, conversion failed for: {type}"); + } + } + } + } + + public void Read(string file, T obj){ + using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))){ + if (reader.Peek() == 0){ + throw new FormatException("Input appears to be a binary file."); + } + + foreach(string line in reader.ReadToEnd().Split(new string[]{ NewLineReal }, StringSplitOptions.RemoveEmptyEntries)){ + int space = line.IndexOf(' '); + + if (space == -1){ + throw new SerializationException($"Invalid file format, missing separator: {line}"); + } + + string property = line.Substring(0, space); + string value = line.Substring(space+1).Replace(NewLineCustom, Environment.NewLine); + + if (props.TryGetValue(property, out PropertyInfo info)){ + if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)) { + serializer = BasicSerializerObj; + } + + if (serializer.TryReadType(info.PropertyType, value, out object converted)){ + info.SetValue(obj, converted); + } + else{ + throw new SerializationException($"Invalid file format, cannot convert value: {value} (property: {property})"); + } + } + else if (!obj.OnReadUnknownProperty(property, value)){ + throw new SerializationException($"Invalid file format, unknown property: {property}+"); + } + } + } + } + + private class BasicTypeConverter : ITypeConverter{ + bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ + switch(Type.GetTypeCode(type)){ + case TypeCode.Boolean: + converted = value.ToString(); + return true; + + case TypeCode.Int32: + converted = ((int)value).ToString(); // cast required for enums + return true; + + case TypeCode.String: + converted = value?.ToString(); + return true; + + default: + converted = null; + return false; + } + } + + bool ITypeConverter.TryReadType(Type type, string value, out object converted){ + switch(Type.GetTypeCode(type)){ + case TypeCode.Boolean: + if (bool.TryParse(value, out bool b)){ + converted = b; + return true; + } + else goto default; + + case TypeCode.Int32: + if (int.TryParse(value, out int i)){ + converted = i; + return true; + } + else goto default; + + case TypeCode.String: + converted = value; + return true; + + default: + converted = null; + return false; + } + } + } + } +} diff --git a/Data/Serialization/ISerializedObject.cs b/Data/Serialization/ISerializedObject.cs new file mode 100644 index 00000000..3d538583 --- /dev/null +++ b/Data/Serialization/ISerializedObject.cs @@ -0,0 +1,5 @@ +namespace TweetDuck.Data.Serialization{ + interface ISerializedObject{ + bool OnReadUnknownProperty(string property, string value); + } +} diff --git a/Data/Serialization/ITypeConverter.cs b/Data/Serialization/ITypeConverter.cs new file mode 100644 index 00000000..67b2126f --- /dev/null +++ b/Data/Serialization/ITypeConverter.cs @@ -0,0 +1,8 @@ +using System; + +namespace TweetDuck.Data.Serialization{ + interface ITypeConverter{ + bool TryWriteType(Type type, object value, out string converted); + bool TryReadType(Type type, string value, out object converted); + } +} diff --git a/Data/Serialization/SingleTypeConverter.cs b/Data/Serialization/SingleTypeConverter.cs new file mode 100644 index 00000000..182a949d --- /dev/null +++ b/Data/Serialization/SingleTypeConverter.cs @@ -0,0 +1,31 @@ +using System; + +namespace TweetDuck.Data.Serialization{ + sealed class SingleTypeConverter<T> : ITypeConverter{ + public delegate string FuncConvertToString<U>(U value); + public delegate U FuncConvertToObject<U>(string value); + + public FuncConvertToString<T> ConvertToString { get; set; } + public FuncConvertToObject<T> ConvertToObject { get; set; } + + bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ + try{ + converted = ConvertToString((T)value); + return true; + }catch{ + converted = null; + return false; + } + } + + bool ITypeConverter.TryReadType(Type type, string value, out object converted){ + try{ + converted = ConvertToObject(value); + return true; + }catch{ + converted = null; + return false; + } + } + } +} diff --git a/Core/Utils/TwoKeyDictionary.cs b/Data/TwoKeyDictionary.cs similarity index 97% rename from Core/Utils/TwoKeyDictionary.cs rename to Data/TwoKeyDictionary.cs index 39db733d..cb96265d 100644 --- a/Core/Utils/TwoKeyDictionary.cs +++ b/Data/TwoKeyDictionary.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; -namespace TweetDuck.Core.Utils{ - class TwoKeyDictionary<K1, K2, V>{ +namespace TweetDuck.Data{ + sealed class TwoKeyDictionary<K1, K2, V>{ private readonly Dictionary<K1, Dictionary<K2, V>> dict; private readonly int innerCapacity; diff --git a/Core/Utils/WindowState.cs b/Data/WindowState.cs similarity index 53% rename from Core/Utils/WindowState.cs rename to Data/WindowState.cs index 2196df35..9ec75951 100644 --- a/Core/Utils/WindowState.cs +++ b/Data/WindowState.cs @@ -2,10 +2,12 @@ using System.Drawing; using System.Windows.Forms; using TweetDuck.Core.Controls; +using TweetDuck.Core.Utils; +using TweetDuck.Data.Serialization; -namespace TweetDuck.Core.Utils{ - [Serializable] - class WindowState{ +namespace TweetDuck.Data{ + [Serializable] // TODO remove attribute with UserConfigLegacy + sealed class WindowState{ private Rectangle rect; private bool isMaximized; @@ -26,5 +28,17 @@ public void Restore(Form form, bool firstTimeFullscreen){ Save(form); } } + + public static readonly SingleTypeConverter<WindowState> Converter = new SingleTypeConverter<WindowState>{ + ConvertToString = value => $"{(value.isMaximized ? 'M' : '_')}{value.rect.X} {value.rect.Y} {value.rect.Width} {value.rect.Height}", + ConvertToObject = value => { + int[] elements = StringUtils.ParseInts(value.Substring(1), ' '); + + return new WindowState{ + rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]), + isMaximized = value[0] == 'M' + }; + } + }; } } diff --git a/Plugins/Plugin.cs b/Plugins/Plugin.cs index e5ccdecc..1d738815 100644 --- a/Plugins/Plugin.cs +++ b/Plugins/Plugin.cs @@ -6,7 +6,7 @@ using TweetDuck.Plugins.Enums; namespace TweetDuck.Plugins{ - class Plugin{ + sealed class Plugin{ public string Identifier { get; } public PluginGroup Group { get; } public PluginEnvironment Environments { get; private set; } @@ -132,8 +132,7 @@ public override int GetHashCode(){ } public override bool Equals(object obj){ - Plugin plugin = obj as Plugin; - return plugin != null && plugin.Identifier.Equals(Identifier); + return obj is Plugin plugin && plugin.Identifier.Equals(Identifier); } public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){ @@ -184,7 +183,7 @@ private static bool LoadMetadata(string path, Plugin plugin, out string error){ plugin.metadata[currentTag] = currentContents; } - currentTag = line.Substring(1, line.Length-2).ToUpperInvariant(); + currentTag = line.Substring(1, line.Length-2).ToUpper(); currentContents = ""; if (line.Equals(endTag[0])){ diff --git a/Plugins/PluginBridge.cs b/Plugins/PluginBridge.cs index 64e2c815..e2f6665d 100644 --- a/Plugins/PluginBridge.cs +++ b/Plugins/PluginBridge.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Text; -using TweetDuck.Core.Utils; +using TweetDuck.Data; using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Events; namespace TweetDuck.Plugins{ - class PluginBridge{ + sealed class PluginBridge{ private static string SanitizeCacheKey(string key){ return key.Replace('\\', '/').Trim(); } @@ -47,9 +47,9 @@ private string GetFullPathOrThrow(int token, PluginFolder folder, string path){ if (fullPath.Length == 0){ switch(folder){ - case PluginFolder.Data: throw new Exception("File path has to be relative to the plugin data folder."); - case PluginFolder.Root: throw new Exception("File path has to be relative to the plugin root folder."); - default: throw new Exception("Invalid folder type "+folder+", this is a "+Program.BrandName+" error."); + case PluginFolder.Data: throw new ArgumentException("File path has to be relative to the plugin data folder."); + case PluginFolder.Root: throw new ArgumentException("File path has to be relative to the plugin root folder."); + default: throw new ArgumentException("Invalid folder type "+folder+", this is a "+Program.BrandName+" error."); } } else{ @@ -67,9 +67,9 @@ private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool try{ return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8); }catch(FileNotFoundException){ - throw new Exception("File not found."); + throw new FileNotFoundException("File not found."); }catch(DirectoryNotFoundException){ - throw new Exception("Directory not found."); + throw new DirectoryNotFoundException("Directory not found."); } } diff --git a/Plugins/PluginConfig.cs b/Plugins/PluginConfig.cs index 7a1b27ac..fe9e8e58 100644 --- a/Plugins/PluginConfig.cs +++ b/Plugins/PluginConfig.cs @@ -28,8 +28,7 @@ public bool IsEnabled(Plugin plugin){ public void Load(string file){ try{ - using(FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) - using(StreamReader reader = new StreamReader(stream, Encoding.UTF8)){ + using(StreamReader reader = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){ string line = reader.ReadLine(); if (line == "#Disabled"){ @@ -49,8 +48,7 @@ public void Load(string file){ public void Save(string file){ try{ - using(FileStream stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None)) - using(StreamWriter writer = new StreamWriter(stream, Encoding.UTF8)){ + using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){ writer.WriteLine("#Disabled"); foreach(string disabled in Disabled){ diff --git a/Plugins/PluginScriptGenerator.cs b/Plugins/PluginScriptGenerator.cs index 0b18c8bb..d05ac36d 100644 --- a/Plugins/PluginScriptGenerator.cs +++ b/Plugins/PluginScriptGenerator.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using TweetDuck.Plugins.Enums; +using TweetDuck.Plugins.Enums; namespace TweetDuck.Plugins{ static class PluginScriptGenerator{ @@ -11,7 +10,7 @@ public static string GeneratePlugin(string pluginIdentifier, string pluginConten return PluginGen .Replace("%params", environment.GetScriptVariables()) .Replace("%id", pluginIdentifier) - .Replace("%token", pluginToken.ToString(CultureInfo.InvariantCulture)) + .Replace("%token", pluginToken.ToString()) .Replace("%contents", pluginContents); } @@ -19,7 +18,7 @@ public static string GeneratePlugin(string pluginIdentifier, string pluginConten /* PluginGen -(function(%params, $i, $d){ +(function(%params, $d){ let tmp = { id: '%id', obj: new class extends PluginBase{%contents} diff --git a/Program.cs b/Program.cs index 3265df59..00631511 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,7 @@ using CefSharp; using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -11,6 +12,7 @@ using TweetDuck.Core.Other; using TweetDuck.Core.Other.Settings.Export; using TweetDuck.Core.Utils; +using TweetDuck.Data; using TweetDuck.Plugins; using TweetDuck.Plugins.Events; using TweetDuck.Updates; @@ -50,15 +52,25 @@ static class Program{ public static UserConfig UserConfig { get; private set; } public static SystemConfig SystemConfig { get; private set; } - public static Reporter Reporter { get; private set; } + public static Reporter Reporter { get; } + public static CultureInfo Culture { get; } public static event EventHandler UserConfigReplaced; + static Program(){ + Culture = CultureInfo.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; + + Reporter = new Reporter(ErrorLogFilePath); + Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :("); + } + [STAThread] private static void Main(){ Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - + WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); SubProcessMessage = NativeMethods.RegisterWindowMessage("TweetDuckSubProcess"); @@ -66,10 +78,7 @@ private static void Main(){ MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } - - Reporter = new Reporter(ErrorLogFilePath); - Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :("); - + if (Arguments.HasFlag(Arguments.ArgRestart)){ for(int attempt = 0; attempt < 21; attempt++){ LockManager.Result lockResult = LockManager.Lock(); @@ -102,7 +111,7 @@ private static void Main(){ if (lockResult == LockManager.Result.HasProcess){ if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray - NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, LockManager.LockingProcess.Id, IntPtr.Zero); + NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, new UIntPtr((uint)LockManager.LockingProcess.Id), IntPtr.Zero); if (WindowsUtils.TrySleepUntil(() => { LockManager.LockingProcess.Refresh(); diff --git a/Reporter.cs b/Reporter.cs index bcd1deca..ef3d70c9 100644 --- a/Reporter.cs +++ b/Reporter.cs @@ -1,14 +1,13 @@ using System; using System.Diagnostics; using System.Drawing; -using System.Globalization; using System.IO; using System.Text; using System.Windows.Forms; using TweetDuck.Core.Other; namespace TweetDuck{ - class Reporter{ + sealed class Reporter{ private readonly string logFile; public Reporter(string logFile){ @@ -17,9 +16,7 @@ public Reporter(string logFile){ public void SetupUnhandledExceptionHandler(string caption){ AppDomain.CurrentDomain.UnhandledException += (sender, args) => { - Exception ex = args.ExceptionObject as Exception; - - if (ex != null){ + if (args.ExceptionObject is Exception ex) { HandleException(caption, "An unhandled exception has occurred.", false, ex); } }; @@ -32,7 +29,7 @@ public bool Log(string data){ build.Append("Please, report all issues to: https://github.com/chylex/TweetDuck/issues\r\n\r\n"); } - build.Append("[").Append(DateTime.Now.ToString("G", CultureInfo.CurrentCulture)).Append("]\r\n"); + build.Append("[").Append(DateTime.Now.ToString("G", Program.Culture)).Append("]\r\n"); build.Append(data).Append("\r\n\r\n"); try{ @@ -87,9 +84,10 @@ public static void HandleEarlyFailure(string caption, string message){ Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error); - form.ActiveControl = form.AddButton("Exit"); - form.ShowDialog(); + using(FormMessage form = new FormMessage(caption, message, MessageBoxIcon.Error)){ + form.ActiveControl = form.AddButton("Exit"); + form.ShowDialog(); + } try{ Process.GetCurrentProcess().Kill(); diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js index 6ff4382b..57d2c65b 100644 --- a/Resources/Scripts/code.js +++ b/Resources/Scripts/code.js @@ -498,7 +498,7 @@ var tryCloseModal1 = function(){ var modal = $("#open-modal"); - return modal.is(":visible") && tryClickSelector("a[rel=dismiss]", modal); + return modal.is(":visible") && tryClickSelector("a.mdl-dismiss", modal); }; var tryCloseModal2 = function(){ diff --git a/TweetDuck.csproj b/TweetDuck.csproj index c689bcae..83d83f28 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -73,6 +73,7 @@ <Compile Include="Configuration\LockManager.cs" /> <Compile Include="Configuration\SystemConfig.cs" /> <Compile Include="Configuration\UserConfig.cs" /> + <Compile Include="Configuration\UserConfigLegacy.cs" /> <Compile Include="Core\Bridge\PropertyBridge.cs" /> <Compile Include="Core\Controls\ControlExtensions.cs" /> <Compile Include="Core\Controls\FlatButton.cs"> @@ -164,7 +165,8 @@ <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs"> <DependentUpon>DialogSettingsRestart.cs</DependentUpon> </Compile> - <Compile Include="Core\Other\Settings\Export\CombinedFileStream.cs" /> + <Compile Include="Core\Utils\StringUtils.cs" /> + <Compile Include="Data\CombinedFileStream.cs" /> <Compile Include="Core\Other\Settings\Export\ExportFileFlags.cs" /> <Compile Include="Core\Other\Settings\Export\ExportManager.cs" /> <Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs"> @@ -195,17 +197,20 @@ <DependentUpon>TabSettingsNotifications.cs</DependentUpon> </Compile> <Compile Include="Core\Bridge\CallbackBridge.cs" /> - <Compile Include="Core\Utils\BrowserProcesses.cs" /> - <Compile Include="Core\Utils\CommandLineArgs.cs" /> + <Compile Include="Data\CommandLineArgs.cs" /> <Compile Include="Core\Utils\CommandLineArgsParser.cs" /> <Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs"> <SubType>Form</SubType> </Compile> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" /> - <Compile Include="Core\Utils\InjectedHTML.cs" /> + <Compile Include="Data\Serialization\FileSerializer.cs" /> + <Compile Include="Data\InjectedHTML.cs" /> + <Compile Include="Data\Serialization\ISerializedObject.cs" /> + <Compile Include="Data\Serialization\ITypeConverter.cs" /> + <Compile Include="Data\Serialization\SingleTypeConverter.cs" /> + <Compile Include="Data\TwoKeyDictionary.cs" /> + <Compile Include="Data\WindowState.cs" /> <Compile Include="Core\Utils\MemoryUsageTracker.cs" /> - <Compile Include="Core\Utils\TwoKeyDictionary.cs" /> - <Compile Include="Core\Utils\WindowState.cs" /> <Compile Include="Core\Utils\WindowsUtils.cs" /> <Compile Include="Core\Bridge\TweetDeckBridge.cs" /> <Compile Include="Core\Other\FormSettings.cs"> diff --git a/Updates/UpdateInfo.cs b/Updates/UpdateInfo.cs index 2f14fce9..8d859eda 100644 --- a/Updates/UpdateInfo.cs +++ b/Updates/UpdateInfo.cs @@ -4,7 +4,7 @@ using TweetDuck.Core.Utils; namespace TweetDuck.Updates{ - class UpdateInfo{ + sealed class UpdateInfo{ public string VersionTag { get; } public string InstallerPath { get; } diff --git a/Updates/UpdaterSettings.cs b/Updates/UpdaterSettings.cs index bba2ef0f..1232e4bb 100644 --- a/Updates/UpdaterSettings.cs +++ b/Updates/UpdaterSettings.cs @@ -1,5 +1,5 @@ namespace TweetDuck.Updates{ - class UpdaterSettings{ + sealed class UpdaterSettings{ public bool AllowPreReleases { get; set; } public string DismissedUpdate { get; set; } public string InstallerDownloadFolder { get; set; } diff --git a/lib/TweetLib.Audio/AudioPlayer.cs b/lib/TweetLib.Audio/AudioPlayer.cs index 787d07f9..7031a8cd 100644 --- a/lib/TweetLib.Audio/AudioPlayer.cs +++ b/lib/TweetLib.Audio/AudioPlayer.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.InteropServices; using TweetLib.Audio.Impl; -using TweetLib.Audio.Utils; namespace TweetLib.Audio{ public abstract class AudioPlayer : IDisposable{ @@ -32,6 +31,11 @@ public static AudioPlayer New(){ public abstract void Play(string file); public abstract void Stop(); - public abstract void Dispose(); + protected abstract void Dispose(bool disposing); + + public void Dispose(){ + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs b/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs index d10219fe..df7b86a8 100644 --- a/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs +++ b/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Media; -using TweetLib.Audio.Utils; namespace TweetLib.Audio.Impl{ sealed class SoundPlayerImplFallback : AudioPlayer{ @@ -39,7 +38,7 @@ public override void Stop(){ player.Stop(); } - public override void Dispose(){ + protected override void Dispose(bool disposing){ player.Dispose(); } diff --git a/lib/TweetLib.Audio/Impl/SoundPlayerImplWMP.cs b/lib/TweetLib.Audio/Impl/SoundPlayerImplWMP.cs index 232c1e7a..c0e19d0a 100644 --- a/lib/TweetLib.Audio/Impl/SoundPlayerImplWMP.cs +++ b/lib/TweetLib.Audio/Impl/SoundPlayerImplWMP.cs @@ -56,7 +56,7 @@ public override void Stop(){ } } - public override void Dispose(){ + protected override void Dispose(bool disposing){ player.close(); Marshal.ReleaseComObject(player); } diff --git a/lib/TweetLib.Audio/Utils/PlaybackErrorEventArgs.cs b/lib/TweetLib.Audio/PlaybackErrorEventArgs.cs similarity index 90% rename from lib/TweetLib.Audio/Utils/PlaybackErrorEventArgs.cs rename to lib/TweetLib.Audio/PlaybackErrorEventArgs.cs index 199138fc..f8f07325 100644 --- a/lib/TweetLib.Audio/Utils/PlaybackErrorEventArgs.cs +++ b/lib/TweetLib.Audio/PlaybackErrorEventArgs.cs @@ -1,6 +1,6 @@ using System; -namespace TweetLib.Audio.Utils{ +namespace TweetLib.Audio{ public sealed class PlaybackErrorEventArgs : EventArgs{ public string Message { get; } public bool Ignore { get; set; } diff --git a/lib/TweetLib.Audio/TweetLib.Audio.csproj b/lib/TweetLib.Audio/TweetLib.Audio.csproj index 053c1efb..b4d9968b 100644 --- a/lib/TweetLib.Audio/TweetLib.Audio.csproj +++ b/lib/TweetLib.Audio/TweetLib.Audio.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> @@ -30,7 +30,7 @@ <ItemGroup> <Compile Include="AudioPlayer.cs" /> <Compile Include="Utils\NativeCoreAudio.cs" /> - <Compile Include="Utils\PlaybackErrorEventArgs.cs" /> + <Compile Include="PlaybackErrorEventArgs.cs" /> <Compile Include="Impl\SoundPlayerImplFallback.cs" /> <Compile Include="Impl\SoundPlayerImplWMP.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> diff --git a/lib/TweetLib.Audio/Utils/NativeCoreAudio.cs b/lib/TweetLib.Audio/Utils/NativeCoreAudio.cs index 91a5e691..74987b7f 100644 --- a/lib/TweetLib.Audio/Utils/NativeCoreAudio.cs +++ b/lib/TweetLib.Audio/Utils/NativeCoreAudio.cs @@ -82,9 +82,7 @@ private static ISimpleAudioVolume GetVolumeObject(string name){ ISimpleAudioVolume volumeObj = null; for(int index = sessions.GetCount()-1; index >= 0; index--){ - IAudioSessionControl2 ctl = sessions.GetSession(index) as IAudioSessionControl2; - - if (ctl != null){ + if (sessions.GetSession(index) is IAudioSessionControl2 ctl){ string identifier = ctl.GetSessionIdentifier(); if (identifier != null && identifier.Contains(name)){ diff --git a/tests/Core/Utils/TestBrowserUtils.cs b/tests/Core/TestBrowserUtils.cs similarity index 80% rename from tests/Core/Utils/TestBrowserUtils.cs rename to tests/Core/TestBrowserUtils.cs index fa0f72f4..2c7a9fff 100644 --- a/tests/Core/Utils/TestBrowserUtils.cs +++ b/tests/Core/TestBrowserUtils.cs @@ -1,7 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using TweetDuck.Core.Utils; -namespace UnitTests.Core.Utils{ +namespace UnitTests.Core{ [TestClass] public class TestBrowserUtils{ [TestMethod] @@ -46,15 +46,5 @@ public void TestGetFileNameFromUrl(){ Assert.IsNull(BrowserUtils.GetFileNameFromUrl("http://test.com/")); } - - [TestMethod] - public void TestConvertPascalCaseToScreamingSnakeCase(){ - Assert.AreEqual("HELP", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("Help")); - Assert.AreEqual("HELP_ME", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe")); - Assert.AreEqual("HELP_ME_PLEASE", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease")); - - Assert.AreEqual("HTML_CODE", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode")); - Assert.AreEqual("CHECK_OUT_MY_HTML_CODE", BrowserUtils.ConvertPascalCaseToScreamingSnakeCase("CheckOutMyHTMLCode")); - } } } diff --git a/tests/Core/Utils/TestCommandLineArgsParser.cs b/tests/Core/TestCommandLineArgsParser.cs similarity index 96% rename from tests/Core/Utils/TestCommandLineArgsParser.cs rename to tests/Core/TestCommandLineArgsParser.cs index ececcfd4..ef7cf88a 100644 --- a/tests/Core/Utils/TestCommandLineArgsParser.cs +++ b/tests/Core/TestCommandLineArgsParser.cs @@ -1,7 +1,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using TweetDuck.Core.Utils; +using TweetDuck.Data; -namespace UnitTests.Core.Utils{ +namespace UnitTests.Core{ [TestClass] public class TestCommandLineArgsParser{ [TestMethod] diff --git a/tests/Core/TestStringUtils.cs b/tests/Core/TestStringUtils.cs new file mode 100644 index 00000000..2b7e4405 --- /dev/null +++ b/tests/Core/TestStringUtils.cs @@ -0,0 +1,36 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TweetDuck.Core.Utils; + +namespace UnitTests.Core{ + [TestClass] + public class TestStringUtils{ + [TestMethod] + public void TestExtractBefore(){ + Assert.AreEqual("missing", StringUtils.ExtractBefore("missing", '_')); + Assert.AreEqual("", StringUtils.ExtractBefore("_empty", '_')); + Assert.AreEqual("some", StringUtils.ExtractBefore("some_text", '_')); + Assert.AreEqual("first", StringUtils.ExtractBefore("first_separator_only", '_')); + Assert.AreEqual("start_index", StringUtils.ExtractBefore("start_index_test", '_', 8)); + } + + [TestMethod] + public void TestParseInts(){ + CollectionAssert.AreEqual(new int[0], StringUtils.ParseInts("", ',')); + CollectionAssert.AreEqual(new int[]{ 1 }, StringUtils.ParseInts("1", ',')); + CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3", ',')); + CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts("1,2,3,", ',')); + CollectionAssert.AreEqual(new int[]{ 1, 2, 3 }, StringUtils.ParseInts(",1,2,,3,", ',')); + CollectionAssert.AreEqual(new int[]{ -50, 50 }, StringUtils.ParseInts("-50,50", ',')); + } + + [TestMethod] + public void TestConvertPascalCaseToScreamingSnakeCase(){ + Assert.AreEqual("HELP", StringUtils.ConvertPascalCaseToScreamingSnakeCase("Help")); + Assert.AreEqual("HELP_ME", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMe")); + Assert.AreEqual("HELP_ME_PLEASE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HelpMePlease")); + + Assert.AreEqual("HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("HTMLCode")); + Assert.AreEqual("CHECK_OUT_MY_HTML_CODE", StringUtils.ConvertPascalCaseToScreamingSnakeCase("CheckOutMyHTMLCode")); + } + } +} diff --git a/tests/Core/Settings/TestCombinedFileStream.cs b/tests/Data/TestCombinedFileStream.cs similarity index 97% rename from tests/Core/Settings/TestCombinedFileStream.cs rename to tests/Data/TestCombinedFileStream.cs index 9299f89d..e49f5365 100644 --- a/tests/Core/Settings/TestCombinedFileStream.cs +++ b/tests/Data/TestCombinedFileStream.cs @@ -1,9 +1,9 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; +using System; using System.IO; -using TweetDuck.Core.Other.Settings.Export; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TweetDuck.Data; -namespace UnitTests.Core.Settings{ +namespace UnitTests.Data{ [TestClass] public class TestCombinedFileStream{ [TestMethod] diff --git a/tests/Core/Utils/TestCommandLineArgs.cs b/tests/Data/TestCommandLineArgs.cs similarity index 97% rename from tests/Core/Utils/TestCommandLineArgs.cs rename to tests/Data/TestCommandLineArgs.cs index 7d13acb4..70da4965 100644 --- a/tests/Core/Utils/TestCommandLineArgs.cs +++ b/tests/Data/TestCommandLineArgs.cs @@ -1,8 +1,8 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using TweetDuck.Core.Utils; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TweetDuck.Data; -namespace UnitTests.Core.Utils{ +namespace UnitTests.Data{ [TestClass] public class TestCommandLineArgs{ [TestMethod] diff --git a/tests/Data/TestFileSerializer.cs b/tests/Data/TestFileSerializer.cs new file mode 100644 index 00000000..231fc943 --- /dev/null +++ b/tests/Data/TestFileSerializer.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TweetDuck.Data.Serialization; + +namespace UnitTests.Data{ + [TestClass] + public class TestFileSerializer{ + private enum TestEnum{ + A, B, C, D, E + } + + private class SerializationTestBasic : ISerializedObject{ + public bool TestBool { get; set; } + public int TestInt { get; set; } + public string TestString { get; set; } + public string TestStringNull { get; set; } + public TestEnum TestEnum { get; set; } + + bool ISerializedObject.OnReadUnknownProperty(string property, string value){ + return false; + } + } + + [TestMethod] + public void TestBasicWriteRead(){ + FileSerializer<SerializationTestBasic> serializer = new FileSerializer<SerializationTestBasic>(); + + SerializationTestBasic write = new SerializationTestBasic{ + TestBool = true, + TestInt = -100, + TestString = "abc"+Environment.NewLine+"def", + TestStringNull = null, + TestEnum = TestEnum.D + }; + + serializer.Write("serialized_basic", write); + + Assert.IsTrue(File.Exists("serialized_basic")); + TestUtils.DeleteFileOnExit("serialized_basic"); + + SerializationTestBasic read = new SerializationTestBasic(); + serializer.Read("serialized_basic", read); + + Assert.IsTrue(read.TestBool); + Assert.AreEqual(-100, read.TestInt); + Assert.AreEqual("abc"+Environment.NewLine+"def", read.TestString); + Assert.IsNull(read.TestStringNull); + Assert.AreEqual(TestEnum.D, read.TestEnum); + } + + // TODO more complex tests + } +} diff --git a/tests/Core/Utils/TestInjectedHTML.cs b/tests/Data/TestInjectedHTML.cs similarity index 98% rename from tests/Core/Utils/TestInjectedHTML.cs rename to tests/Data/TestInjectedHTML.cs index 005fc38a..7add28df 100644 --- a/tests/Core/Utils/TestInjectedHTML.cs +++ b/tests/Data/TestInjectedHTML.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; -using TweetDuck.Core.Utils; +using TweetDuck.Data; -namespace UnitTests.Core.Utils{ +namespace UnitTests.Data{ [TestClass] public class TestInjectedHTML{ private static IEnumerable<InjectedHTML.Position> Positions => Enum.GetValues(typeof(InjectedHTML.Position)).Cast<InjectedHTML.Position>(); diff --git a/tests/Core/Utils/TestTwoKeyDictionary.cs b/tests/Data/TestTwoKeyDictionary.cs similarity index 99% rename from tests/Core/Utils/TestTwoKeyDictionary.cs rename to tests/Data/TestTwoKeyDictionary.cs index 142f8fb4..4a984ae1 100644 --- a/tests/Core/Utils/TestTwoKeyDictionary.cs +++ b/tests/Data/TestTwoKeyDictionary.cs @@ -1,10 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; -using TweetDuck.Core.Utils; -using System.Collections.Generic; +using TweetDuck.Data; -namespace UnitTests.Core.Utils{ +namespace UnitTests.Data{ [TestClass] public class TestTwoKeyDictionary{ private static TwoKeyDictionary<string, int, string> CreateDict(){ diff --git a/tests/UnitTests.csproj b/tests/UnitTests.csproj index 2e67154b..a5bb36ad 100644 --- a/tests/UnitTests.csproj +++ b/tests/UnitTests.csproj @@ -47,12 +47,14 @@ </Otherwise> </Choose> <ItemGroup> - <Compile Include="Core\Settings\TestCombinedFileStream.cs" /> - <Compile Include="Core\Utils\TestBrowserUtils.cs" /> - <Compile Include="Core\Utils\TestCommandLineArgs.cs" /> - <Compile Include="Core\Utils\TestCommandLineArgsParser.cs" /> - <Compile Include="Core\Utils\TestInjectedHTML.cs" /> - <Compile Include="Core\Utils\TestTwoKeyDictionary.cs" /> + <Compile Include="Core\TestStringUtils.cs" /> + <Compile Include="Data\TestCombinedFileStream.cs" /> + <Compile Include="Core\TestBrowserUtils.cs" /> + <Compile Include="Data\TestCommandLineArgs.cs" /> + <Compile Include="Core\TestCommandLineArgsParser.cs" /> + <Compile Include="Data\TestFileSerializer.cs" /> + <Compile Include="Data\TestInjectedHTML.cs" /> + <Compile Include="Data\TestTwoKeyDictionary.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="TestUtils.cs" /> </ItemGroup>