1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-02 11:34:08 +02:00

Update .NET & begin refactoring code into a core lib ()

* Switch to .NET Framework 4.7.2 & C# 8.0, update libraries

* Add TweetLib.Core project targeting .NET Standard 2.0

* Enable reference nullability checks for TweetLib.Core

* Move a bunch of utility classes into TweetLib.Core & refactor

* Partially move TweetDuck plugin & update system to TweetLib.Core

* Move some constants and CultureInfo setup to TweetLib.Core

* Move some configuration classes to TweetLib.Core

* Minor refactoring and warning suppression

* Add App to TweetLib.Core

* Add IAppErrorHandler w/ implementation

* Continue moving config, plugin, and update classes to TweetLib.Core

* Fix a few nullability checks

* Update installers to check for .NET Framework 4.7.2
This commit is contained in:
Daniel Chýlek 2019-05-26 14:55:12 +02:00 committed by GitHub
parent aca438b837
commit 1ccefe853a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 926 additions and 794 deletions
Configuration
Core
Data
Plugins
Program.cs
Properties
Reporter.csTweetDuck.csprojTweetDuck.sln
Updates
bld
lib

View File

@ -1,5 +1,5 @@
using System; using System;
using TweetDuck.Data; using TweetLib.Core.Collections;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
static class Arguments{ static class Arguments{
@ -22,8 +22,8 @@ public static bool HasFlag(string flag){
return Current.HasFlag(flag); return Current.HasFlag(flag);
} }
public static string GetValue(string key, string defaultValue){ public static string GetValue(string key){
return Current.GetValue(key, defaultValue); return Current.GetValue(key);
} }
public static CommandLineArgs GetCurrentClean(){ public static CommandLineArgs GetCurrentClean(){

View File

@ -1,13 +1,13 @@
using System; using System;
using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using TweetDuck.Configuration.Instance;
using TweetDuck.Core.Utils;
using TweetDuck.Data; 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{ namespace TweetDuck.Configuration{
sealed class ConfigManager{ sealed class ConfigManager : IConfigManager{
public UserConfig User { get; } public UserConfig User { get; }
public SystemConfig System { get; } public SystemConfig System { get; }
public PluginConfig Plugins { get; } public PluginConfig Plugins { get; }
@ -16,7 +16,7 @@ sealed class ConfigManager{
private readonly FileConfigInstance<UserConfig> infoUser; private readonly FileConfigInstance<UserConfig> infoUser;
private readonly FileConfigInstance<SystemConfig> infoSystem; private readonly FileConfigInstance<SystemConfig> infoSystem;
private readonly PluginConfigInstance infoPlugins; private readonly PluginConfigInstance<PluginConfig> infoPlugins;
private readonly IConfigInstance<BaseConfig>[] infoList; private readonly IConfigInstance<BaseConfig>[] infoList;
@ -28,7 +28,7 @@ public ConfigManager(){
infoList = new IConfigInstance<BaseConfig>[]{ infoList = new IConfigInstance<BaseConfig>[]{
infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"), infoUser = new FileConfigInstance<UserConfig>(Program.UserConfigFilePath, User, "program options"),
infoSystem = new FileConfigInstance<SystemConfig>(Program.SystemConfigFilePath, System, "system 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 // TODO refactor further
@ -70,59 +70,13 @@ public void ReloadAll(){
infoPlugins.Reload(); infoPlugins.Reload();
} }
private void TriggerProgramRestartRequested(){ void IConfigManager.TriggerProgramRestartRequested(){
ProgramRestartRequested?.Invoke(this, EventArgs.Empty); ProgramRestartRequested?.Invoke(this, EventArgs.Empty);
} }
private IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance){ IConfigInstance<BaseConfig> IConfigManager.GetInstanceInfo(BaseConfig instance){
Type instanceType = instance.GetType(); Type instanceType = instance.GetType();
return Array.Find(infoList, info => info.Instance.GetType() == instanceType); // TODO handle null 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();
}
}
}
} }
} }

View File

@ -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();
}
}
}

View File

@ -1,20 +1,41 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using TweetDuck.Plugins; using TweetLib.Core.Features.Configuration;
using TweetDuck.Plugins.Events; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Events;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class PluginConfig : ConfigManager.BaseConfig, IPluginConfig{ sealed class PluginConfig : BaseConfig, IPluginConfig{
private static readonly string[] DefaultDisabled = { private static readonly string[] DefaultDisabled = {
"official/clear-columns", "official/clear-columns",
"official/reply-account" "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; 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){ public void SetEnabled(Plugin plugin, bool enabled){
if ((enabled && disabled.Remove(plugin.Identifier)) || (!enabled && disabled.Add(plugin.Identifier))){ 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){ public bool IsEnabled(Plugin plugin){
return !disabled.Contains(plugin.Identifier); 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);
}
} }
} }

View File

@ -1,5 +1,7 @@
namespace TweetDuck.Configuration{ using TweetLib.Core.Features.Configuration;
sealed class SystemConfig : ConfigManager.BaseConfig{
namespace TweetDuck.Configuration{
sealed class SystemConfig : BaseConfig{
// CONFIGURATION DATA // CONFIGURATION DATA
@ -17,9 +19,9 @@ public bool HardwareAcceleration{
// END OF CONFIG // 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); return new SystemConfig(configManager);
} }
} }

View File

@ -5,9 +5,10 @@
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetDuck.Data;
using TweetLib.Core.Features.Configuration;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class UserConfig : ConfigManager.BaseConfig{ sealed class UserConfig : BaseConfig{
// CONFIGURATION DATA // CONFIGURATION DATA
@ -135,9 +136,9 @@ public string SpellCheckLanguage{
// END OF CONFIG // 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); return new UserConfig(configManager);
} }
} }

View File

@ -1,4 +1,5 @@
using System.Windows.Forms; using System.Diagnostics.CodeAnalysis;
using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Management; using TweetDuck.Core.Management;
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
@ -6,6 +7,7 @@
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
class TweetDeckBridge{ class TweetDeckBridge{
public static string FontSize { get; private set; } public static string FontSize { get; private set; }
public static string NotificationHeadLayout { get; private set; } public static string NotificationHeadLayout { get; private set; }
@ -63,7 +65,7 @@ public void DisplayTooltip(string text){
} }
// Notification only // Notification only
public sealed class Notification : TweetDeckBridge{ public sealed class Notification : TweetDeckBridge{
public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){} public Notification(FormBrowser form, FormNotificationMain notification) : base(form, notification){}

View File

@ -1,9 +1,11 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Updates; using TweetLib.Core.Features.Updates;
namespace TweetDuck.Core.Bridge{ namespace TweetDuck.Core.Bridge{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
class UpdateBridge{ class UpdateBridge{
private readonly UpdateHandler updates; private readonly UpdateHandler updates;
private readonly Control sync; private readonly Control sync;

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Bridge; using TweetDuck.Core.Bridge;
@ -14,9 +15,10 @@
using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Events;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetDuck.Updates; using TweetDuck.Updates;
using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Features.Updates;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{ sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{
@ -71,7 +73,7 @@ public FormBrowser(){
this.notification = new FormNotificationTweet(this, plugins); this.notification = new FormNotificationTweet(this, plugins);
this.notification.Show(); 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.updates.CheckFinished += updates_CheckFinished;
this.updateBridge = new UpdateBridge(updates, this); this.updateBridge = new UpdateBridge(updates, this);

View File

@ -17,8 +17,6 @@ public static bool TryBringToFront<T>() where T : Form{
else return false; else return false;
} }
public static bool HasAnyDialogs => Application.OpenForms.OfType<IAppDialog>().Any();
public static void CloseAllDialogs(){ public static void CloseAllDialogs(){
foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){ foreach(IAppDialog dialog in Application.OpenForms.OfType<IAppDialog>().Reverse()){
((Form)dialog).Close(); ((Form)dialog).Close();

View File

@ -12,6 +12,7 @@
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Other.Analytics; using TweetDuck.Core.Other.Analytics;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Handling{ namespace TweetDuck.Core.Handling{
abstract class ContextMenuBase : IContextMenuHandler{ abstract class ContextMenuBase : IContextMenuHandler{

View File

@ -1,6 +1,6 @@
using System; using System;
using CefSharp; using CefSharp;
using TweetDuck.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Core.Management{ namespace TweetDuck.Core.Management{
sealed class ContextInfo{ sealed class ContextInfo{
@ -107,7 +107,7 @@ public sealed class Builder{
private string unsafeLinkUrl = string.Empty; private string unsafeLinkUrl = string.Empty;
private string mediaUrl = string.Empty; private string mediaUrl = string.Empty;
private ChirpInfo chirp = default(ChirpInfo); private ChirpInfo chirp = default;
public void AddContext(IContextMenuParams parameters){ public void AddContext(IContextMenuParams parameters){
ContextMenuType flags = parameters.TypeFlags; ContextMenuType flags = parameters.TypeFlags;

View File

@ -3,9 +3,10 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Data;
using TweetDuck.Plugins; 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{ namespace TweetDuck.Core.Management{
sealed class ProfileManager{ sealed class ProfileManager{

View File

@ -6,10 +6,10 @@
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Handling; using TweetDuck.Core.Handling;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetLib.Core.Data;
using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Core.Notification{ namespace TweetDuck.Core.Notification{
abstract partial class FormNotificationMain : FormNotificationBase{ abstract partial class FormNotificationMain : FormNotificationBase{

View File

@ -6,9 +6,9 @@
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetLib.Core.Data;
namespace TweetDuck.Core.Notification.Screenshot{ namespace TweetDuck.Core.Notification.Screenshot{
sealed class FormNotificationScreenshotable : FormNotificationBase{ sealed class FormNotificationScreenshotable : FormNotificationBase{

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
namespace TweetDuck.Core.Notification.Screenshot{ namespace TweetDuck.Core.Notification.Screenshot{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
sealed class ScreenshotBridge{ sealed class ScreenshotBridge{
private readonly Control owner; private readonly Control owner;

View File

@ -2,7 +2,8 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using TweetDuck.Data.Serialization; using TweetLib.Core.Serialization;
using TweetLib.Core.Serialization.Converters;
namespace TweetDuck.Core.Other.Analytics{ namespace TweetDuck.Core.Other.Analytics{
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")] [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]

View File

@ -9,6 +9,8 @@
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetLib.Core;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Other.Analytics{ namespace TweetDuck.Core.Other.Analytics{
sealed class AnalyticsManager : IDisposable{ sealed class AnalyticsManager : IDisposable{
@ -20,7 +22,7 @@ sealed class AnalyticsManager : IDisposable{
#else #else
"https://tweetduck.chylex.com/breadcrumb/report" "https://tweetduck.chylex.com/breadcrumb/report"
#endif #endif
); );
public AnalyticsFile File { get; } public AnalyticsFile File { get; }
@ -80,7 +82,7 @@ private void ScheduleReportIn(TimeSpan delay, string message = null){
private void SetLastDataCollectionTime(DateTime dt, 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.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind);
File.LastCollectionVersion = Program.VersionTag; File.LastCollectionVersion = Program.VersionTag;
File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture); File.LastCollectionMessage = message ?? dt.ToString("g", Lib.Culture);
File.Save(); File.Save();
RestartTimer(); RestartTimer();
@ -117,7 +119,7 @@ private void SendReport(){
System.Diagnostics.Debugger.Break(); System.Diagnostics.Debugger.Break();
#endif #endif
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection()); WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
}).ContinueWith(task => browser.InvokeAsyncSafe(() => { }).ContinueWith(task => browser.InvokeAsyncSafe(() => {
if (task.Status == TaskStatus.RanToCompletion){ if (task.Status == TaskStatus.RanToCompletion){
SetLastDataCollectionTime(DateTime.Now); SetLastDataCollectionTime(DateTime.Now);

View File

@ -11,7 +11,10 @@
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; 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{ namespace TweetDuck.Core.Other.Analytics{
static class AnalyticsReportGenerator{ static class AnalyticsReportGenerator{
@ -27,7 +30,7 @@ public static AnalyticsReport Create(AnalyticsFile file, ExternalInfo info, Plug
{ "System Edition" , SystemEdition }, { "System Edition" , SystemEdition },
{ "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" }, { "System Environment" , Environment.Is64BitOperatingSystem ? "64-bit" : "32-bit" },
{ "System Build" , SystemBuild }, { "System Build" , SystemBuild },
{ "System Locale" , Program.Culture.Name.ToLower() }, { "System Locale" , Lib.Culture.Name.ToLower() },
0, 0,
{ "RAM" , Exact(RamSize) }, { "RAM" , Exact(RamSize) },
{ "GPU" , GpuVendor }, { "GPU" , GpuVendor },

View File

@ -6,6 +6,7 @@
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Controls; using TweetDuck.Plugins.Controls;
using TweetLib.Core.Features.Plugins;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{
sealed partial class FormPlugins : Form, FormManager.IAppDialog{ sealed partial class FormPlugins : Form, FormManager.IAppDialog{

View File

@ -10,7 +10,7 @@
using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Updates; using TweetLib.Core.Features.Updates;
namespace TweetDuck.Core.Other{ namespace TweetDuck.Core.Other{
sealed partial class FormSettings : Form, FormManager.IAppDialog{ sealed partial class FormSettings : Form, FormManager.IAppDialog{
@ -195,7 +195,7 @@ private void control_MouseWheel(object sender, MouseEventArgs e){
private sealed class SettingsTab{ private sealed class SettingsTab{
public Button Button { get; } public Button Button { get; }
public BaseTabSettings Control => control ?? (control = constructor()); public BaseTabSettings Control => control ??= constructor();
public bool IsInitialized => control != null; public bool IsInitialized => control != null;
private readonly Func<BaseTabSettings> constructor; private readonly Func<BaseTabSettings> constructor;

View File

@ -5,8 +5,6 @@
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsAnalytics : Form{ sealed partial class DialogSettingsAnalytics : Form{
public string CefArgs => textBoxReport.Text;
public DialogSettingsAnalytics(AnalyticsReport report){ public DialogSettingsAnalytics(AnalyticsReport report){
InitializeComponent(); InitializeComponent();

View File

@ -2,7 +2,7 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetLib.Core.Collections;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsCefArgs : Form{ sealed partial class DialogSettingsCefArgs : Form{

View File

@ -4,8 +4,8 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Management; using TweetDuck.Core.Management;
using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsManage : Form{ sealed partial class DialogSettingsManage : Form{

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Data; using TweetLib.Core.Collections;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{ sealed partial class DialogSettingsRestart : Form{
@ -18,7 +18,7 @@ public DialogSettingsRestart(CommandLineArgs currentArgs){
tbDataFolder.Enabled = false; tbDataFolder.Enabled = false;
} }
else{ else{
tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder, string.Empty); tbDataFolder.Text = currentArgs.GetValue(Arguments.ArgDataFolder) ?? string.Empty;
tbDataFolder.TextChanged += control_Change; tbDataFolder.TextChanged += control_Change;
} }

View File

@ -6,7 +6,8 @@
using TweetDuck.Core.Handling.General; using TweetDuck.Core.Handling.General;
using TweetDuck.Core.Other.Settings.Dialogs; using TweetDuck.Core.Other.Settings.Dialogs;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Updates; using TweetLib.Core.Features.Updates;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Other.Settings{ namespace TweetDuck.Core.Other.Settings{
sealed partial class TabSettingsGeneral : BaseTabSettings{ sealed partial class TabSettingsGeneral : BaseTabSettings{

View File

@ -12,8 +12,8 @@
using TweetDuck.Core.Notification; using TweetDuck.Core.Notification;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Core{ namespace TweetDuck.Core{
sealed class TweetDeckBrowser : IDisposable{ sealed class TweetDeckBrowser : IDisposable{

View File

@ -3,16 +3,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion; 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 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")); 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){ public static void OpenExternalBrowser(string url){
if (string.IsNullOrWhiteSpace(url))return; if (string.IsNullOrWhiteSpace(url))return;
switch(CheckUrl(url)){ switch(UrlUtils.Check(url)){
case UrlCheckResult.Fine: case UrlUtils.CheckResult.Fine:
if (FormGuide.CheckGuideUrl(url, out string hash)){ if (FormGuide.CheckGuideUrl(url, out string hash)){
FormGuide.Show(hash); FormGuide.Show(hash);
} }
@ -117,9 +99,9 @@ public static void OpenExternalBrowser(string url){
break; break;
case UrlCheckResult.Tracking: case UrlUtils.CheckResult.Tracking:
if (Config.IgnoreTrackingUrlWarning){ 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)){ 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){ if (result == DialogResult.Ignore || result == DialogResult.Yes){
goto case UrlCheckResult.Fine; goto case UrlUtils.CheckResult.Fine;
} }
} }
break; break;
case UrlCheckResult.Invalid: case UrlUtils.CheckResult.Invalid:
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK); FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
break; 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){ public static string GetErrorName(CefErrorCode code){
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); 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){ public static int Scale(int baseValue, double scaleFactor){
return (int)Math.Round(baseValue*scaleFactor); return (int)Math.Round(baseValue*scaleFactor);
} }

View File

@ -8,7 +8,9 @@
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Data; using TweetDuck.Data;
using System.Linq; using System.Linq;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using TweetLib.Core.Utils;
using Cookie = CefSharp.Cookie; using Cookie = CefSharp.Cookie;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
@ -71,7 +73,7 @@ public static string GetMediaLink(string url, ImageQuality quality){
} }
public static string GetImageFileName(string url){ public static string GetImageFileName(string url){
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url)); return UrlUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
} }
public static void ViewImage(string url, ImageQuality quality){ 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()); string file = Path.Combine(BrowserCache.CacheFolder, GetImageFileName(url) ?? Path.GetRandomFileName());
if (WindowsUtils.FileExistsAndNotEmpty(file)){ if (FileUtils.FileExistsAndNotEmpty(file)){
ViewImageInternal(file); ViewImageInternal(file);
} }
else{ else{
@ -143,7 +145,7 @@ void OnFailure(Exception ex){
} }
public static void DownloadVideo(string url, string username){ public static void DownloadVideo(string url, string username){
string filename = BrowserUtils.GetFileNameFromUrl(url); string filename = UrlUtils.GetFileNameFromUrl(url);
string ext = Path.GetExtension(filename); string ext = Path.GetExtension(filename);
using(SaveFileDialog dialog = new SaveFileDialog{ 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); }, scheduler);
} }
} }

View File

@ -3,7 +3,6 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; 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 Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
private static readonly bool IsWindows8OrNewer; private static readonly bool IsWindows8OrNewer;
private static bool HasMicrosoftBeenBroughtTo2008Yet;
public static int CurrentProcessID { get; } public static int CurrentProcessID { get; }
public static bool ShouldAvoidToolWindow { get; } public static bool ShouldAvoidToolWindow { get; }
@ -32,47 +30,6 @@ static WindowsUtils(){
ShouldAvoidToolWindow = IsWindows8OrNewer; 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){ public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
try{ try{

View File

@ -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);
}
}

View File

@ -1,8 +1,8 @@
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetLib.Core.Serialization.Converters;
using TweetDuck.Data.Serialization; using TweetLib.Core.Utils;
namespace TweetDuck.Data{ namespace TweetDuck.Data{
sealed class WindowState{ sealed class WindowState{

View File

@ -3,7 +3,8 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Controls; using TweetDuck.Core.Controls;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins.Enums; using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Plugins.Controls{ namespace TweetDuck.Plugins.Controls{
sealed partial class PluginControl : UserControl{ sealed partial class PluginControl : UserControl{

View File

@ -1,5 +0,0 @@
namespace TweetDuck.Plugins.Enums{
enum PluginFolder{
Root, Data
}
}

View File

@ -1,13 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDuck.Core.Utils; using TweetLib.Core.Collections;
using TweetDuck.Data; using TweetLib.Core.Data;
using TweetDuck.Plugins.Enums; using TweetLib.Core.Features.Plugins;
using TweetDuck.Plugins.Events; using TweetLib.Core.Features.Plugins.Enums;
using TweetLib.Core.Features.Plugins.Events;
using TweetLib.Core.Utils;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
[SuppressMessage("ReSharper", "UnusedMember.Global")]
sealed class PluginBridge{ sealed class PluginBridge{
private static string SanitizeCacheKey(string key){ private static string SanitizeCacheKey(string key){
return key.Replace('\\', '/').Trim(); 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){ public void WriteFile(int token, string path, string contents){
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
WindowsUtils.CreateDirectoryForFile(fullPath); FileUtils.CreateDirectoryForFile(fullPath);
File.WriteAllText(fullPath, contents, Encoding.UTF8); File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[token, SanitizeCacheKey(path)] = contents; fileCache[token, SanitizeCacheKey(path)] = contents;
} }

View File

@ -6,10 +6,12 @@
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events;
using TweetDuck.Resources; 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{ namespace TweetDuck.Plugins{
sealed class PluginManager{ sealed class PluginManager{
@ -126,12 +128,19 @@ IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
} }
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){ 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; Plugin plugin;
try{ try{
plugin = PluginLoader.FromFolder(fullDir, group); plugin = PluginLoader.FromFolder(name, fullDir, Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name), group);
}catch(Exception e){ }catch(Exception e){
loadErrors.Add(group.GetIdentifierPrefix()+Path.GetFileName(fullDir)+": "+e.Message); loadErrors.Add($"{group.GetIdentifierPrefix()}{name}: {e.Message}");
continue; continue;
} }

View File

@ -2,10 +2,8 @@
using CefSharp.WinForms; using CefSharp.WinForms;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core; using TweetDuck.Core;
@ -14,14 +12,16 @@
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Core.Management; using TweetDuck.Core.Management;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetLib.Core;
using TweetLib.Core.Collections;
using TweetLib.Core.Utils;
namespace TweetDuck{ namespace TweetDuck{
static class Program{ static class Program{
public const string BrandName = "TweetDuck"; public const string BrandName = Lib.BrandName;
public const string Website = "https://tweetduck.chylex.com"; 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 string ProgramPath = AppDomain.CurrentDomain.BaseDirectory;
public static readonly bool IsPortable = File.Exists(Path.Combine(ProgramPath, "makeportable")); 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 readonly LockManager LockManager = new LockManager(Path.Combine(StoragePath, ".lock"));
private static bool HasCleanedUp; private static bool HasCleanedUp;
public static CultureInfo Culture { get; }
public static Reporter Reporter { get; } public static Reporter Reporter { get; }
public static ConfigManager Config { get; } public static ConfigManager Config { get; }
static Program(){ 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 = new Reporter(ErrorLogFilePath);
Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :("); Reporter.SetupUnhandledExceptionHandler("TweetDuck Has Failed :(");
Config = new ConfigManager(); Config = new ConfigManager();
Lib.Initialize(new App.Builder{
ErrorHandler = Reporter
});
} }
[STAThread] [STAThread]
@ -75,7 +70,7 @@ private static void Main(){
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore"); 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); FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
return; return;
} }
@ -131,7 +126,7 @@ private static void Main(){
} }
try{ try{
RequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze, null)); RequestHandlerBase.LoadResourceRewriteRules(Arguments.GetValue(Arguments.ArgFreeze));
}catch(Exception e){ }catch(Exception e){
FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: "+e.Message, FormMessage.OK); FormMessage.Error("Resource Freeze", "Error parsing resource rewrite rules: "+e.Message, FormMessage.OK);
return; return;
@ -168,7 +163,7 @@ private static void Main(){
// ProgramPath has a trailing backslash // ProgramPath has a trailing backslash
string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : ""); 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)){ if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
Application.Exit(); Application.Exit();
@ -180,7 +175,7 @@ private static void Main(){
} }
private static string GetDataStoragePath(){ 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 (custom != null && (custom.Contains(Path.DirectorySeparatorChar) || custom.Contains(Path.AltDirectorySeparatorChar))){
if (Path.GetInvalidPathChars().Any(custom.Contains)){ if (Path.GetInvalidPathChars().Any(custom.Contains)){

View File

@ -19,7 +19,7 @@ namespace TweetDuck.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // 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.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {

View File

@ -6,9 +6,11 @@
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetLib.Core;
using TweetLib.Core.Application;
namespace TweetDuck{ namespace TweetDuck{
sealed class Reporter{ sealed class Reporter : IAppErrorHandler{
private readonly string logFile; private readonly string logFile;
public Reporter(string logFile){ public Reporter(string logFile){
@ -28,8 +30,12 @@ public bool LogVerbose(string data){
} }
public bool LogImportant(string data){ public bool LogImportant(string data){
return ((IAppErrorHandler)this).Log(data);
}
bool IAppErrorHandler.Log(string text){
#if DEBUG #if DEBUG
Debug.WriteLine(data); Debug.WriteLine(text);
#endif #endif
StringBuilder build = new StringBuilder(); 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("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("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("]\r\n");
build.Append(data).Append("\r\n\r\n"); build.Append(text).Append("\r\n\r\n");
try{ try{
File.AppendAllText(logFile, build.ToString(), Encoding.UTF8); File.AppendAllText(logFile, build.ToString(), Encoding.UTF8);

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <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.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\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')" /> <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> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TweetDuck</RootNamespace> <RootNamespace>TweetDuck</RootNamespace>
<AssemblyName>TweetDuck</AssemblyName> <AssemblyName>TweetDuck</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<LangVersion>8.0</LangVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<UseVSHostingProcess>false</UseVSHostingProcess> <UseVSHostingProcess>false</UseVSHostingProcess>
<ApplicationIcon>Resources\Images\icon.ico</ApplicationIcon> <ApplicationIcon>Resources\Images\icon.ico</ApplicationIcon>
@ -33,7 +34,6 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\x86\Release\</OutputPath> <OutputPath>bin\x86\Release\</OutputPath>
@ -43,7 +43,6 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<LangVersion>7</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
@ -55,10 +54,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Configuration\Arguments.cs" /> <Compile Include="Configuration\Arguments.cs" />
<Compile Include="Configuration\Instance\FileConfigInstance.cs" />
<Compile Include="Configuration\ConfigManager.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\LockManager.cs" />
<Compile Include="Configuration\SystemConfig.cs" /> <Compile Include="Configuration\SystemConfig.cs" />
<Compile Include="Configuration\UserConfig.cs" /> <Compile Include="Configuration\UserConfig.cs" />
@ -198,10 +194,7 @@
<DependentUpon>TabSettingsFeedback.cs</DependentUpon> <DependentUpon>TabSettingsFeedback.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\TweetDeckBrowser.cs" /> <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="Core\Utils\TwitterUtils.cs" />
<Compile Include="Data\CombinedFileStream.cs" />
<Compile Include="Core\Management\ProfileManager.cs" /> <Compile Include="Core\Management\ProfileManager.cs" />
<Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs"> <Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs">
<SubType>UserControl</SubType> <SubType>UserControl</SubType>
@ -231,19 +224,11 @@
<DependentUpon>TabSettingsNotifications.cs</DependentUpon> <DependentUpon>TabSettingsNotifications.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" /> <Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" />
<Compile Include="Data\CommandLineArgs.cs" />
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs"> <Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" /> <Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
<Compile Include="Data\ResourceLink.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="Data\WindowState.cs" />
<Compile Include="Core\Utils\WindowsUtils.cs" /> <Compile Include="Core\Utils\WindowsUtils.cs" />
<Compile Include="Core\Bridge\TweetDeckBridge.cs" /> <Compile Include="Core\Bridge\TweetDeckBridge.cs" />
@ -262,25 +247,15 @@
<Compile Include="Plugins\Controls\PluginListFlowLayout.cs"> <Compile Include="Plugins\Controls\PluginListFlowLayout.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </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="Plugins\PluginBridge.cs" />
<Compile Include="Configuration\PluginConfig.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\PluginManager.cs" />
<Compile Include="Plugins\PluginScriptGenerator.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="Reporter.cs" /> <Compile Include="Reporter.cs" />
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
<Compile Include="Updates\FormUpdateDownload.cs"> <Compile Include="Updates\FormUpdateDownload.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@ -297,9 +272,6 @@
<Compile Include="Core\Utils\BrowserUtils.cs" /> <Compile Include="Core\Utils\BrowserUtils.cs" />
<Compile Include="Core\Utils\NativeMethods.cs" /> <Compile Include="Core\Utils\NativeMethods.cs" />
<Compile Include="Updates\UpdateCheckClient.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="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\ScriptLoader.cs" /> <Compile Include="Resources\ScriptLoader.cs" />
@ -380,6 +352,10 @@
<None Include="Resources\Scripts\update.js" /> <None Include="Resources\Scripts\update.js" />
</ItemGroup> </ItemGroup>
<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"> <ProjectReference Include="subprocess\TweetDuck.Browser.csproj">
<Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project> <Project>{b10b0017-819e-4f71-870f-8256b36a26aa}</Project>
<Name>TweetDuck.Browser</Name> <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.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.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\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> </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.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')" /> <Import Project="packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.67.0.0\build\CefSharp.WinForms.targets')" />

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio Version 16
VisualStudioVersion = 15.0.27130.2027 VisualStudioVersion = 16.0.28729.10
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck", "TweetDuck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
EndProject EndProject
@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetTest.System", "lib\Twe
EndProject EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Unit", "lib\TweetTest.Unit\TweetTest.Unit.fsproj", "{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}" Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TweetTest.Unit", "lib\TweetTest.Unit\TweetTest.Unit.fsproj", "{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86 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.ActiveCfg = Debug|x86
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.Build.0 = Debug|x86 {EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Debug|x86.Build.0 = Debug|x86
{EEE1071A-28FA-48B1-82A1-9CBDC5C3F2C3}.Release|x86.ActiveCfg = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetLib.Core.Features.Updates;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed partial class FormUpdateDownload : Form{ sealed partial class FormUpdateDownload : Form{

View File

@ -6,10 +6,12 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web.Script.Serialization; using System.Web.Script.Serialization;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetLib.Core.Features.Updates;
using TweetLib.Core.Utils;
using JsonObject = System.Collections.Generic.IDictionary<string, object>; using JsonObject = System.Collections.Generic.IDictionary<string, object>;
namespace TweetDuck.Updates{ 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 ApiLatestRelease = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
private const string UpdaterAssetName = "TweetDuck.Update.exe"; private const string UpdaterAssetName = "TweetDuck.Update.exe";
@ -19,10 +21,12 @@ public UpdateCheckClient(string installerFolder){
this.installerFolder = 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>(); 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.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json";
client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => { client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => {
@ -65,10 +69,9 @@ string AssetDownloadUrl(JsonObject obj){
private static Exception ExpandWebException(Exception e){ private static Exception ExpandWebException(Exception e){
if (e is WebException we && we.Response is HttpWebResponse response){ if (e is WebException we && we.Response is HttpWebResponse response){
try{ try{
using(Stream stream = response.GetResponseStream()) using var stream = response.GetResponseStream();
using(StreamReader reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8"))){ using var reader = new StreamReader(stream, Encoding.GetEncoding(response.CharacterSet ?? "utf-8"));
return new Reporter.ExpandedLogException(e, reader.ReadToEnd()); return new Reporter.ExpandedLogException(e, reader.ReadToEnd());
}
}catch{ }catch{
// whatever // whatever
} }

View File

@ -76,14 +76,14 @@ function TDGetNetFrameworkVersion: Cardinal; forward;
function TDIsVCMissing: Boolean; forward; function TDIsVCMissing: Boolean; forward;
procedure TDInstallVCRedist; 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; function InitializeSetup: Boolean;
begin begin
UpdatePath := ExpandConstant('{param:UPDATEPATH}') UpdatePath := ExpandConstant('{param:UPDATEPATH}')
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}') ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
VisitedTasksPage := False 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 begin
Result := False Result := False
Exit Exit

View File

@ -64,14 +64,14 @@ function TDGetNetFrameworkVersion: Cardinal; forward;
function TDIsVCMissing: Boolean; forward; function TDIsVCMissing: Boolean; forward;
procedure TDInstallVCRedist; 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; function InitializeSetup: Boolean;
begin begin
UpdatePath := ExpandConstant('{param:UPDATEPATH}') UpdatePath := ExpandConstant('{param:UPDATEPATH}')
ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}') ForceRedistPrompt := ExpandConstant('{param:PROMPTREDIST}')
VisitedTasksPage := False 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 begin
Result := False Result := False
Exit Exit

View File

@ -81,7 +81,7 @@ procedure TDExecuteFullDownload; forward;
var IsPortable: Boolean; var IsPortable: Boolean;
var UpdatePath: String; 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; function InitializeSetup: Boolean;
begin begin
IsPortable := ExpandConstant('{param:PORTABLE}') = '1' 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')) idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe'))
end; 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 begin
Result := False Result := False
Exit Exit

View File

@ -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"> <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')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TweetLib.Communication</RootNamespace> <RootNamespace>TweetLib.Communication</RootNamespace>
<AssemblyName>TweetLib.Communication</AssemblyName> <AssemblyName>TweetLib.Communication</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
@ -23,7 +23,7 @@
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<LangVersion>7</LangVersion> <LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath> <OutputPath>bin\x86\Release\</OutputPath>
@ -51,6 +51,6 @@
<PropertyGroup> <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> <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> </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> </Target>
</Project> </Project>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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> </packages>

28
lib/TweetLib.Core/App.cs Normal file
View File

@ -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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -2,8 +2,8 @@
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace TweetDuck.Data{ namespace TweetLib.Core.Collections{
sealed class CommandLineArgs{ public sealed class CommandLineArgs{
public static CommandLineArgs FromStringArray(char entryChar, string[] array){ public static CommandLineArgs FromStringArray(char entryChar, string[] array){
CommandLineArgs args = new CommandLineArgs(); CommandLineArgs args = new CommandLineArgs();
ReadStringArray(entryChar, array, args); ReadStringArray(entryChar, array, args);
@ -15,7 +15,7 @@ public static void ReadStringArray(char entryChar, string[] array, CommandLineAr
string entry = array[index]; string entry = array[index];
if (entry.Length > 0 && entry[0] == entryChar){ if (entry.Length > 0 && entry[0] == entryChar){
if (index < array.Length-1){ if (index < array.Length - 1){
string potentialValue = array[index+1]; string potentialValue = array[index+1];
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){ if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
@ -52,7 +52,7 @@ public static CommandLineArgs ReadCefArguments(string argumentString){
} }
else{ else{
key = matchValue.Substring(0, indexEquals).TrimStart('-'); key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue.Substring(indexEquals+1).Trim('"'); value = matchValue.Substring(indexEquals + 1).Trim('"');
} }
if (key.Length != 0){ if (key.Length != 0){
@ -66,7 +66,7 @@ public static CommandLineArgs ReadCefArguments(string argumentString){
private readonly HashSet<string> flags = new HashSet<string>(); private readonly HashSet<string> flags = new HashSet<string>();
private readonly Dictionary<string, string> values = new Dictionary<string, 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){ public void AddFlag(string flag){
flags.Add(flag.ToLower()); flags.Add(flag.ToLower());
@ -84,12 +84,8 @@ public void SetValue(string key, string value){
values[key.ToLower()] = value; values[key.ToLower()] = value;
} }
public bool HasValue(string key){ public string? GetValue(string key){
return values.ContainsKey(key.ToLower()); return values.TryGetValue(key.ToLower(), out string val) ? val : null;
}
public string GetValue(string key, string defaultValue){
return values.TryGetValue(key.ToLower(), out string val) ? val : defaultValue;
} }
public void RemoveValue(string key){ public void RemoveValue(string key){
@ -103,7 +99,7 @@ public CommandLineArgs Clone(){
copy.AddFlag(flag); copy.AddFlag(flag);
} }
foreach(KeyValuePair<string, string> kvp in values){ foreach(var kvp in values){
copy.SetValue(kvp.Key, kvp.Value); copy.SetValue(kvp.Key, kvp.Value);
} }
@ -115,7 +111,7 @@ public void ToDictionary(IDictionary<string, string> target){
target[flag] = "1"; target[flag] = "1";
} }
foreach(KeyValuePair<string, string> kvp in values){ foreach(var kvp in values){
target[kvp.Key] = kvp.Value; target[kvp.Key] = kvp.Value;
} }
} }
@ -127,11 +123,11 @@ public override string ToString(){
build.Append(flag).Append(' '); 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("\" "); 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();
} }
} }
} }

View File

@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace TweetDuck.Data{ namespace TweetLib.Core.Collections{
sealed class TwoKeyDictionary<K1, K2, V>{ public sealed class TwoKeyDictionary<K1, K2, V>{
private readonly Dictionary<K1, Dictionary<K2, V>> dict; private readonly Dictionary<K1, Dictionary<K2, V>> dict;
private readonly int innerCapacity; private readonly int innerCapacity;
@ -85,7 +85,8 @@ public bool Remove(K1 outerKey, K2 innerKey){
return true; return true;
} }
else return false;
return false;
} }
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){ 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); return innerDict.TryGetValue(innerKey, out value);
} }
else{ else{
value = default(V); value = default!;
return false; return false;
} }
} }

View File

@ -1,11 +1,11 @@
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDuck.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Data{ namespace TweetLib.Core.Data{
sealed class CombinedFileStream : IDisposable{ public sealed class CombinedFileStream : IDisposable{
public const char KeySeparator = '|'; private const char KeySeparator = '|';
private readonly Stream stream; private readonly Stream stream;
@ -45,7 +45,7 @@ public void WriteFile(string identifier, string path){
stream.Write(contents, 0, contents.Length); stream.Write(contents, 0, contents.Length);
} }
public Entry ReadFile(){ public Entry? ReadFile(){
int nameLength = stream.ReadByte(); int nameLength = stream.ReadByte();
if (nameLength == -1){ if (nameLength == -1){
@ -64,7 +64,7 @@ public Entry ReadFile(){
return new Entry(Encoding.UTF8.GetString(name), contents); return new Entry(Encoding.UTF8.GetString(name), contents);
} }
public string SkipFile(){ public string? SkipFile(){
int nameLength = stream.ReadByte(); int nameLength = stream.ReadByte();
if (nameLength == -1){ if (nameLength == -1){
@ -120,7 +120,7 @@ public void WriteToFile(string path){
public void WriteToFile(string path, bool createDirectory){ public void WriteToFile(string path, bool createDirectory){
if (createDirectory){ if (createDirectory){
WindowsUtils.CreateDirectoryForFile(path); FileUtils.CreateDirectoryForFile(path);
} }
File.WriteAllBytes(path, contents); File.WriteAllBytes(path, contents);

View File

@ -1,7 +1,7 @@
using System; using System;
namespace TweetDuck.Data{ namespace TweetLib.Core.Data{
sealed class InjectedHTML{ public sealed class InjectedHTML{
public enum Position{ public enum Position{
Before, After Before, After
} }
@ -27,7 +27,7 @@ public string InjectInto(string targetHTML){
switch(position){ switch(position){
case Position.Before: cutIndex = index; break; 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; default: return targetHTML;
} }

View File

@ -1,14 +1,14 @@
using System; using System;
namespace TweetDuck.Data{ namespace TweetLib.Core.Data{
sealed class Result<T>{ public sealed class Result<T>{
public bool HasValue => exception == null; public bool HasValue => exception == null;
public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result."); 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."); public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");
private readonly T value; private readonly T value;
private readonly Exception exception; private readonly Exception? exception;
public Result(T value){ public Result(T value){
this.value = value; this.value = value;
@ -16,7 +16,7 @@ public Result(T value){
} }
public Result(Exception exception){ public Result(Exception exception){
this.value = default(T); this.value = default!;
this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
} }
@ -25,12 +25,12 @@ public void Handle(Action<T> onSuccess, Action<Exception> onException){
onSuccess(value); onSuccess(value);
} }
else{ else{
onException(exception); onException(exception!);
} }
} }
public Result<R> Select<R>(Func<T, R> map){ 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!);
} }
} }
} }

View File

@ -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();
}
}
}
}

View File

@ -1,22 +1,20 @@
using System; using System;
using System.IO; using System.IO;
using TweetDuck.Data.Serialization; using TweetLib.Core.Serialization;
namespace TweetDuck.Configuration.Instance{
sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{
private const string ErrorTitle = "Configuration Error";
namespace TweetLib.Core.Features.Configuration{
public sealed class FileConfigInstance<T> : IConfigInstance<T> where T : BaseConfig{
public T Instance { get; } public T Instance { get; }
public FileSerializer<T> Serializer { get; } public FileSerializer<T> Serializer { get; }
private readonly string filenameMain; private readonly string filenameMain;
private readonly string filenameBackup; 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.filenameMain = filename;
this.filenameBackup = filename+".bak"; this.filenameBackup = filename + ".bak";
this.errorIdentifier = errorIdentifier; this.identifier = identifier;
this.Instance = instance; this.Instance = instance;
this.Serializer = new FileSerializer<T>(); this.Serializer = new FileSerializer<T>();
@ -27,14 +25,14 @@ private void LoadInternal(bool backup){
} }
public void Load(){ public void Load(){
Exception firstException = null; Exception? firstException = null;
for(int attempt = 0; attempt < 2; attempt++){ for(int attempt = 0; attempt < 2; attempt++){
try{ try{
LoadInternal(attempt > 0); LoadInternal(attempt > 0);
if (firstException != null){ // silently log exception that caused a backup restore if (firstException != null){ // silently log exception that caused a backup restore
Program.Reporter.LogImportant(firstException.ToString()); App.ErrorHandler.Log(firstException.ToString());
} }
return; return;
@ -49,13 +47,13 @@ public void Load(){
} }
if (firstException is FormatException){ 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){ 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){ 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); Serializer.Write(filenameMain, Instance);
}catch(SerializationSoftException e){ }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){ }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>()); Serializer.Write(filenameMain, Instance.ConstructWithDefaults<T>());
LoadInternal(false); LoadInternal(false);
}catch(Exception e){ }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){ }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(filenameMain);
File.Delete(filenameBackup); File.Delete(filenameBackup);
}catch(Exception e){ }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; return;
} }
Reload(); Reload();
} }
private static void OnException(string message, Exception e){
App.ErrorHandler.HandleException("Configuration Error", message, true, e);
}
} }
} }

View File

@ -1,5 +1,5 @@
namespace TweetDuck.Configuration.Instance{ namespace TweetLib.Core.Features.Configuration{
interface IConfigInstance<out T>{ public interface IConfigInstance<out T>{
T Instance { get; } T Instance { get; }
void Save(); void Save();

View File

@ -0,0 +1,6 @@
namespace TweetLib.Core.Features.Configuration{
public interface IConfigManager{
void TriggerProgramRestartRequested();
IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance);
}
}

View File

@ -1,12 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using TweetDuck.Plugins.Events; using TweetLib.Core.Features.Plugins.Events;
namespace TweetDuck.Plugins{
interface IPluginConfig{
IEnumerable<string> DisabledPlugins { get; }
namespace TweetLib.Core.Features.Plugins.Config{
public interface IPluginConfig{
event EventHandler<PluginChangedStateEventArgs> PluginChangedState; event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
IEnumerable<string> DisabledPlugins { get; }
void Reset(IEnumerable<string> newDisabledPlugins);
void SetEnabled(Plugin plugin, bool enabled); void SetEnabled(Plugin plugin, bool enabled);
bool IsEnabled(Plugin plugin); bool IsEnabled(Plugin plugin);

View File

@ -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);
}
}
}

View File

@ -4,15 +4,15 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace TweetDuck.Plugins.Enums{ namespace TweetLib.Core.Features.Plugins.Enums{
[Flags] [Flags]
enum PluginEnvironment{ public enum PluginEnvironment{
None = 0, None = 0,
Browser = 1, Browser = 1,
Notification = 2 Notification = 2
} }
static class PluginEnvironmentExtensions{ public static class PluginEnvironmentExtensions{
public static IEnumerable<PluginEnvironment> Values{ public static IEnumerable<PluginEnvironment> Values{
get{ get{
yield return PluginEnvironment.Browser; yield return PluginEnvironment.Browser;
@ -24,7 +24,7 @@ public static bool IncludesDisabledPlugins(this PluginEnvironment environment){
return environment == PluginEnvironment.Browser; return environment == PluginEnvironment.Browser;
} }
public static string GetPluginScriptFile(this PluginEnvironment environment){ public static string? GetPluginScriptFile(this PluginEnvironment environment){
switch(environment){ switch(environment){
case PluginEnvironment.Browser: return "browser.js"; case PluginEnvironment.Browser: return "browser.js";
case PluginEnvironment.Notification: return "notification.js"; case PluginEnvironment.Notification: return "notification.js";
@ -73,7 +73,7 @@ public bool TryGetValue(PluginEnvironment key, out T value){
return true; return true;
} }
else{ else{
value = default(T); value = default;
return false; return false;
} }
} }

View File

@ -0,0 +1,5 @@
namespace TweetLib.Core.Features.Plugins.Enums{
public enum PluginFolder{
Root, Data
}
}

View File

@ -1,9 +1,9 @@
namespace TweetDuck.Plugins.Enums{ namespace TweetLib.Core.Features.Plugins.Enums{
enum PluginGroup{ public enum PluginGroup{
Official, Custom Official, Custom
} }
static class PluginGroupExtensions{ public static class PluginGroupExtensions{
public static string GetIdentifierPrefix(this PluginGroup group){ public static string GetIdentifierPrefix(this PluginGroup group){
switch(group){ switch(group){
case PluginGroup.Official: return "official/"; case PluginGroup.Official: return "official/";

View File

@ -1,7 +1,7 @@
using System; using System;
namespace TweetDuck.Plugins.Events{ namespace TweetLib.Core.Features.Plugins.Events{
sealed class PluginChangedStateEventArgs : EventArgs{ public sealed class PluginChangedStateEventArgs : EventArgs{
public Plugin Plugin { get; } public Plugin Plugin { get; }
public bool IsEnabled { get; } public bool IsEnabled { get; }

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace TweetDuck.Plugins.Events{ namespace TweetLib.Core.Features.Plugins.Events{
sealed class PluginErrorEventArgs : EventArgs{ public sealed class PluginErrorEventArgs : EventArgs{
public bool HasErrors => Errors.Count > 0; public bool HasErrors => Errors.Count > 0;
public IList<string> Errors { get; } public IList<string> Errors { get; }

View File

@ -1,10 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using TweetDuck.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetLib.Core.Features.Plugins{
sealed class Plugin{ public sealed class Plugin{
private static readonly Version AppVersion = new Version(Program.VersionTag); private static readonly Version AppVersion = new Version(Lib.VersionTag);
public string Identifier { get; } public string Identifier { get; }
public PluginGroup Group { get; } public PluginGroup Group { get; }
@ -62,7 +62,7 @@ private Plugin(PluginGroup group, string identifier, string pathRoot, string pat
public string GetScriptPath(PluginEnvironment environment){ public string GetScriptPath(PluginEnvironment environment){
if (Environments.HasFlag(environment)){ if (Environments.HasFlag(environment)){
string file = environment.GetPluginScriptFile(); string? file = environment.GetPluginScriptFile();
return file != null ? Path.Combine(pathRoot, file) : string.Empty; return file != null ? Path.Combine(pathRoot, file) : string.Empty;
} }
else{ else{
@ -124,7 +124,7 @@ public override bool Equals(object obj){
public sealed class Builder{ public sealed class Builder{
private static readonly Version DefaultRequiredVersion = new Version(0, 0, 0, 0); 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 Description { get; set; } = string.Empty;
public string Author { get; set; } = "(anonymous)"; public string Author { get; set; } = "(anonymous)";
public string Version { get; set; } = string.Empty; 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.group = group;
this.pathRoot = pathRoot; this.pathRoot = pathRoot;
this.pathData = pathData; this.pathData = pathData;
this.identifier = group.GetIdentifierPrefix()+name; this.identifier = group.GetIdentifierPrefix() + name;
} }
public void AddEnvironment(PluginEnvironment environment){ public void AddEnvironment(PluginEnvironment environment){

View File

@ -2,40 +2,35 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using TweetDuck.Plugins.Enums; using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetLib.Core.Features.Plugins{
static class PluginLoader{ public static class PluginLoader{
private static readonly string[] EndTag = { "[END]" }; private static readonly string[] EndTag = { "[END]" };
public static Plugin FromFolder(string path, PluginGroup group){ public static Plugin FromFolder(string name, string pathRoot, string pathData, PluginGroup group){
string name = Path.GetFileName(path); Plugin.Builder builder = new Plugin.Builder(group, name, pathRoot, pathData);
if (string.IsNullOrEmpty(name)){ foreach(var environment in Directory.EnumerateFiles(pathRoot, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).Select(EnvironmentFromFileName)){
throw new ArgumentException("Could not extract directory name from path: "+path); builder.AddEnvironment(environment);
}
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)));
} }
string metaFile = Path.Combine(path, ".meta"); string metaFile = Path.Combine(pathRoot, ".meta");
if (!File.Exists(metaFile)){ if (!File.Exists(metaFile)){
throw new ArgumentException("Plugin is missing a .meta file"); 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)){ 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){ if (currentTag != null){
SetProperty(builder, currentTag, currentContents); SetProperty(builder, currentTag, currentContents);
} }
currentTag = line.Substring(1, line.Length-2).ToUpper(); currentTag = line.Substring(1, line.Length - 2).ToUpper();
currentContents = string.Empty; currentContents = string.Empty;
if (line.Equals(EndTag[0])){ if (line.Equals(EndTag[0])){
@ -43,16 +38,20 @@ public static Plugin FromFolder(string path, PluginGroup group){
} }
} }
else if (currentTag != null){ else if (currentTag != null){
currentContents = currentContents.Length == 0 ? line : currentContents+Environment.NewLine+line; currentContents = currentContents.Length == 0 ? line : currentContents + Environment.NewLine + line;
} }
else{ else{
throw new FormatException("Missing metadata tag before value: "+line); throw new FormatException($"Missing metadata tag before value: {line}");
} }
} }
return builder.BuildAndSetup(); 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){ private static void SetProperty(Plugin.Builder builder, string tag, string value){
switch(tag){ switch(tag){
case "NAME": builder.Name = value; break; 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 "WEBSITE": builder.Website = value; break;
case "CONFIGFILE": builder.ConfigFile = value; break; case "CONFIGFILE": builder.ConfigFile = value; break;
case "CONFIGDEFAULT": builder.ConfigDefault = 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; 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); default: throw new FormatException($"Invalid metadata tag: {tag}");
} }
} }
} }

View File

@ -1,10 +1,11 @@
using System.Linq; using System.Linq;
using TweetDuck.Plugins.Enums; using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.Plugins.Enums;
namespace TweetDuck.Plugins{ namespace TweetLib.Core.Features.Plugins{
static class PluginScriptGenerator{ public static class PluginScriptGenerator{
public static string GenerateConfig(IPluginConfig config){ 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){ public static string GeneratePlugin(string pluginIdentifier, string pluginContents, int pluginToken, PluginEnvironment environment){

View File

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace TweetLib.Core.Features.Updates{
public interface IUpdateCheckClient{
bool CanCheck { get; }
Task<UpdateInfo> Check();
}
}

View File

@ -1,8 +1,8 @@
using System; using System;
using TweetDuck.Data; using TweetLib.Core.Data;
namespace TweetDuck.Updates{ namespace TweetLib.Core.Features.Updates{
sealed class UpdateCheckEventArgs : EventArgs{ public sealed class UpdateCheckEventArgs : EventArgs{
public int EventId { get; } public int EventId { get; }
public Result<UpdateInfo> Result { get; } public Result<UpdateInfo> Result { get; }

View File

@ -1,4 +1,4 @@
namespace TweetDuck.Updates{ namespace TweetLib.Core.Features.Updates{
public enum UpdateDownloadStatus{ public enum UpdateDownloadStatus{
None = 0, None = 0,
InProgress, InProgress,

View File

@ -1,34 +1,38 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using TweetDuck.Data; using System.Timers;
using Timer = System.Windows.Forms.Timer; using TweetLib.Core.Data;
using Timer = System.Timers.Timer;
namespace TweetDuck.Updates{ namespace TweetLib.Core.Features.Updates{
sealed class UpdateHandler : IDisposable{ public sealed class UpdateHandler : IDisposable{
public const int CheckCodeUpdatesDisabled = -1; public const int CheckCodeUpdatesDisabled = -1;
private readonly UpdateCheckClient client; private readonly IUpdateCheckClient client;
private readonly TaskScheduler scheduler; private readonly TaskScheduler scheduler;
private readonly Timer timer; private readonly Timer timer;
public event EventHandler<UpdateCheckEventArgs> CheckFinished; public event EventHandler<UpdateCheckEventArgs> CheckFinished;
private ushort lastEventId; private ushort lastEventId;
public UpdateHandler(string installerFolder){ public UpdateHandler(IUpdateCheckClient client, TaskScheduler scheduler){
this.client = new UpdateCheckClient(installerFolder); this.client = client;
this.scheduler = TaskScheduler.FromCurrentSynchronizationContext(); this.scheduler = scheduler;
this.timer = new Timer(); this.timer = new Timer{
this.timer.Tick += timer_Tick; AutoReset = false,
Enabled = false
};
this.timer.Elapsed += timer_Elapsed;
} }
public void Dispose(){ public void Dispose(){
timer.Dispose(); timer.Dispose();
} }
private void timer_Tick(object sender, EventArgs e){ private void timer_Elapsed(object sender, ElapsedEventArgs e){
timer.Stop();
Check(false); Check(false);
} }
@ -39,9 +43,9 @@ public void StartTimer(){
timer.Stop(); timer.Stop();
if (Program.Config.User.EnableUpdateCheck){ if (client.CanCheck){
DateTime now = DateTime.Now; 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){ if (nextHour.TotalMinutes < 15){
nextHour = nextHour.Add(TimeSpan.FromHours(1)); nextHour = nextHour.Add(TimeSpan.FromHours(1));
@ -53,7 +57,7 @@ public void StartTimer(){
} }
public int Check(bool force){ public int Check(bool force){
if (Program.Config.User.EnableUpdateCheck || force){ if (client.CanCheck || force){
int nextEventId = unchecked(++lastEventId); int nextEventId = unchecked(++lastEventId);
Task<UpdateInfo> checkTask = client.Check(); Task<UpdateInfo> checkTask = client.Check();

View File

@ -1,20 +1,20 @@
using System; using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using TweetDuck.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Updates{ namespace TweetLib.Core.Features.Updates{
sealed class UpdateInfo{ public sealed class UpdateInfo{
public string VersionTag { get; } public string VersionTag { get; }
public string ReleaseNotes { get; } public string ReleaseNotes { get; }
public string InstallerPath { get; } public string InstallerPath { get; }
public UpdateDownloadStatus DownloadStatus { get; private set; } 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 downloadUrl;
private readonly string installerFolder; private readonly string installerFolder;
private WebClient currentDownload; private WebClient? currentDownload;
public UpdateInfo(string versionTag, string releaseNotes, string downloadUrl, string installerFolder){ public UpdateInfo(string versionTag, string releaseNotes, string downloadUrl, string installerFolder){
this.downloadUrl = downloadUrl; this.downloadUrl = downloadUrl;
@ -22,11 +22,11 @@ public UpdateInfo(string versionTag, string releaseNotes, string downloadUrl, st
this.VersionTag = versionTag; this.VersionTag = versionTag;
this.ReleaseNotes = releaseNotes; this.ReleaseNotes = releaseNotes;
this.InstallerPath = Path.Combine(installerFolder, $"TweetDuck.{versionTag}.exe"); this.InstallerPath = Path.Combine(installerFolder, $"{Lib.BrandName}.{versionTag}.exe");
} }
public void BeginSilentDownload(){ public void BeginSilentDownload(){
if (WindowsUtils.FileExistsAndNotEmpty(InstallerPath)){ if (FileUtils.FileExistsAndNotEmpty(InstallerPath)){
DownloadStatus = UpdateDownloadStatus.Done; DownloadStatus = UpdateDownloadStatus.Done;
return; return;
} }
@ -48,7 +48,9 @@ public void BeginSilentDownload(){
return; return;
} }
currentDownload = BrowserUtils.DownloadFileAsync(downloadUrl, InstallerPath, null, () => { WebClient client = WebUtils.NewClient($"{Lib.BrandName} {Lib.VersionTag}");
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(InstallerPath, () => {
DownloadStatus = UpdateDownloadStatus.Done; DownloadStatus = UpdateDownloadStatus.Done;
currentDownload = null; currentDownload = null;
}, e => { }, e => {
@ -56,6 +58,8 @@ public void BeginSilentDownload(){
DownloadStatus = UpdateDownloadStatus.Failed; DownloadStatus = UpdateDownloadStatus.Failed;
currentDownload = null; currentDownload = null;
}); });
client.DownloadFileAsync(new Uri(downloadUrl), InstallerPath);
} }
} }

24
lib/TweetLib.Core/Lib.cs Normal file
View File

@ -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();
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -1,11 +1,11 @@
using System; using System;
namespace TweetDuck.Data.Serialization{ namespace TweetLib.Core.Serialization.Converters{
sealed class SingleTypeConverter<T> : ITypeConverter{ public sealed class SingleTypeConverter<T> : ITypeConverter{
public Func<T, string> ConvertToString { get; set; } public Func<T, string> ConvertToString { get; set; }
public Func<string, T> ConvertToObject { 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{ try{
converted = ConvertToString((T)value); converted = ConvertToString((T)value);
return true; 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{ try{
converted = ConvertToObject(value); converted = ConvertToObject(value);
return true; return true;

View File

@ -4,10 +4,11 @@
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using TweetDuck.Core.Utils; using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils;
namespace TweetDuck.Data.Serialization{ namespace TweetLib.Core.Serialization{
sealed class FileSerializer<T>{ public sealed class FileSerializer<T>{
private const string NewLineReal = "\r\n"; private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\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(); return build.Append(data.Substring(index)).ToString();
} }
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
private readonly Dictionary<string, PropertyInfo> props; private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters; private readonly Dictionary<Type, ITypeConverter> converters;
@ -66,7 +65,7 @@ public void RegisterTypeConverter(Type type, ITypeConverter converter){
public void Write(string file, T obj){ public void Write(string file, T obj){
LinkedList<string> errors = new LinkedList<string>(); 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))){ using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
foreach(KeyValuePair<string, PropertyInfo> prop in props){ foreach(KeyValuePair<string, PropertyInfo> prop in props){
@ -74,10 +73,10 @@ public void Write(string file, T obj){
object value = prop.Value.GetValue(obj); object value = prop.Value.GetValue(obj);
if (!converters.TryGetValue(type, out ITypeConverter serializer)){ 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){ if (converted != null){
writer.Write(prop.Key); writer.Write(prop.Key);
writer.Write(' '); writer.Write(' ');
@ -142,10 +141,10 @@ public void Read(string file, T obj){
if (props.TryGetValue(property, out PropertyInfo info)){ if (props.TryGetValue(property, out PropertyInfo info)){
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){ 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); info.SetValue(obj, converted);
} }
else{ else{
@ -165,53 +164,5 @@ public void ReadIfExists(string file, T obj){
}catch(FileNotFoundException){ }catch(FileNotFoundException){
}catch(DirectoryNotFoundException){} }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;
}
}
}
} }
} }

View File

@ -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);
}
}

View File

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace TweetDuck.Data.Serialization{ namespace TweetLib.Core.Serialization{
sealed class SerializationSoftException : Exception{ public sealed class SerializationSoftException : Exception{
public IList<string> Errors { get; } public IList<string> Errors { get; }
public SerializationSoftException(IList<string> errors) : base(string.Join(Environment.NewLine, errors)){ public SerializationSoftException(IList<string> errors) : base(string.Join(Environment.NewLine, errors)){

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -3,8 +3,8 @@
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
namespace TweetDuck.Core.Utils{ namespace TweetLib.Core.Utils{
static class LocaleUtils{ public static class LocaleUtils{
// https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/ // https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/
public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{ public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{
"af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE", "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 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; this.Code = code;
try{ try{

View File

@ -2,8 +2,8 @@
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace TweetDuck.Core.Utils{ namespace TweetLib.Core.Utils{
static class StringUtils{ public static class StringUtils{
public static readonly string[] EmptyArray = new string[0]; public static readonly string[] EmptyArray = new string[0];
public static string ExtractBefore(string str, char search, int startIndex = 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 => { return Regex.Replace(str, @"[a-zA-Z]", match => {
int code = match.Value[0]; int code = match.Value[0];
int start = code <= 90 ? 65 : 97; int start = code <= 90 ? 65 : 97;
return ((char)(start+(code-start+13)%26)).ToString(); return ((char)(start + (code - start + 13) % 26)).ToString();
}); });
} }

View File

@ -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;
}
}
}

View File

@ -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();
}
};
}
}
}

View File

@ -1,9 +1,4 @@
using System; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Diagnostics.Contracts;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Configuration;
using TweetDuck.Core.Other;
namespace TweetTest.Configuration{ namespace TweetTest.Configuration{
[TestClass] [TestClass]

View File

@ -2,7 +2,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data; using TweetLib.Core.Data;
namespace TweetTest.Data{ namespace TweetTest.Data{
[TestClass] [TestClass]

View File

@ -1,11 +1,13 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using TweetDuck.Data.Serialization; using TweetLib.Core.Serialization;
namespace TweetTest.Data{ namespace TweetTest.Data{
[TestClass] [TestClass]
public class TestFileSerializer : TestIO{ public class TestFileSerializer : TestIO{
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private enum TestEnum{ private enum TestEnum{
A, B, C, D, E A, B, C, D, E
} }

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <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> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform> <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TweetTest</RootNamespace> <RootNamespace>TweetTest</RootNamespace>
<AssemblyName>TweetTest.System</AssemblyName> <AssemblyName>TweetTest.System</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
@ -57,6 +57,10 @@
<Project>{2389a7cd-e0d3-4706-8294-092929a33a2d}</Project> <Project>{2389a7cd-e0d3-4706-8294-092929a33a2d}</Project>
<Name>TweetDuck</Name> <Name>TweetDuck</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\TweetLib.Core\TweetLib.Core.csproj">
<Project>{93ba3cb4-a812-4949-b07d-8d393fb38937}</Project>
<Name>TweetLib.Core</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
@ -85,7 +89,7 @@
<PropertyGroup> <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> <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> </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> </Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <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> </packages>

View File

@ -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/"))

View File

@ -1,7 +1,7 @@
namespace TweetTest.Core.StringUtils namespace TweetTest.Core.StringUtils
open Xunit open Xunit
open TweetDuck.Core.Utils open TweetLib.Core.Utils
module ExtractBefore = module ExtractBefore =

View File

@ -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/"))

View File

@ -1,7 +1,7 @@
namespace TweetTest.Data.CommandLineArgs namespace TweetTest.Data.CommandLineArgs
open Xunit open Xunit
open TweetDuck.Data open TweetLib.Core.Collections
type _TestData = type _TestData =
@ -139,52 +139,33 @@ module Flags =
module Values = module Values =
[<Theory>] [<Fact>]
[<InlineData("val1")>] let ``GetValue returns null if key is missing`` () =
[<InlineData("val2")>] Assert.Null(_TestData.values.GetValue("missing"))
let ``HasValue returns false if key is missing`` (key: string) =
Assert.False(_TestData.empty.HasValue(key))
Assert.False(_TestData.flags.HasValue(key))
[<Theory>] [<Theory>]
[<InlineData("flag1")>] [<InlineData("flag1")>]
[<InlineData("flag2")>] [<InlineData("flag2")>]
[<InlineData("flag3")>] [<InlineData("flag3")>]
let ``HasValue returns false if the name specifies a flag`` (key: string) = let ``GetValue returns null if the name specifies a flag`` (key: string) =
Assert.False(_TestData.flags.HasValue(key)) Assert.Null(_TestData.flags.GetValue(key))
[<Fact>] [<Fact>]
let ``HasValue returns true if the name specifies both a flag and a value key`` () = let ``GetValue returns correct value if the name specifies both a flag and a value key`` () =
Assert.True(_TestData.duplicate.HasValue("duplicate")) Assert.NotNull(_TestData.duplicate.GetValue("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))
[<Theory>] [<Theory>]
[<InlineData("val1", "hello")>] [<InlineData("val1", "hello")>]
[<InlineData("val2", "world")>] [<InlineData("val2", "world")>]
let ``GetValue returns correct value if key is present`` (key: string, expectedValue: string) = 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>] [<Theory>]
[<InlineData("VAL1", "hello")>] [<InlineData("VAL1", "hello")>]
[<InlineData("VaL1", "hello")>] [<InlineData("VaL1", "hello")>]
let ``GetValue is case-insensitive`` (key: string, expectedValue: string) = let ``GetValue is case-insensitive`` (key: string, expectedValue: string) =
Assert.Equal(expectedValue, _TestData.values.GetValue(key, "")) 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"))
[<Fact>] [<Fact>]
let ``SetValue adds new value`` () = let ``SetValue adds new value`` () =
@ -192,7 +173,7 @@ module Values =
args.SetValue("val3", "this is nice") args.SetValue("val3", "this is nice")
Assert.Equal(3, args.Count) Assert.Equal(3, args.Count)
Assert.Equal("this is nice", args.GetValue("val3", "")) Assert.Equal("this is nice", args.GetValue("val3"))
[<Fact>] [<Fact>]
let ``SetValue replaces existing value`` () = let ``SetValue replaces existing value`` () =
@ -200,7 +181,7 @@ module Values =
args.SetValue("val2", "mom") args.SetValue("val2", "mom")
Assert.Equal(2, args.Count) Assert.Equal(2, args.Count)
Assert.Equal("mom", args.GetValue("val2", "")) Assert.Equal("mom", args.GetValue("val2"))
[<Theory>] [<Theory>]
[<InlineData("val1")>] [<InlineData("val1")>]
@ -210,7 +191,7 @@ module Values =
args.RemoveValue(key) args.RemoveValue(key)
Assert.Equal(1, args.Count) Assert.Equal(1, args.Count)
Assert.False(args.HasValue(key)) Assert.Null(args.GetValue(key))
[<Theory>] [<Theory>]
[<InlineData("VAL1")>] [<InlineData("VAL1")>]
@ -220,7 +201,7 @@ module Values =
args.RemoveValue(key) args.RemoveValue(key)
Assert.Equal(1, args.Count) Assert.Equal(1, args.Count)
Assert.False(args.HasValue(key)) Assert.Null(args.GetValue(key))
[<Fact>] [<Fact>]
let ``RemoveValue does nothing if key is missing`` () = let ``RemoveValue does nothing if key is missing`` () =
@ -239,8 +220,8 @@ module Clone =
Assert.True(clone.HasFlag("flag1")) Assert.True(clone.HasFlag("flag1"))
Assert.True(clone.HasFlag("flag2")) Assert.True(clone.HasFlag("flag2"))
Assert.True(clone.HasFlag("flag3")) Assert.True(clone.HasFlag("flag3"))
Assert.Equal("hello", clone.GetValue("val1", "")) Assert.Equal("hello", clone.GetValue("val1"))
Assert.Equal("world", clone.GetValue("val2", "")) Assert.Equal("world", clone.GetValue("val2"))
[<Fact>] [<Fact>]
let ``cloning creates a new object`` () = let ``cloning creates a new object`` () =
@ -259,7 +240,7 @@ module Clone =
Assert.True(original.HasFlag("flag1")) Assert.True(original.HasFlag("flag1"))
Assert.False(original.HasFlag("flag4")) Assert.False(original.HasFlag("flag4"))
Assert.Equal("hello", original.GetValue("val1", "")) Assert.Equal("hello", original.GetValue("val1"))
module ToDictionary = module ToDictionary =
@ -327,8 +308,8 @@ module FromStringArray =
Assert.True(args.HasFlag("-flag1")) Assert.True(args.HasFlag("-flag1"))
Assert.True(args.HasFlag("-flag2")) Assert.True(args.HasFlag("-flag2"))
Assert.True(args.HasFlag("-flag3")) Assert.True(args.HasFlag("-flag3"))
Assert.Equal("first value", args.GetValue("-val1", "")) Assert.Equal("first value", args.GetValue("-val1"))
Assert.Equal("second value", args.GetValue("-val2", "")) Assert.Equal("second value", args.GetValue("-val2"))
module ReadCefArguments = module ReadCefArguments =
@ -346,23 +327,23 @@ module ReadCefArguments =
let args = CommandLineArgs.ReadCefArguments("--first-value=10 --second-value=\"long string with spaces\"") let args = CommandLineArgs.ReadCefArguments("--first-value=10 --second-value=\"long string with spaces\"")
Assert.Equal(2, args.Count) Assert.Equal(2, args.Count)
Assert.Equal("10", args.GetValue("first-value", "")) Assert.Equal("10", args.GetValue("first-value"))
Assert.Equal("long string with spaces", args.GetValue("second-value", "")) Assert.Equal("long string with spaces", args.GetValue("second-value"))
[<Fact>] [<Fact>]
let ``reads flags as value keys with values of 1`` () = let ``reads flags as value keys with values of 1`` () =
let args = CommandLineArgs.ReadCefArguments("--first-flag-as-value --second-flag-as-value") let args = CommandLineArgs.ReadCefArguments("--first-flag-as-value --second-flag-as-value")
Assert.Equal(2, args.Count) Assert.Equal(2, args.Count)
Assert.Equal("1", args.GetValue("first-flag-as-value", "")) Assert.Equal("1", args.GetValue("first-flag-as-value"))
Assert.Equal("1", args.GetValue("second-flag-as-value", "")) Assert.Equal("1", args.GetValue("second-flag-as-value"))
[<Fact>] [<Fact>]
let ``reads complex string with whitespace correctly`` () = 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 ") 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(4, args.Count)
Assert.Equal("55.5", args.GetValue("first-value", "")) Assert.Equal("55.5", args.GetValue("first-value"))
Assert.Equal("long string", args.GetValue("second-value", "")) Assert.Equal("long string", args.GetValue("second-value"))
Assert.Equal("1", args.GetValue("first-flag-as-value", "")) Assert.Equal("1", args.GetValue("first-flag-as-value"))
Assert.Equal("1", args.GetValue("second-flag-as-value", "")) Assert.Equal("1", args.GetValue("second-flag-as-value"))

View File

@ -1,7 +1,7 @@
namespace TweetTest.Data.InjectedHTML namespace TweetTest.Data.InjectedHTML
open Xunit open Xunit
open TweetDuck.Data open TweetLib.Core.Data
module Inject = module Inject =

View File

@ -1,7 +1,7 @@
namespace TweetTest.Data.Result namespace TweetTest.Data.Result
open Xunit open Xunit
open TweetDuck.Data open TweetLib.Core.Data
open System open System

View File

@ -1,7 +1,7 @@
namespace TweetTest.Data.TwoKeyDictionary namespace TweetTest.Data.TwoKeyDictionary
open Xunit open Xunit
open TweetDuck.Data open TweetLib.Core.Collections
open System.Collections.Generic open System.Collections.Generic

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <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.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.0\build\xunit.core.props" Condition="Exists('..\..\packages\xunit.core.2.4.0\build\xunit.core.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.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.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.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\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')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -14,7 +14,7 @@
<RootNamespace>TweetTest.Unit</RootNamespace> <RootNamespace>TweetTest.Unit</RootNamespace>
<AssemblyName>TweetTest.Unit</AssemblyName> <AssemblyName>TweetTest.Unit</AssemblyName>
<UseStandardResourceNames>True</UseStandardResourceNames> <UseStandardResourceNames>True</UseStandardResourceNames>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFSharpCoreVersion>4.4.3.0</TargetFSharpCoreVersion> <TargetFSharpCoreVersion>4.4.3.0</TargetFSharpCoreVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Name>TweetTest.Unit</Name> <Name>TweetTest.Unit</Name>
@ -49,23 +49,10 @@
</Otherwise> </Otherwise>
</Choose> </Choose>
<Import Project="$(FSharpTargetsPath)" /> <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> <ItemGroup>
<Compile Include="Core\TestBrowserUtils.fs" />
<Compile Include="Core\TestStringUtils.fs" /> <Compile Include="Core\TestStringUtils.fs" />
<Compile Include="Core\TestTwitterUtils.fs" /> <Compile Include="Core\TestTwitterUtils.fs" />
<Compile Include="Core\TestUrlUtils.fs" />
<Compile Include="Data\TestCommandLineArgs.fs" /> <Compile Include="Data\TestCommandLineArgs.fs" />
<Compile Include="Data\TestInjectedHTML.fs" /> <Compile Include="Data\TestInjectedHTML.fs" />
<Compile Include="Data\TestResult.fs" /> <Compile Include="Data\TestResult.fs" />
@ -73,8 +60,13 @@
<Content Include="packages.config" /> <Content Include="packages.config" />
</ItemGroup> </ItemGroup>
<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"> <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>
<Reference Include="mscorlib" /> <Reference Include="mscorlib" />
<Reference Include="FSharp.Core"> <Reference Include="FSharp.Core">
@ -91,20 +83,33 @@
<Private>True</Private> <Private>True</Private>
</ProjectReference> </ProjectReference>
<Reference Include="xunit.abstractions"> <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>
<Reference Include="xunit.assert"> <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>
<Reference Include="xunit.core"> <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>
<Reference Include="xunit.execution.desktop"> <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> </Reference>
</ItemGroup> </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')" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<Import Project="..\..\packages\xunit.core.2.4.0\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.4.0\build\xunit.core.targets')" /> <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. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Microsoft.CodeCoverage" version="15.8.0" targetFramework="net452" /> <package id="Microsoft.CodeCoverage" version="16.1.0" targetFramework="net472" />
<package id="Microsoft.NET.Test.Sdk" version="15.8.0" targetFramework="net452" /> <package id="Microsoft.NET.Test.Sdk" version="16.1.0" targetFramework="net472" />
<package id="xunit" version="2.4.0" targetFramework="net452" /> <package id="xunit" version="2.4.1" targetFramework="net472" />
<package id="xunit.abstractions" version="2.0.2" targetFramework="net452" /> <package id="xunit.abstractions" version="2.0.3" targetFramework="net472" />
<package id="xunit.analyzers" version="0.10.0" targetFramework="net452" /> <package id="xunit.analyzers" version="0.10.0" targetFramework="net452" />
<package id="xunit.assert" version="2.4.0" targetFramework="net452" /> <package id="xunit.assert" version="2.4.1" targetFramework="net472" />
<package id="xunit.core" version="2.4.0" targetFramework="net452" /> <package id="xunit.core" version="2.4.1" targetFramework="net472" />
<package id="xunit.extensibility.core" version="2.4.0" targetFramework="net452" /> <package id="xunit.extensibility.core" version="2.4.1" targetFramework="net472" />
<package id="xunit.extensibility.execution" version="2.4.0" targetFramework="net452" /> <package id="xunit.extensibility.execution" version="2.4.1" targetFramework="net472" />
<package id="xunit.runner.visualstudio" version="2.4.0" targetFramework="net452" developmentDependency="true" /> <package id="xunit.runner.visualstudio" version="2.4.1" targetFramework="net472" developmentDependency="true" />
</packages> </packages>

Some files were not shown because too many files have changed in this diff Show More