From 2820fc8acf4f933ff9cd734d0886e294e29fe78e Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Tue, 4 Jul 2017 22:01:33 +0200 Subject: [PATCH 01/17] Fix some modals not closing when pressing the back button --- Resources/Scripts/code.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Scripts/code.js b/Resources/Scripts/code.js index 12d2a6f9..26c324d2 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(){ From 9afb58e4a75d4cd53ffe7a99018b1b9cce6b7dd3 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Thu, 6 Jul 2017 03:30:15 +0200 Subject: [PATCH 02/17] Remove unused 'using' statement --- Core/FormBrowser.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index 7615f00a..3843debf 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -1,7 +1,6 @@ using CefSharp; using CefSharp.WinForms; using System; -using System.Diagnostics; using System.Drawing; using System.Linq; using System.Windows.Forms; From 1645079bc0c577849eb15358025ef98bba5c6e71 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Thu, 6 Jul 2017 03:47:59 +0200 Subject: [PATCH 03/17] Allow plugins to modify screenshot css and include a 'td-screenshot' body class --- Core/FormBrowser.cs | 2 +- .../FormNotificationScreenshotable.cs | 17 ++++++++++++++--- .../Screenshot/TweetScreenshotManager.cs | 7 +++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index 3843debf..8686438d 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -456,7 +456,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/Notification/Screenshot/FormNotificationScreenshotable.cs b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs index e1e427b8..a56b55d2 100644 --- a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs +++ b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs @@ -4,11 +4,16 @@ using System.Windows.Forms; using TweetDuck.Core.Bridge; using TweetDuck.Core.Utils; +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 +22,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 }; From 4bff006743dac74e4181b59eb692088a496f63d5 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Thu, 6 Jul 2017 20:58:06 +0200 Subject: [PATCH 04/17] Refactor (move files into different namespaces) --- Configuration/Arguments.cs | 2 +- Configuration/UserConfig.cs | 2 +- Core/FormBrowser.cs | 2 +- Core/Notification/FormNotificationMain.cs | 1 + .../Screenshot/FormNotificationScreenshotable.cs | 1 + Core/Notification/SoundNotification.cs | 1 - Core/Other/Settings/Dialogs/DialogSettingsRestart.cs | 2 +- Core/Other/Settings/Export/ExportManager.cs | 1 + Core/Other/Settings/TabSettingsSounds.cs | 2 +- Core/Utils/CommandLineArgsParser.cs | 1 + .../Settings/Export => Data}/CombinedFileStream.cs | 2 +- {Core/Utils => Data}/CommandLineArgs.cs | 2 +- {Core/Utils => Data}/InjectedHTML.cs | 2 +- {Core/Utils => Data}/TwoKeyDictionary.cs | 2 +- {Core/Utils => Data}/WindowState.cs | 2 +- Plugins/PluginBridge.cs | 2 +- Program.cs | 1 + TweetDuck.csproj | 10 +++++----- lib/TweetLib.Audio/AudioPlayer.cs | 1 - lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs | 1 - .../{Utils => }/PlaybackErrorEventArgs.cs | 2 +- lib/TweetLib.Audio/TweetLib.Audio.csproj | 4 ++-- tests/Core/{Utils => }/TestBrowserUtils.cs | 2 +- tests/Core/{Utils => }/TestCommandLineArgsParser.cs | 3 ++- .../{Core/Settings => Data}/TestCombinedFileStream.cs | 8 ++++---- tests/{Core/Utils => Data}/TestCommandLineArgs.cs | 8 ++++---- tests/{Core/Utils => Data}/TestInjectedHTML.cs | 4 ++-- tests/{Core/Utils => Data}/TestTwoKeyDictionary.cs | 6 +++--- 28 files changed, 40 insertions(+), 37 deletions(-) rename {Core/Other/Settings/Export => Data}/CombinedFileStream.cs (98%) rename {Core/Utils => Data}/CommandLineArgs.cs (99%) rename {Core/Utils => Data}/InjectedHTML.cs (96%) rename {Core/Utils => Data}/TwoKeyDictionary.cs (98%) rename {Core/Utils => Data}/WindowState.cs (96%) rename lib/TweetLib.Audio/{Utils => }/PlaybackErrorEventArgs.cs (90%) rename tests/Core/{Utils => }/TestBrowserUtils.cs (99%) rename tests/Core/{Utils => }/TestCommandLineArgsParser.cs (96%) rename tests/{Core/Settings => Data}/TestCombinedFileStream.cs (97%) rename tests/{Core/Utils => Data}/TestCommandLineArgs.cs (97%) rename tests/{Core/Utils => Data}/TestInjectedHTML.cs (98%) rename tests/{Core/Utils => Data}/TestTwoKeyDictionary.cs (99%) 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 0c386971..7a47c1c8 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -6,7 +6,7 @@ using TweetDuck.Core; using TweetDuck.Core.Controls; using TweetDuck.Core.Notification; -using TweetDuck.Core.Utils; +using TweetDuck.Data; namespace TweetDuck.Configuration{ [Serializable] diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index 8686438d..59e55395 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{ diff --git a/Core/Notification/FormNotificationMain.cs b/Core/Notification/FormNotificationMain.cs index a230c6b2..eec4cc0d 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; diff --git a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs index a56b55d2..e308aa4f 100644 --- a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs +++ b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs @@ -4,6 +4,7 @@ using System.Windows.Forms; using TweetDuck.Core.Bridge; using TweetDuck.Core.Utils; +using TweetDuck.Data; using TweetDuck.Plugins; using TweetDuck.Resources; 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/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/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/Other/Settings/Export/CombinedFileStream.cs b/Data/CombinedFileStream.cs similarity index 98% rename from Core/Other/Settings/Export/CombinedFileStream.cs rename to Data/CombinedFileStream.cs index 792a28f0..48fab228 100644 --- a/Core/Other/Settings/Export/CombinedFileStream.cs +++ b/Data/CombinedFileStream.cs @@ -2,7 +2,7 @@ using System.IO; using System.Text; -namespace TweetDuck.Core.Other.Settings.Export{ +namespace TweetDuck.Data{ class CombinedFileStream : IDisposable{ public const char KeySeparator = '|'; diff --git a/Core/Utils/CommandLineArgs.cs b/Data/CommandLineArgs.cs similarity index 99% rename from Core/Utils/CommandLineArgs.cs rename to Data/CommandLineArgs.cs index 295233ec..e81a886b 100644 --- a/Core/Utils/CommandLineArgs.cs +++ b/Data/CommandLineArgs.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Text; -namespace TweetDuck.Core.Utils{ +namespace TweetDuck.Data{ class CommandLineArgs{ public static CommandLineArgs FromStringArray(char entryChar, string[] array){ CommandLineArgs args = new CommandLineArgs(); diff --git a/Core/Utils/InjectedHTML.cs b/Data/InjectedHTML.cs similarity index 96% rename from Core/Utils/InjectedHTML.cs rename to Data/InjectedHTML.cs index 7982ff36..7268f987 100644 --- a/Core/Utils/InjectedHTML.cs +++ b/Data/InjectedHTML.cs @@ -1,6 +1,6 @@ using System; -namespace TweetDuck.Core.Utils{ +namespace TweetDuck.Data{ class InjectedHTML{ public enum Position{ Before, After diff --git a/Core/Utils/TwoKeyDictionary.cs b/Data/TwoKeyDictionary.cs similarity index 98% rename from Core/Utils/TwoKeyDictionary.cs rename to Data/TwoKeyDictionary.cs index 39db733d..5655b68c 100644 --- a/Core/Utils/TwoKeyDictionary.cs +++ b/Data/TwoKeyDictionary.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace TweetDuck.Core.Utils{ +namespace TweetDuck.Data{ 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 96% rename from Core/Utils/WindowState.cs rename to Data/WindowState.cs index 2196df35..a29efac5 100644 --- a/Core/Utils/WindowState.cs +++ b/Data/WindowState.cs @@ -3,7 +3,7 @@ using System.Windows.Forms; using TweetDuck.Core.Controls; -namespace TweetDuck.Core.Utils{ +namespace TweetDuck.Data{ [Serializable] class WindowState{ private Rectangle rect; diff --git a/Plugins/PluginBridge.cs b/Plugins/PluginBridge.cs index 64e2c815..91dee055 100644 --- a/Plugins/PluginBridge.cs +++ b/Plugins/PluginBridge.cs @@ -2,7 +2,7 @@ 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; diff --git a/Program.cs b/Program.cs index 1ac12c63..232ff4e4 100644 --- a/Program.cs +++ b/Program.cs @@ -11,6 +11,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; diff --git a/TweetDuck.csproj b/TweetDuck.csproj index 8cc74af7..bab4a95c 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -164,7 +164,7 @@ <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs"> <DependentUpon>DialogSettingsRestart.cs</DependentUpon> </Compile> - <Compile Include="Core\Other\Settings\Export\CombinedFileStream.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,15 +195,15 @@ <DependentUpon>TabSettingsNotifications.cs</DependentUpon> </Compile> <Compile Include="Core\Bridge\CallbackBridge.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="Core\Utils\TwoKeyDictionary.cs" /> - <Compile Include="Core\Utils\WindowState.cs" /> + <Compile Include="Data\InjectedHTML.cs" /> + <Compile Include="Data\TwoKeyDictionary.cs" /> + <Compile Include="Data\WindowState.cs" /> <Compile Include="Core\Utils\WindowsUtils.cs" /> <Compile Include="Core\Bridge\TweetDeckBridge.cs" /> <Compile Include="Core\Other\FormSettings.cs"> diff --git a/lib/TweetLib.Audio/AudioPlayer.cs b/lib/TweetLib.Audio/AudioPlayer.cs index 787d07f9..70730f45 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{ diff --git a/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs b/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs index d10219fe..9bc73f93 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{ 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/tests/Core/Utils/TestBrowserUtils.cs b/tests/Core/TestBrowserUtils.cs similarity index 99% rename from tests/Core/Utils/TestBrowserUtils.cs rename to tests/Core/TestBrowserUtils.cs index fa0f72f4..c71e0f65 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] 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/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/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(){ From 4c610ea32d529ff3fe4c101e4dbbc6e9ffa9179a Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Thu, 6 Jul 2017 20:58:40 +0200 Subject: [PATCH 05/17] Move TweetDeck URL into a constant --- Core/FormBrowser.cs | 2 +- Core/Notification/FormNotificationBase.cs | 4 ++-- Core/Utils/BrowserUtils.cs | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index 59e55395..b920d814 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -384,7 +384,7 @@ public void UpdateProperties(PropertyBridge.Properties properties){ } public void ReloadToTweetDeck(){ - browser.ExecuteScriptAsync("window.location.href = 'https://tweetdeck.twitter.com'"); + browser.ExecuteScriptAsync($"window.location.href = '{BrowserUtils.TweetDeckURL}'"); } // callback handlers diff --git a/Core/Notification/FormNotificationBase.cs b/Core/Notification/FormNotificationBase.cs index 81a3bdb5..92ef43d3 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); @@ -180,7 +180,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/Utils/BrowserUtils.cs b/Core/Utils/BrowserUtils.cs index 22bbfb5a..6a85e986 100644 --- a/Core/Utils/BrowserUtils.cs +++ b/Core/Utils/BrowserUtils.cs @@ -10,6 +10,8 @@ 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; From 71b306d5fd4782a70aef09782171a2be7e9cce46 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Thu, 6 Jul 2017 21:26:43 +0200 Subject: [PATCH 06/17] Fix unit test project file after refactoring --- tests/UnitTests.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/UnitTests.csproj b/tests/UnitTests.csproj index 2e67154b..e017293a 100644 --- a/tests/UnitTests.csproj +++ b/tests/UnitTests.csproj @@ -47,12 +47,12 @@ </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="Data\TestCombinedFileStream.cs" /> + <Compile Include="Core\TestBrowserUtils.cs" /> + <Compile Include="Data\TestCommandLineArgs.cs" /> + <Compile Include="Core\TestCommandLineArgsParser.cs" /> + <Compile Include="Data\TestInjectedHTML.cs" /> + <Compile Include="Data\TestTwoKeyDictionary.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="TestUtils.cs" /> </ItemGroup> From 796fb348a3b584e037f51e14dc17b7f3d366c523 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 00:48:00 +0200 Subject: [PATCH 07/17] Add classes for serializing objects to/from text files --- Data/Serialization/FileSerializer.cs | 129 ++++++++++++++++++++++++ Data/Serialization/ISerializedObject.cs | 5 + Data/Serialization/ITypeConverter.cs | 8 ++ TweetDuck.csproj | 3 + tests/Data/TestFileSerializer.cs | 45 +++++++++ tests/UnitTests.csproj | 1 + 6 files changed, 191 insertions(+) create mode 100644 Data/Serialization/FileSerializer.cs create mode 100644 Data/Serialization/ISerializedObject.cs create mode 100644 Data/Serialization/ITypeConverter.cs create mode 100644 tests/Data/TestFileSerializer.cs diff --git a/Data/Serialization/FileSerializer.cs b/Data/Serialization/FileSerializer.cs new file mode 100644 index 00000000..11317da0 --- /dev/null +++ b/Data/Serialization/FileSerializer.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; + +namespace TweetDuck.Data.Serialization{ + class FileSerializer<T> where T : ISerializedObject{ + private const string NewLineReal = "\r\n"; + private const string NewLineCustom = "\r~\n"; + + private static readonly ITypeConverter BasicSerializerObj = new BasicSerializer(); + + private readonly Dictionary<string, PropertyInfo> props; + private readonly Dictionary<Type, ITypeConverter> serializers; + + public FileSerializer(){ + this.props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.CanWrite).ToDictionary(prop => prop.Name); + this.serializers = new Dictionary<Type, ITypeConverter>(); + } + + public void RegisterSerializer(Type type, ITypeConverter serializer){ + serializers[type] = serializer; + } + + 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 (!serializers.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))){ + 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 (!serializers.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 BasicSerializer : 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/TweetDuck.csproj b/TweetDuck.csproj index bab4a95c..9376e6bd 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -201,7 +201,10 @@ <SubType>Form</SubType> </Compile> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.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\TwoKeyDictionary.cs" /> <Compile Include="Data\WindowState.cs" /> <Compile Include="Core\Utils\WindowsUtils.cs" /> diff --git a/tests/Data/TestFileSerializer.cs b/tests/Data/TestFileSerializer.cs new file mode 100644 index 00000000..5454b2b5 --- /dev/null +++ b/tests/Data/TestFileSerializer.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TweetDuck.Data.Serialization; + +namespace UnitTests.Data{ + [TestClass] + public class TestFileSerializer{ + private class SerializationTestBasic : ISerializedObject{ + public bool TestBool { get; set; } + public int TestInt { get; set; } + public string TestString { get; set; } + public string TestStringNull { 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 + }; + + 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); + } + } +} diff --git a/tests/UnitTests.csproj b/tests/UnitTests.csproj index e017293a..dfb7360d 100644 --- a/tests/UnitTests.csproj +++ b/tests/UnitTests.csproj @@ -51,6 +51,7 @@ <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" /> From 38c2781cd38a539e9718c678c7eb102f3c1b5ace Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 00:53:19 +0200 Subject: [PATCH 08/17] Add an enum test to FileSerializer unit test --- tests/Data/TestFileSerializer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/Data/TestFileSerializer.cs b/tests/Data/TestFileSerializer.cs index 5454b2b5..231fc943 100644 --- a/tests/Data/TestFileSerializer.cs +++ b/tests/Data/TestFileSerializer.cs @@ -6,11 +6,16 @@ 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; @@ -25,7 +30,8 @@ public void TestBasicWriteRead(){ TestBool = true, TestInt = -100, TestString = "abc"+Environment.NewLine+"def", - TestStringNull = null + TestStringNull = null, + TestEnum = TestEnum.D }; serializer.Write("serialized_basic", write); @@ -40,6 +46,9 @@ public void TestBasicWriteRead(){ 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 } } From d431b63c27893d143e0f99ec5169f9e98d77b1e9 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 01:47:14 +0200 Subject: [PATCH 09/17] Add SingleTypeConverter and update names in FileSerializer --- Data/Serialization/FileSerializer.cs | 12 ++++----- Data/Serialization/SingleTypeConverter.cs | 31 +++++++++++++++++++++++ TweetDuck.csproj | 1 + 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 Data/Serialization/SingleTypeConverter.cs diff --git a/Data/Serialization/FileSerializer.cs b/Data/Serialization/FileSerializer.cs index 11317da0..97bef964 100644 --- a/Data/Serialization/FileSerializer.cs +++ b/Data/Serialization/FileSerializer.cs @@ -13,15 +13,15 @@ class FileSerializer<T> where T : ISerializedObject{ private static readonly ITypeConverter BasicSerializerObj = new BasicSerializer(); private readonly Dictionary<string, PropertyInfo> props; - private readonly Dictionary<Type, ITypeConverter> serializers; + 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.serializers = new Dictionary<Type, ITypeConverter>(); + this.converters = new Dictionary<Type, ITypeConverter>(); } - public void RegisterSerializer(Type type, ITypeConverter serializer){ - serializers[type] = serializer; + public void RegisterTypeConverter(Type type, ITypeConverter converter){ + converters[type] = converter; } public void Write(string file, T obj){ @@ -30,7 +30,7 @@ public void Write(string file, T obj){ Type type = prop.Value.PropertyType; object value = prop.Value.GetValue(obj); - if (!serializers.TryGetValue(type, out ITypeConverter serializer)) { + if (!converters.TryGetValue(type, out ITypeConverter serializer)) { serializer = BasicSerializerObj; } @@ -60,7 +60,7 @@ public void Read(string file, T obj){ string value = line.Substring(space+1).Replace(NewLineCustom, Environment.NewLine); if (props.TryGetValue(property, out PropertyInfo info)){ - if (!serializers.TryGetValue(info.PropertyType, out ITypeConverter serializer)) { + if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)) { serializer = BasicSerializerObj; } diff --git a/Data/Serialization/SingleTypeConverter.cs b/Data/Serialization/SingleTypeConverter.cs new file mode 100644 index 00000000..26a075c0 --- /dev/null +++ b/Data/Serialization/SingleTypeConverter.cs @@ -0,0 +1,31 @@ +using System; + +namespace TweetDuck.Data.Serialization{ + 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/TweetDuck.csproj b/TweetDuck.csproj index 9376e6bd..718dc6ed 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -205,6 +205,7 @@ <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\WindowsUtils.cs" /> From 424c0e596ca4a18de17095c6c5098abb972209b3 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 02:56:02 +0200 Subject: [PATCH 10/17] Add legacy config detection and replace UserConfig serialization with FileSerializer --- Configuration/UserConfig.cs | 142 +++++------------- Configuration/UserConfigLegacy.cs | 210 +++++++++++++++++++++++++++ Data/Serialization/FileSerializer.cs | 4 + Data/WindowState.cs | 16 +- TweetDuck.csproj | 1 + 5 files changed, 269 insertions(+), 104 deletions(-) create mode 100644 Configuration/UserConfigLegacy.cs diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index 7a47c1c8..fc54aabf 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -1,26 +1,37 @@ using System; using System.Drawing; using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; +using System.Linq; using TweetDuck.Core; using TweetDuck.Core.Controls; using TweetDuck.Core.Notification; 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 = value.Split(' ').Select(int.Parse).ToArray(); + return new Point(elements[0], elements[1]); + } + }); + + Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{ + ConvertToString = value => $"{value.Width} {value.Height}", + ConvertToObject = value => { + int[] elements = value.Split(' ').Select(int.Parse).ToArray(); + return new Size(elements[0], elements[1]); + } + }); } - - private const int CurrentFileVersion = 11; - + // START OF CONFIGURATION public WindowState BrowserWindow { get; set; } @@ -101,25 +112,18 @@ public TrayIcon.Behavior TrayBehavior{ // END OF CONFIGURATION - [field:NonSerialized] public event EventHandler MuteToggled; - - [field:NonSerialized] public event EventHandler ZoomLevelChanged; - - [field:NonSerialized] public event EventHandler TrayBehaviorChanged; - - [NonSerialized] - private string file; - - private int fileVersion; + + private readonly string file; + 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(); @@ -139,71 +143,8 @@ private UserConfig(string file){ 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; - ++fileVersion; - } - - // update the version - fileVersion = CurrentFileVersion; - Save(); + bool ISerializedObject.OnReadUnknownProperty(string property, string value){ + return false; } public bool Save(){ @@ -219,10 +160,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); @@ -231,22 +169,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; @@ -258,11 +194,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/Data/Serialization/FileSerializer.cs b/Data/Serialization/FileSerializer.cs index 97bef964..9a00ce7b 100644 --- a/Data/Serialization/FileSerializer.cs +++ b/Data/Serialization/FileSerializer.cs @@ -49,6 +49,10 @@ public void Write(string file, T obj){ 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(' '); diff --git a/Data/WindowState.cs b/Data/WindowState.cs index a29efac5..b8ece111 100644 --- a/Data/WindowState.cs +++ b/Data/WindowState.cs @@ -1,10 +1,12 @@ using System; using System.Drawing; +using System.Linq; using System.Windows.Forms; using TweetDuck.Core.Controls; +using TweetDuck.Data.Serialization; namespace TweetDuck.Data{ - [Serializable] + [Serializable] // TODO remove attribute with UserConfigLegacy 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 = value.Substring(1).Split(' ').Select(int.Parse).ToArray(); + + return new WindowState{ + rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]), + isMaximized = value[0] == 'M' + }; + } + }; } } diff --git a/TweetDuck.csproj b/TweetDuck.csproj index 718dc6ed..90560f96 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"> From 5a21d2cb104833888493b95de8b518c30dbbc7c2 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 15:52:13 +0200 Subject: [PATCH 11/17] Add StringUtils with unit tests and use it --- Configuration/UserConfig.cs | 5 +++-- Core/Handling/ContextMenuBase.cs | 6 +----- Core/Utils/BrowserUtils.cs | 7 +------ Core/Utils/StringUtils.cs | 20 ++++++++++++++++++ Data/CombinedFileStream.cs | 7 +++---- Data/WindowState.cs | 4 ++-- TweetDuck.csproj | 1 + tests/Core/TestBrowserUtils.cs | 10 --------- tests/Core/TestStringUtils.cs | 36 ++++++++++++++++++++++++++++++++ tests/UnitTests.csproj | 1 + 10 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 Core/Utils/StringUtils.cs create mode 100644 tests/Core/TestStringUtils.cs diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index fc54aabf..53ae9a68 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -5,6 +5,7 @@ using TweetDuck.Core; using TweetDuck.Core.Controls; using TweetDuck.Core.Notification; +using TweetDuck.Core.Utils; using TweetDuck.Data; using TweetDuck.Data.Serialization; @@ -18,7 +19,7 @@ static UserConfig(){ Serializer.RegisterTypeConverter(typeof(Point), new SingleTypeConverter<Point>{ ConvertToString = value => $"{value.X} {value.Y}", ConvertToObject = value => { - int[] elements = value.Split(' ').Select(int.Parse).ToArray(); + int[] elements = StringUtils.ParseInts(value, ' '); return new Point(elements[0], elements[1]); } }); @@ -26,7 +27,7 @@ static UserConfig(){ Serializer.RegisterTypeConverter(typeof(Size), new SingleTypeConverter<Size>{ ConvertToString = value => $"{value.Width} {value.Height}", ConvertToObject = value => { - int[] elements = value.Split(' ').Select(int.Parse).ToArray(); + int[] elements = StringUtils.ParseInts(value, ' '); return new Size(elements[0], elements[1]); } }); diff --git a/Core/Handling/ContextMenuBase.cs b/Core/Handling/ContextMenuBase.cs index 05325c07..beaa5248 100644 --- a/Core/Handling/ContextMenuBase.cs +++ b/Core/Handling/ContextMenuBase.cs @@ -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/Utils/BrowserUtils.cs b/Core/Utils/BrowserUtils.cs index 6a85e986..9cfe5b82 100644 --- a/Core/Utils/BrowserUtils.cs +++ b/Core/Utils/BrowserUtils.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.IO; using System.Net; -using System.Text.RegularExpressions; using System.Windows.Forms; namespace TweetDuck.Core.Utils{ @@ -63,12 +62,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/StringUtils.cs b/Core/Utils/StringUtils.cs new file mode 100644 index 00000000..53cd89de --- /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").ToUpperInvariant(); + } + } +} diff --git a/Data/CombinedFileStream.cs b/Data/CombinedFileStream.cs index 48fab228..061f4e59 100644 --- a/Data/CombinedFileStream.cs +++ b/Data/CombinedFileStream.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Text; +using TweetDuck.Core.Utils; namespace TweetDuck.Data{ class CombinedFileStream : IDisposable{ @@ -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/Data/WindowState.cs b/Data/WindowState.cs index b8ece111..ce8b8efe 100644 --- a/Data/WindowState.cs +++ b/Data/WindowState.cs @@ -1,8 +1,8 @@ using System; using System.Drawing; -using System.Linq; using System.Windows.Forms; using TweetDuck.Core.Controls; +using TweetDuck.Core.Utils; using TweetDuck.Data.Serialization; namespace TweetDuck.Data{ @@ -32,7 +32,7 @@ public void Restore(Form form, bool firstTimeFullscreen){ 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 = value.Substring(1).Split(' ').Select(int.Parse).ToArray(); + int[] elements = StringUtils.ParseInts(value.Substring(1), ' '); return new WindowState{ rect = new Rectangle(elements[0], elements[1], elements[2], elements[3]), diff --git a/TweetDuck.csproj b/TweetDuck.csproj index 90560f96..916ce771 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -165,6 +165,7 @@ <Compile Include="Core\Other\Settings\Dialogs\DialogSettingsRestart.Designer.cs"> <DependentUpon>DialogSettingsRestart.cs</DependentUpon> </Compile> + <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" /> diff --git a/tests/Core/TestBrowserUtils.cs b/tests/Core/TestBrowserUtils.cs index c71e0f65..2c7a9fff 100644 --- a/tests/Core/TestBrowserUtils.cs +++ b/tests/Core/TestBrowserUtils.cs @@ -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/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/UnitTests.csproj b/tests/UnitTests.csproj index dfb7360d..a5bb36ad 100644 --- a/tests/UnitTests.csproj +++ b/tests/UnitTests.csproj @@ -47,6 +47,7 @@ </Otherwise> </Choose> <ItemGroup> + <Compile Include="Core\TestStringUtils.cs" /> <Compile Include="Data\TestCombinedFileStream.cs" /> <Compile Include="Core\TestBrowserUtils.cs" /> <Compile Include="Data\TestCommandLineArgs.cs" /> From c63e6a1e495aa7b8e3cb13bb762d5aa475c2fad2 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 16:15:10 +0200 Subject: [PATCH 12/17] More refactoring (seal classes, fix names and comments) --- Configuration/UserConfig.cs | 1 - Data/CombinedFileStream.cs | 2 +- Data/CommandLineArgs.cs | 2 +- Data/InjectedHTML.cs | 2 +- Data/Serialization/FileSerializer.cs | 6 +++--- Data/Serialization/SingleTypeConverter.cs | 2 +- Data/TwoKeyDictionary.cs | 2 +- Data/WindowState.cs | 2 +- Plugins/Plugin.cs | 2 +- Plugins/PluginBridge.cs | 2 +- Plugins/PluginScriptGenerator.cs | 2 +- Reporter.cs | 2 +- Updates/UpdateInfo.cs | 2 +- Updates/UpdaterSettings.cs | 2 +- 14 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index 53ae9a68..1aa10751 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -1,7 +1,6 @@ using System; using System.Drawing; using System.IO; -using System.Linq; using TweetDuck.Core; using TweetDuck.Core.Controls; using TweetDuck.Core.Notification; diff --git a/Data/CombinedFileStream.cs b/Data/CombinedFileStream.cs index 061f4e59..223bded6 100644 --- a/Data/CombinedFileStream.cs +++ b/Data/CombinedFileStream.cs @@ -4,7 +4,7 @@ using TweetDuck.Core.Utils; namespace TweetDuck.Data{ - class CombinedFileStream : IDisposable{ + sealed class CombinedFileStream : IDisposable{ public const char KeySeparator = '|'; private readonly Stream stream; diff --git a/Data/CommandLineArgs.cs b/Data/CommandLineArgs.cs index e81a886b..0f525993 100644 --- a/Data/CommandLineArgs.cs +++ b/Data/CommandLineArgs.cs @@ -2,7 +2,7 @@ using System.Text; namespace TweetDuck.Data{ - class CommandLineArgs{ + sealed class CommandLineArgs{ public static CommandLineArgs FromStringArray(char entryChar, string[] array){ CommandLineArgs args = new CommandLineArgs(); ReadStringArray(entryChar, array, args); diff --git a/Data/InjectedHTML.cs b/Data/InjectedHTML.cs index 7268f987..6b797b48 100644 --- a/Data/InjectedHTML.cs +++ b/Data/InjectedHTML.cs @@ -1,7 +1,7 @@ using System; namespace TweetDuck.Data{ - class InjectedHTML{ + sealed class InjectedHTML{ public enum Position{ Before, After } diff --git a/Data/Serialization/FileSerializer.cs b/Data/Serialization/FileSerializer.cs index 9a00ce7b..052ad227 100644 --- a/Data/Serialization/FileSerializer.cs +++ b/Data/Serialization/FileSerializer.cs @@ -6,11 +6,11 @@ using System.Runtime.Serialization; namespace TweetDuck.Data.Serialization{ - class FileSerializer<T> where T : ISerializedObject{ + 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 BasicSerializer(); + private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); private readonly Dictionary<string, PropertyInfo> props; private readonly Dictionary<Type, ITypeConverter> converters; @@ -82,7 +82,7 @@ public void Read(string file, T obj){ } } - private class BasicSerializer : ITypeConverter{ + private class BasicTypeConverter : ITypeConverter{ bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ switch(Type.GetTypeCode(type)){ case TypeCode.Boolean: diff --git a/Data/Serialization/SingleTypeConverter.cs b/Data/Serialization/SingleTypeConverter.cs index 26a075c0..182a949d 100644 --- a/Data/Serialization/SingleTypeConverter.cs +++ b/Data/Serialization/SingleTypeConverter.cs @@ -1,7 +1,7 @@ using System; namespace TweetDuck.Data.Serialization{ - class SingleTypeConverter<T> : ITypeConverter{ + sealed class SingleTypeConverter<T> : ITypeConverter{ public delegate string FuncConvertToString<U>(U value); public delegate U FuncConvertToObject<U>(string value); diff --git a/Data/TwoKeyDictionary.cs b/Data/TwoKeyDictionary.cs index 5655b68c..cb96265d 100644 --- a/Data/TwoKeyDictionary.cs +++ b/Data/TwoKeyDictionary.cs @@ -2,7 +2,7 @@ using System.Linq; namespace TweetDuck.Data{ - class TwoKeyDictionary<K1, K2, V>{ + sealed class TwoKeyDictionary<K1, K2, V>{ private readonly Dictionary<K1, Dictionary<K2, V>> dict; private readonly int innerCapacity; diff --git a/Data/WindowState.cs b/Data/WindowState.cs index ce8b8efe..9ec75951 100644 --- a/Data/WindowState.cs +++ b/Data/WindowState.cs @@ -7,7 +7,7 @@ namespace TweetDuck.Data{ [Serializable] // TODO remove attribute with UserConfigLegacy - class WindowState{ + sealed class WindowState{ private Rectangle rect; private bool isMaximized; diff --git a/Plugins/Plugin.cs b/Plugins/Plugin.cs index e5ccdecc..9cbc48b6 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; } diff --git a/Plugins/PluginBridge.cs b/Plugins/PluginBridge.cs index 91dee055..fc384b8d 100644 --- a/Plugins/PluginBridge.cs +++ b/Plugins/PluginBridge.cs @@ -7,7 +7,7 @@ using TweetDuck.Plugins.Events; namespace TweetDuck.Plugins{ - class PluginBridge{ + sealed class PluginBridge{ private static string SanitizeCacheKey(string key){ return key.Replace('\\', '/').Trim(); } diff --git a/Plugins/PluginScriptGenerator.cs b/Plugins/PluginScriptGenerator.cs index 0b18c8bb..d90afb15 100644 --- a/Plugins/PluginScriptGenerator.cs +++ b/Plugins/PluginScriptGenerator.cs @@ -19,7 +19,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/Reporter.cs b/Reporter.cs index bcd1deca..940a8533 100644 --- a/Reporter.cs +++ b/Reporter.cs @@ -8,7 +8,7 @@ using TweetDuck.Core.Other; namespace TweetDuck{ - class Reporter{ + sealed class Reporter{ private readonly string logFile; public Reporter(string logFile){ 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; } From 8de7e13aa3de0d03e2f1e4bf505f53a2dea9510f Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 19:22:33 +0200 Subject: [PATCH 13/17] Reorganize and refactor UserConfig and PluginConfig --- Configuration/UserConfig.cs | 107 ++++++++++++++++-------------------- Plugins/PluginConfig.cs | 6 +- 2 files changed, 49 insertions(+), 64 deletions(-) diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index 1aa10751..956f8997 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -32,115 +32,102 @@ static UserConfig(){ }); } - // START OF CONFIGURATION + // CONFIGURATION DATA - public WindowState BrowserWindow { get; set; } - public WindowState PluginsWindow { get; set; } + public WindowState BrowserWindow { get; set; } = new WindowState(); + public WindowState PluginsWindow { get; set; } = new WindowState(); - 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 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 int NotificationIdlePauseSeconds { get; set; } - public int NotificationDurationValue { get; set; } - public int NotificationScrollSpeed { get; set; } + public bool EnableUpdateCheck { get; set; } = true; + public string DismissedUpdate { get; set; } = null; - public TweetNotification.Position NotificationPosition { get; set; } - public Point CustomNotificationPosition { get; set; } - public int NotificationEdgeDistance { get; set; } - public int NotificationDisplay { 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 TweetNotification.Size NotificationSize { get; set; } - public Size CustomNotificationSize { get; set; } + public bool DisplayNotificationTimer { get; set; } = true; + public bool NotificationTimerCountDown { get; set; } = false; + public int NotificationDurationValue { get; set; } = 25; - public bool EnableSpellCheck { get; set; } - public bool ExpandLinksOnHover { get; set; } - public bool SwitchAccountSelectors { get; set; } - public bool EnableTrayHighlight { 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 bool EnableUpdateCheck { get; set; } - public string DismissedUpdate { 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 string CustomCefArgs { get; set; } - public string CustomBrowserCSS { get; set; } - public string CustomNotificationCSS { get; set; } + private string _notificationSoundPath; + + 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 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 public event EventHandler MuteToggled; public event EventHandler ZoomLevelChanged; public event EventHandler TrayBehaviorChanged; private readonly string file; - - private bool muteNotifications; - private int zoomLevel; - private string notificationSoundPath; - private TrayIcon.Behavior trayBehavior; 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; - EnableUpdateCheck = true; - ExpandLinksOnHover = true; - SwitchAccountSelectors = true; - EnableTrayHighlight = true; - PluginsWindow = new WindowState(); } bool ISerializedObject.OnReadUnknownProperty(string property, string value){ 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){ From 9811f40a53c0479ac03f54e20ad162f987c027ad Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 22:56:36 +0200 Subject: [PATCH 14/17] Go fuck yourself CurrentCulture and stop messing with string interpolation --- Core/Utils/BrowserUtils.cs | 3 +-- Program.cs | 7 +++++++ Reporter.cs | 3 +-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Core/Utils/BrowserUtils.cs b/Core/Utils/BrowserUtils.cs index 9cfe5b82..672ab1b3 100644 --- a/Core/Utils/BrowserUtils.cs +++ b/Core/Utils/BrowserUtils.cs @@ -2,7 +2,6 @@ using System; using System.Diagnostics; using System.Drawing; -using System.Globalization; using System.IO; using System.Net; using System.Windows.Forms; @@ -13,7 +12,7 @@ static class BrowserUtils{ public static string HeaderAcceptLanguage{ get{ - string culture = CultureInfo.CurrentCulture.Name; + string culture = Program.Culture.Name; if (culture == "en"){ return "en-us,en"; diff --git a/Program.cs b/Program.cs index 232ff4e4..b2a4bb78 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; @@ -51,9 +52,15 @@ 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 CultureInfo Culture { get; } public static event EventHandler UserConfigReplaced; + static Program(){ + Culture = CultureInfo.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; + [STAThread] private static void Main(){ Application.EnableVisualStyles(); diff --git a/Reporter.cs b/Reporter.cs index 940a8533..42c2e326 100644 --- a/Reporter.cs +++ b/Reporter.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using System.Drawing; -using System.Globalization; using System.IO; using System.Text; using System.Windows.Forms; @@ -32,7 +31,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{ From eb8159ca0f0714c48b3b7b890d9f4a0d165899f2 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 23:49:57 +0200 Subject: [PATCH 15/17] Add a tooltip to text box in the Sounds tab in Options --- Core/Other/Settings/TabSettingsSounds.Designer.cs | 1 + 1 file changed, 1 insertion(+) 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 // From 14d44528b09043731d72f0833879c4ad223034f6 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Fri, 7 Jul 2017 23:53:04 +0200 Subject: [PATCH 16/17] Fuck CultureInfo some more and fix analysis violations (dispose pattern, lang features) --- Core/FormBrowser.cs | 2 +- Core/Handling/ContextMenuBase.cs | 2 +- Core/Notification/FormNotificationMain.cs | 8 ++------ Core/Other/Settings/TabSettingsNotifications.cs | 8 ++++---- Core/Utils/BrowserUtils.cs | 2 +- Core/Utils/StringUtils.cs | 2 +- Data/CommandLineArgs.cs | 14 +++++++------- Plugins/Plugin.cs | 5 ++--- Plugins/PluginScriptGenerator.cs | 5 ++--- Program.cs | 13 +++++++------ Reporter.cs | 4 +--- lib/TweetLib.Audio/AudioPlayer.cs | 7 ++++++- lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs | 2 +- lib/TweetLib.Audio/Impl/SoundPlayerImplWMP.cs | 2 +- lib/TweetLib.Audio/Utils/NativeCoreAudio.cs | 4 +--- 15 files changed, 38 insertions(+), 42 deletions(-) diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index b920d814..5dba72a2 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -198,7 +198,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){ diff --git a/Core/Handling/ContextMenuBase.cs b/Core/Handling/ContextMenuBase.cs index beaa5248..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"); } diff --git a/Core/Notification/FormNotificationMain.cs b/Core/Notification/FormNotificationMain.cs index eec4cc0d..7e6e9a41 100644 --- a/Core/Notification/FormNotificationMain.cs +++ b/Core/Notification/FormNotificationMain.cs @@ -18,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/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/Utils/BrowserUtils.cs b/Core/Utils/BrowserUtils.cs index 672ab1b3..b8f4213d 100644 --- a/Core/Utils/BrowserUtils.cs +++ b/Core/Utils/BrowserUtils.cs @@ -18,7 +18,7 @@ public static string HeaderAcceptLanguage{ return "en-us,en"; } else{ - return culture.ToLowerInvariant()+",en;q=0.9"; + return culture.ToLower()+",en;q=0.9"; } } } diff --git a/Core/Utils/StringUtils.cs b/Core/Utils/StringUtils.cs index 53cd89de..13e1f36c 100644 --- a/Core/Utils/StringUtils.cs +++ b/Core/Utils/StringUtils.cs @@ -14,7 +14,7 @@ public static int[] ParseInts(string str, char separator){ } 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(); + return Regex.Replace(str, @"(\p{Ll})(\P{Ll})|(\P{Ll})(\P{Ll}\p{Ll})", "$1$3_$2$4").ToUpper(); } } } diff --git a/Data/CommandLineArgs.cs b/Data/CommandLineArgs.cs index 0f525993..0caa437f 100644 --- a/Data/CommandLineArgs.cs +++ b/Data/CommandLineArgs.cs @@ -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/Plugins/Plugin.cs b/Plugins/Plugin.cs index 9cbc48b6..1d738815 100644 --- a/Plugins/Plugin.cs +++ b/Plugins/Plugin.cs @@ -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/PluginScriptGenerator.cs b/Plugins/PluginScriptGenerator.cs index d90afb15..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); } diff --git a/Program.cs b/Program.cs index b2a4bb78..d43f04a4 100644 --- a/Program.cs +++ b/Program.cs @@ -51,7 +51,7 @@ 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; @@ -61,21 +61,22 @@ static Program(){ 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"); if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){ 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(); diff --git a/Reporter.cs b/Reporter.cs index 42c2e326..e6803cd3 100644 --- a/Reporter.cs +++ b/Reporter.cs @@ -16,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); } }; diff --git a/lib/TweetLib.Audio/AudioPlayer.cs b/lib/TweetLib.Audio/AudioPlayer.cs index 70730f45..7031a8cd 100644 --- a/lib/TweetLib.Audio/AudioPlayer.cs +++ b/lib/TweetLib.Audio/AudioPlayer.cs @@ -31,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 9bc73f93..df7b86a8 100644 --- a/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs +++ b/lib/TweetLib.Audio/Impl/SoundPlayerImplFallback.cs @@ -38,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/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)){ From 0ded03ab9261abd4200fc3373f276ea9e8082681 Mon Sep 17 00:00:00 2001 From: chylex <contact@chylex.com> Date: Sat, 8 Jul 2017 00:21:41 +0200 Subject: [PATCH 17/17] Fix more analysis violations (exceptions, native method pointers, form disposal) --- Core/Controls/ControlExtensions.cs | 2 +- Core/Utils/NativeMethods.cs | 4 ++-- Plugins/PluginBridge.cs | 10 +++++----- Program.cs | 2 +- Reporter.cs | 7 ++++--- 5 files changed, 13 insertions(+), 12 deletions(-) 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/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/Plugins/PluginBridge.cs b/Plugins/PluginBridge.cs index fc384b8d..e2f6665d 100644 --- a/Plugins/PluginBridge.cs +++ b/Plugins/PluginBridge.cs @@ -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/Program.cs b/Program.cs index d43f04a4..0c228a8b 100644 --- a/Program.cs +++ b/Program.cs @@ -109,7 +109,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 e6803cd3..ef3d70c9 100644 --- a/Reporter.cs +++ b/Reporter.cs @@ -84,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();