diff --git a/Configuration/Arguments.cs b/Configuration/Arguments.cs index 9764f610..38e653bf 100644 --- a/Configuration/Arguments.cs +++ b/Configuration/Arguments.cs @@ -1,5 +1,5 @@ using System; -using TweetDuck.Data; +using TweetLib.Core.Collections; namespace TweetDuck.Configuration{ static class Arguments{ @@ -22,8 +22,8 @@ public static bool HasFlag(string flag){ return Current.HasFlag(flag); } - public static string GetValue(string key, string defaultValue){ - return Current.GetValue(key, defaultValue); + public static string GetValue(string key){ + return Current.GetValue(key); } public static CommandLineArgs GetCurrentClean(){ diff --git a/Configuration/ConfigManager.cs b/Configuration/ConfigManager.cs index d5145ba6..547f3413 100644 --- a/Configuration/ConfigManager.cs +++ b/Configuration/ConfigManager.cs @@ -1,13 +1,13 @@ using System; -using System.Collections.Generic; using System.Drawing; -using TweetDuck.Configuration.Instance; -using TweetDuck.Core.Utils; using TweetDuck.Data; -using TweetDuck.Data.Serialization; +using TweetLib.Core.Features.Configuration; +using TweetLib.Core.Features.Plugins.Config; +using TweetLib.Core.Serialization.Converters; +using TweetLib.Core.Utils; namespace TweetDuck.Configuration{ - sealed class ConfigManager{ + sealed class ConfigManager : IConfigManager{ public UserConfig User { get; } public SystemConfig System { get; } public PluginConfig Plugins { get; } @@ -16,7 +16,7 @@ sealed class ConfigManager{ private readonly FileConfigInstance<UserConfig> infoUser; private readonly FileConfigInstance<SystemConfig> infoSystem; - private readonly PluginConfigInstance infoPlugins; + private readonly PluginConfigInstance<PluginConfig> infoPlugins; private readonly IConfigInstance<BaseConfig>[] infoList; @@ -28,7 +28,7 @@ public ConfigManager(){ infoList = new IConfigInstance<BaseConfig>[]{ infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"), infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system options"), - infoPlugins = new PluginConfigInstance(Program.PluginConfigFilePath, Plugins) + infoPlugins = new PluginConfigInstance<PluginConfig>(Program.PluginConfigFilePath, Plugins) }; // TODO refactor further @@ -70,59 +70,13 @@ public void ReloadAll(){ infoPlugins.Reload(); } - private void TriggerProgramRestartRequested(){ + void IConfigManager.TriggerProgramRestartRequested(){ ProgramRestartRequested?.Invoke(this, EventArgs.Empty); } - private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){ + IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){ Type instanceType = instance.GetType(); return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null } - - public abstract class BaseConfig{ - private readonly ConfigManager configManager; - - protected BaseConfig(ConfigManager configManager){ - this.configManager = configManager; - } - - // Management - - public void Save(){ - configManager.GetInstanceInfo(this).Save(); - } - - public void Reload(){ - configManager.GetInstanceInfo(this).Reload(); - } - - public void Reset(){ - configManager.GetInstanceInfo(this).Reset(); - } - - // Construction methods - - public T ConstructWithDefaults<T>() where T : BaseConfig{ - return ConstructWithDefaults(configManager) as T; - } - - protected abstract BaseConfig ConstructWithDefaults(ConfigManager configManager); - - // Utility methods - - protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){ - if (!EqualityComparer<T>.Default.Equals(field, value)){ - field = value; - eventHandler?.Invoke(this, EventArgs.Empty); - } - } - - protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){ - if (!EqualityComparer<T>.Default.Equals(field, value)){ - field = value; - configManager.TriggerProgramRestartRequested(); - } - } - } } } diff --git a/Configuration/Instance/PluginConfigInstance.cs b/Configuration/Instance/PluginConfigInstance.cs deleted file mode 100644 index 20fe9aea..00000000 --- a/Configuration/Instance/PluginConfigInstance.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace TweetDuck.Configuration.Instance{ - class PluginConfigInstance : IConfigInstance<PluginConfig>{ - public PluginConfig Instance { get; } - - private readonly string filename; - - public PluginConfigInstance(string filename, PluginConfig instance){ - this.filename = filename; - this.Instance = instance; - } - - public void Load(){ - try{ - using(StreamReader reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)){ - string line = reader.ReadLine(); - - if (line == "#Disabled"){ - HashSet<string> newDisabled = new HashSet<string>(); - - while((line = reader.ReadLine()) != null){ - newDisabled.Add(line); - } - - Instance.ReloadSilently(newDisabled); - } - } - }catch(FileNotFoundException){ - }catch(DirectoryNotFoundException){ - }catch(Exception e){ - Program.Reporter.HandleException("Plugin Configuration Error", "Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", true, e); - } - } - - public void Save(){ - try{ - using(StreamWriter writer = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8)){ - writer.WriteLine("#Disabled"); - - foreach(string identifier in Instance.DisabledPlugins){ - writer.WriteLine(identifier); - } - } - }catch(Exception e){ - Program.Reporter.HandleException("Plugin Configuration Error", "Could not save the plugin configuration file.", true, e); - } - } - - public void Reload(){ - Load(); - } - - public void Reset(){ - try{ - File.Delete(filename); - Instance.ReloadSilently(Instance.ConstructWithDefaults<PluginConfig>().DisabledPlugins); - }catch(Exception e){ - Program.Reporter.HandleException("Plugin Configuration Error", "Could not delete the plugin configuration file.", true, e); - return; - } - - Reload(); - } - } -} diff --git a/Configuration/PluginConfig.cs b/Configuration/PluginConfig.cs index f1940ac1..b9d3d284 100644 --- a/Configuration/PluginConfig.cs +++ b/Configuration/PluginConfig.cs @@ -1,20 +1,41 @@ using System; using System.Collections.Generic; -using TweetDuck.Plugins; -using TweetDuck.Plugins.Events; +using TweetLib.Core.Features.Configuration; +using TweetLib.Core.Features.Plugins; +using TweetLib.Core.Features.Plugins.Config; +using TweetLib.Core.Features.Plugins.Events; namespace TweetDuck.Configuration{ - sealed class PluginConfig : ConfigManager.BaseConfig, IPluginConfig{ + sealed class PluginConfig : BaseConfig, IPluginConfig{ private static readonly string[] DefaultDisabled = { "official/clear-columns", "official/reply-account" }; - // CONFIGURATION + // CONFIGURATION DATA - public IEnumerable<string> DisabledPlugins => disabled; + private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled); + + // EVENTS public event EventHandler<PluginChangedStateEventArgs> PluginChangedState; + + // END OF CONFIG + + public PluginConfig(IConfigManager configManager) : base(configManager){} + + protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){ + return new PluginConfig(configManager); + } + + // INTERFACE IMPLEMENTATION + + IEnumerable<string> IPluginConfig.DisabledPlugins => disabled; + + void IPluginConfig.Reset(IEnumerable<string> newDisabledPlugins){ + disabled.Clear(); + disabled.UnionWith(newDisabledPlugins); + } public void SetEnabled(Plugin plugin, bool enabled){ if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){ @@ -26,20 +47,5 @@ public void SetEnabled(Plugin plugin, bool enabled){ public bool IsEnabled(Plugin plugin){ return !disabled.Contains(plugin.Identifier); } - - public void ReloadSilently(IEnumerable<string> newDisabled){ - disabled.Clear(); - disabled.UnionWith(newDisabled); - } - - private readonly HashSet<string> disabled = new HashSet<string>(DefaultDisabled); - - // END OF CONFIG - - public PluginConfig(ConfigManager configManager) : base(configManager){} - - protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){ - return new PluginConfig(configManager); - } } } diff --git a/Configuration/SystemConfig.cs b/Configuration/SystemConfig.cs index c7f8ed60..3377cafa 100644 --- a/Configuration/SystemConfig.cs +++ b/Configuration/SystemConfig.cs @@ -1,5 +1,7 @@ -namespace TweetDuck.Configuration{ - sealed class SystemConfig : ConfigManager.BaseConfig{ +using TweetLib.Core.Features.Configuration; + +namespace TweetDuck.Configuration{ + sealed class SystemConfig : BaseConfig{ // CONFIGURATION DATA @@ -17,9 +19,9 @@ public bool HardwareAcceleration{ // END OF CONFIG - public SystemConfig(ConfigManager configManager) : base(configManager){} + public SystemConfig(IConfigManager configManager) : base(configManager){} - protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){ + protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){ return new SystemConfig(configManager); } } diff --git a/Configuration/UserConfig.cs b/Configuration/UserConfig.cs index 35195034..6f2c861b 100644 --- a/Configuration/UserConfig.cs +++ b/Configuration/UserConfig.cs @@ -5,9 +5,10 @@ using TweetDuck.Core.Other; using TweetDuck.Core.Utils; using TweetDuck.Data; +using TweetLib.Core.Features.Configuration; namespace TweetDuck.Configuration{ - sealed class UserConfig : ConfigManager.BaseConfig{ + sealed class UserConfig : BaseConfig{ // CONFIGURATION DATA @@ -135,9 +136,9 @@ public string SpellCheckLanguage{ // END OF CONFIG - public UserConfig(ConfigManager configManager) : base(configManager){} + public UserConfig(IConfigManager configManager) : base(configManager){} - protected override ConfigManager.BaseConfig ConstructWithDefaults(ConfigManager configManager){ + protected override BaseConfig ConstructWithDefaults(IConfigManager configManager){ return new UserConfig(configManager); } } diff --git a/Core/Bridge/TweetDeckBridge.cs b/Core/Bridge/TweetDeckBridge.cs index d71ebc94..7452ce0e 100644 --- a/Core/Bridge/TweetDeckBridge.cs +++ b/Core/Bridge/TweetDeckBridge.cs @@ -1,4 +1,5 @@ -using System.Windows.Forms; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Forms; using TweetDuck.Core.Controls; using TweetDuck.Core.Management; using TweetDuck.Core.Notification; @@ -6,6 +7,7 @@ using TweetDuck.Core.Utils; namespace TweetDuck.Core.Bridge{ + [SuppressMessage("ReSharper", "UnusedMember.Global")] class TweetDeckBridge{ public static string FontSize { get; private set; } public static string NotificationHeadLayout { get; private set; } @@ -63,7 +65,7 @@ public void DisplayTooltip(string text){ } // Notification only - + public sealed class Notification : TweetDeckBridge{ public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){} diff --git a/Core/Bridge/UpdateBridge.cs b/Core/Bridge/UpdateBridge.cs index f2b72680..139e731a 100644 --- a/Core/Bridge/UpdateBridge.cs +++ b/Core/Bridge/UpdateBridge.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Windows.Forms; using TweetDuck.Core.Controls; -using TweetDuck.Updates; +using TweetLib.Core.Features.Updates; namespace TweetDuck.Core.Bridge{ + [SuppressMessage("ReSharper", "UnusedMember.Global")] class UpdateBridge{ private readonly UpdateHandler updates; private readonly Control sync; diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index 510db7be..bade752a 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -1,5 +1,6 @@ using System; using System.Drawing; +using System.Threading.Tasks; using System.Windows.Forms; using TweetDuck.Configuration; using TweetDuck.Core.Bridge; @@ -14,9 +15,10 @@ using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Utils; using TweetDuck.Plugins; -using TweetDuck.Plugins.Events; using TweetDuck.Resources; using TweetDuck.Updates; +using TweetLib.Core.Features.Plugins.Events; +using TweetLib.Core.Features.Updates; namespace TweetDuck.Core{ sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{ @@ -71,7 +73,7 @@ public FormBrowser(){ this.notification = new FormNotificationTweet(this, plugins); this.notification.Show(); - this.updates = new UpdateHandler(Program.InstallerPath); + this.updates = new UpdateHandler(new UpdateCheckClient(Program.InstallerPath), TaskScheduler.FromCurrentSynchronizationContext()); this.updates.CheckFinished += updates_CheckFinished; this.updateBridge = new UpdateBridge(updates, this); diff --git a/Core/FormManager.cs b/Core/FormManager.cs index eab58f45..42e23b2c 100644 --- a/Core/FormManager.cs +++ b/Core/FormManager.cs @@ -17,8 +17,6 @@ public static bool TryBringToFront<T>() where T : Form{ else return false; } - public static bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any(); - public static void CloseAllDialogs(){ foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){ ((Form)dialog).Close(); diff --git a/Core/Handling/ContextMenuBase.cs b/Core/Handling/ContextMenuBase.cs index 33265ee5..2c3f3094 100644 --- a/Core/Handling/ContextMenuBase.cs +++ b/Core/Handling/ContextMenuBase.cs @@ -12,6 +12,7 @@ using TweetDuck.Core.Other; using TweetDuck.Core.Other.Analytics; using TweetDuck.Resources; +using TweetLib.Core.Utils; namespace TweetDuck.Core.Handling{ abstract class ContextMenuBase : IContextMenuHandler{ diff --git a/Core/Management/ContextInfo.cs b/Core/Management/ContextInfo.cs index c739e82b..f9bd7834 100644 --- a/Core/Management/ContextInfo.cs +++ b/Core/Management/ContextInfo.cs @@ -1,6 +1,6 @@ using System; using CefSharp; -using TweetDuck.Core.Utils; +using TweetLib.Core.Utils; namespace TweetDuck.Core.Management{ sealed class ContextInfo{ @@ -107,7 +107,7 @@ public sealed class Builder{ private string unsafeLinkUrl = string.Empty; private string mediaUrl = string.Empty; - private ChirpInfo chirp = default(ChirpInfo); + private ChirpInfo chirp = default; public void AddContext(IContextMenuParams parameters){ ContextMenuType flags = parameters.TypeFlags; diff --git a/Core/Management/ProfileManager.cs b/Core/Management/ProfileManager.cs index 2abd7548..f6935599 100644 --- a/Core/Management/ProfileManager.cs +++ b/Core/Management/ProfileManager.cs @@ -3,9 +3,10 @@ using System.IO; using System.Linq; using TweetDuck.Core.Other; -using TweetDuck.Data; using TweetDuck.Plugins; -using TweetDuck.Plugins.Enums; +using TweetLib.Core.Data; +using TweetLib.Core.Features.Plugins; +using TweetLib.Core.Features.Plugins.Enums; namespace TweetDuck.Core.Management{ sealed class ProfileManager{ diff --git a/Core/Notification/FormNotificationMain.cs b/Core/Notification/FormNotificationMain.cs index 1b553343..ba0897e0 100644 --- a/Core/Notification/FormNotificationMain.cs +++ b/Core/Notification/FormNotificationMain.cs @@ -6,10 +6,10 @@ using TweetDuck.Core.Controls; using TweetDuck.Core.Handling; using TweetDuck.Core.Utils; -using TweetDuck.Data; using TweetDuck.Plugins; -using TweetDuck.Plugins.Enums; using TweetDuck.Resources; +using TweetLib.Core.Data; +using TweetLib.Core.Features.Plugins.Enums; namespace TweetDuck.Core.Notification{ abstract partial class FormNotificationMain : FormNotificationBase{ diff --git a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs index 9d12c474..5be3454d 100644 --- a/Core/Notification/Screenshot/FormNotificationScreenshotable.cs +++ b/Core/Notification/Screenshot/FormNotificationScreenshotable.cs @@ -6,9 +6,9 @@ using TweetDuck.Core.Controls; using TweetDuck.Core.Other; using TweetDuck.Core.Utils; -using TweetDuck.Data; using TweetDuck.Plugins; using TweetDuck.Resources; +using TweetLib.Core.Data; namespace TweetDuck.Core.Notification.Screenshot{ sealed class FormNotificationScreenshotable : FormNotificationBase{ diff --git a/Core/Notification/Screenshot/ScreenshotBridge.cs b/Core/Notification/Screenshot/ScreenshotBridge.cs index eeb841ff..873f6dc9 100644 --- a/Core/Notification/Screenshot/ScreenshotBridge.cs +++ b/Core/Notification/Screenshot/ScreenshotBridge.cs @@ -1,8 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Windows.Forms; using TweetDuck.Core.Controls; namespace TweetDuck.Core.Notification.Screenshot{ + [SuppressMessage("ReSharper", "UnusedMember.Global")] sealed class ScreenshotBridge{ private readonly Control owner; diff --git a/Core/Other/Analytics/AnalyticsFile.cs b/Core/Other/Analytics/AnalyticsFile.cs index a83f4d16..72b6c8e8 100644 --- a/Core/Other/Analytics/AnalyticsFile.cs +++ b/Core/Other/Analytics/AnalyticsFile.cs @@ -2,7 +2,8 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; -using TweetDuck.Data.Serialization; +using TweetLib.Core.Serialization; +using TweetLib.Core.Serialization.Converters; namespace TweetDuck.Core.Other.Analytics{ [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")] diff --git a/Core/Other/Analytics/AnalyticsManager.cs b/Core/Other/Analytics/AnalyticsManager.cs index d8c41ae6..6eae23a2 100644 --- a/Core/Other/Analytics/AnalyticsManager.cs +++ b/Core/Other/Analytics/AnalyticsManager.cs @@ -9,6 +9,8 @@ using TweetDuck.Core.Controls; using TweetDuck.Core.Utils; using TweetDuck.Plugins; +using TweetLib.Core; +using TweetLib.Core.Utils; namespace TweetDuck.Core.Other.Analytics{ sealed class AnalyticsManager : IDisposable{ @@ -20,7 +22,7 @@ sealed class AnalyticsManager : IDisposable{ #else "https://tweetduck.chylex.com/breadcrumb/report" #endif - ); + ); public AnalyticsFile File { get; } @@ -80,7 +82,7 @@ private void ScheduleReportIn(TimeSpan delay, string message = null){ private void SetLastDataCollectionTime(DateTime dt, string message = null){ File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind); File.LastCollectionVersion = Program.VersionTag; - File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture); + File.LastCollectionMessage = message ?? dt.ToString("g", Lib.Culture); File.Save(); RestartTimer(); @@ -117,7 +119,7 @@ private void SendReport(){ System.Diagnostics.Debugger.Break(); #endif - BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection()); + WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection()); }).ContinueWith(task => browser.InvokeAsyncSafe(() => { if (task.Status == TaskStatus.RanToCompletion){ SetLastDataCollectionTime(DateTime.Now); diff --git a/Core/Other/Analytics/AnalyticsReportGenerator.cs b/Core/Other/Analytics/AnalyticsReportGenerator.cs index 77bb0cd9..e4345d7d 100644 --- a/Core/Other/Analytics/AnalyticsReportGenerator.cs +++ b/Core/Other/Analytics/AnalyticsReportGenerator.cs @@ -11,7 +11,10 @@ using TweetDuck.Core.Notification; using TweetDuck.Core.Utils; using TweetDuck.Plugins; -using TweetDuck.Plugins.Enums; +using TweetLib.Core; +using TweetLib.Core.Features.Plugins; +using TweetLib.Core.Features.Plugins.Enums; +using TweetLib.Core.Utils; namespace TweetDuck.Core.Other.Analytics{ static class AnalyticsReportGenerator{ @@ -27,7 +30,7 @@ public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, Plug { "System Edition" , SystemEdition }, { "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" }, { "System Build" , SystemBuild }, - { "System Locale" , Program.Culture.Name.ToLower() }, + { "System Locale" , Lib.Culture.Name.ToLower() }, 0, { "RAM" , Exact(RamSize) }, { "GPU" , GpuVendor }, diff --git a/Core/Other/FormPlugins.cs b/Core/Other/FormPlugins.cs index 6f1ec1f5..40dd0530 100644 --- a/Core/Other/FormPlugins.cs +++ b/Core/Other/FormPlugins.cs @@ -6,6 +6,7 @@ using TweetDuck.Configuration; using TweetDuck.Plugins; using TweetDuck.Plugins.Controls; +using TweetLib.Core.Features.Plugins; namespace TweetDuck.Core.Other{ sealed partial class FormPlugins : Form, FormManager.IAppDialog{ diff --git a/Core/Other/FormSettings.cs b/Core/Other/FormSettings.cs index 59742025..1732a9bb 100644 --- a/Core/Other/FormSettings.cs +++ b/Core/Other/FormSettings.cs @@ -10,7 +10,7 @@ using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Utils; using TweetDuck.Plugins; -using TweetDuck.Updates; +using TweetLib.Core.Features.Updates; namespace TweetDuck.Core.Other{ sealed partial class FormSettings : Form, FormManager.IAppDialog{ @@ -195,7 +195,7 @@ private void control_MouseWheel(object sender, MouseEventArgs e){ private sealed class SettingsTab{ public Button Button { get; } - public BaseTabSettings Control => control ?? (control = constructor()); + public BaseTabSettings Control => control ??= constructor(); public bool IsInitialized => control != null; private readonly Func<BaseTabSettings> constructor; diff --git a/Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs b/Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs index 3cd169cf..6e6aa778 100644 --- a/Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs +++ b/Core/Other/Settings/Dialogs/DialogSettingsAnalytics.cs @@ -5,8 +5,6 @@ namespace TweetDuck.Core.Other.Settings.Dialogs{ sealed partial class DialogSettingsAnalytics : Form{ - public string CefArgs => textBoxReport.Text; - public DialogSettingsAnalytics(AnalyticsReport report){ InitializeComponent(); diff --git a/Core/Other/Settings/Dialogs/DialogSettingsCefArgs.cs b/Core/Other/Settings/Dialogs/DialogSettingsCefArgs.cs index 472357c3..64009f28 100644 --- a/Core/Other/Settings/Dialogs/DialogSettingsCefArgs.cs +++ b/Core/Other/Settings/Dialogs/DialogSettingsCefArgs.cs @@ -2,7 +2,7 @@ using System.Windows.Forms; using TweetDuck.Core.Controls; using TweetDuck.Core.Utils; -using TweetDuck.Data; +using TweetLib.Core.Collections; namespace TweetDuck.Core.Other.Settings.Dialogs{ sealed partial class DialogSettingsCefArgs : Form{ diff --git a/Core/Other/Settings/Dialogs/DialogSettingsManage.cs b/Core/Other/Settings/Dialogs/DialogSettingsManage.cs index 24169154..a4c04239 100644 --- a/Core/Other/Settings/Dialogs/DialogSettingsManage.cs +++ b/Core/Other/Settings/Dialogs/DialogSettingsManage.cs @@ -4,8 +4,8 @@ using System.Windows.Forms; using TweetDuck.Configuration; using TweetDuck.Core.Management; -using TweetDuck.Core.Utils; using TweetDuck.Plugins; +using TweetLib.Core.Utils; namespace TweetDuck.Core.Other.Settings.Dialogs{ sealed partial class DialogSettingsManage : Form{ diff --git a/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs b/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs index 9e10c3ee..6e471fe8 100644 --- a/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs +++ b/Core/Other/Settings/Dialogs/DialogSettingsRestart.cs @@ -1,7 +1,7 @@ using System; using System.Windows.Forms; using TweetDuck.Configuration; -using TweetDuck.Data; +using TweetLib.Core.Collections; namespace TweetDuck.Core.Other.Settings.Dialogs{ sealed partial class DialogSettingsRestart : Form{ @@ -18,7 +18,7 @@ public DialogSettingsRestart(CommandLineArgs currentArgs){ tbDataFolder.Enabled = false; } else{ - tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty); + tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty; tbDataFolder.TextChanged += control_Change; } diff --git a/Core/Other/Settings/TabSettingsGeneral.cs b/Core/Other/Settings/TabSettingsGeneral.cs index ffc96589..1d213110 100644 --- a/Core/Other/Settings/TabSettingsGeneral.cs +++ b/Core/Other/Settings/TabSettingsGeneral.cs @@ -6,7 +6,8 @@ using TweetDuck.Core.Handling.General; using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Utils; -using TweetDuck.Updates; +using TweetLib.Core.Features.Updates; +using TweetLib.Core.Utils; namespace TweetDuck.Core.Other.Settings{ sealed partial class TabSettingsGeneral : BaseTabSettings{ diff --git a/Core/TweetDeckBrowser.cs b/Core/TweetDeckBrowser.cs index 8510ff58..299d9db6 100644 --- a/Core/TweetDeckBrowser.cs +++ b/Core/TweetDeckBrowser.cs @@ -12,8 +12,8 @@ using TweetDuck.Core.Notification; using TweetDuck.Core.Utils; using TweetDuck.Plugins; -using TweetDuck.Plugins.Enums; using TweetDuck.Resources; +using TweetLib.Core.Features.Plugins.Enums; namespace TweetDuck.Core{ sealed class TweetDeckBrowser : IDisposable{ diff --git a/Core/Utils/BrowserUtils.cs b/Core/Utils/BrowserUtils.cs index 1ce0b383..9b965c08 100644 --- a/Core/Utils/BrowserUtils.cs +++ b/Core/Utils/BrowserUtils.cs @@ -3,16 +3,16 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Net; using System.Windows.Forms; using CefSharp.WinForms; using TweetDuck.Configuration; using TweetDuck.Core.Other; +using TweetLib.Core.Utils; namespace TweetDuck.Core.Utils{ static class BrowserUtils{ - public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion; - public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"+Cef.ChromiumVersion+" Safari/537.36"; + public static string UserAgentVanilla => Program.BrandName + " " + Application.ProductVersion; + public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + Cef.ChromiumVersion + " Safari/537.36"; public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak")); @@ -74,29 +74,11 @@ void UpdateZoomLevel(object sender, EventArgs args){ }; } - private const string TwitterTrackingUrl = "t.co"; - - public enum UrlCheckResult{ - Invalid, Tracking, Fine - } - - public static UrlCheckResult CheckUrl(string url){ - if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){ - string scheme = uri.Scheme; - - if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){ - return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine; - } - } - - return UrlCheckResult.Invalid; - } - public static void OpenExternalBrowser(string url){ if (string.IsNullOrWhiteSpace(url))return; - switch(CheckUrl(url)){ - case UrlCheckResult.Fine: + switch(UrlUtils.Check(url)){ + case UrlUtils.CheckResult.Fine: if (FormGuide.CheckGuideUrl(url, out string hash)){ FormGuide.Show(hash); } @@ -117,9 +99,9 @@ public static void OpenExternalBrowser(string url){ break; - case UrlCheckResult.Tracking: + case UrlUtils.CheckResult.Tracking: if (Config.IgnoreTrackingUrlWarning){ - goto case UrlCheckResult.Fine; + goto case UrlUtils.CheckResult.Fine; } using(FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, MessageBoxIcon.Warning)){ @@ -135,13 +117,13 @@ public static void OpenExternalBrowser(string url){ } if (result == DialogResult.Ignore || result == DialogResult.Yes){ - goto case UrlCheckResult.Fine; + goto case UrlUtils.CheckResult.Fine; } } break; - case UrlCheckResult.Invalid: + case UrlUtils.CheckResult.Invalid: FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK); break; } @@ -174,50 +156,10 @@ public static void OpenExternalSearch(string query){ } } - public static string GetFileNameFromUrl(string url){ - string file = Path.GetFileName(new Uri(url).AbsolutePath); - return string.IsNullOrEmpty(file) ? null : file; - } - public static string GetErrorName(CefErrorCode code){ return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); } - public static WebClient CreateWebClient(){ - WindowsUtils.EnsureTLS12(); - - WebClient client = new WebClient{ Proxy = null }; - client.Headers[HttpRequestHeader.UserAgent] = UserAgentVanilla; - return client; - } - - public static WebClient DownloadFileAsync(string url, string target, string cookie, Action onSuccess, Action<Exception> onFailure){ - WebClient client = CreateWebClient(); - - if (cookie != null){ - client.Headers[HttpRequestHeader.Cookie] = cookie; - } - - client.DownloadFileCompleted += (sender, args) => { - if (args.Cancelled){ - try{ - File.Delete(target); - }catch{ - // didn't want it deleted anyways - } - } - else if (args.Error != null){ - onFailure?.Invoke(args.Error); - } - else{ - onSuccess?.Invoke(); - } - }; - - client.DownloadFileAsync(new Uri(url), target); - return client; - } - public static int Scale(int baseValue, double scaleFactor){ return (int)Math.Round(baseValue*scaleFactor); } diff --git a/Core/Utils/TwitterUtils.cs b/Core/Utils/TwitterUtils.cs index 3ab66328..0f8b21b4 100644 --- a/Core/Utils/TwitterUtils.cs +++ b/Core/Utils/TwitterUtils.cs @@ -8,7 +8,9 @@ using TweetDuck.Core.Other; using TweetDuck.Data; using System.Linq; +using System.Net; using System.Threading.Tasks; +using TweetLib.Core.Utils; using Cookie = CefSharp.Cookie; namespace TweetDuck.Core.Utils{ @@ -71,7 +73,7 @@ public static string GetMediaLink(string url, ImageQuality quality){ } public static string GetImageFileName(string url){ - return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url)); + return UrlUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url)); } public static void ViewImage(string url, ImageQuality quality){ @@ -88,7 +90,7 @@ void ViewImageInternal(string path){ string file = Path.Combine(BrowserCache.CacheFolder, GetImageFileName(url) ?? Path.GetRandomFileName()); - if (WindowsUtils.FileExistsAndNotEmpty(file)){ + if (FileUtils.FileExistsAndNotEmpty(file)){ ViewImageInternal(file); } else{ @@ -143,7 +145,7 @@ void OnFailure(Exception ex){ } public static void DownloadVideo(string url, string username){ - string filename = BrowserUtils.GetFileNameFromUrl(url); + string filename = UrlUtils.GetFileNameFromUrl(url); string ext = Path.GetExtension(filename); using(SaveFileDialog dialog = new SaveFileDialog{ @@ -178,7 +180,10 @@ private static void DownloadFileAuth(string url, string target, Action onSuccess } } - BrowserUtils.DownloadFileAsync(url, target, cookieStr, onSuccess, onFailure); + WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome); + client.Headers[HttpRequestHeader.Cookie] = cookieStr; + client.DownloadFileCompleted += WebUtils.FileDownloadCallback(target, onSuccess, onFailure); + client.DownloadFileAsync(new Uri(url), target); }, scheduler); } } diff --git a/Core/Utils/WindowsUtils.cs b/Core/Utils/WindowsUtils.cs index 5d20578b..7186fb31 100644 --- a/Core/Utils/WindowsUtils.cs +++ b/Core/Utils/WindowsUtils.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; -using System.Net; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; @@ -16,7 +15,6 @@ static class WindowsUtils{ private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false); private static readonly bool IsWindows8OrNewer; - private static bool HasMicrosoftBeenBroughtTo2008Yet; public static int CurrentProcessID { get; } public static bool ShouldAvoidToolWindow { get; } @@ -32,47 +30,6 @@ static WindowsUtils(){ ShouldAvoidToolWindow = IsWindows8OrNewer; } - - public static void EnsureTLS12(){ - if (!HasMicrosoftBeenBroughtTo2008Yet){ - ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11); - HasMicrosoftBeenBroughtTo2008Yet = true; - } - } - - public static void CreateDirectoryForFile(string file){ - string dir = Path.GetDirectoryName(file); - - if (dir == null){ - throw new ArgumentException("Invalid file path: "+file); - } - else if (dir.Length > 0){ - Directory.CreateDirectory(dir); - } - } - - public static bool CheckFolderWritePermission(string path){ - string testFile = Path.Combine(path, ".test"); - - try{ - Directory.CreateDirectory(path); - - using(File.Create(testFile)){} - File.Delete(testFile); - return true; - }catch{ - return false; - } - } - - public static bool FileExistsAndNotEmpty(string path){ - try{ - return new FileInfo(path).Length > 0; - }catch{ - return false; - } - } public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){ try{ diff --git a/Data/Serialization/ITypeConverter.cs b/Data/Serialization/ITypeConverter.cs deleted file mode 100644 index 67b2126f..00000000 --- a/Data/Serialization/ITypeConverter.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace TweetDuck.Data.Serialization{ - interface ITypeConverter{ - bool TryWriteType(Type type, object value, out string converted); - bool TryReadType(Type type, string value, out object converted); - } -} diff --git a/Data/WindowState.cs b/Data/WindowState.cs index d0b7d24c..879858a6 100644 --- a/Data/WindowState.cs +++ b/Data/WindowState.cs @@ -1,8 +1,8 @@ using System.Drawing; using System.Windows.Forms; using TweetDuck.Core.Controls; -using TweetDuck.Core.Utils; -using TweetDuck.Data.Serialization; +using TweetLib.Core.Serialization.Converters; +using TweetLib.Core.Utils; namespace TweetDuck.Data{ sealed class WindowState{ diff --git a/Plugins/Controls/PluginControl.cs b/Plugins/Controls/PluginControl.cs index 5cbb7568..f716e27a 100644 --- a/Plugins/Controls/PluginControl.cs +++ b/Plugins/Controls/PluginControl.cs @@ -3,7 +3,8 @@ using System.Windows.Forms; using TweetDuck.Core.Controls; using TweetDuck.Core.Utils; -using TweetDuck.Plugins.Enums; +using TweetLib.Core.Features.Plugins; +using TweetLib.Core.Features.Plugins.Enums; namespace TweetDuck.Plugins.Controls{ sealed partial class PluginControl : UserControl{ diff --git a/Plugins/Enums/PluginFolder.cs b/Plugins/Enums/PluginFolder.cs deleted file mode 100644 index 5187c9bf..00000000 --- a/Plugins/Enums/PluginFolder.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace TweetDuck.Plugins.Enums{ - enum PluginFolder{ - Root, Data - } -} diff --git a/Plugins/PluginBridge.cs b/Plugins/PluginBridge.cs index 8006c434..b6160164 100644 --- a/Plugins/PluginBridge.cs +++ b/Plugins/PluginBridge.cs @@ -1,13 +1,17 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; -using TweetDuck.Core.Utils; -using TweetDuck.Data; -using TweetDuck.Plugins.Enums; -using TweetDuck.Plugins.Events; +using TweetLib.Core.Collections; +using TweetLib.Core.Data; +using TweetLib.Core.Features.Plugins; +using TweetLib.Core.Features.Plugins.Enums; +using TweetLib.Core.Features.Plugins.Events; +using TweetLib.Core.Utils; namespace TweetDuck.Plugins{ + [SuppressMessage("ReSharper", "UnusedMember.Global")] sealed class PluginBridge{ private static string SanitizeCacheKey(string key){ return key.Replace('\\', '/').Trim(); @@ -80,7 +84,7 @@ private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool public void WriteFile(int token, string path, string contents){ string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); - WindowsUtils.CreateDirectoryForFile(fullPath); + FileUtils.CreateDirectoryForFile(fullPath); File.WriteAllText(fullPath, contents, Encoding.UTF8); fileCache[token, SanitizeCacheKey(path)] = contents; } diff --git a/Plugins/PluginManager.cs b/Plugins/PluginManager.cs index 39a71613..a5a8381b 100644 --- a/Plugins/PluginManager.cs +++ b/Plugins/PluginManager.cs @@ -6,10 +6,12 @@ using System.Linq; using System.Windows.Forms; using TweetDuck.Core.Utils; -using TweetDuck.Data; -using TweetDuck.Plugins.Enums; -using TweetDuck.Plugins.Events; using TweetDuck.Resources; +using TweetLib.Core.Data; +using TweetLib.Core.Features.Plugins; +using TweetLib.Core.Features.Plugins.Config; +using TweetLib.Core.Features.Plugins.Enums; +using TweetLib.Core.Features.Plugins.Events; namespace TweetDuck.Plugins{ sealed class PluginManager{ @@ -126,12 +128,19 @@ IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){ } foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){ + string name = Path.GetFileName(fullDir); + + if (string.IsNullOrEmpty(name)){ + loadErrors.Add($"{group.GetIdentifierPrefix()}(?): Could not extract directory name from path: {fullDir}"); + continue; + } + Plugin plugin; try{ - plugin = PluginLoader.FromFolder(fullDir, group); + plugin = PluginLoader.FromFolder(name, fullDir, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name), group); }catch(Exception e){ - loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message); + loadErrors.Add($"{group.GetIdentifierPrefix()}{name}: {e.Message}"); continue; } diff --git a/Program.cs b/Program.cs index 9e96a48e..878f88ac 100644 --- a/Program.cs +++ b/Program.cs @@ -2,10 +2,8 @@ using CefSharp.WinForms; using System; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; -using System.Threading; using System.Windows.Forms; using TweetDuck.Configuration; using TweetDuck.Core; @@ -14,14 +12,16 @@ using TweetDuck.Core.Other; using TweetDuck.Core.Management; using TweetDuck.Core.Utils; -using TweetDuck.Data; +using TweetLib.Core; +using TweetLib.Core.Collections; +using TweetLib.Core.Utils; namespace TweetDuck{ static class Program{ - public const string BrandName = "TweetDuck"; - public const string Website = "https://tweetduck.chylex.com"; + public const string BrandName = Lib.BrandName; + public const string VersionTag = Lib.VersionTag; - public const string VersionTag = "1.17.4"; + public const string Website = "https://tweetduck.chylex.com"; public static readonly string ProgramPath = AppDomain.CurrentDomain.BaseDirectory; public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable")); @@ -48,23 +48,18 @@ static class Program{ private static readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock")); private static bool HasCleanedUp; - public static CultureInfo Culture { get; } public static Reporter Reporter { get; } public static ConfigManager Config { get; } - + static Program(){ - Culture = CultureInfo.CurrentCulture; - Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; - - #if DEBUG - CultureInfo.DefaultThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us"); // force english exceptions - #endif - Reporter = new Reporter(ErrorLogFilePath); Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :("); Config = new ConfigManager(); + + Lib.Initialize(new App.Builder{ + ErrorHandler = Reporter + }); } [STAThread] @@ -75,7 +70,7 @@ private static void Main(){ WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); - if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){ + if (!FileUtils.CheckFolderWritePermission(StoragePath)){ FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK); return; } @@ -131,7 +126,7 @@ private static void Main(){ } try{ - RequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze, null)); + RequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze)); }catch(Exception e){ FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: "+e.Message, FormMessage.OK); return; @@ -168,7 +163,7 @@ private static void Main(){ // ProgramPath has a trailing backslash string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : ""); - bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath); + bool runElevated = !IsPortable || !FileUtils.CheckFolderWritePermission(ProgramPath); if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){ Application.Exit(); @@ -180,7 +175,7 @@ private static void Main(){ } private static string GetDataStoragePath(){ - string custom = Arguments.GetValue(Arguments.ArgDataFolder, null); + string custom = Arguments.GetValue(Arguments.ArgDataFolder); if (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){ if (Path.GetInvalidPathChars().Any(custom.Contains)){ diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 1fe43609..804017fe 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace TweetDuck.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/Reporter.cs b/Reporter.cs index 2f0bc90a..7c7c7b92 100644 --- a/Reporter.cs +++ b/Reporter.cs @@ -6,9 +6,11 @@ using System.Windows.Forms; using TweetDuck.Configuration; using TweetDuck.Core.Other; +using TweetLib.Core; +using TweetLib.Core.Application; namespace TweetDuck{ - sealed class Reporter{ + sealed class Reporter : IAppErrorHandler{ private readonly string logFile; public Reporter(string logFile){ @@ -28,8 +30,12 @@ public bool LogVerbose(string data){ } public bool LogImportant(string data){ + return ((IAppErrorHandler)this).Log(data); + } + + bool IAppErrorHandler.Log(string text){ #if DEBUG - Debug.WriteLine(data); + Debug.WriteLine(text); #endif StringBuilder build = new StringBuilder(); @@ -38,8 +44,8 @@ public bool LogImportant(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", Program.Culture)).Append("]\r\n"); - build.Append(data).Append("\r\n\r\n"); + build.Append("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("]\r\n"); + build.Append(text).Append("\r\n\r\n"); try{ File.AppendAllText(logFile, build.ToString(), Encoding.UTF8); diff --git a/TweetDuck.csproj b/TweetDuck.csproj index f7822d6a..474369ef 100644 --- a/TweetDuck.csproj +++ b/TweetDuck.csproj @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props" Condition="Exists('packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" /> + <Import Project="packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" /> <Import Project="packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props')" /> <Import Project="packages\CefSharp.Common.67.0.0\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.67.0.0\build\CefSharp.Common.props')" /> <Import Project="packages\cef.redist.x86.3.3396.1786\build\cef.redist.x86.props" Condition="Exists('packages\cef.redist.x86.3.3396.1786\build\cef.redist.x86.props')" /> @@ -14,7 +14,8 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>TweetDuck</RootNamespace> <AssemblyName>TweetDuck</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> + <LangVersion>8.0</LangVersion> <FileAlignment>512</FileAlignment> <UseVSHostingProcess>false</UseVSHostingProcess> <ApplicationIcon>Resources\Images\icon.ico</ApplicationIcon> @@ -33,7 +34,6 @@ <ErrorReport>prompt</ErrorReport> <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> <Prefer32Bit>false</Prefer32Bit> - <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <OutputPath>bin\x86\Release\</OutputPath> @@ -43,7 +43,6 @@ <ErrorReport>prompt</ErrorReport> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <Prefer32Bit>false</Prefer32Bit> - <LangVersion>7</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System" /> @@ -55,10 +54,7 @@ </ItemGroup> <ItemGroup> <Compile Include="Configuration\Arguments.cs" /> - <Compile Include="Configuration\Instance\FileConfigInstance.cs" /> <Compile Include="Configuration\ConfigManager.cs" /> - <Compile Include="Configuration\Instance\IConfigInstance.cs" /> - <Compile Include="Configuration\Instance\PluginConfigInstance.cs" /> <Compile Include="Configuration\LockManager.cs" /> <Compile Include="Configuration\SystemConfig.cs" /> <Compile Include="Configuration\UserConfig.cs" /> @@ -198,10 +194,7 @@ <DependentUpon>TabSettingsFeedback.cs</DependentUpon> </Compile> <Compile Include="Core\TweetDeckBrowser.cs" /> - <Compile Include="Core\Utils\LocaleUtils.cs" /> - <Compile Include="Core\Utils\StringUtils.cs" /> <Compile Include="Core\Utils\TwitterUtils.cs" /> - <Compile Include="Data\CombinedFileStream.cs" /> <Compile Include="Core\Management\ProfileManager.cs" /> <Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs"> <SubType>UserControl</SubType> @@ -231,19 +224,11 @@ <DependentUpon>TabSettingsNotifications.cs</DependentUpon> </Compile> <Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" /> - <Compile Include="Data\CommandLineArgs.cs" /> <Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs"> <SubType>Form</SubType> </Compile> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" /> <Compile Include="Data\ResourceLink.cs" /> - <Compile Include="Data\Result.cs" /> - <Compile Include="Data\Serialization\FileSerializer.cs" /> - <Compile Include="Data\InjectedHTML.cs" /> - <Compile Include="Data\Serialization\ITypeConverter.cs" /> - <Compile Include="Data\Serialization\SerializationSoftException.cs" /> - <Compile Include="Data\Serialization\SingleTypeConverter.cs" /> - <Compile Include="Data\TwoKeyDictionary.cs" /> <Compile Include="Data\WindowState.cs" /> <Compile Include="Core\Utils\WindowsUtils.cs" /> <Compile Include="Core\Bridge\TweetDeckBridge.cs" /> @@ -262,25 +247,15 @@ <Compile Include="Plugins\Controls\PluginListFlowLayout.cs"> <SubType>Component</SubType> </Compile> - <Compile Include="Plugins\Enums\PluginFolder.cs" /> - <Compile Include="Plugins\IPluginConfig.cs" /> - <Compile Include="Plugins\Plugin.cs" /> - <Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" /> <Compile Include="Plugins\PluginBridge.cs" /> <Compile Include="Configuration\PluginConfig.cs" /> - <Compile Include="Plugins\Enums\PluginEnvironment.cs" /> - <Compile Include="Plugins\Enums\PluginGroup.cs" /> - <Compile Include="Plugins\Events\PluginErrorEventArgs.cs" /> - <Compile Include="Plugins\PluginLoader.cs" /> <Compile Include="Plugins\PluginManager.cs" /> - <Compile Include="Plugins\PluginScriptGenerator.cs" /> <Compile Include="Properties\Resources.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>Resources.resx</DependentUpon> </Compile> <Compile Include="Reporter.cs" /> - <Compile Include="Updates\UpdateCheckEventArgs.cs" /> <Compile Include="Updates\FormUpdateDownload.cs"> <SubType>Form</SubType> </Compile> @@ -297,9 +272,6 @@ <Compile Include="Core\Utils\BrowserUtils.cs" /> <Compile Include="Core\Utils\NativeMethods.cs" /> <Compile Include="Updates\UpdateCheckClient.cs" /> - <Compile Include="Updates\UpdateDownloadStatus.cs" /> - <Compile Include="Updates\UpdateHandler.cs" /> - <Compile Include="Updates\UpdateInfo.cs" /> <Compile Include="Program.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Resources\ScriptLoader.cs" /> @@ -380,6 +352,10 @@ <None Include="Resources\Scripts\update.js" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="lib\TweetLib.Core\TweetLib.Core.csproj"> + <Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project> + <Name>TweetLib.Core</Name> + </ProjectReference> <ProjectReference Include="subprocess\TweetDuck.Browser.csproj"> <Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project> <Name>TweetDuck.Browser</Name> @@ -431,7 +407,7 @@ IF EXIST "$(ProjectDir)bld\post_build.exe" ( <Error Condition="!Exists('packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.props'))" /> <Error Condition="!Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets'))" /> - <Error Condition="!Exists('packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props'))" /> + <Error Condition="!Exists('packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props'))" /> </Target> <Import Project="packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.67.0.0\build\CefSharp.Common.targets')" /> <Import Project="packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets')" /> diff --git a/TweetDuck.sln b/TweetDuck.sln index 9eb12e29..bea9c2f1 100644 --- a/TweetDuck.sln +++ b/TweetDuck.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28729.10 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}" EndProject @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetTest.System", "lib\Twe EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Unit", "lib\TweetTest.Unit\TweetTest.Unit.fsproj", "{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -42,6 +44,10 @@ Global {EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.ActiveCfg = Debug|x86 {EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.Build.0 = Debug|x86 {EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Release|x86.ActiveCfg = Debug|x86 + {93BA3CB4-A812-4949-B07D-8D393FB38937}.Debug|x86.ActiveCfg = Debug|x86 + {93BA3CB4-A812-4949-B07D-8D393FB38937}.Debug|x86.Build.0 = Debug|x86 + {93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.ActiveCfg = Release|x86 + {93BA3CB4-A812-4949-B07D-8D393FB38937}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Updates/FormUpdateDownload.cs b/Updates/FormUpdateDownload.cs index 6d00ca4a..c1ad5949 100644 --- a/Updates/FormUpdateDownload.cs +++ b/Updates/FormUpdateDownload.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Forms; +using TweetLib.Core.Features.Updates; namespace TweetDuck.Updates{ sealed partial class FormUpdateDownload : Form{ diff --git a/Updates/UpdateCheckClient.cs b/Updates/UpdateCheckClient.cs index b8e051e6..4013f56b 100644 --- a/Updates/UpdateCheckClient.cs +++ b/Updates/UpdateCheckClient.cs @@ -6,10 +6,12 @@ using System.Threading.Tasks; using System.Web.Script.Serialization; using TweetDuck.Core.Utils; +using TweetLib.Core.Features.Updates; +using TweetLib.Core.Utils; using JsonObject = System.Collections.Generic.IDictionary<string, object>; namespace TweetDuck.Updates{ - sealed class UpdateCheckClient{ + sealed class UpdateCheckClient : IUpdateCheckClient{ private const string ApiLatestRelease = "https://api.github.com/repos/chylex/TweetDuck/releases/latest"; private const string UpdaterAssetName = "TweetDuck.Update.exe"; @@ -19,10 +21,12 @@ public UpdateCheckClient(string installerFolder){ this.installerFolder = installerFolder; } - public Task<UpdateInfo> Check(){ + bool IUpdateCheckClient.CanCheck => Program.Config.User.EnableUpdateCheck; + + Task<UpdateInfo> IUpdateCheckClient.Check(){ TaskCompletionSource<UpdateInfo> result = new TaskCompletionSource<UpdateInfo>(); - WebClient client = BrowserUtils.CreateWebClient(); + WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla); client.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json"; client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => { @@ -65,10 +69,9 @@ string AssetDownloadUrl(JsonObject obj){ private static Exception ExpandWebException(Exception e){ if (e is WebException we && we.Response is HttpWebResponse response){ try{ - using(Stream stream = response.GetResponseStream()) - using(StreamReader reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8"))){ - return new Reporter.ExpandedLogException(e, reader.ReadToEnd()); - } + using var stream = response.GetResponseStream(); + using var reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8")); + return new Reporter.ExpandedLogException(e, reader.ReadToEnd()); }catch{ // whatever } diff --git a/bld/gen_full.iss b/bld/gen_full.iss index 9c61f4a6..ea4a9143 100644 --- a/bld/gen_full.iss +++ b/bld/gen_full.iss @@ -76,14 +76,14 @@ function TDGetNetFrameworkVersion: Cardinal; forward; function TDIsVCMissing: Boolean; forward; procedure TDInstallVCRedist; forward; -{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. } +{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. } function InitializeSetup: Boolean; begin UpdatePath := ExpandConstant('{param:UPDATEPATH}') ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}') VisitedTasksPage := False - if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then + if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then begin Result := False Exit diff --git a/bld/gen_port.iss b/bld/gen_port.iss index 06b1fafa..199750ad 100644 --- a/bld/gen_port.iss +++ b/bld/gen_port.iss @@ -64,14 +64,14 @@ function TDGetNetFrameworkVersion: Cardinal; forward; function TDIsVCMissing: Boolean; forward; procedure TDInstallVCRedist; forward; -{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. } +{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. } function InitializeSetup: Boolean; begin UpdatePath := ExpandConstant('{param:UPDATEPATH}') ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}') VisitedTasksPage := False - if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then + if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then begin Result := False Exit diff --git a/bld/gen_upd.iss b/bld/gen_upd.iss index 80673b19..986e8580 100644 --- a/bld/gen_upd.iss +++ b/bld/gen_upd.iss @@ -81,7 +81,7 @@ procedure TDExecuteFullDownload; forward; var IsPortable: Boolean; var UpdatePath: String; -{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. Prepare full download package if required. } +{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. Prepare full download package if required. } function InitializeSetup: Boolean; begin IsPortable := ExpandConstant('{param:PORTABLE}') = '1' @@ -99,7 +99,7 @@ begin idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe')) end; - if (TDGetNetFrameworkVersion() < 379893) and (MsgBox('{#MyAppName} requires .NET Framework 4.5.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then + if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then begin Result := False Exit diff --git a/lib/TweetLib.Communication/TweetLib.Communication.csproj b/lib/TweetLib.Communication/TweetLib.Communication.csproj index 95dfbdb9..151a6613 100644 --- a/lib/TweetLib.Communication/TweetLib.Communication.csproj +++ b/lib/TweetLib.Communication/TweetLib.Communication.csproj @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" /> + <Import Project="..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -10,7 +10,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>TweetLib.Communication</RootNamespace> <AssemblyName>TweetLib.Communication</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <NuGetPackageImportStamp> </NuGetPackageImportStamp> @@ -23,7 +23,7 @@ <PlatformTarget>x86</PlatformTarget> <ErrorReport>prompt</ErrorReport> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> - <LangVersion>7</LangVersion> + <LangVersion>8.0</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <OutputPath>bin\x86\Release\</OutputPath> @@ -51,6 +51,6 @@ <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props'))" /> </Target> </Project> \ No newline at end of file diff --git a/lib/TweetLib.Communication/packages.config b/lib/TweetLib.Communication/packages.config index d1d28893..ae425812 100644 --- a/lib/TweetLib.Communication/packages.config +++ b/lib/TweetLib.Communication/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Microsoft.Net.Compilers" version="2.9.0" targetFramework="net452" developmentDependency="true" /> + <package id="Microsoft.Net.Compilers" version="3.0.0" targetFramework="net472" developmentDependency="true" /> </packages> \ No newline at end of file diff --git a/lib/TweetLib.Core/App.cs b/lib/TweetLib.Core/App.cs new file mode 100644 index 00000000..1fb5f973 --- /dev/null +++ b/lib/TweetLib.Core/App.cs @@ -0,0 +1,28 @@ +using System; +using TweetLib.Core.Application; + +namespace TweetLib.Core{ + public sealed class App{ + public static IAppErrorHandler ErrorHandler { get; private set; } + + // Builder + + public sealed class Builder{ + public IAppErrorHandler? ErrorHandler { get; set; } + + // Validation + + internal void Initialize(){ + App.ErrorHandler = Validate(ErrorHandler, nameof(ErrorHandler))!; + } + + private T Validate<T>(T obj, string name){ + if (obj == null){ + throw new InvalidOperationException("Missing property " + name + " on the provided App."); + } + + return obj; + } + } + } +} diff --git a/lib/TweetLib.Core/Application/IAppErrorHandler.cs b/lib/TweetLib.Core/Application/IAppErrorHandler.cs new file mode 100644 index 00000000..f4620f90 --- /dev/null +++ b/lib/TweetLib.Core/Application/IAppErrorHandler.cs @@ -0,0 +1,8 @@ +using System; + +namespace TweetLib.Core.Application{ + public interface IAppErrorHandler{ + bool Log(string text); + void HandleException(string caption, string message, bool canIgnore, Exception e); + } +} diff --git a/Data/CommandLineArgs.cs b/lib/TweetLib.Core/Collections/CommandLineArgs.cs similarity index 84% rename from Data/CommandLineArgs.cs rename to lib/TweetLib.Core/Collections/CommandLineArgs.cs index 39b90f30..8db67f41 100644 --- a/Data/CommandLineArgs.cs +++ b/lib/TweetLib.Core/Collections/CommandLineArgs.cs @@ -2,8 +2,8 @@ using System.Text; using System.Text.RegularExpressions; -namespace TweetDuck.Data{ - sealed class CommandLineArgs{ +namespace TweetLib.Core.Collections{ + public sealed class CommandLineArgs{ public static CommandLineArgs FromStringArray(char entryChar, string[] array){ CommandLineArgs args = new CommandLineArgs(); ReadStringArray(entryChar, array, args); @@ -15,7 +15,7 @@ public static void ReadStringArray(char entryChar, string[] array, CommandLineAr string entry = array[index]; if (entry.Length > 0 && entry[0] == entryChar){ - if (index < array.Length-1){ + if (index < array.Length - 1){ string potentialValue = array[index+1]; if (potentialValue.Length > 0 && potentialValue[0] == entryChar){ @@ -52,7 +52,7 @@ public static CommandLineArgs ReadCefArguments(string argumentString){ } else{ key = matchValue.Substring(0, indexEquals).TrimStart('-'); - value = matchValue.Substring(indexEquals+1).Trim('"'); + value = matchValue.Substring(indexEquals + 1).Trim('"'); } if (key.Length != 0){ @@ -66,7 +66,7 @@ public static CommandLineArgs ReadCefArguments(string argumentString){ private readonly HashSet<string> flags = new HashSet<string>(); private readonly Dictionary<string, string> values = new Dictionary<string, string>(); - public int Count => flags.Count+values.Count; + public int Count => flags.Count + values.Count; public void AddFlag(string flag){ flags.Add(flag.ToLower()); @@ -84,12 +84,8 @@ public void SetValue(string key, string value){ values[key.ToLower()] = value; } - public bool HasValue(string key){ - return values.ContainsKey(key.ToLower()); - } - - public string GetValue(string key, string defaultValue){ - return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue; + public string? GetValue(string key){ + return values.TryGetValue(key.ToLower(), out string val) ? val : null; } public void RemoveValue(string key){ @@ -103,7 +99,7 @@ public CommandLineArgs Clone(){ copy.AddFlag(flag); } - foreach(KeyValuePair<string, string> kvp in values){ + foreach(var kvp in values){ copy.SetValue(kvp.Key, kvp.Value); } @@ -115,7 +111,7 @@ public void ToDictionary(IDictionary<string, string> target){ target[flag] = "1"; } - foreach(KeyValuePair<string, string> kvp in values){ + foreach(var kvp in values){ target[kvp.Key] = kvp.Value; } } @@ -127,11 +123,11 @@ public override string ToString(){ build.Append(flag).Append(' '); } - foreach(KeyValuePair<string, string> kvp in values){ + foreach(var kvp in values){ build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" "); } - return build.Length == 0 ? string.Empty : build.Remove(build.Length-1, 1).ToString(); + return build.Length == 0 ? string.Empty : build.Remove(build.Length - 1, 1).ToString(); } } } diff --git a/Data/TwoKeyDictionary.cs b/lib/TweetLib.Core/Collections/TwoKeyDictionary.cs similarity index 95% rename from Data/TwoKeyDictionary.cs rename to lib/TweetLib.Core/Collections/TwoKeyDictionary.cs index 1acd8199..e9e1890d 100644 --- a/Data/TwoKeyDictionary.cs +++ b/lib/TweetLib.Core/Collections/TwoKeyDictionary.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; -namespace TweetDuck.Data{ - sealed class TwoKeyDictionary<K1, K2, V>{ +namespace TweetLib.Core.Collections{ + public sealed class TwoKeyDictionary<K1, K2, V>{ private readonly Dictionary<K1, Dictionary<K2, V>> dict; private readonly int innerCapacity; @@ -85,7 +85,8 @@ public bool Remove(K1 outerKey, K2 innerKey){ return true; } - else return false; + + return false; } public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){ @@ -93,7 +94,7 @@ public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){ return innerDict.TryGetValue(innerKey, out value); } else{ - value = default(V); + value = default!; return false; } } diff --git a/Data/CombinedFileStream.cs b/lib/TweetLib.Core/Data/CombinedFileStream.cs similarity index 92% rename from Data/CombinedFileStream.cs rename to lib/TweetLib.Core/Data/CombinedFileStream.cs index 94f4880b..5a18a0b0 100644 --- a/Data/CombinedFileStream.cs +++ b/lib/TweetLib.Core/Data/CombinedFileStream.cs @@ -1,11 +1,11 @@ using System; using System.IO; using System.Text; -using TweetDuck.Core.Utils; +using TweetLib.Core.Utils; -namespace TweetDuck.Data{ - sealed class CombinedFileStream : IDisposable{ - public const char KeySeparator = '|'; +namespace TweetLib.Core.Data{ + public sealed class CombinedFileStream : IDisposable{ + private const char KeySeparator = '|'; private readonly Stream stream; @@ -45,7 +45,7 @@ public void WriteFile(string identifier, string path){ stream.Write(contents, 0, contents.Length); } - public Entry ReadFile(){ + public Entry? ReadFile(){ int nameLength = stream.ReadByte(); if (nameLength == -1){ @@ -64,7 +64,7 @@ public Entry ReadFile(){ return new Entry(Encoding.UTF8.GetString(name), contents); } - public string SkipFile(){ + public string? SkipFile(){ int nameLength = stream.ReadByte(); if (nameLength == -1){ @@ -120,7 +120,7 @@ public void WriteToFile(string path){ public void WriteToFile(string path, bool createDirectory){ if (createDirectory){ - WindowsUtils.CreateDirectoryForFile(path); + FileUtils.CreateDirectoryForFile(path); } File.WriteAllBytes(path, contents); diff --git a/Data/InjectedHTML.cs b/lib/TweetLib.Core/Data/InjectedHTML.cs similarity index 85% rename from Data/InjectedHTML.cs rename to lib/TweetLib.Core/Data/InjectedHTML.cs index 147ce5ab..2831cf16 100644 --- a/Data/InjectedHTML.cs +++ b/lib/TweetLib.Core/Data/InjectedHTML.cs @@ -1,7 +1,7 @@ using System; -namespace TweetDuck.Data{ - sealed class InjectedHTML{ +namespace TweetLib.Core.Data{ + public sealed class InjectedHTML{ public enum Position{ Before, After } @@ -27,7 +27,7 @@ public string InjectInto(string targetHTML){ switch(position){ case Position.Before: cutIndex = index; break; - case Position.After: cutIndex = index+search.Length; break; + case Position.After: cutIndex = index + search.Length; break; default: return targetHTML; } diff --git a/Data/Result.cs b/lib/TweetLib.Core/Data/Result.cs similarity index 82% rename from Data/Result.cs rename to lib/TweetLib.Core/Data/Result.cs index 2c582723..97438256 100644 --- a/Data/Result.cs +++ b/lib/TweetLib.Core/Data/Result.cs @@ -1,14 +1,14 @@ using System; -namespace TweetDuck.Data{ - sealed class Result<T>{ +namespace TweetLib.Core.Data{ + public sealed class Result<T>{ public bool HasValue => exception == null; public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result."); public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result."); private readonly T value; - private readonly Exception exception; + private readonly Exception? exception; public Result(T value){ this.value = value; @@ -16,7 +16,7 @@ public Result(T value){ } public Result(Exception exception){ - this.value = default(T); + this.value = default!; this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); } @@ -25,12 +25,12 @@ public void Handle(Action<T> onSuccess, Action<Exception> onException){ onSuccess(value); } else{ - onException(exception); + onException(exception!); } } public Result<R> Select<R>(Func<T, R> map){ - return HasValue ? new Result<R>(map(value)) : new Result<R>(exception); + return HasValue ? new Result<R>(map(value)) : new Result<R>(exception!); } } } diff --git a/lib/TweetLib.Core/Features/Configuration/BaseConfig.cs b/lib/TweetLib.Core/Features/Configuration/BaseConfig.cs new file mode 100644 index 00000000..d586ac34 --- /dev/null +++ b/lib/TweetLib.Core/Features/Configuration/BaseConfig.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace TweetLib.Core.Features.Configuration{ + public abstract class BaseConfig{ + private readonly IConfigManager configManager; + + protected BaseConfig(IConfigManager configManager){ + this.configManager = configManager; + } + + // Management + + public void Save(){ + configManager.GetInstanceInfo(this).Save(); + } + + public void Reload(){ + configManager.GetInstanceInfo(this).Reload(); + } + + public void Reset(){ + configManager.GetInstanceInfo(this).Reset(); + } + + // Construction methods + + public T ConstructWithDefaults<T>() where T : BaseConfig{ + return (T)ConstructWithDefaults(configManager); + } + + protected abstract BaseConfig ConstructWithDefaults(IConfigManager configManager); + + // Utility methods + + protected void UpdatePropertyWithEvent<T>(ref T field, T value, EventHandler eventHandler){ + if (!EqualityComparer<T>.Default.Equals(field, value)){ + field = value; + eventHandler?.Invoke(this, EventArgs.Empty); + } + } + + protected void UpdatePropertyWithRestartRequest<T>(ref T field, T value){ + if (!EqualityComparer<T>.Default.Equals(field, value)){ + field = value; + configManager.TriggerProgramRestartRequested(); + } + } + } +} diff --git a/Configuration/Instance/FileConfigInstance.cs b/lib/TweetLib.Core/Features/Configuration/FileConfigInstance.cs similarity index 54% rename from Configuration/Instance/FileConfigInstance.cs rename to lib/TweetLib.Core/Features/Configuration/FileConfigInstance.cs index f7d14e3c..28468093 100644 --- a/Configuration/Instance/FileConfigInstance.cs +++ b/lib/TweetLib.Core/Features/Configuration/FileConfigInstance.cs @@ -1,22 +1,20 @@ using System; using System.IO; -using TweetDuck.Data.Serialization; - -namespace TweetDuck.Configuration.Instance{ - sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{ - private const string ErrorTitle = "Configuration Error"; +using TweetLib.Core.Serialization; +namespace TweetLib.Core.Features.Configuration{ + public sealed class FileConfigInstance<T> : IConfigInstance<T> where T : BaseConfig{ public T Instance { get; } public FileSerializer<T> Serializer { get; } private readonly string filenameMain; private readonly string filenameBackup; - private readonly string errorIdentifier; + private readonly string identifier; - public FileConfigInstance(string filename, T instance, string errorIdentifier){ + public FileConfigInstance(string filename, T instance, string identifier){ this.filenameMain = filename; - this.filenameBackup = filename+".bak"; - this.errorIdentifier = errorIdentifier; + this.filenameBackup = filename + ".bak"; + this.identifier = identifier; this.Instance = instance; this.Serializer = new FileSerializer<T>(); @@ -27,14 +25,14 @@ private void LoadInternal(bool backup){ } public void Load(){ - Exception firstException = null; + Exception? firstException = null; for(int attempt = 0; attempt < 2; attempt++){ try{ LoadInternal(attempt > 0); if (firstException != null){ // silently log exception that caused a backup restore - Program.Reporter.LogImportant(firstException.ToString()); + App.ErrorHandler.Log(firstException.ToString()); } return; @@ -49,13 +47,13 @@ public void Load(){ } if (firstException is FormatException){ - Program.Reporter.HandleException(ErrorTitle, "The configuration file for "+errorIdentifier+" is outdated or corrupted. If you continue, your "+errorIdentifier+" will be reset.", true, firstException); + OnException($"The configuration file for {identifier} is outdated or corrupted. If you continue, your {identifier} will be reset.", firstException); } else if (firstException is SerializationSoftException sse){ - Program.Reporter.HandleException(ErrorTitle, $"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for "+errorIdentifier+". If you continue, some of your "+errorIdentifier+" will be reset.", true, firstException); + OnException($"{sse.Errors.Count} error{(sse.Errors.Count == 1 ? " was" : "s were")} encountered while loading the configuration file for {identifier}. If you continue, some of your {identifier} will be reset.", firstException); } else if (firstException != null){ - Program.Reporter.HandleException(ErrorTitle, "Could not open the configuration file for "+errorIdentifier+". If you continue, your "+errorIdentifier+" will be reset.", true, firstException); + OnException($"Could not open the configuration file for {identifier}. If you continue, your {identifier} will be reset.", firstException); } } @@ -68,9 +66,9 @@ public void Save(){ Serializer.Write(filenameMain, Instance); }catch(SerializationSoftException e){ - Program.Reporter.HandleException(ErrorTitle, $"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for "+errorIdentifier+".", true, e); + OnException($"{e.Errors.Count} error{(e.Errors.Count == 1 ? " was" : "s were")} encountered while saving the configuration file for {identifier}.", e); }catch(Exception e){ - Program.Reporter.HandleException(ErrorTitle, "Could not save the configuration file for "+errorIdentifier+".", true, e); + OnException($"Could not save the configuration file for {identifier}.", e); } } @@ -82,10 +80,10 @@ public void Reload(){ Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>()); LoadInternal(false); }catch(Exception e){ - Program.Reporter.HandleException(ErrorTitle, "Could not regenerate the configuration file for "+errorIdentifier+".", true, e); + OnException($"Could not regenerate the configuration file for {identifier}.", e); } }catch(Exception e){ - Program.Reporter.HandleException(ErrorTitle, "Could not reload the configuration file for "+errorIdentifier+".", true, e); + OnException($"Could not reload the configuration file for {identifier}.", e); } } @@ -94,11 +92,15 @@ public void Reset(){ File.Delete(filenameMain); File.Delete(filenameBackup); }catch(Exception e){ - Program.Reporter.HandleException(ErrorTitle, "Could not delete configuration files to reset "+errorIdentifier+".", true, e); + OnException($"Could not delete configuration files to reset {identifier}.", e); return; } Reload(); } + + private static void OnException(string message, Exception e){ + App.ErrorHandler.HandleException("Configuration Error", message, true, e); + } } } diff --git a/Configuration/Instance/IConfigInstance.cs b/lib/TweetLib.Core/Features/Configuration/IConfigInstance.cs similarity index 51% rename from Configuration/Instance/IConfigInstance.cs rename to lib/TweetLib.Core/Features/Configuration/IConfigInstance.cs index 8a171dfd..b36f5819 100644 --- a/Configuration/Instance/IConfigInstance.cs +++ b/lib/TweetLib.Core/Features/Configuration/IConfigInstance.cs @@ -1,5 +1,5 @@ -namespace TweetDuck.Configuration.Instance{ - interface IConfigInstance<out T>{ +namespace TweetLib.Core.Features.Configuration{ + public interface IConfigInstance<out T>{ T Instance { get; } void Save(); diff --git a/lib/TweetLib.Core/Features/Configuration/IConfigManager.cs b/lib/TweetLib.Core/Features/Configuration/IConfigManager.cs new file mode 100644 index 00000000..4023c4c0 --- /dev/null +++ b/lib/TweetLib.Core/Features/Configuration/IConfigManager.cs @@ -0,0 +1,6 @@ +namespace TweetLib.Core.Features.Configuration{ + public interface IConfigManager{ + void TriggerProgramRestartRequested(); + IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance); + } +} diff --git a/Plugins/IPluginConfig.cs b/lib/TweetLib.Core/Features/Plugins/Config/IPluginConfig.cs similarity index 60% rename from Plugins/IPluginConfig.cs rename to lib/TweetLib.Core/Features/Plugins/Config/IPluginConfig.cs index afe22f4f..07007bc5 100644 --- a/Plugins/IPluginConfig.cs +++ b/lib/TweetLib.Core/Features/Plugins/Config/IPluginConfig.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; -using TweetDuck.Plugins.Events; - -namespace TweetDuck.Plugins{ - interface IPluginConfig{ - IEnumerable<string> DisabledPlugins { get; } +using TweetLib.Core.Features.Plugins.Events; +namespace TweetLib.Core.Features.Plugins.Config{ + public interface IPluginConfig{ event EventHandler<PluginChangedStateEventArgs> PluginChangedState; + + IEnumerable<string> DisabledPlugins { get; } + void Reset(IEnumerable<string> newDisabledPlugins); void SetEnabled(Plugin plugin, bool enabled); bool IsEnabled(Plugin plugin); diff --git a/lib/TweetLib.Core/Features/Plugins/Config/PluginConfigInstance.cs b/lib/TweetLib.Core/Features/Plugins/Config/PluginConfigInstance.cs new file mode 100644 index 00000000..91326383 --- /dev/null +++ b/lib/TweetLib.Core/Features/Plugins/Config/PluginConfigInstance.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using TweetLib.Core.Features.Configuration; + +namespace TweetLib.Core.Features.Plugins.Config{ + public sealed class PluginConfigInstance<T> : IConfigInstance<T> where T : BaseConfig, IPluginConfig{ + public T Instance { get; } + + private readonly string filename; + + public PluginConfigInstance(string filename, T instance){ + this.filename = filename; + this.Instance = instance; + } + + public void Load(){ + try{ + using var reader = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8); + string line = reader.ReadLine(); + + if (line == "#Disabled"){ + HashSet<string> newDisabled = new HashSet<string>(); + + while((line = reader.ReadLine()) != null){ + newDisabled.Add(line); + } + + Instance.Reset(newDisabled); + } + }catch(FileNotFoundException){ + }catch(DirectoryNotFoundException){ + }catch(Exception e){ + OnException("Could not read the plugin configuration file. If you continue, the list of disabled plugins will be reset to default.", e); + } + } + + public void Save(){ + try{ + using var writer = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8); + writer.WriteLine("#Disabled"); + + foreach(string identifier in Instance.DisabledPlugins){ + writer.WriteLine(identifier); + } + }catch(Exception e){ + OnException("Could not save the plugin configuration file.", e); + } + } + + public void Reload(){ + Load(); + } + + public void Reset(){ + try{ + File.Delete(filename); + Instance.Reset(Instance.ConstructWithDefaults<T>().DisabledPlugins); + }catch(Exception e){ + OnException("Could not delete the plugin configuration file.", e); + return; + } + + Reload(); + } + + private static void OnException(string message, Exception e){ + App.ErrorHandler.HandleException("Plugin Configuration Error", message, true, e); + } + } +} diff --git a/Plugins/Enums/PluginEnvironment.cs b/lib/TweetLib.Core/Features/Plugins/Enums/PluginEnvironment.cs similarity index 92% rename from Plugins/Enums/PluginEnvironment.cs rename to lib/TweetLib.Core/Features/Plugins/Enums/PluginEnvironment.cs index 4b46b1ca..6e57371d 100644 --- a/Plugins/Enums/PluginEnvironment.cs +++ b/lib/TweetLib.Core/Features/Plugins/Enums/PluginEnvironment.cs @@ -4,15 +4,15 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; -namespace TweetDuck.Plugins.Enums{ +namespace TweetLib.Core.Features.Plugins.Enums{ [Flags] - enum PluginEnvironment{ + public enum PluginEnvironment{ None = 0, Browser = 1, Notification = 2 } - static class PluginEnvironmentExtensions{ + public static class PluginEnvironmentExtensions{ public static IEnumerable<PluginEnvironment> Values{ get{ yield return PluginEnvironment.Browser; @@ -24,7 +24,7 @@ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){ return environment == PluginEnvironment.Browser; } - public static string GetPluginScriptFile(this PluginEnvironment environment){ + public static string? GetPluginScriptFile(this PluginEnvironment environment){ switch(environment){ case PluginEnvironment.Browser: return "browser.js"; case PluginEnvironment.Notification: return "notification.js"; @@ -73,7 +73,7 @@ public bool TryGetValue(PluginEnvironment key, out T value){ return true; } else{ - value = default(T); + value = default; return false; } } diff --git a/lib/TweetLib.Core/Features/Plugins/Enums/PluginFolder.cs b/lib/TweetLib.Core/Features/Plugins/Enums/PluginFolder.cs new file mode 100644 index 00000000..5b01f201 --- /dev/null +++ b/lib/TweetLib.Core/Features/Plugins/Enums/PluginFolder.cs @@ -0,0 +1,5 @@ +namespace TweetLib.Core.Features.Plugins.Enums{ + public enum PluginFolder{ + Root, Data + } +} diff --git a/Plugins/Enums/PluginGroup.cs b/lib/TweetLib.Core/Features/Plugins/Enums/PluginGroup.cs similarity index 82% rename from Plugins/Enums/PluginGroup.cs rename to lib/TweetLib.Core/Features/Plugins/Enums/PluginGroup.cs index 3d082e4c..69907607 100644 --- a/Plugins/Enums/PluginGroup.cs +++ b/lib/TweetLib.Core/Features/Plugins/Enums/PluginGroup.cs @@ -1,9 +1,9 @@ -namespace TweetDuck.Plugins.Enums{ - enum PluginGroup{ +namespace TweetLib.Core.Features.Plugins.Enums{ + public enum PluginGroup{ Official, Custom } - static class PluginGroupExtensions{ + public static class PluginGroupExtensions{ public static string GetIdentifierPrefix(this PluginGroup group){ switch(group){ case PluginGroup.Official: return "official/"; diff --git a/Plugins/Events/PluginChangedStateEventArgs.cs b/lib/TweetLib.Core/Features/Plugins/Events/PluginChangedStateEventArgs.cs similarity index 69% rename from Plugins/Events/PluginChangedStateEventArgs.cs rename to lib/TweetLib.Core/Features/Plugins/Events/PluginChangedStateEventArgs.cs index 5d643c23..fffeaa55 100644 --- a/Plugins/Events/PluginChangedStateEventArgs.cs +++ b/lib/TweetLib.Core/Features/Plugins/Events/PluginChangedStateEventArgs.cs @@ -1,7 +1,7 @@ using System; -namespace TweetDuck.Plugins.Events{ - sealed class PluginChangedStateEventArgs : EventArgs{ +namespace TweetLib.Core.Features.Plugins.Events{ + public sealed class PluginChangedStateEventArgs : EventArgs{ public Plugin Plugin { get; } public bool IsEnabled { get; } diff --git a/Plugins/Events/PluginErrorEventArgs.cs b/lib/TweetLib.Core/Features/Plugins/Events/PluginErrorEventArgs.cs similarity index 70% rename from Plugins/Events/PluginErrorEventArgs.cs rename to lib/TweetLib.Core/Features/Plugins/Events/PluginErrorEventArgs.cs index 57fb7bff..ebb732de 100644 --- a/Plugins/Events/PluginErrorEventArgs.cs +++ b/lib/TweetLib.Core/Features/Plugins/Events/PluginErrorEventArgs.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -namespace TweetDuck.Plugins.Events{ - sealed class PluginErrorEventArgs : EventArgs{ +namespace TweetLib.Core.Features.Plugins.Events{ + public sealed class PluginErrorEventArgs : EventArgs{ public bool HasErrors => Errors.Count > 0; public IList<string> Errors { get; } diff --git a/Plugins/Plugin.cs b/lib/TweetLib.Core/Features/Plugins/Plugin.cs similarity index 95% rename from Plugins/Plugin.cs rename to lib/TweetLib.Core/Features/Plugins/Plugin.cs index 1741b56e..6f3a65fd 100644 --- a/Plugins/Plugin.cs +++ b/lib/TweetLib.Core/Features/Plugins/Plugin.cs @@ -1,10 +1,10 @@ using System; using System.IO; -using TweetDuck.Plugins.Enums; +using TweetLib.Core.Features.Plugins.Enums; -namespace TweetDuck.Plugins{ - sealed class Plugin{ - private static readonly Version AppVersion = new Version(Program.VersionTag); +namespace TweetLib.Core.Features.Plugins{ + public sealed class Plugin{ + private static readonly Version AppVersion = new Version(Lib.VersionTag); public string Identifier { get; } public PluginGroup Group { get; } @@ -62,7 +62,7 @@ private Plugin(PluginGroup group, string identifier, string pathRoot, string pat public string GetScriptPath(PluginEnvironment environment){ if (Environments.HasFlag(environment)){ - string file = environment.GetPluginScriptFile(); + string? file = environment.GetPluginScriptFile(); return file != null ? Path.Combine(pathRoot, file) : string.Empty; } else{ @@ -124,7 +124,7 @@ public override bool Equals(object obj){ public sealed class Builder{ private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0); - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Author { get; set; } = "(anonymous)"; public string Version { get; set; } = string.Empty; @@ -144,7 +144,7 @@ public Builder(PluginGroup group, string name, string pathRoot, string pathData) this.group = group; this.pathRoot = pathRoot; this.pathData = pathData; - this.identifier = group.GetIdentifierPrefix()+name; + this.identifier = group.GetIdentifierPrefix() + name; } public void AddEnvironment(PluginEnvironment environment){ diff --git a/Plugins/PluginLoader.cs b/lib/TweetLib.Core/Features/Plugins/PluginLoader.cs similarity index 58% rename from Plugins/PluginLoader.cs rename to lib/TweetLib.Core/Features/Plugins/PluginLoader.cs index 08d1852e..205b90c2 100644 --- a/Plugins/PluginLoader.cs +++ b/lib/TweetLib.Core/Features/Plugins/PluginLoader.cs @@ -2,40 +2,35 @@ using System.IO; using System.Linq; using System.Text; -using TweetDuck.Plugins.Enums; +using TweetLib.Core.Features.Plugins.Enums; -namespace TweetDuck.Plugins{ - static class PluginLoader{ +namespace TweetLib.Core.Features.Plugins{ + public static class PluginLoader{ private static readonly string[] EndTag = { "[END]" }; - public static Plugin FromFolder(string path, PluginGroup group){ - string name = Path.GetFileName(path); + public static Plugin FromFolder(string name, string pathRoot, string pathData, PluginGroup group){ + Plugin.Builder builder = new Plugin.Builder(group, name, pathRoot, pathData); - if (string.IsNullOrEmpty(name)){ - throw new ArgumentException("Could not extract directory name from path: "+path); - } - - Plugin.Builder builder = new Plugin.Builder(group, name, path, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name)); - - foreach(string file in Directory.EnumerateFiles(path, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName)){ - builder.AddEnvironment(PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal))); + foreach(var environment in Directory.EnumerateFiles(pathRoot, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).Select(EnvironmentFromFileName)){ + builder.AddEnvironment(environment); } - string metaFile = Path.Combine(path, ".meta"); + string metaFile = Path.Combine(pathRoot, ".meta"); if (!File.Exists(metaFile)){ throw new ArgumentException("Plugin is missing a .meta file"); } - string currentTag = null, currentContents = string.Empty; + string? currentTag = null; + string currentContents = string.Empty; foreach(string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(line => line.TrimEnd()).Where(line => line.Length > 0)){ - if (line[0] == '[' && line[line.Length-1] == ']'){ + if (line[0] == '[' && line[line.Length - 1] == ']'){ if (currentTag != null){ SetProperty(builder, currentTag, currentContents); } - currentTag = line.Substring(1, line.Length-2).ToUpper(); + currentTag = line.Substring(1, line.Length - 2).ToUpper(); currentContents = string.Empty; if (line.Equals(EndTag[0])){ @@ -43,16 +38,20 @@ public static Plugin FromFolder(string path, PluginGroup group){ } } else if (currentTag != null){ - currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line; + currentContents = currentContents.Length == 0 ? line : currentContents + Environment.NewLine + line; } else{ - throw new FormatException("Missing metadata tag before value: "+line); + throw new FormatException($"Missing metadata tag before value: {line}"); } } return builder.BuildAndSetup(); } + private static PluginEnvironment EnvironmentFromFileName(string file){ + return PluginEnvironmentExtensions.Values.FirstOrDefault(env => file.Equals(env.GetPluginScriptFile(), StringComparison.Ordinal)); + } + private static void SetProperty(Plugin.Builder builder, string tag, string value){ switch(tag){ case "NAME": builder.Name = value; break; @@ -62,8 +61,8 @@ private static void SetProperty(Plugin.Builder builder, string tag, string value case "WEBSITE": builder.Website = value; break; case "CONFIGFILE": builder.ConfigFile = value; break; case "CONFIGDEFAULT": builder.ConfigDefault = value; break; - case "REQUIRES": builder.RequiredVersion = Version.TryParse(value, out Version version) ? version : throw new FormatException("Invalid required minimum version: "+value); break; - default: throw new FormatException("Invalid metadata tag: "+tag); + case "REQUIRES": builder.RequiredVersion = Version.TryParse(value, out Version version) ? version : throw new FormatException($"Invalid required minimum version: {value}"); break; + default: throw new FormatException($"Invalid metadata tag: {tag}"); } } } diff --git a/Plugins/PluginScriptGenerator.cs b/lib/TweetLib.Core/Features/Plugins/PluginScriptGenerator.cs similarity index 77% rename from Plugins/PluginScriptGenerator.cs rename to lib/TweetLib.Core/Features/Plugins/PluginScriptGenerator.cs index 83292cea..03b22a36 100644 --- a/Plugins/PluginScriptGenerator.cs +++ b/lib/TweetLib.Core/Features/Plugins/PluginScriptGenerator.cs @@ -1,10 +1,11 @@ using System.Linq; -using TweetDuck.Plugins.Enums; +using TweetLib.Core.Features.Plugins.Config; +using TweetLib.Core.Features.Plugins.Enums; -namespace TweetDuck.Plugins{ - static class PluginScriptGenerator{ +namespace TweetLib.Core.Features.Plugins{ + public static class PluginScriptGenerator{ public static string GenerateConfig(IPluginConfig config){ - return "window.TD_PLUGINS.disabled = ["+string.Join(",", config.DisabledPlugins.Select(id => $"\"{id}\""))+"]"; + return "window.TD_PLUGINS.disabled = [" + string.Join(",", config.DisabledPlugins.Select(id => '"' + id + '"')) + "]"; } public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){ diff --git a/lib/TweetLib.Core/Features/Updates/IUpdateCheckClient.cs b/lib/TweetLib.Core/Features/Updates/IUpdateCheckClient.cs new file mode 100644 index 00000000..7467bf33 --- /dev/null +++ b/lib/TweetLib.Core/Features/Updates/IUpdateCheckClient.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace TweetLib.Core.Features.Updates{ + public interface IUpdateCheckClient{ + bool CanCheck { get; } + Task<UpdateInfo> Check(); + } +} diff --git a/Updates/UpdateCheckEventArgs.cs b/lib/TweetLib.Core/Features/Updates/UpdateCheckEventArgs.cs similarity index 68% rename from Updates/UpdateCheckEventArgs.cs rename to lib/TweetLib.Core/Features/Updates/UpdateCheckEventArgs.cs index 7938290e..0d97b236 100644 --- a/Updates/UpdateCheckEventArgs.cs +++ b/lib/TweetLib.Core/Features/Updates/UpdateCheckEventArgs.cs @@ -1,8 +1,8 @@ using System; -using TweetDuck.Data; +using TweetLib.Core.Data; -namespace TweetDuck.Updates{ - sealed class UpdateCheckEventArgs : EventArgs{ +namespace TweetLib.Core.Features.Updates{ + public sealed class UpdateCheckEventArgs : EventArgs{ public int EventId { get; } public Result<UpdateInfo> Result { get; } diff --git a/Updates/UpdateDownloadStatus.cs b/lib/TweetLib.Core/Features/Updates/UpdateDownloadStatus.cs similarity index 91% rename from Updates/UpdateDownloadStatus.cs rename to lib/TweetLib.Core/Features/Updates/UpdateDownloadStatus.cs index 3b4d7c17..84794d22 100644 --- a/Updates/UpdateDownloadStatus.cs +++ b/lib/TweetLib.Core/Features/Updates/UpdateDownloadStatus.cs @@ -1,4 +1,4 @@ -namespace TweetDuck.Updates{ +namespace TweetLib.Core.Features.Updates{ public enum UpdateDownloadStatus{ None = 0, InProgress, diff --git a/Updates/UpdateHandler.cs b/lib/TweetLib.Core/Features/Updates/UpdateHandler.cs similarity index 71% rename from Updates/UpdateHandler.cs rename to lib/TweetLib.Core/Features/Updates/UpdateHandler.cs index e9a47d52..de2d36d6 100644 --- a/Updates/UpdateHandler.cs +++ b/lib/TweetLib.Core/Features/Updates/UpdateHandler.cs @@ -1,34 +1,38 @@ using System; using System.Threading; using System.Threading.Tasks; -using TweetDuck.Data; -using Timer = System.Windows.Forms.Timer; +using System.Timers; +using TweetLib.Core.Data; +using Timer = System.Timers.Timer; -namespace TweetDuck.Updates{ - sealed class UpdateHandler : IDisposable{ +namespace TweetLib.Core.Features.Updates{ + public sealed class UpdateHandler : IDisposable{ public const int CheckCodeUpdatesDisabled = -1; - private readonly UpdateCheckClient client; + private readonly IUpdateCheckClient client; private readonly TaskScheduler scheduler; private readonly Timer timer; public event EventHandler<UpdateCheckEventArgs> CheckFinished; private ushort lastEventId; - public UpdateHandler(string installerFolder){ - this.client = new UpdateCheckClient(installerFolder); - this.scheduler = TaskScheduler.FromCurrentSynchronizationContext(); + public UpdateHandler(IUpdateCheckClient client, TaskScheduler scheduler){ + this.client = client; + this.scheduler = scheduler; - this.timer = new Timer(); - this.timer.Tick += timer_Tick; + this.timer = new Timer{ + AutoReset = false, + Enabled = false + }; + + this.timer.Elapsed += timer_Elapsed; } public void Dispose(){ timer.Dispose(); } - private void timer_Tick(object sender, EventArgs e){ - timer.Stop(); + private void timer_Elapsed(object sender, ElapsedEventArgs e){ Check(false); } @@ -39,9 +43,9 @@ public void StartTimer(){ timer.Stop(); - if (Program.Config.User.EnableUpdateCheck){ + if (client.CanCheck){ DateTime now = DateTime.Now; - TimeSpan nextHour = now.AddSeconds(60*(60-now.Minute)-now.Second)-now; + TimeSpan nextHour = now.AddSeconds(60 * (60 - now.Minute) - now.Second) - now; if (nextHour.TotalMinutes < 15){ nextHour = nextHour.Add(TimeSpan.FromHours(1)); @@ -53,7 +57,7 @@ public void StartTimer(){ } public int Check(bool force){ - if (Program.Config.User.EnableUpdateCheck || force){ + if (client.CanCheck || force){ int nextEventId = unchecked(++lastEventId); Task<UpdateInfo> checkTask = client.Check(); diff --git a/Updates/UpdateInfo.cs b/lib/TweetLib.Core/Features/Updates/UpdateInfo.cs similarity index 82% rename from Updates/UpdateInfo.cs rename to lib/TweetLib.Core/Features/Updates/UpdateInfo.cs index b9a02e20..b55a9d5d 100644 --- a/Updates/UpdateInfo.cs +++ b/lib/TweetLib.Core/Features/Updates/UpdateInfo.cs @@ -1,20 +1,20 @@ using System; using System.IO; using System.Net; -using TweetDuck.Core.Utils; +using TweetLib.Core.Utils; -namespace TweetDuck.Updates{ - sealed class UpdateInfo{ +namespace TweetLib.Core.Features.Updates{ + public sealed class UpdateInfo{ public string VersionTag { get; } public string ReleaseNotes { get; } public string InstallerPath { get; } public UpdateDownloadStatus DownloadStatus { get; private set; } - public Exception DownloadError { get; private set; } + public Exception? DownloadError { get; private set; } private readonly string downloadUrl; private readonly string installerFolder; - private WebClient currentDownload; + private WebClient? currentDownload; public UpdateInfo(string versionTag, string releaseNotes, string downloadUrl, string installerFolder){ this.downloadUrl = downloadUrl; @@ -22,11 +22,11 @@ public UpdateInfo(string versionTag, string releaseNotes, string downloadUrl, st this.VersionTag = versionTag; this.ReleaseNotes = releaseNotes; - this.InstallerPath = Path.Combine(installerFolder, $"TweetDuck.{versionTag}.exe"); + this.InstallerPath = Path.Combine(installerFolder, $"{Lib.BrandName}.{versionTag}.exe"); } public void BeginSilentDownload(){ - if (WindowsUtils.FileExistsAndNotEmpty(InstallerPath)){ + if (FileUtils.FileExistsAndNotEmpty(InstallerPath)){ DownloadStatus = UpdateDownloadStatus.Done; return; } @@ -48,7 +48,9 @@ public void BeginSilentDownload(){ return; } - currentDownload = BrowserUtils.DownloadFileAsync(downloadUrl, InstallerPath, null, () => { + WebClient client = WebUtils.NewClient($"{Lib.BrandName} {Lib.VersionTag}"); + + client.DownloadFileCompleted += WebUtils.FileDownloadCallback(InstallerPath, () => { DownloadStatus = UpdateDownloadStatus.Done; currentDownload = null; }, e => { @@ -56,6 +58,8 @@ public void BeginSilentDownload(){ DownloadStatus = UpdateDownloadStatus.Failed; currentDownload = null; }); + + client.DownloadFileAsync(new Uri(downloadUrl), InstallerPath); } } diff --git a/lib/TweetLib.Core/Lib.cs b/lib/TweetLib.Core/Lib.cs new file mode 100644 index 00000000..cc56e197 --- /dev/null +++ b/lib/TweetLib.Core/Lib.cs @@ -0,0 +1,24 @@ +using System.Globalization; +using System.Threading; + +namespace TweetLib.Core{ + public static class Lib{ + public const string BrandName = "TweetDuck"; + public const string VersionTag = "1.17.4"; + + public static CultureInfo Culture { get; private set; } + + public static void Initialize(App.Builder app){ + Culture = CultureInfo.CurrentCulture; + + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; + + #if DEBUG + CultureInfo.DefaultThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-us"); // force english exceptions + #endif + + app.Initialize(); + } + } +} diff --git a/lib/TweetLib.Core/Serialization/Converters/ClrTypeConverter.cs b/lib/TweetLib.Core/Serialization/Converters/ClrTypeConverter.cs new file mode 100644 index 00000000..f579eb68 --- /dev/null +++ b/lib/TweetLib.Core/Serialization/Converters/ClrTypeConverter.cs @@ -0,0 +1,55 @@ +using System; + +namespace TweetLib.Core.Serialization.Converters{ + internal sealed class ClrTypeConverter : ITypeConverter{ + public static ITypeConverter Instance { get; } = new ClrTypeConverter(); + + private ClrTypeConverter(){} + + 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; + } + } + } +} \ No newline at end of file diff --git a/Data/Serialization/SingleTypeConverter.cs b/lib/TweetLib.Core/Serialization/Converters/SingleTypeConverter.cs similarity index 80% rename from Data/Serialization/SingleTypeConverter.cs rename to lib/TweetLib.Core/Serialization/Converters/SingleTypeConverter.cs index d0b004ed..a29d06ae 100644 --- a/Data/Serialization/SingleTypeConverter.cs +++ b/lib/TweetLib.Core/Serialization/Converters/SingleTypeConverter.cs @@ -1,11 +1,11 @@ using System; -namespace TweetDuck.Data.Serialization{ - sealed class SingleTypeConverter<T> : ITypeConverter{ +namespace TweetLib.Core.Serialization.Converters{ + public sealed class SingleTypeConverter<T> : ITypeConverter{ public Func<T, string> ConvertToString { get; set; } public Func<string, T> ConvertToObject { get; set; } - bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ + bool ITypeConverter.TryWriteType(Type type, object value, out string? converted){ try{ converted = ConvertToString((T)value); return true; @@ -15,7 +15,7 @@ bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ } } - bool ITypeConverter.TryReadType(Type type, string value, out object converted){ + bool ITypeConverter.TryReadType(Type type, string value, out object? converted){ try{ converted = ConvertToObject(value); return true; diff --git a/Data/Serialization/FileSerializer.cs b/lib/TweetLib.Core/Serialization/FileSerializer.cs similarity index 73% rename from Data/Serialization/FileSerializer.cs rename to lib/TweetLib.Core/Serialization/FileSerializer.cs index 2da467a3..e6278a79 100644 --- a/Data/Serialization/FileSerializer.cs +++ b/lib/TweetLib.Core/Serialization/FileSerializer.cs @@ -4,10 +4,11 @@ using System.Linq; using System.Reflection; using System.Text; -using TweetDuck.Core.Utils; +using TweetLib.Core.Serialization.Converters; +using TweetLib.Core.Utils; -namespace TweetDuck.Data.Serialization{ - sealed class FileSerializer<T>{ +namespace TweetLib.Core.Serialization{ + public sealed class FileSerializer<T>{ private const string NewLineReal = "\r\n"; private const string NewLineCustom = "\r~\n"; @@ -49,8 +50,6 @@ private static string UnescapeStream(StreamReader reader){ return build.Append(data.Substring(index)).ToString(); } - private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter(); - private readonly Dictionary<string, PropertyInfo> props; private readonly Dictionary<Type, ITypeConverter> converters; @@ -66,7 +65,7 @@ public void RegisterTypeConverter(Type type, ITypeConverter converter){ public void Write(string file, T obj){ LinkedList<string> errors = new LinkedList<string>(); - WindowsUtils.CreateDirectoryForFile(file); + FileUtils.CreateDirectoryForFile(file); using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ foreach(KeyValuePair<string, PropertyInfo> prop in props){ @@ -74,10 +73,10 @@ public void Write(string file, T obj){ object value = prop.Value.GetValue(obj); if (!converters.TryGetValue(type, out ITypeConverter serializer)){ - serializer = BasicSerializerObj; + serializer = ClrTypeConverter.Instance; } - if (serializer.TryWriteType(type, value, out string converted)){ + if (serializer.TryWriteType(type, value, out string? converted)){ if (converted != null){ writer.Write(prop.Key); writer.Write(' '); @@ -142,10 +141,10 @@ public void Read(string file, T obj){ if (props.TryGetValue(property, out PropertyInfo info)){ if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){ - serializer = BasicSerializerObj; + serializer = ClrTypeConverter.Instance; } - if (serializer.TryReadType(info.PropertyType, value, out object converted)){ + if (serializer.TryReadType(info.PropertyType, value, out object? converted)){ info.SetValue(obj, converted); } else{ @@ -165,53 +164,5 @@ public void ReadIfExists(string file, T obj){ }catch(FileNotFoundException){ }catch(DirectoryNotFoundException){} } - - private sealed class BasicTypeConverter : ITypeConverter{ - bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ - switch(Type.GetTypeCode(type)){ - case TypeCode.Boolean: - converted = value.ToString(); - return true; - - case TypeCode.Int32: - converted = ((int)value).ToString(); // cast required for enums - return true; - - case TypeCode.String: - converted = value?.ToString(); - return true; - - default: - converted = null; - return false; - } - } - - bool ITypeConverter.TryReadType(Type type, string value, out object converted){ - switch(Type.GetTypeCode(type)){ - case TypeCode.Boolean: - if (bool.TryParse(value, out bool b)){ - converted = b; - return true; - } - else goto default; - - case TypeCode.Int32: - if (int.TryParse(value, out int i)){ - converted = i; - return true; - } - else goto default; - - case TypeCode.String: - converted = value; - return true; - - default: - converted = null; - return false; - } - } - } } } diff --git a/lib/TweetLib.Core/Serialization/ITypeConverter.cs b/lib/TweetLib.Core/Serialization/ITypeConverter.cs new file mode 100644 index 00000000..e1501333 --- /dev/null +++ b/lib/TweetLib.Core/Serialization/ITypeConverter.cs @@ -0,0 +1,8 @@ +using System; + +namespace TweetLib.Core.Serialization{ + public interface ITypeConverter{ + bool TryWriteType(Type type, object value, out string? converted); + bool TryReadType(Type type, string value, out object? converted); + } +} diff --git a/Data/Serialization/SerializationSoftException.cs b/lib/TweetLib.Core/Serialization/SerializationSoftException.cs similarity index 71% rename from Data/Serialization/SerializationSoftException.cs rename to lib/TweetLib.Core/Serialization/SerializationSoftException.cs index 2791fde4..67ed9e96 100644 --- a/Data/Serialization/SerializationSoftException.cs +++ b/lib/TweetLib.Core/Serialization/SerializationSoftException.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -namespace TweetDuck.Data.Serialization{ - sealed class SerializationSoftException : Exception{ +namespace TweetLib.Core.Serialization{ + public sealed class SerializationSoftException : Exception{ public IList<string> Errors { get; } public SerializationSoftException(IList<string> errors) : base(string.Join(Environment.NewLine, errors)){ diff --git a/lib/TweetLib.Core/TweetLib.Core.csproj b/lib/TweetLib.Core/TweetLib.Core.csproj new file mode 100644 index 00000000..b2494f9b --- /dev/null +++ b/lib/TweetLib.Core/TweetLib.Core.csproj @@ -0,0 +1,10 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + <Platforms>x86</Platforms> + <LangVersion>8.0</LangVersion> + <NullableContextOptions>enable</NullableContextOptions> + </PropertyGroup> + +</Project> diff --git a/lib/TweetLib.Core/Utils/FileUtils.cs b/lib/TweetLib.Core/Utils/FileUtils.cs new file mode 100644 index 00000000..eddfa554 --- /dev/null +++ b/lib/TweetLib.Core/Utils/FileUtils.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; + +namespace TweetLib.Core.Utils{ + public static class FileUtils{ + public static void CreateDirectoryForFile(string file){ + string dir = Path.GetDirectoryName(file); + + if (dir == null){ + throw new ArgumentException("Invalid file path: "+file); + } + else if (dir.Length > 0){ + Directory.CreateDirectory(dir); + } + } + + public static bool CheckFolderWritePermission(string path){ + string testFile = Path.Combine(path, ".test"); + + try{ + Directory.CreateDirectory(path); + + using(File.Create(testFile)){} + File.Delete(testFile); + return true; + }catch{ + return false; + } + } + + public static bool FileExistsAndNotEmpty(string path){ + try{ + return new FileInfo(path).Length > 0; + }catch{ + return false; + } + } + } +} diff --git a/Core/Utils/LocaleUtils.cs b/lib/TweetLib.Core/Utils/LocaleUtils.cs similarity index 94% rename from Core/Utils/LocaleUtils.cs rename to lib/TweetLib.Core/Utils/LocaleUtils.cs index a083d882..ea0e1c59 100644 --- a/Core/Utils/LocaleUtils.cs +++ b/lib/TweetLib.Core/Utils/LocaleUtils.cs @@ -3,8 +3,8 @@ using System.Globalization; using System.Linq; -namespace TweetDuck.Core.Utils{ - static class LocaleUtils{ +namespace TweetLib.Core.Utils{ + public static class LocaleUtils{ // https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/ public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{ "af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE", @@ -33,9 +33,9 @@ public sealed class Item : IComparable<Item>{ private string Name => info?.NativeName ?? Code; - private readonly CultureInfo info; + private readonly CultureInfo? info; - public Item(string code, string alt = null){ + public Item(string code, string? alt = null){ this.Code = code; try{ diff --git a/Core/Utils/StringUtils.cs b/lib/TweetLib.Core/Utils/StringUtils.cs similarity index 91% rename from Core/Utils/StringUtils.cs rename to lib/TweetLib.Core/Utils/StringUtils.cs index cfa27d84..71afde4d 100644 --- a/Core/Utils/StringUtils.cs +++ b/lib/TweetLib.Core/Utils/StringUtils.cs @@ -2,8 +2,8 @@ using System.Linq; using System.Text.RegularExpressions; -namespace TweetDuck.Core.Utils{ - static class StringUtils{ +namespace TweetLib.Core.Utils{ + public static class StringUtils{ public static readonly string[] EmptyArray = new string[0]; public static string ExtractBefore(string str, char search, int startIndex = 0){ @@ -23,7 +23,7 @@ public static string ConvertRot13(string str){ return Regex.Replace(str, @"[a-zA-Z]", match => { int code = match.Value[0]; int start = code <= 90 ? 65 : 97; - return ((char)(start+(code-start+13)%26)).ToString(); + return ((char)(start + (code - start + 13) % 26)).ToString(); }); } diff --git a/lib/TweetLib.Core/Utils/UrlUtils.cs b/lib/TweetLib.Core/Utils/UrlUtils.cs new file mode 100644 index 00000000..8af96a85 --- /dev/null +++ b/lib/TweetLib.Core/Utils/UrlUtils.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; + +namespace TweetLib.Core.Utils{ + public static class UrlUtils{ + private const string TwitterTrackingUrl = "t.co"; + + public enum CheckResult{ + Invalid, Tracking, Fine + } + + public static CheckResult Check(string url){ + if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){ + string scheme = uri.Scheme; + + if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){ + return uri.Host == TwitterTrackingUrl ? CheckResult.Tracking : CheckResult.Fine; + } + } + + return CheckResult.Invalid; + } + + public static string? GetFileNameFromUrl(string url){ + string file = Path.GetFileName(new Uri(url).AbsolutePath); + return string.IsNullOrEmpty(file) ? null : file; + } + } +} diff --git a/lib/TweetLib.Core/Utils/WebUtils.cs b/lib/TweetLib.Core/Utils/WebUtils.cs new file mode 100644 index 00000000..cc9fb47f --- /dev/null +++ b/lib/TweetLib.Core/Utils/WebUtils.cs @@ -0,0 +1,44 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Net; + +namespace TweetLib.Core.Utils{ + public static class WebUtils{ + private static bool HasMicrosoftBeenBroughtTo2008Yet; + + private static void EnsureTLS12(){ + if (!HasMicrosoftBeenBroughtTo2008Yet){ + ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; + ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11); + HasMicrosoftBeenBroughtTo2008Yet = true; + } + } + + public static WebClient NewClient(string userAgent){ + EnsureTLS12(); + + WebClient client = new WebClient{ Proxy = null }; + client.Headers[HttpRequestHeader.UserAgent] = userAgent; + return client; + } + + public static AsyncCompletedEventHandler FileDownloadCallback(string file, Action? onSuccess, Action<Exception>? onFailure){ + return (sender, args) => { + if (args.Cancelled){ + try{ + File.Delete(file); + }catch{ + // didn't want it deleted anyways + } + } + else if (args.Error != null){ + onFailure?.Invoke(args.Error); + } + else{ + onSuccess?.Invoke(); + } + }; + } + } +} diff --git a/lib/TweetTest.System/Configuration/TestUserConfig.cs b/lib/TweetTest.System/Configuration/TestUserConfig.cs index e8179c61..816bb6bf 100644 --- a/lib/TweetTest.System/Configuration/TestUserConfig.cs +++ b/lib/TweetTest.System/Configuration/TestUserConfig.cs @@ -1,9 +1,4 @@ -using System; -using System.Diagnostics.Contracts; -using System.IO; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TweetDuck.Configuration; -using TweetDuck.Core.Other; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TweetTest.Configuration{ [TestClass] diff --git a/lib/TweetTest.System/Data/TestCombinedFileStream.cs b/lib/TweetTest.System/Data/TestCombinedFileStream.cs index c258792e..cdbaf4d3 100644 --- a/lib/TweetTest.System/Data/TestCombinedFileStream.cs +++ b/lib/TweetTest.System/Data/TestCombinedFileStream.cs @@ -2,7 +2,7 @@ using System.IO; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; -using TweetDuck.Data; +using TweetLib.Core.Data; namespace TweetTest.Data{ [TestClass] diff --git a/lib/TweetTest.System/Data/TestFileSerializer.cs b/lib/TweetTest.System/Data/TestFileSerializer.cs index 2a47a272..9f6c3ffd 100644 --- a/lib/TweetTest.System/Data/TestFileSerializer.cs +++ b/lib/TweetTest.System/Data/TestFileSerializer.cs @@ -1,11 +1,13 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; -using TweetDuck.Data.Serialization; +using TweetLib.Core.Serialization; namespace TweetTest.Data{ [TestClass] public class TestFileSerializer : TestIO{ + [SuppressMessage("ReSharper", "UnusedMember.Local")] private enum TestEnum{ A, B, C, D, E } diff --git a/lib/TweetTest.System/TweetTest.System.csproj b/lib/TweetTest.System/TweetTest.System.csproj index 7d207d5d..12baacfa 100644 --- a/lib/TweetTest.System/TweetTest.System.csproj +++ b/lib/TweetTest.System/TweetTest.System.csproj @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" /> + <Import Project="..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">x86</Platform> @@ -9,7 +9,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>TweetTest</RootNamespace> <AssemblyName>TweetTest.System</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> @@ -57,6 +57,10 @@ <Project>{2389a7cd-e0d3-4706-8294-092929a33a2d}</Project> <Name>TweetDuck</Name> </ProjectReference> + <ProjectReference Include="..\TweetLib.Core\TweetLib.Core.csproj"> + <Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project> + <Name>TweetLib.Core</Name> + </ProjectReference> </ItemGroup> <ItemGroup> <None Include="packages.config" /> @@ -85,7 +89,7 @@ <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props'))" /> </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/lib/TweetTest.System/packages.config b/lib/TweetTest.System/packages.config index d1d28893..ae425812 100644 --- a/lib/TweetTest.System/packages.config +++ b/lib/TweetTest.System/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Microsoft.Net.Compilers" version="2.9.0" targetFramework="net452" developmentDependency="true" /> + <package id="Microsoft.Net.Compilers" version="3.0.0" targetFramework="net472" developmentDependency="true" /> </packages> \ No newline at end of file diff --git a/lib/TweetTest.Unit/Core/TestBrowserUtils.fs b/lib/TweetTest.Unit/Core/TestBrowserUtils.fs deleted file mode 100644 index 55ee61a1..00000000 --- a/lib/TweetTest.Unit/Core/TestBrowserUtils.fs +++ /dev/null @@ -1,79 +0,0 @@ -namespace TweetTest.Core.BrowserUtils - -open Xunit -open TweetDuck.Core.Utils - - -module CheckUrl = - type Result = BrowserUtils.UrlCheckResult - - [<Fact>] - let ``accepts HTTP protocol`` () = - Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://example.com")) - - [<Fact>] - let ``accepts HTTPS protocol`` () = - Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("https://example.com")) - - [<Fact>] - let ``accepts FTP protocol`` () = - Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("ftp://example.com")) - - [<Fact>] - let ``accepts MAILTO protocol`` () = - Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("mailto://someone@example.com")) - - [<Fact>] - let ``accepts URL with port, path, query, and hash`` () = - Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://www.example.co.uk:80/path?key=abc&array[]=5#hash")) - - [<Fact>] - let ``accepts IPv4 address`` () = - Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://127.0.0.1")) - - [<Fact>] - let ``accepts IPv6 address`` () = - Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://[2001:db8:0:0:0:ff00:42:8329]")) - - [<Fact>] - let ``recognizes t.co as tracking URL`` () = - Assert.Equal(Result.Tracking, BrowserUtils.CheckUrl("http://t.co/12345")) - - [<Fact>] - let ``rejects empty URL`` () = - Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("")) - - [<Fact>] - let ``rejects missing protocol`` () = - Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("www.example.com")) - - [<Fact>] - let ``rejects banned protocol`` () = - Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("file://example.com")) - - -module GetFileNameFromUrl = - - [<Fact>] - let ``simple file URL returns file name`` () = - Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html")) - - [<Fact>] - let ``file URL with query returns file name`` () = - Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html?version=2")) - - [<Fact>] - let ``file URL w/o extension returns file name`` () = - Assert.Equal("index", BrowserUtils.GetFileNameFromUrl("http://example.com/index")) - - [<Fact>] - let ``file URL with trailing dot returns file name with dot`` () = - Assert.Equal("index.", BrowserUtils.GetFileNameFromUrl("http://example.com/index.")) - - [<Fact>] - let ``root URL returns null`` () = - Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com")) - - [<Fact>] - let ``path URL returns null`` () = - Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com/path/")) diff --git a/lib/TweetTest.Unit/Core/TestStringUtils.fs b/lib/TweetTest.Unit/Core/TestStringUtils.fs index 4196bfba..6ee450c2 100644 --- a/lib/TweetTest.Unit/Core/TestStringUtils.fs +++ b/lib/TweetTest.Unit/Core/TestStringUtils.fs @@ -1,7 +1,7 @@ namespace TweetTest.Core.StringUtils open Xunit -open TweetDuck.Core.Utils +open TweetLib.Core.Utils module ExtractBefore = diff --git a/lib/TweetTest.Unit/Core/TestUrlUtils.fs b/lib/TweetTest.Unit/Core/TestUrlUtils.fs new file mode 100644 index 00000000..36ec2fc7 --- /dev/null +++ b/lib/TweetTest.Unit/Core/TestUrlUtils.fs @@ -0,0 +1,79 @@ +namespace TweetTest.Core.UrlUtils + +open Xunit +open TweetLib.Core.Utils + + +module Check = + type Result = UrlUtils.CheckResult + + [<Fact>] + let ``accepts HTTP protocol`` () = + Assert.Equal(Result.Fine, UrlUtils.Check("http://example.com")) + + [<Fact>] + let ``accepts HTTPS protocol`` () = + Assert.Equal(Result.Fine, UrlUtils.Check("https://example.com")) + + [<Fact>] + let ``accepts FTP protocol`` () = + Assert.Equal(Result.Fine, UrlUtils.Check("ftp://example.com")) + + [<Fact>] + let ``accepts MAILTO protocol`` () = + Assert.Equal(Result.Fine, UrlUtils.Check("mailto://someone@example.com")) + + [<Fact>] + let ``accepts URL with port, path, query, and hash`` () = + Assert.Equal(Result.Fine, UrlUtils.Check("http://www.example.co.uk:80/path?key=abc&array[]=5#hash")) + + [<Fact>] + let ``accepts IPv4 address`` () = + Assert.Equal(Result.Fine, UrlUtils.Check("http://127.0.0.1")) + + [<Fact>] + let ``accepts IPv6 address`` () = + Assert.Equal(Result.Fine, UrlUtils.Check("http://[2001:db8:0:0:0:ff00:42:8329]")) + + [<Fact>] + let ``recognizes t.co as tracking URL`` () = + Assert.Equal(Result.Tracking, UrlUtils.Check("http://t.co/12345")) + + [<Fact>] + let ``rejects empty URL`` () = + Assert.Equal(Result.Invalid, UrlUtils.Check("")) + + [<Fact>] + let ``rejects missing protocol`` () = + Assert.Equal(Result.Invalid, UrlUtils.Check("www.example.com")) + + [<Fact>] + let ``rejects banned protocol`` () = + Assert.Equal(Result.Invalid, UrlUtils.Check("file://example.com")) + + +module GetFileNameFromUrl = + + [<Fact>] + let ``simple file URL returns file name`` () = + Assert.Equal("index.html", UrlUtils.GetFileNameFromUrl("http://example.com/index.html")) + + [<Fact>] + let ``file URL with query returns file name`` () = + Assert.Equal("index.html", UrlUtils.GetFileNameFromUrl("http://example.com/index.html?version=2")) + + [<Fact>] + let ``file URL w/o extension returns file name`` () = + Assert.Equal("index", UrlUtils.GetFileNameFromUrl("http://example.com/index")) + + [<Fact>] + let ``file URL with trailing dot returns file name with dot`` () = + Assert.Equal("index.", UrlUtils.GetFileNameFromUrl("http://example.com/index.")) + + [<Fact>] + let ``root URL returns null`` () = + Assert.Null(UrlUtils.GetFileNameFromUrl("http://example.com")) + + [<Fact>] + let ``path URL returns null`` () = + Assert.Null(UrlUtils.GetFileNameFromUrl("http://example.com/path/")) diff --git a/lib/TweetTest.Unit/Data/TestCommandLineArgs.fs b/lib/TweetTest.Unit/Data/TestCommandLineArgs.fs index 1831bfd4..bd8a3a2c 100644 --- a/lib/TweetTest.Unit/Data/TestCommandLineArgs.fs +++ b/lib/TweetTest.Unit/Data/TestCommandLineArgs.fs @@ -1,7 +1,7 @@ namespace TweetTest.Data.CommandLineArgs open Xunit -open TweetDuck.Data +open TweetLib.Core.Collections type _TestData = @@ -139,52 +139,33 @@ module Flags = module Values = - - [<Theory>] - [<InlineData("val1")>] - [<InlineData("val2")>] - let ``HasValue returns false if key is missing`` (key: string) = - Assert.False(_TestData.empty.HasValue(key)) - Assert.False(_TestData.flags.HasValue(key)) + + [<Fact>] + let ``GetValue returns null if key is missing`` () = + Assert.Null(_TestData.values.GetValue("missing")) [<Theory>] [<InlineData("flag1")>] [<InlineData("flag2")>] [<InlineData("flag3")>] - let ``HasValue returns false if the name specifies a flag`` (key: string) = - Assert.False(_TestData.flags.HasValue(key)) + let ``GetValue returns null if the name specifies a flag`` (key: string) = + Assert.Null(_TestData.flags.GetValue(key)) [<Fact>] - let ``HasValue returns true if the name specifies both a flag and a value key`` () = - Assert.True(_TestData.duplicate.HasValue("duplicate")) - - [<Theory>] - [<InlineData("val1")>] - [<InlineData("val2")>] - let ``HasValue returns true if key is present`` (key: string) = - Assert.True(_TestData.values.HasValue(key)) - - [<Theory>] - [<InlineData("VAL1")>] - [<InlineData("VaL1")>] - let ``HasValue is case-insensitive`` (key: string) = - Assert.True(_TestData.values.HasValue(key)) + let ``GetValue returns correct value if the name specifies both a flag and a value key`` () = + Assert.NotNull(_TestData.duplicate.GetValue("duplicate")) [<Theory>] [<InlineData("val1", "hello")>] [<InlineData("val2", "world")>] let ``GetValue returns correct value if key is present`` (key: string, expectedValue: string) = - Assert.Equal(expectedValue, _TestData.values.GetValue(key, "")) + Assert.Equal(expectedValue, _TestData.values.GetValue(key)) [<Theory>] [<InlineData("VAL1", "hello")>] [<InlineData("VaL1", "hello")>] let ``GetValue is case-insensitive`` (key: string, expectedValue: string) = - Assert.Equal(expectedValue, _TestData.values.GetValue(key, "")) - - [<Fact>] - let ``GetValue returns default value if key is missing`` () = - Assert.Equal("oh no", _TestData.values.GetValue("missing", "oh no")) + Assert.Equal(expectedValue, _TestData.values.GetValue(key)) [<Fact>] let ``SetValue adds new value`` () = @@ -192,7 +173,7 @@ module Values = args.SetValue("val3", "this is nice") Assert.Equal(3, args.Count) - Assert.Equal("this is nice", args.GetValue("val3", "")) + Assert.Equal("this is nice", args.GetValue("val3")) [<Fact>] let ``SetValue replaces existing value`` () = @@ -200,7 +181,7 @@ module Values = args.SetValue("val2", "mom") Assert.Equal(2, args.Count) - Assert.Equal("mom", args.GetValue("val2", "")) + Assert.Equal("mom", args.GetValue("val2")) [<Theory>] [<InlineData("val1")>] @@ -210,7 +191,7 @@ module Values = args.RemoveValue(key) Assert.Equal(1, args.Count) - Assert.False(args.HasValue(key)) + Assert.Null(args.GetValue(key)) [<Theory>] [<InlineData("VAL1")>] @@ -220,7 +201,7 @@ module Values = args.RemoveValue(key) Assert.Equal(1, args.Count) - Assert.False(args.HasValue(key)) + Assert.Null(args.GetValue(key)) [<Fact>] let ``RemoveValue does nothing if key is missing`` () = @@ -239,8 +220,8 @@ module Clone = Assert.True(clone.HasFlag("flag1")) Assert.True(clone.HasFlag("flag2")) Assert.True(clone.HasFlag("flag3")) - Assert.Equal("hello", clone.GetValue("val1", "")) - Assert.Equal("world", clone.GetValue("val2", "")) + Assert.Equal("hello", clone.GetValue("val1")) + Assert.Equal("world", clone.GetValue("val2")) [<Fact>] let ``cloning creates a new object`` () = @@ -259,7 +240,7 @@ module Clone = Assert.True(original.HasFlag("flag1")) Assert.False(original.HasFlag("flag4")) - Assert.Equal("hello", original.GetValue("val1", "")) + Assert.Equal("hello", original.GetValue("val1")) module ToDictionary = @@ -327,8 +308,8 @@ module FromStringArray = Assert.True(args.HasFlag("-flag1")) Assert.True(args.HasFlag("-flag2")) Assert.True(args.HasFlag("-flag3")) - Assert.Equal("first value", args.GetValue("-val1", "")) - Assert.Equal("second value", args.GetValue("-val2", "")) + Assert.Equal("first value", args.GetValue("-val1")) + Assert.Equal("second value", args.GetValue("-val2")) module ReadCefArguments = @@ -346,23 +327,23 @@ module ReadCefArguments = let args = CommandLineArgs.ReadCefArguments("--first-value=10 --second-value=\"long string with spaces\"") Assert.Equal(2, args.Count) - Assert.Equal("10", args.GetValue("first-value", "")) - Assert.Equal("long string with spaces", args.GetValue("second-value", "")) + Assert.Equal("10", args.GetValue("first-value")) + Assert.Equal("long string with spaces", args.GetValue("second-value")) [<Fact>] let ``reads flags as value keys with values of 1`` () = let args = CommandLineArgs.ReadCefArguments("--first-flag-as-value --second-flag-as-value") Assert.Equal(2, args.Count) - Assert.Equal("1", args.GetValue("first-flag-as-value", "")) - Assert.Equal("1", args.GetValue("second-flag-as-value", "")) + Assert.Equal("1", args.GetValue("first-flag-as-value")) + Assert.Equal("1", args.GetValue("second-flag-as-value")) [<Fact>] let ``reads complex string with whitespace correctly`` () = let args = CommandLineArgs.ReadCefArguments("\t--first-value=55.5\r\n--first-flag-as-value\r\n --second-value=\"long string\"\t--second-flag-as-value ") Assert.Equal(4, args.Count) - Assert.Equal("55.5", args.GetValue("first-value", "")) - Assert.Equal("long string", args.GetValue("second-value", "")) - Assert.Equal("1", args.GetValue("first-flag-as-value", "")) - Assert.Equal("1", args.GetValue("second-flag-as-value", "")) + Assert.Equal("55.5", args.GetValue("first-value")) + Assert.Equal("long string", args.GetValue("second-value")) + Assert.Equal("1", args.GetValue("first-flag-as-value")) + Assert.Equal("1", args.GetValue("second-flag-as-value")) diff --git a/lib/TweetTest.Unit/Data/TestInjectedHTML.fs b/lib/TweetTest.Unit/Data/TestInjectedHTML.fs index 8ef0d648..4e34e5ee 100644 --- a/lib/TweetTest.Unit/Data/TestInjectedHTML.fs +++ b/lib/TweetTest.Unit/Data/TestInjectedHTML.fs @@ -1,7 +1,7 @@ namespace TweetTest.Data.InjectedHTML open Xunit -open TweetDuck.Data +open TweetLib.Core.Data module Inject = diff --git a/lib/TweetTest.Unit/Data/TestResult.fs b/lib/TweetTest.Unit/Data/TestResult.fs index 167c7d84..d7e090ac 100644 --- a/lib/TweetTest.Unit/Data/TestResult.fs +++ b/lib/TweetTest.Unit/Data/TestResult.fs @@ -1,7 +1,7 @@ namespace TweetTest.Data.Result open Xunit -open TweetDuck.Data +open TweetLib.Core.Data open System diff --git a/lib/TweetTest.Unit/Data/TestTwoKeyDictionary.fs b/lib/TweetTest.Unit/Data/TestTwoKeyDictionary.fs index 01aab6a1..1878254c 100644 --- a/lib/TweetTest.Unit/Data/TestTwoKeyDictionary.fs +++ b/lib/TweetTest.Unit/Data/TestTwoKeyDictionary.fs @@ -1,7 +1,7 @@ namespace TweetTest.Data.TwoKeyDictionary open Xunit -open TweetDuck.Data +open TweetLib.Core.Collections open System.Collections.Generic diff --git a/lib/TweetTest.Unit/TweetTest.Unit.fsproj b/lib/TweetTest.Unit/TweetTest.Unit.fsproj index c5cddc2a..1e132ede 100644 --- a/lib/TweetTest.Unit/TweetTest.Unit.fsproj +++ b/lib/TweetTest.Unit/TweetTest.Unit.fsproj @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\..\packages\xunit.runner.visualstudio.2.4.0\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.4.0\build\net20\xunit.runner.visualstudio.props')" /> - <Import Project="..\..\packages\xunit.core.2.4.0\build\xunit.core.props" Condition="Exists('..\..\packages\xunit.core.2.4.0\build\xunit.core.props')" /> - <Import Project="..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.props" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.props')" /> - <Import Project="..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.props" Condition="Exists('..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.props')" /> + <Import Project="..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" /> + <Import Project="..\..\packages\xunit.core.2.4.1\build\xunit.core.props" Condition="Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.props')" /> + <Import Project="..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.props" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.props')" /> + <Import Project="..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.props" Condition="Exists('..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -14,7 +14,7 @@ <RootNamespace>TweetTest.Unit</RootNamespace> <AssemblyName>TweetTest.Unit</AssemblyName> <UseStandardResourceNames>True</UseStandardResourceNames> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <TargetFSharpCoreVersion>4.4.3.0</TargetFSharpCoreVersion> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <Name>TweetTest.Unit</Name> @@ -49,23 +49,10 @@ </Otherwise> </Choose> <Import Project="$(FSharpTargetsPath)" /> - <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> - <PropertyGroup> - <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> - </PropertyGroup> - <Error Condition="!Exists('..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.props'))" /> - <Error Condition="!Exists('..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.targets'))" /> - <Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.props'))" /> - <Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.targets'))" /> - <Error Condition="!Exists('..\..\packages\xunit.core.2.4.0\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.0\build\xunit.core.props'))" /> - <Error Condition="!Exists('..\..\packages\xunit.core.2.4.0\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.0\build\xunit.core.targets'))" /> - <Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.4.0\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.4.0\build\net20\xunit.runner.visualstudio.props'))" /> - </Target> - <Import Project="..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.targets" Condition="Exists('..\..\packages\Microsoft.CodeCoverage.15.8.0\build\netstandard1.0\Microsoft.CodeCoverage.targets')" /> <ItemGroup> - <Compile Include="Core\TestBrowserUtils.fs" /> <Compile Include="Core\TestStringUtils.fs" /> <Compile Include="Core\TestTwitterUtils.fs" /> + <Compile Include="Core\TestUrlUtils.fs" /> <Compile Include="Data\TestCommandLineArgs.fs" /> <Compile Include="Data\TestInjectedHTML.fs" /> <Compile Include="Data\TestResult.fs" /> @@ -73,8 +60,13 @@ <Content Include="packages.config" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\TweetLib.Core\TweetLib.Core.csproj"> + <Name>TweetLib.Core</Name> + <Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project> + <Private>True</Private> + </ProjectReference> <Reference Include="Microsoft.VisualStudio.CodeCoverage.Shim"> - <HintPath>..\..\packages\Microsoft.CodeCoverage.15.8.0\lib\net45\Microsoft.VisualStudio.CodeCoverage.Shim.dll</HintPath> + <HintPath>..\..\packages\Microsoft.CodeCoverage.16.1.0\lib\net45\Microsoft.VisualStudio.CodeCoverage.Shim.dll</HintPath> </Reference> <Reference Include="mscorlib" /> <Reference Include="FSharp.Core"> @@ -91,20 +83,33 @@ <Private>True</Private> </ProjectReference> <Reference Include="xunit.abstractions"> - <HintPath>..\..\packages\xunit.abstractions.2.0.2\lib\net35\xunit.abstractions.dll</HintPath> + <HintPath>..\..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll</HintPath> </Reference> <Reference Include="xunit.assert"> - <HintPath>..\..\packages\xunit.assert.2.4.0\lib\netstandard1.1\xunit.assert.dll</HintPath> + <HintPath>..\..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll</HintPath> </Reference> <Reference Include="xunit.core"> - <HintPath>..\..\packages\xunit.extensibility.core.2.4.0\lib\net452\xunit.core.dll</HintPath> + <HintPath>..\..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll</HintPath> </Reference> <Reference Include="xunit.execution.desktop"> - <HintPath>..\..\packages\xunit.extensibility.execution.2.4.0\lib\net452\xunit.execution.desktop.dll</HintPath> + <HintPath>..\..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll</HintPath> </Reference> </ItemGroup> - <Import Project="..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.targets" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.15.8.0\build\net45\Microsoft.Net.Test.Sdk.targets')" /> - <Import Project="..\..\packages\xunit.core.2.4.0\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.4.0\build\xunit.core.targets')" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.targets'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.props'))" /> + <Error Condition="!Exists('..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.targets'))" /> + <Error Condition="!Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.1\build\xunit.core.props'))" /> + <Error Condition="!Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" /> + <Error Condition="!Exists('..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.runner.visualstudio.2.4.1\build\net20\xunit.runner.visualstudio.props'))" /> + </Target> + <Import Project="..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.targets" Condition="Exists('..\..\packages\Microsoft.CodeCoverage.16.1.0\build\netstandard1.0\Microsoft.CodeCoverage.targets')" /> + <Import Project="..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.targets" Condition="Exists('..\..\packages\Microsoft.NET.Test.Sdk.16.1.0\build\net40\Microsoft.NET.Test.Sdk.targets')" /> + <Import Project="..\..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.targets')" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/lib/TweetTest.Unit/packages.config b/lib/TweetTest.Unit/packages.config index a8b83559..7875eab9 100644 --- a/lib/TweetTest.Unit/packages.config +++ b/lib/TweetTest.Unit/packages.config @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Microsoft.CodeCoverage" version="15.8.0" targetFramework="net452" /> - <package id="Microsoft.NET.Test.Sdk" version="15.8.0" targetFramework="net452" /> - <package id="xunit" version="2.4.0" targetFramework="net452" /> - <package id="xunit.abstractions" version="2.0.2" targetFramework="net452" /> + <package id="Microsoft.CodeCoverage" version="16.1.0" targetFramework="net472" /> + <package id="Microsoft.NET.Test.Sdk" version="16.1.0" targetFramework="net472" /> + <package id="xunit" version="2.4.1" targetFramework="net472" /> + <package id="xunit.abstractions" version="2.0.3" targetFramework="net472" /> <package id="xunit.analyzers" version="0.10.0" targetFramework="net452" /> - <package id="xunit.assert" version="2.4.0" targetFramework="net452" /> - <package id="xunit.core" version="2.4.0" targetFramework="net452" /> - <package id="xunit.extensibility.core" version="2.4.0" targetFramework="net452" /> - <package id="xunit.extensibility.execution" version="2.4.0" targetFramework="net452" /> - <package id="xunit.runner.visualstudio" version="2.4.0" targetFramework="net452" developmentDependency="true" /> + <package id="xunit.assert" version="2.4.1" targetFramework="net472" /> + <package id="xunit.core" version="2.4.1" targetFramework="net472" /> + <package id="xunit.extensibility.core" version="2.4.1" targetFramework="net472" /> + <package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net472" /> + <package id="xunit.runner.visualstudio" version="2.4.1" targetFramework="net472" developmentDependency="true" /> </packages> \ No newline at end of file diff --git a/packages.config b/packages.config index 9345c4c4..70a9d869 100644 --- a/packages.config +++ b/packages.config @@ -4,5 +4,5 @@ <package id="cef.redist.x86" version="3.3396.1786" targetFramework="net452" /> <package id="CefSharp.Common" version="67.0.0" targetFramework="net452" /> <package id="CefSharp.WinForms" version="67.0.0" targetFramework="net452" /> - <package id="Microsoft.Net.Compilers" version="2.9.0" targetFramework="net452" developmentDependency="true" /> + <package id="Microsoft.Net.Compilers" version="3.0.0" targetFramework="net472" developmentDependency="true" /> </packages> \ No newline at end of file diff --git a/subprocess/TweetDuck.Browser.csproj b/subprocess/TweetDuck.Browser.csproj index 6e0f93e7..ee61b1fa 100644 --- a/subprocess/TweetDuck.Browser.csproj +++ b/subprocess/TweetDuck.Browser.csproj @@ -1,6 +1,6 @@ -<?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="..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" /> + <Import Project="..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -9,7 +9,7 @@ <OutputType>WinExe</OutputType> <RootNamespace>TweetDuck.Browser</RootNamespace> <AssemblyName>TweetDuck.Browser</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <NuGetPackageImportStamp> @@ -18,7 +18,7 @@ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> <PlatformTarget>x86</PlatformTarget> <OutputPath>bin\x86\Debug\</OutputPath> - <LangVersion>7</LangVersion> + <LangVersion>8.0</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <PlatformTarget>x86</PlatformTarget> @@ -51,6 +51,6 @@ editbin /largeaddressaware /TSAWARE "$(TargetPath)"</PostBuildEvent> <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props'))" /> + <Error Condition="!Exists('..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props'))" /> </Target> </Project> \ No newline at end of file diff --git a/subprocess/packages.config b/subprocess/packages.config index d1d28893..ae425812 100644 --- a/subprocess/packages.config +++ b/subprocess/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Microsoft.Net.Compilers" version="2.9.0" targetFramework="net452" developmentDependency="true" /> + <package id="Microsoft.Net.Compilers" version="3.0.0" targetFramework="net472" developmentDependency="true" /> </packages> \ No newline at end of file diff --git a/video/Properties/Resources.Designer.cs b/video/Properties/Resources.Designer.cs index 15b8d2c9..c16b1c5b 100644 --- a/video/Properties/Resources.Designer.cs +++ b/video/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace TweetDuck.Video.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/video/TweetDuck.Video.csproj b/video/TweetDuck.Video.csproj index 7c8f9b71..38c77e40 100644 --- a/video/TweetDuck.Video.csproj +++ b/video/TweetDuck.Video.csproj @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Import Project="..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" /> + <Import Project="..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -9,7 +9,7 @@ <OutputType>WinExe</OutputType> <RootNamespace>TweetDuck.Video</RootNamespace> <AssemblyName>TweetDuck.Video</AssemblyName> - <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <ResolveComReferenceSilent>True</ResolveComReferenceSilent> @@ -25,7 +25,7 @@ <ErrorReport>prompt</ErrorReport> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <Prefer32Bit>true</Prefer32Bit> - <LangVersion>7</LangVersion> + <LangVersion>8.0</LangVersion> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <OutputPath>bin\x86\Release\</OutputPath> @@ -108,6 +108,6 @@ <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Net.Compilers.2.9.0\build\Microsoft.Net.Compilers.props'))" /> + <Error Condition="!Exists('..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Net.Compilers.3.0.0\build\Microsoft.Net.Compilers.props'))" /> </Target> </Project> \ No newline at end of file diff --git a/video/packages.config b/video/packages.config index d1d28893..ae425812 100644 --- a/video/packages.config +++ b/video/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Microsoft.Net.Compilers" version="2.9.0" targetFramework="net452" developmentDependency="true" /> + <package id="Microsoft.Net.Compilers" version="3.0.0" targetFramework="net472" developmentDependency="true" /> </packages> \ No newline at end of file