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 (#264)
* 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:
parent
aca438b837
commit
1ccefe853a
Configuration
Core
Bridge
FormBrowser.csFormManager.csHandling
Management
Notification
Other
TweetDeckBrowser.csUtils
Data
Plugins
Program.csProperties
Reporter.csTweetDuck.csprojTweetDuck.slnUpdates
bld
lib
TweetLib.Communication
TweetLib.Core
App.cs
Application
Collections
Data
Features
Lib.csSerialization
TweetLib.Core.csprojUtils
TweetTest.System
TweetTest.Unit
@ -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(){
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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){}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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{
|
||||||
|
@ -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;
|
||||||
|
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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")]
|
||||||
|
@ -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);
|
||||||
|
@ -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 },
|
||||||
|
@ -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{
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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{
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
namespace TweetDuck.Plugins.Enums{
|
|
||||||
enum PluginFolder{
|
|
||||||
Root, Data
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
Program.cs
35
Program.cs
@ -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)){
|
||||||
|
2
Properties/Resources.Designer.cs
generated
2
Properties/Resources.Designer.cs
generated
@ -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 {
|
||||||
|
14
Reporter.cs
14
Reporter.cs
@ -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);
|
||||||
|
@ -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')" />
|
||||||
|
@ -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
|
||||||
|
@ -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{
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
@ -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
28
lib/TweetLib.Core/App.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
lib/TweetLib.Core/Application/IAppErrorHandler.cs
Normal file
8
lib/TweetLib.Core/Application/IAppErrorHandler.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -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!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
50
lib/TweetLib.Core/Features/Configuration/BaseConfig.cs
Normal file
50
lib/TweetLib.Core/Features/Configuration/BaseConfig.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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();
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace TweetLib.Core.Features.Configuration{
|
||||||
|
public interface IConfigManager{
|
||||||
|
void TriggerProgramRestartRequested();
|
||||||
|
IConfigInstance<BaseConfig> GetInstanceInfo(BaseConfig instance);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
5
lib/TweetLib.Core/Features/Plugins/Enums/PluginFolder.cs
Normal file
5
lib/TweetLib.Core/Features/Plugins/Enums/PluginFolder.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
namespace TweetLib.Core.Features.Plugins.Enums{
|
||||||
|
public enum PluginFolder{
|
||||||
|
Root, Data
|
||||||
|
}
|
||||||
|
}
|
@ -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/";
|
@ -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; }
|
||||||
|
|
@ -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; }
|
@ -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){
|
@ -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}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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){
|
8
lib/TweetLib.Core/Features/Updates/IUpdateCheckClient.cs
Normal file
8
lib/TweetLib.Core/Features/Updates/IUpdateCheckClient.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace TweetLib.Core.Features.Updates{
|
||||||
|
public interface IUpdateCheckClient{
|
||||||
|
bool CanCheck { get; }
|
||||||
|
Task<UpdateInfo> Check();
|
||||||
|
}
|
||||||
|
}
|
@ -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; }
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
namespace TweetDuck.Updates{
|
namespace TweetLib.Core.Features.Updates{
|
||||||
public enum UpdateDownloadStatus{
|
public enum UpdateDownloadStatus{
|
||||||
None = 0,
|
None = 0,
|
||||||
InProgress,
|
InProgress,
|
@ -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();
|
||||||
|
|
@ -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
24
lib/TweetLib.Core/Lib.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
8
lib/TweetLib.Core/Serialization/ITypeConverter.cs
Normal file
8
lib/TweetLib.Core/Serialization/ITypeConverter.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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)){
|
10
lib/TweetLib.Core/TweetLib.Core.csproj
Normal file
10
lib/TweetLib.Core/TweetLib.Core.csproj
Normal 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>
|
39
lib/TweetLib.Core/Utils/FileUtils.cs
Normal file
39
lib/TweetLib.Core/Utils/FileUtils.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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{
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
29
lib/TweetLib.Core/Utils/UrlUtils.cs
Normal file
29
lib/TweetLib.Core/Utils/UrlUtils.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
lib/TweetLib.Core/Utils/WebUtils.cs
Normal file
44
lib/TweetLib.Core/Utils/WebUtils.cs
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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]
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
@ -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/"))
|
|
@ -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 =
|
||||||
|
79
lib/TweetTest.Unit/Core/TestUrlUtils.fs
Normal file
79
lib/TweetTest.Unit/Core/TestUrlUtils.fs
Normal 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/"))
|
@ -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"))
|
||||||
|
@ -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 =
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user