mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-05-01 17:34:10 +02:00
Move a bunch of utility classes into TweetLib.Core & refactor
This commit is contained in:
parent
01df118549
commit
dd6e712755
Configuration
Core
Handling
Management
Notification
Other
Analytics
Settings
Utils
Data
Plugins
Program.csTweetDuck.csprojUpdates
lib
TweetLib.Core
Collections
Data
Serialization
Utils
TweetTest.System
TweetTest.Unit
@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
static class Arguments{
|
||||
|
@ -2,9 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using TweetDuck.Configuration.Instance;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Data.Serialization;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Configuration{
|
||||
sealed class ConfigManager{
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
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{
|
||||
|
@ -12,6 +12,7 @@
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Other.Analytics;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Handling{
|
||||
abstract class ContextMenuBase : IContextMenuHandler{
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using CefSharp;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Management{
|
||||
sealed class ContextInfo{
|
||||
|
@ -3,9 +3,9 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetLib.Core.Data;
|
||||
|
||||
namespace TweetDuck.Core.Management{
|
||||
sealed class ProfileManager{
|
||||
|
@ -6,10 +6,10 @@
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Handling;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Data;
|
||||
|
||||
namespace TweetDuck.Core.Notification{
|
||||
abstract partial class FormNotificationMain : FormNotificationBase{
|
||||
|
@ -6,9 +6,9 @@
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Data;
|
||||
|
||||
namespace TweetDuck.Core.Notification.Screenshot{
|
||||
sealed class FormNotificationScreenshotable : FormNotificationBase{
|
||||
|
@ -2,7 +2,8 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using TweetDuck.Data.Serialization;
|
||||
using TweetLib.Core.Serialization;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
|
||||
|
@ -9,6 +9,7 @@
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
sealed class AnalyticsManager : IDisposable{
|
||||
@ -20,7 +21,7 @@ sealed class AnalyticsManager : IDisposable{
|
||||
#else
|
||||
"https://tweetduck.chylex.com/breadcrumb/report"
|
||||
#endif
|
||||
);
|
||||
);
|
||||
|
||||
public AnalyticsFile File { get; }
|
||||
|
||||
@ -117,7 +118,7 @@ private void SendReport(){
|
||||
System.Diagnostics.Debugger.Break();
|
||||
#endif
|
||||
|
||||
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||
WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
|
||||
}).ContinueWith(task => browser.InvokeAsyncSafe(() => {
|
||||
if (task.Status == TaskStatus.RanToCompletion){
|
||||
SetLastDataCollectionTime(DateTime.Now);
|
||||
|
@ -12,6 +12,7 @@
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Analytics{
|
||||
static class AnalyticsReportGenerator{
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsCefArgs : Form{
|
||||
|
@ -4,8 +4,8 @@
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Management;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Plugins;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsManage : Form{
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Collections;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings.Dialogs{
|
||||
sealed partial class DialogSettingsRestart : Form{
|
||||
|
@ -7,6 +7,7 @@
|
||||
using TweetDuck.Core.Other.Settings.Dialogs;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Updates;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Other.Settings{
|
||||
sealed partial class TabSettingsGeneral : BaseTabSettings{
|
||||
|
@ -3,16 +3,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp.WinForms;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
static class BrowserUtils{
|
||||
public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion;
|
||||
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"+Cef.ChromiumVersion+" Safari/537.36";
|
||||
public static string UserAgentVanilla => Program.BrandName + " " + Application.ProductVersion;
|
||||
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + Cef.ChromiumVersion + " Safari/537.36";
|
||||
|
||||
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
|
||||
|
||||
@ -74,29 +74,11 @@ void UpdateZoomLevel(object sender, EventArgs args){
|
||||
};
|
||||
}
|
||||
|
||||
private const string TwitterTrackingUrl = "t.co";
|
||||
|
||||
public enum UrlCheckResult{
|
||||
Invalid, Tracking, Fine
|
||||
}
|
||||
|
||||
public static UrlCheckResult CheckUrl(string url){
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
|
||||
string scheme = uri.Scheme;
|
||||
|
||||
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
|
||||
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
|
||||
}
|
||||
}
|
||||
|
||||
return UrlCheckResult.Invalid;
|
||||
}
|
||||
|
||||
public static void OpenExternalBrowser(string url){
|
||||
if (string.IsNullOrWhiteSpace(url))return;
|
||||
|
||||
switch(CheckUrl(url)){
|
||||
case UrlCheckResult.Fine:
|
||||
switch(UrlUtils.Check(url)){
|
||||
case UrlUtils.CheckResult.Fine:
|
||||
if (FormGuide.CheckGuideUrl(url, out string hash)){
|
||||
FormGuide.Show(hash);
|
||||
}
|
||||
@ -117,9 +99,9 @@ public static void OpenExternalBrowser(string url){
|
||||
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Tracking:
|
||||
case UrlUtils.CheckResult.Tracking:
|
||||
if (Config.IgnoreTrackingUrlWarning){
|
||||
goto case UrlCheckResult.Fine;
|
||||
goto case UrlUtils.CheckResult.Fine;
|
||||
}
|
||||
|
||||
using(FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, MessageBoxIcon.Warning)){
|
||||
@ -135,13 +117,13 @@ public static void OpenExternalBrowser(string url){
|
||||
}
|
||||
|
||||
if (result == DialogResult.Ignore || result == DialogResult.Yes){
|
||||
goto case UrlCheckResult.Fine;
|
||||
goto case UrlUtils.CheckResult.Fine;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case UrlCheckResult.Invalid:
|
||||
case UrlUtils.CheckResult.Invalid:
|
||||
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
|
||||
break;
|
||||
}
|
||||
@ -174,50 +156,10 @@ public static void OpenExternalSearch(string query){
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFileNameFromUrl(string url){
|
||||
string file = Path.GetFileName(new Uri(url).AbsolutePath);
|
||||
return string.IsNullOrEmpty(file) ? null : file;
|
||||
}
|
||||
|
||||
public static string GetErrorName(CefErrorCode code){
|
||||
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
|
||||
}
|
||||
|
||||
public static WebClient CreateWebClient(){
|
||||
WindowsUtils.EnsureTLS12();
|
||||
|
||||
WebClient client = new WebClient{ Proxy = null };
|
||||
client.Headers[HttpRequestHeader.UserAgent] = UserAgentVanilla;
|
||||
return client;
|
||||
}
|
||||
|
||||
public static WebClient DownloadFileAsync(string url, string target, string cookie, Action onSuccess, Action<Exception> onFailure){
|
||||
WebClient client = CreateWebClient();
|
||||
|
||||
if (cookie != null){
|
||||
client.Headers[HttpRequestHeader.Cookie] = cookie;
|
||||
}
|
||||
|
||||
client.DownloadFileCompleted += (sender, args) => {
|
||||
if (args.Cancelled){
|
||||
try{
|
||||
File.Delete(target);
|
||||
}catch{
|
||||
// didn't want it deleted anyways
|
||||
}
|
||||
}
|
||||
else if (args.Error != null){
|
||||
onFailure?.Invoke(args.Error);
|
||||
}
|
||||
else{
|
||||
onSuccess?.Invoke();
|
||||
}
|
||||
};
|
||||
|
||||
client.DownloadFileAsync(new Uri(url), target);
|
||||
return client;
|
||||
}
|
||||
|
||||
public static int Scale(int baseValue, double scaleFactor){
|
||||
return (int)Math.Round(baseValue*scaleFactor);
|
||||
}
|
||||
|
@ -8,7 +8,9 @@
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Data;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using TweetLib.Core.Utils;
|
||||
using Cookie = CefSharp.Cookie;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
@ -71,7 +73,7 @@ public static string GetMediaLink(string url, ImageQuality quality){
|
||||
}
|
||||
|
||||
public static string GetImageFileName(string url){
|
||||
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
||||
return UrlUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
|
||||
}
|
||||
|
||||
public static void ViewImage(string url, ImageQuality quality){
|
||||
@ -88,7 +90,7 @@ void ViewImageInternal(string path){
|
||||
|
||||
string file = Path.Combine(BrowserCache.CacheFolder, GetImageFileName(url) ?? Path.GetRandomFileName());
|
||||
|
||||
if (WindowsUtils.FileExistsAndNotEmpty(file)){
|
||||
if (FileUtils.FileExistsAndNotEmpty(file)){
|
||||
ViewImageInternal(file);
|
||||
}
|
||||
else{
|
||||
@ -143,7 +145,7 @@ void OnFailure(Exception ex){
|
||||
}
|
||||
|
||||
public static void DownloadVideo(string url, string username){
|
||||
string filename = BrowserUtils.GetFileNameFromUrl(url);
|
||||
string filename = UrlUtils.GetFileNameFromUrl(url);
|
||||
string ext = Path.GetExtension(filename);
|
||||
|
||||
using(SaveFileDialog dialog = new SaveFileDialog{
|
||||
@ -178,7 +180,10 @@ private static void DownloadFileAuth(string url, string target, Action onSuccess
|
||||
}
|
||||
}
|
||||
|
||||
BrowserUtils.DownloadFileAsync(url, target, cookieStr, onSuccess, onFailure);
|
||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome);
|
||||
client.Headers[HttpRequestHeader.Cookie] = cookieStr;
|
||||
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(target, onSuccess, onFailure);
|
||||
client.DownloadFileAsync(new Uri(url), target);
|
||||
}, scheduler);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@ -16,7 +15,6 @@ static class WindowsUtils{
|
||||
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
|
||||
|
||||
private static readonly bool IsWindows8OrNewer;
|
||||
private static bool HasMicrosoftBeenBroughtTo2008Yet;
|
||||
|
||||
public static int CurrentProcessID { get; }
|
||||
public static bool ShouldAvoidToolWindow { get; }
|
||||
@ -32,47 +30,6 @@ static WindowsUtils(){
|
||||
|
||||
ShouldAvoidToolWindow = IsWindows8OrNewer;
|
||||
}
|
||||
|
||||
public static void EnsureTLS12(){
|
||||
if (!HasMicrosoftBeenBroughtTo2008Yet){
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
|
||||
HasMicrosoftBeenBroughtTo2008Yet = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateDirectoryForFile(string file){
|
||||
string dir = Path.GetDirectoryName(file);
|
||||
|
||||
if (dir == null){
|
||||
throw new ArgumentException("Invalid file path: "+file);
|
||||
}
|
||||
else if (dir.Length > 0){
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckFolderWritePermission(string path){
|
||||
string testFile = Path.Combine(path, ".test");
|
||||
|
||||
try{
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
using(File.Create(testFile)){}
|
||||
File.Delete(testFile);
|
||||
return true;
|
||||
}catch{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool FileExistsAndNotEmpty(string path){
|
||||
try{
|
||||
return new FileInfo(path).Length > 0;
|
||||
}catch{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
|
||||
try{
|
||||
|
@ -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.Windows.Forms;
|
||||
using TweetDuck.Core.Controls;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data.Serialization;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class WindowState{
|
||||
|
@ -2,10 +2,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetLib.Core.Collections;
|
||||
using TweetLib.Core.Data;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginBridge{
|
||||
@ -80,7 +81,7 @@ private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool
|
||||
public void WriteFile(int token, string path, string contents){
|
||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||
|
||||
WindowsUtils.CreateDirectoryForFile(fullPath);
|
||||
FileUtils.CreateDirectoryForFile(fullPath);
|
||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||
fileCache[token, SanitizeCacheKey(path)] = contents;
|
||||
}
|
||||
|
@ -6,10 +6,10 @@
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetDuck.Plugins.Enums;
|
||||
using TweetDuck.Plugins.Events;
|
||||
using TweetDuck.Resources;
|
||||
using TweetLib.Core.Data;
|
||||
|
||||
namespace TweetDuck.Plugins{
|
||||
sealed class PluginManager{
|
||||
|
@ -14,7 +14,8 @@
|
||||
using TweetDuck.Core.Other;
|
||||
using TweetDuck.Core.Management;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Collections;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck{
|
||||
static class Program{
|
||||
@ -75,7 +76,7 @@ private static void Main(){
|
||||
|
||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||
|
||||
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
||||
if (!FileUtils.CheckFolderWritePermission(StoragePath)){
|
||||
FormMessage.Warning("Permission Error", "TweetDuck does not have write permissions to the storage folder: "+StoragePath, FormMessage.OK);
|
||||
return;
|
||||
}
|
||||
@ -168,7 +169,7 @@ private static void Main(){
|
||||
|
||||
// ProgramPath has a trailing backslash
|
||||
string updaterArgs = "/SP- /SILENT /FORCECLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+Arguments.GetCurrentForInstallerCmd()+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||
bool runElevated = !IsPortable || !FileUtils.CheckFolderWritePermission(ProgramPath);
|
||||
|
||||
if (WindowsUtils.OpenAssociatedProgram(mainForm.UpdateInstallerPath, updaterArgs, runElevated)){
|
||||
Application.Exit();
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?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">
|
||||
<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')" />
|
||||
@ -197,10 +197,7 @@
|
||||
<DependentUpon>TabSettingsFeedback.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\TweetDeckBrowser.cs" />
|
||||
<Compile Include="Core\Utils\LocaleUtils.cs" />
|
||||
<Compile Include="Core\Utils\StringUtils.cs" />
|
||||
<Compile Include="Core\Utils\TwitterUtils.cs" />
|
||||
<Compile Include="Data\CombinedFileStream.cs" />
|
||||
<Compile Include="Core\Management\ProfileManager.cs" />
|
||||
<Compile Include="Core\Other\Settings\TabSettingsAdvanced.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
@ -230,19 +227,11 @@
|
||||
<DependentUpon>TabSettingsNotifications.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\Screenshot\ScreenshotBridge.cs" />
|
||||
<Compile Include="Data\CommandLineArgs.cs" />
|
||||
<Compile Include="Core\Notification\Screenshot\FormNotificationScreenshotable.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||
<Compile Include="Data\ResourceLink.cs" />
|
||||
<Compile Include="Data\Result.cs" />
|
||||
<Compile Include="Data\Serialization\FileSerializer.cs" />
|
||||
<Compile Include="Data\InjectedHTML.cs" />
|
||||
<Compile Include="Data\Serialization\ITypeConverter.cs" />
|
||||
<Compile Include="Data\Serialization\SerializationSoftException.cs" />
|
||||
<Compile Include="Data\Serialization\SingleTypeConverter.cs" />
|
||||
<Compile Include="Data\TwoKeyDictionary.cs" />
|
||||
<Compile Include="Data\WindowState.cs" />
|
||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Script.Serialization;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
using JsonObject = System.Collections.Generic.IDictionary<string, object>;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
@ -22,7 +23,7 @@ public UpdateCheckClient(string installerFolder){
|
||||
public Task<UpdateInfo> Check(){
|
||||
TaskCompletionSource<UpdateInfo> result = new TaskCompletionSource<UpdateInfo>();
|
||||
|
||||
WebClient client = BrowserUtils.CreateWebClient();
|
||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
|
||||
client.Headers[HttpRequestHeader.Accept] = "application/vnd.github.v3+json";
|
||||
|
||||
client.DownloadStringTaskAsync(ApiLatestRelease).ContinueWith(task => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Data;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateCheckEventArgs : EventArgs{
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Data;
|
||||
using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Updates{
|
||||
sealed class UpdateInfo{
|
||||
@ -26,7 +27,7 @@ public UpdateInfo(string versionTag, string releaseNotes, string downloadUrl, st
|
||||
}
|
||||
|
||||
public void BeginSilentDownload(){
|
||||
if (WindowsUtils.FileExistsAndNotEmpty(InstallerPath)){
|
||||
if (FileUtils.FileExistsAndNotEmpty(InstallerPath)){
|
||||
DownloadStatus = UpdateDownloadStatus.Done;
|
||||
return;
|
||||
}
|
||||
@ -48,7 +49,9 @@ public void BeginSilentDownload(){
|
||||
return;
|
||||
}
|
||||
|
||||
currentDownload = BrowserUtils.DownloadFileAsync(downloadUrl, InstallerPath, null, () => {
|
||||
WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
|
||||
|
||||
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(InstallerPath, () => {
|
||||
DownloadStatus = UpdateDownloadStatus.Done;
|
||||
currentDownload = null;
|
||||
}, e => {
|
||||
@ -56,6 +59,8 @@ public void BeginSilentDownload(){
|
||||
DownloadStatus = UpdateDownloadStatus.Failed;
|
||||
currentDownload = null;
|
||||
});
|
||||
|
||||
client.DownloadFileAsync(new Uri(downloadUrl), InstallerPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class CommandLineArgs{
|
||||
namespace TweetLib.Core.Collections{
|
||||
public sealed class CommandLineArgs{
|
||||
public static CommandLineArgs FromStringArray(char entryChar, string[] array){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
ReadStringArray(entryChar, array, args);
|
||||
@ -15,7 +15,7 @@ public static void ReadStringArray(char entryChar, string[] array, CommandLineAr
|
||||
string entry = array[index];
|
||||
|
||||
if (entry.Length > 0 && entry[0] == entryChar){
|
||||
if (index < array.Length-1){
|
||||
if (index < array.Length - 1){
|
||||
string potentialValue = array[index+1];
|
||||
|
||||
if (potentialValue.Length > 0 && potentialValue[0] == entryChar){
|
||||
@ -52,7 +52,7 @@ public static CommandLineArgs ReadCefArguments(string argumentString){
|
||||
}
|
||||
else{
|
||||
key = matchValue.Substring(0, indexEquals).TrimStart('-');
|
||||
value = matchValue.Substring(indexEquals+1).Trim('"');
|
||||
value = matchValue.Substring(indexEquals + 1).Trim('"');
|
||||
}
|
||||
|
||||
if (key.Length != 0){
|
||||
@ -66,7 +66,7 @@ public static CommandLineArgs ReadCefArguments(string argumentString){
|
||||
private readonly HashSet<string> flags = new HashSet<string>();
|
||||
private readonly Dictionary<string, string> values = new Dictionary<string, string>();
|
||||
|
||||
public int Count => flags.Count+values.Count;
|
||||
public int Count => flags.Count + values.Count;
|
||||
|
||||
public void AddFlag(string flag){
|
||||
flags.Add(flag.ToLower());
|
||||
@ -103,7 +103,7 @@ public CommandLineArgs Clone(){
|
||||
copy.AddFlag(flag);
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in values){
|
||||
foreach(var kvp in values){
|
||||
copy.SetValue(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ public void ToDictionary(IDictionary<string, string> target){
|
||||
target[flag] = "1";
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in values){
|
||||
foreach(var kvp in values){
|
||||
target[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
@ -127,11 +127,11 @@ public override string ToString(){
|
||||
build.Append(flag).Append(' ');
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, string> kvp in values){
|
||||
foreach(var kvp in values){
|
||||
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
|
||||
}
|
||||
|
||||
return build.Length == 0 ? string.Empty : build.Remove(build.Length-1, 1).ToString();
|
||||
return build.Length == 0 ? string.Empty : build.Remove(build.Length - 1, 1).ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class TwoKeyDictionary<K1, K2, V>{
|
||||
namespace TweetLib.Core.Collections{
|
||||
public sealed class TwoKeyDictionary<K1, K2, V>{
|
||||
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
||||
private readonly int innerCapacity;
|
||||
|
||||
@ -85,7 +85,8 @@ public bool Remove(K1 outerKey, K2 innerKey){
|
||||
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
||||
@ -93,7 +94,7 @@ public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
||||
return innerDict.TryGetValue(innerKey, out value);
|
||||
}
|
||||
else{
|
||||
value = default(V);
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class CombinedFileStream : IDisposable{
|
||||
public const char KeySeparator = '|';
|
||||
namespace TweetLib.Core.Data{
|
||||
public sealed class CombinedFileStream : IDisposable{
|
||||
private const char KeySeparator = '|';
|
||||
|
||||
private readonly Stream stream;
|
||||
|
||||
@ -45,7 +45,7 @@ public void WriteFile(string identifier, string path){
|
||||
stream.Write(contents, 0, contents.Length);
|
||||
}
|
||||
|
||||
public Entry ReadFile(){
|
||||
public Entry? ReadFile(){
|
||||
int nameLength = stream.ReadByte();
|
||||
|
||||
if (nameLength == -1){
|
||||
@ -64,7 +64,7 @@ public Entry ReadFile(){
|
||||
return new Entry(Encoding.UTF8.GetString(name), contents);
|
||||
}
|
||||
|
||||
public string SkipFile(){
|
||||
public string? SkipFile(){
|
||||
int nameLength = stream.ReadByte();
|
||||
|
||||
if (nameLength == -1){
|
||||
@ -120,7 +120,7 @@ public void WriteToFile(string path){
|
||||
|
||||
public void WriteToFile(string path, bool createDirectory){
|
||||
if (createDirectory){
|
||||
WindowsUtils.CreateDirectoryForFile(path);
|
||||
FileUtils.CreateDirectoryForFile(path);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(path, contents);
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class InjectedHTML{
|
||||
namespace TweetLib.Core.Data{
|
||||
public sealed class InjectedHTML{
|
||||
public enum Position{
|
||||
Before, After
|
||||
}
|
||||
@ -27,7 +27,7 @@ public string InjectInto(string targetHTML){
|
||||
|
||||
switch(position){
|
||||
case Position.Before: cutIndex = index; break;
|
||||
case Position.After: cutIndex = index+search.Length; break;
|
||||
case Position.After: cutIndex = index + search.Length; break;
|
||||
default: return targetHTML;
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDuck.Data{
|
||||
sealed class Result<T>{
|
||||
namespace TweetLib.Core.Data{
|
||||
public sealed class Result<T>{
|
||||
public bool HasValue => exception == null;
|
||||
|
||||
public T Value => HasValue ? value : throw new InvalidOperationException("Requested value from a failed result.");
|
||||
public Exception Exception => exception ?? throw new InvalidOperationException("Requested exception from a successful result.");
|
||||
|
||||
private readonly T value;
|
||||
private readonly Exception exception;
|
||||
private readonly Exception? exception;
|
||||
|
||||
public Result(T value){
|
||||
this.value = value;
|
||||
@ -16,7 +16,7 @@ public Result(T value){
|
||||
}
|
||||
|
||||
public Result(Exception exception){
|
||||
this.value = default(T);
|
||||
this.value = default;
|
||||
this.exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
@ -25,12 +25,12 @@ public void Handle(Action<T> onSuccess, Action<Exception> onException){
|
||||
onSuccess(value);
|
||||
}
|
||||
else{
|
||||
onException(exception);
|
||||
onException(exception!!);
|
||||
}
|
||||
}
|
||||
|
||||
public Result<R> Select<R>(Func<T, R> map){
|
||||
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception);
|
||||
return HasValue ? new Result<R>(map(value)) : new Result<R>(exception!!);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
namespace TweetDuck.Data.Serialization{
|
||||
sealed class SingleTypeConverter<T> : ITypeConverter{
|
||||
namespace TweetLib.Core.Serialization.Converters{
|
||||
public sealed class SingleTypeConverter<T> : ITypeConverter{
|
||||
public Func<T, string> ConvertToString { get; set; }
|
||||
public Func<string, T> ConvertToObject { get; set; }
|
||||
|
||||
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
|
||||
bool ITypeConverter.TryWriteType(Type type, object value, out string? converted){
|
||||
try{
|
||||
converted = ConvertToString((T)value);
|
||||
return true;
|
||||
@ -15,7 +15,7 @@ bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
|
||||
}
|
||||
}
|
||||
|
||||
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
|
||||
bool ITypeConverter.TryReadType(Type type, string value, out object? converted){
|
||||
try{
|
||||
converted = ConvertToObject(value);
|
||||
return true;
|
@ -4,10 +4,11 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using TweetDuck.Core.Utils;
|
||||
using TweetLib.Core.Serialization.Converters;
|
||||
using TweetLib.Core.Utils;
|
||||
|
||||
namespace TweetDuck.Data.Serialization{
|
||||
sealed class FileSerializer<T>{
|
||||
namespace TweetLib.Core.Serialization{
|
||||
public sealed class FileSerializer<T>{
|
||||
private const string NewLineReal = "\r\n";
|
||||
private const string NewLineCustom = "\r~\n";
|
||||
|
||||
@ -49,8 +50,6 @@ private static string UnescapeStream(StreamReader reader){
|
||||
return build.Append(data.Substring(index)).ToString();
|
||||
}
|
||||
|
||||
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
|
||||
|
||||
private readonly Dictionary<string, PropertyInfo> props;
|
||||
private readonly Dictionary<Type, ITypeConverter> converters;
|
||||
|
||||
@ -66,7 +65,7 @@ public void RegisterTypeConverter(Type type, ITypeConverter converter){
|
||||
public void Write(string file, T obj){
|
||||
LinkedList<string> errors = new LinkedList<string>();
|
||||
|
||||
WindowsUtils.CreateDirectoryForFile(file);
|
||||
FileUtils.CreateDirectoryForFile(file);
|
||||
|
||||
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||
foreach(KeyValuePair<string, PropertyInfo> prop in props){
|
||||
@ -74,10 +73,10 @@ public void Write(string file, T obj){
|
||||
object value = prop.Value.GetValue(obj);
|
||||
|
||||
if (!converters.TryGetValue(type, out ITypeConverter serializer)){
|
||||
serializer = BasicSerializerObj;
|
||||
serializer = ClrTypeConverter.Instance;
|
||||
}
|
||||
|
||||
if (serializer.TryWriteType(type, value, out string converted)){
|
||||
if (serializer.TryWriteType(type, value, out string? converted)){
|
||||
if (converted != null){
|
||||
writer.Write(prop.Key);
|
||||
writer.Write(' ');
|
||||
@ -142,10 +141,10 @@ public void Read(string file, T obj){
|
||||
|
||||
if (props.TryGetValue(property, out PropertyInfo info)){
|
||||
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){
|
||||
serializer = BasicSerializerObj;
|
||||
serializer = ClrTypeConverter.Instance;
|
||||
}
|
||||
|
||||
if (serializer.TryReadType(info.PropertyType, value, out object converted)){
|
||||
if (serializer.TryReadType(info.PropertyType, value, out object? converted)){
|
||||
info.SetValue(obj, converted);
|
||||
}
|
||||
else{
|
||||
@ -165,53 +164,5 @@ public void ReadIfExists(string file, T obj){
|
||||
}catch(FileNotFoundException){
|
||||
}catch(DirectoryNotFoundException){}
|
||||
}
|
||||
|
||||
private sealed class BasicTypeConverter : ITypeConverter{
|
||||
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
|
||||
switch(Type.GetTypeCode(type)){
|
||||
case TypeCode.Boolean:
|
||||
converted = value.ToString();
|
||||
return true;
|
||||
|
||||
case TypeCode.Int32:
|
||||
converted = ((int)value).ToString(); // cast required for enums
|
||||
return true;
|
||||
|
||||
case TypeCode.String:
|
||||
converted = value?.ToString();
|
||||
return true;
|
||||
|
||||
default:
|
||||
converted = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
|
||||
switch(Type.GetTypeCode(type)){
|
||||
case TypeCode.Boolean:
|
||||
if (bool.TryParse(value, out bool b)){
|
||||
converted = b;
|
||||
return true;
|
||||
}
|
||||
else goto default;
|
||||
|
||||
case TypeCode.Int32:
|
||||
if (int.TryParse(value, out int i)){
|
||||
converted = i;
|
||||
return true;
|
||||
}
|
||||
else goto default;
|
||||
|
||||
case TypeCode.String:
|
||||
converted = value;
|
||||
return true;
|
||||
|
||||
default:
|
||||
converted = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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.Collections.Generic;
|
||||
|
||||
namespace TweetDuck.Data.Serialization{
|
||||
sealed class SerializationSoftException : Exception{
|
||||
namespace TweetLib.Core.Serialization{
|
||||
public sealed class SerializationSoftException : Exception{
|
||||
public IList<string> Errors { get; }
|
||||
|
||||
public SerializationSoftException(IList<string> errors) : base(string.Join(Environment.NewLine, errors)){
|
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.Linq;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
static class LocaleUtils{
|
||||
namespace TweetLib.Core.Utils{
|
||||
public static class LocaleUtils{
|
||||
// https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/
|
||||
public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{
|
||||
"af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE",
|
||||
@ -33,9 +33,9 @@ public sealed class Item : IComparable<Item>{
|
||||
|
||||
private string Name => info?.NativeName ?? Code;
|
||||
|
||||
private readonly CultureInfo info;
|
||||
private readonly CultureInfo? info;
|
||||
|
||||
public Item(string code, string alt = null){
|
||||
public Item(string code, string? alt = null){
|
||||
this.Code = code;
|
||||
|
||||
try{
|
@ -2,8 +2,8 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace TweetDuck.Core.Utils{
|
||||
static class StringUtils{
|
||||
namespace TweetLib.Core.Utils{
|
||||
public static class StringUtils{
|
||||
public static readonly string[] EmptyArray = new string[0];
|
||||
|
||||
public static string ExtractBefore(string str, char search, int startIndex = 0){
|
||||
@ -23,7 +23,7 @@ public static string ConvertRot13(string str){
|
||||
return Regex.Replace(str, @"[a-zA-Z]", match => {
|
||||
int code = match.Value[0];
|
||||
int start = code <= 90 ? 65 : 97;
|
||||
return ((char)(start+(code-start+13)%26)).ToString();
|
||||
return ((char)(start + (code - start + 13) % 26)).ToString();
|
||||
});
|
||||
}
|
||||
|
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 System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Configuration;
|
||||
using TweetDuck.Core.Other;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace TweetTest.Configuration{
|
||||
[TestClass]
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Data;
|
||||
using TweetLib.Core.Data;
|
||||
|
||||
namespace TweetTest.Data{
|
||||
[TestClass]
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDuck.Data.Serialization;
|
||||
using TweetLib.Core.Serialization;
|
||||
|
||||
namespace TweetTest.Data{
|
||||
[TestClass]
|
||||
|
@ -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
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Core.Utils
|
||||
open TweetLib.Core.Utils
|
||||
|
||||
|
||||
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
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
open TweetLib.Core.Collections
|
||||
|
||||
|
||||
type _TestData =
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace TweetTest.Data.InjectedHTML
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
open TweetLib.Core.Data
|
||||
|
||||
|
||||
module Inject =
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace TweetTest.Data.Result
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
open TweetLib.Core.Data
|
||||
open System
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace TweetTest.Data.TwoKeyDictionary
|
||||
|
||||
open Xunit
|
||||
open TweetDuck.Data
|
||||
open TweetLib.Core.Collections
|
||||
open System.Collections.Generic
|
||||
|
||||
|
||||
|
@ -50,9 +50,9 @@
|
||||
</Choose>
|
||||
<Import Project="$(FSharpTargetsPath)" />
|
||||
<ItemGroup>
|
||||
<Compile Include="Core\TestBrowserUtils.fs" />
|
||||
<Compile Include="Core\TestStringUtils.fs" />
|
||||
<Compile Include="Core\TestTwitterUtils.fs" />
|
||||
<Compile Include="Core\TestUrlUtils.fs" />
|
||||
<Compile Include="Data\TestCommandLineArgs.fs" />
|
||||
<Compile Include="Data\TestInjectedHTML.fs" />
|
||||
<Compile Include="Data\TestResult.fs" />
|
||||
|
Loading…
Reference in New Issue
Block a user