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

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

This commit is contained in:
chylex 2019-05-09 16:26:40 +02:00
parent 01df118549
commit dd6e712755
54 changed files with 375 additions and 355 deletions

View File

@ -1,5 +1,5 @@
using System; using System;
using TweetDuck.Data; using TweetLib.Core.Collections;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
static class Arguments{ static class Arguments{

View File

@ -2,9 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using TweetDuck.Configuration.Instance; using TweetDuck.Configuration.Instance;
using TweetDuck.Core.Utils;
using TweetDuck.Data; using TweetDuck.Data;
using TweetDuck.Data.Serialization; using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils;
namespace TweetDuck.Configuration{ namespace TweetDuck.Configuration{
sealed class ConfigManager{ sealed class ConfigManager{

View File

@ -1,6 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using TweetDuck.Data.Serialization; using TweetLib.Core.Serialization;
namespace TweetDuck.Configuration.Instance{ namespace TweetDuck.Configuration.Instance{
sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{ sealed class FileConfigInstance<T> : IConfigInstance<T> where T : ConfigManager.BaseConfig{

View File

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

View File

@ -1,6 +1,6 @@
using System; using System;
using CefSharp; using CefSharp;
using TweetDuck.Core.Utils; using TweetLib.Core.Utils;
namespace TweetDuck.Core.Management{ namespace TweetDuck.Core.Management{
sealed class ContextInfo{ sealed class ContextInfo{

View File

@ -3,9 +3,9 @@
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 TweetDuck.Plugins.Enums;
using TweetLib.Core.Data;
namespace TweetDuck.Core.Management{ namespace TweetDuck.Core.Management{
sealed class ProfileManager{ sealed class ProfileManager{

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@
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.Utils;
namespace TweetDuck.Core.Other.Analytics{ namespace TweetDuck.Core.Other.Analytics{
sealed class AnalyticsManager : IDisposable{ sealed class AnalyticsManager : IDisposable{
@ -20,7 +21,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; }
@ -117,7 +118,7 @@ private void SendReport(){
System.Diagnostics.Debugger.Break(); System.Diagnostics.Debugger.Break();
#endif #endif
BrowserUtils.CreateWebClient().UploadValues(CollectionUrl, "POST", report.ToNameValueCollection()); WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection());
}).ContinueWith(task => browser.InvokeAsyncSafe(() => { }).ContinueWith(task => browser.InvokeAsyncSafe(() => {
if (task.Status == TaskStatus.RanToCompletion){ if (task.Status == TaskStatus.RanToCompletion){
SetLastDataCollectionTime(DateTime.Now); SetLastDataCollectionTime(DateTime.Now);

View File

@ -12,6 +12,7 @@
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetDuck.Plugins; using TweetDuck.Plugins;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Other.Analytics{ namespace TweetDuck.Core.Other.Analytics{
static class AnalyticsReportGenerator{ static class AnalyticsReportGenerator{

View File

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

View File

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

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Windows.Forms; using System.Windows.Forms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Data; using TweetLib.Core.Collections;
namespace TweetDuck.Core.Other.Settings.Dialogs{ namespace TweetDuck.Core.Other.Settings.Dialogs{
sealed partial class DialogSettingsRestart : Form{ sealed partial class DialogSettingsRestart : Form{

View File

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

View File

@ -3,16 +3,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
using CefSharp.WinForms; using CefSharp.WinForms;
using TweetDuck.Configuration; using TweetDuck.Configuration;
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetLib.Core.Utils;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
static class BrowserUtils{ static class BrowserUtils{
public static string UserAgentVanilla => Program.BrandName+" "+Application.ProductVersion; public static string UserAgentVanilla => Program.BrandName + " " + Application.ProductVersion;
public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"+Cef.ChromiumVersion+" Safari/537.36"; public static string UserAgentChrome => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + Cef.ChromiumVersion + " Safari/537.36";
public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak")); public static readonly bool HasDevTools = File.Exists(Path.Combine(Program.ProgramPath, "devtools_resources.pak"));
@ -74,29 +74,11 @@ void UpdateZoomLevel(object sender, EventArgs args){
}; };
} }
private const string TwitterTrackingUrl = "t.co";
public enum UrlCheckResult{
Invalid, Tracking, Fine
}
public static UrlCheckResult CheckUrl(string url){
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
string scheme = uri.Scheme;
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
return uri.Host == TwitterTrackingUrl ? UrlCheckResult.Tracking : UrlCheckResult.Fine;
}
}
return UrlCheckResult.Invalid;
}
public static void OpenExternalBrowser(string url){ public static void OpenExternalBrowser(string url){
if (string.IsNullOrWhiteSpace(url))return; if (string.IsNullOrWhiteSpace(url))return;
switch(CheckUrl(url)){ switch(UrlUtils.Check(url)){
case UrlCheckResult.Fine: case UrlUtils.CheckResult.Fine:
if (FormGuide.CheckGuideUrl(url, out string hash)){ if (FormGuide.CheckGuideUrl(url, out string hash)){
FormGuide.Show(hash); FormGuide.Show(hash);
} }
@ -117,9 +99,9 @@ public static void OpenExternalBrowser(string url){
break; break;
case UrlCheckResult.Tracking: case UrlUtils.CheckResult.Tracking:
if (Config.IgnoreTrackingUrlWarning){ if (Config.IgnoreTrackingUrlWarning){
goto case UrlCheckResult.Fine; goto case UrlUtils.CheckResult.Fine;
} }
using(FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, MessageBoxIcon.Warning)){ using(FormMessage form = new FormMessage("Blocked URL", "TweetDuck has blocked a tracking url due to privacy concerns. Do you want to visit it anyway?\n"+url, MessageBoxIcon.Warning)){
@ -135,13 +117,13 @@ public static void OpenExternalBrowser(string url){
} }
if (result == DialogResult.Ignore || result == DialogResult.Yes){ if (result == DialogResult.Ignore || result == DialogResult.Yes){
goto case UrlCheckResult.Fine; goto case UrlUtils.CheckResult.Fine;
} }
} }
break; break;
case UrlCheckResult.Invalid: case UrlUtils.CheckResult.Invalid:
FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK); FormMessage.Warning("Blocked URL", "A potentially malicious URL was blocked from opening:\n"+url, FormMessage.OK);
break; break;
} }
@ -174,50 +156,10 @@ public static void OpenExternalSearch(string query){
} }
} }
public static string GetFileNameFromUrl(string url){
string file = Path.GetFileName(new Uri(url).AbsolutePath);
return string.IsNullOrEmpty(file) ? null : file;
}
public static string GetErrorName(CefErrorCode code){ public static string GetErrorName(CefErrorCode code){
return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty); return StringUtils.ConvertPascalCaseToScreamingSnakeCase(Enum.GetName(typeof(CefErrorCode), code) ?? string.Empty);
} }
public static WebClient CreateWebClient(){
WindowsUtils.EnsureTLS12();
WebClient client = new WebClient{ Proxy = null };
client.Headers[HttpRequestHeader.UserAgent] = UserAgentVanilla;
return client;
}
public static WebClient DownloadFileAsync(string url, string target, string cookie, Action onSuccess, Action<Exception> onFailure){
WebClient client = CreateWebClient();
if (cookie != null){
client.Headers[HttpRequestHeader.Cookie] = cookie;
}
client.DownloadFileCompleted += (sender, args) => {
if (args.Cancelled){
try{
File.Delete(target);
}catch{
// didn't want it deleted anyways
}
}
else if (args.Error != null){
onFailure?.Invoke(args.Error);
}
else{
onSuccess?.Invoke();
}
};
client.DownloadFileAsync(new Uri(url), target);
return client;
}
public static int Scale(int baseValue, double scaleFactor){ public static int Scale(int baseValue, double scaleFactor){
return (int)Math.Round(baseValue*scaleFactor); return (int)Math.Round(baseValue*scaleFactor);
} }

View File

@ -8,7 +8,9 @@
using TweetDuck.Core.Other; using TweetDuck.Core.Other;
using TweetDuck.Data; using TweetDuck.Data;
using System.Linq; using System.Linq;
using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using TweetLib.Core.Utils;
using Cookie = CefSharp.Cookie; using Cookie = CefSharp.Cookie;
namespace TweetDuck.Core.Utils{ namespace TweetDuck.Core.Utils{
@ -71,7 +73,7 @@ public static string GetMediaLink(string url, ImageQuality quality){
} }
public static string GetImageFileName(string url){ public static string GetImageFileName(string url){
return BrowserUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url)); return UrlUtils.GetFileNameFromUrl(ExtractMediaBaseLink(url));
} }
public static void ViewImage(string url, ImageQuality quality){ public static void ViewImage(string url, ImageQuality quality){
@ -88,7 +90,7 @@ void ViewImageInternal(string path){
string file = Path.Combine(BrowserCache.CacheFolder, GetImageFileName(url) ?? Path.GetRandomFileName()); string file = Path.Combine(BrowserCache.CacheFolder, GetImageFileName(url) ?? Path.GetRandomFileName());
if (WindowsUtils.FileExistsAndNotEmpty(file)){ if (FileUtils.FileExistsAndNotEmpty(file)){
ViewImageInternal(file); ViewImageInternal(file);
} }
else{ else{
@ -143,7 +145,7 @@ void OnFailure(Exception ex){
} }
public static void DownloadVideo(string url, string username){ public static void DownloadVideo(string url, string username){
string filename = BrowserUtils.GetFileNameFromUrl(url); string filename = UrlUtils.GetFileNameFromUrl(url);
string ext = Path.GetExtension(filename); string ext = Path.GetExtension(filename);
using(SaveFileDialog dialog = new SaveFileDialog{ using(SaveFileDialog dialog = new SaveFileDialog{
@ -178,7 +180,10 @@ private static void DownloadFileAuth(string url, string target, Action onSuccess
} }
} }
BrowserUtils.DownloadFileAsync(url, target, cookieStr, onSuccess, onFailure); WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentChrome);
client.Headers[HttpRequestHeader.Cookie] = cookieStr;
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(target, onSuccess, onFailure);
client.DownloadFileAsync(new Uri(url), target);
}, scheduler); }, scheduler);
} }
} }

View File

@ -3,7 +3,6 @@
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@ -16,7 +15,6 @@ static class WindowsUtils{
private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false); private static readonly Lazy<Regex> RegexOffsetClipboardHtml = new Lazy<Regex>(() => new Regex(@"(?<=EndHTML:|EndFragment:)(\d+)"), false);
private static readonly bool IsWindows8OrNewer; private static readonly bool IsWindows8OrNewer;
private static bool HasMicrosoftBeenBroughtTo2008Yet;
public static int CurrentProcessID { get; } public static int CurrentProcessID { get; }
public static bool ShouldAvoidToolWindow { get; } public static bool ShouldAvoidToolWindow { get; }
@ -32,47 +30,6 @@ static WindowsUtils(){
ShouldAvoidToolWindow = IsWindows8OrNewer; ShouldAvoidToolWindow = IsWindows8OrNewer;
} }
public static void EnsureTLS12(){
if (!HasMicrosoftBeenBroughtTo2008Yet){
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
HasMicrosoftBeenBroughtTo2008Yet = true;
}
}
public static void CreateDirectoryForFile(string file){
string dir = Path.GetDirectoryName(file);
if (dir == null){
throw new ArgumentException("Invalid file path: "+file);
}
else if (dir.Length > 0){
Directory.CreateDirectory(dir);
}
}
public static bool CheckFolderWritePermission(string path){
string testFile = Path.Combine(path, ".test");
try{
Directory.CreateDirectory(path);
using(File.Create(testFile)){}
File.Delete(testFile);
return true;
}catch{
return false;
}
}
public static bool FileExistsAndNotEmpty(string path){
try{
return new FileInfo(path).Length > 0;
}catch{
return false;
}
}
public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){ public static bool OpenAssociatedProgram(string file, string arguments = "", bool runElevated = false){
try{ try{

View File

@ -1,8 +0,0 @@
using System;
namespace TweetDuck.Data.Serialization{
interface ITypeConverter{
bool TryWriteType(Type type, object value, out string converted);
bool TryReadType(Type type, string value, out object converted);
}
}

View File

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

View File

@ -2,10 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using TweetDuck.Core.Utils;
using TweetDuck.Data;
using TweetDuck.Plugins.Enums; using TweetDuck.Plugins.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
using TweetLib.Core.Collections;
using TweetLib.Core.Data;
using TweetLib.Core.Utils;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class PluginBridge{ 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){ public void WriteFile(int token, string path, string contents){
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path); string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
WindowsUtils.CreateDirectoryForFile(fullPath); FileUtils.CreateDirectoryForFile(fullPath);
File.WriteAllText(fullPath, contents, Encoding.UTF8); File.WriteAllText(fullPath, contents, Encoding.UTF8);
fileCache[token, SanitizeCacheKey(path)] = contents; fileCache[token, SanitizeCacheKey(path)] = contents;
} }

View File

@ -6,10 +6,10 @@
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.Enums;
using TweetDuck.Plugins.Events; using TweetDuck.Plugins.Events;
using TweetDuck.Resources; using TweetDuck.Resources;
using TweetLib.Core.Data;
namespace TweetDuck.Plugins{ namespace TweetDuck.Plugins{
sealed class PluginManager{ sealed class PluginManager{

View File

@ -14,7 +14,8 @@
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.Collections;
using TweetLib.Core.Utils;
namespace TweetDuck{ namespace TweetDuck{
static class Program{ static class Program{
@ -75,7 +76,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;
} }
@ -168,7 +169,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();

View File

@ -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"> <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\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')" />
@ -197,10 +197,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>
@ -230,19 +227,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" />

View File

@ -6,6 +6,7 @@
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.Utils;
using JsonObject = System.Collections.Generic.IDictionary<string, object>; using JsonObject = System.Collections.Generic.IDictionary<string, object>;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
@ -22,7 +23,7 @@ public UpdateCheckClient(string installerFolder){
public Task<UpdateInfo> Check(){ public Task<UpdateInfo> 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 => {

View File

@ -1,5 +1,5 @@
using System; using System;
using TweetDuck.Data; using TweetLib.Core.Data;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed class UpdateCheckEventArgs : EventArgs{ sealed class UpdateCheckEventArgs : EventArgs{

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using TweetDuck.Data; using TweetLib.Core.Data;
using Timer = System.Windows.Forms.Timer; using Timer = System.Windows.Forms.Timer;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{

View File

@ -2,6 +2,7 @@
using System.IO; using System.IO;
using System.Net; using System.Net;
using TweetDuck.Core.Utils; using TweetDuck.Core.Utils;
using TweetLib.Core.Utils;
namespace TweetDuck.Updates{ namespace TweetDuck.Updates{
sealed class UpdateInfo{ sealed class UpdateInfo{
@ -26,7 +27,7 @@ public UpdateInfo(string versionTag, string releaseNotes, string downloadUrl, st
} }
public void BeginSilentDownload(){ public void BeginSilentDownload(){
if (WindowsUtils.FileExistsAndNotEmpty(InstallerPath)){ if (FileUtils.FileExistsAndNotEmpty(InstallerPath)){
DownloadStatus = UpdateDownloadStatus.Done; DownloadStatus = UpdateDownloadStatus.Done;
return; return;
} }
@ -48,7 +49,9 @@ public void BeginSilentDownload(){
return; return;
} }
currentDownload = BrowserUtils.DownloadFileAsync(downloadUrl, InstallerPath, null, () => { WebClient client = WebUtils.NewClient(BrowserUtils.UserAgentVanilla);
client.DownloadFileCompleted += WebUtils.FileDownloadCallback(InstallerPath, () => {
DownloadStatus = UpdateDownloadStatus.Done; DownloadStatus = UpdateDownloadStatus.Done;
currentDownload = null; currentDownload = null;
}, e => { }, e => {
@ -56,6 +59,8 @@ public void BeginSilentDownload(){
DownloadStatus = UpdateDownloadStatus.Failed; DownloadStatus = UpdateDownloadStatus.Failed;
currentDownload = null; currentDownload = null;
}); });
client.DownloadFileAsync(new Uri(downloadUrl), InstallerPath);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,55 @@
using System;
namespace TweetLib.Core.Serialization.Converters{
internal sealed class ClrTypeConverter : ITypeConverter{
public static ITypeConverter Instance { get; } = new ClrTypeConverter();
private ClrTypeConverter(){}
bool ITypeConverter.TryWriteType(Type type, object value, out string? converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
converted = value.ToString();
return true;
case TypeCode.Int32:
converted = ((int)value).ToString(); // cast required for enums
return true;
case TypeCode.String:
converted = value?.ToString();
return true;
default:
converted = null;
return false;
}
}
bool ITypeConverter.TryReadType(Type type, string value, out object? converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
if (bool.TryParse(value, out bool b)){
converted = b;
return true;
}
else goto default;
case TypeCode.Int32:
if (int.TryParse(value, out int i)){
converted = i;
return true;
}
else goto default;
case TypeCode.String:
converted = value;
return true;
default:
converted = null;
return false;
}
}
}
}

View File

@ -1,11 +1,11 @@
using System; using System;
namespace TweetDuck.Data.Serialization{ namespace TweetLib.Core.Serialization.Converters{
sealed class SingleTypeConverter<T> : ITypeConverter{ public sealed class SingleTypeConverter<T> : ITypeConverter{
public Func<T, string> ConvertToString { get; set; } public Func<T, string> ConvertToString { get; set; }
public Func<string, T> ConvertToObject { get; set; } public Func<string, T> ConvertToObject { get; set; }
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){ bool ITypeConverter.TryWriteType(Type type, object value, out string? converted){
try{ try{
converted = ConvertToString((T)value); converted = ConvertToString((T)value);
return true; return true;
@ -15,7 +15,7 @@ bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
} }
} }
bool ITypeConverter.TryReadType(Type type, string value, out object converted){ bool ITypeConverter.TryReadType(Type type, string value, out object? converted){
try{ try{
converted = ConvertToObject(value); converted = ConvertToObject(value);
return true; return true;

View File

@ -4,10 +4,11 @@
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using TweetDuck.Core.Utils; using TweetLib.Core.Serialization.Converters;
using TweetLib.Core.Utils;
namespace TweetDuck.Data.Serialization{ namespace TweetLib.Core.Serialization{
sealed class FileSerializer<T>{ public sealed class FileSerializer<T>{
private const string NewLineReal = "\r\n"; private const string NewLineReal = "\r\n";
private const string NewLineCustom = "\r~\n"; private const string NewLineCustom = "\r~\n";
@ -49,8 +50,6 @@ private static string UnescapeStream(StreamReader reader){
return build.Append(data.Substring(index)).ToString(); return build.Append(data.Substring(index)).ToString();
} }
private static readonly ITypeConverter BasicSerializerObj = new BasicTypeConverter();
private readonly Dictionary<string, PropertyInfo> props; private readonly Dictionary<string, PropertyInfo> props;
private readonly Dictionary<Type, ITypeConverter> converters; private readonly Dictionary<Type, ITypeConverter> converters;
@ -66,7 +65,7 @@ public void RegisterTypeConverter(Type type, ITypeConverter converter){
public void Write(string file, T obj){ public void Write(string file, T obj){
LinkedList<string> errors = new LinkedList<string>(); LinkedList<string> errors = new LinkedList<string>();
WindowsUtils.CreateDirectoryForFile(file); FileUtils.CreateDirectoryForFile(file);
using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){ using(StreamWriter writer = new StreamWriter(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
foreach(KeyValuePair<string, PropertyInfo> prop in props){ foreach(KeyValuePair<string, PropertyInfo> prop in props){
@ -74,10 +73,10 @@ public void Write(string file, T obj){
object value = prop.Value.GetValue(obj); object value = prop.Value.GetValue(obj);
if (!converters.TryGetValue(type, out ITypeConverter serializer)){ if (!converters.TryGetValue(type, out ITypeConverter serializer)){
serializer = BasicSerializerObj; serializer = ClrTypeConverter.Instance;
} }
if (serializer.TryWriteType(type, value, out string converted)){ if (serializer.TryWriteType(type, value, out string? converted)){
if (converted != null){ if (converted != null){
writer.Write(prop.Key); writer.Write(prop.Key);
writer.Write(' '); writer.Write(' ');
@ -142,10 +141,10 @@ public void Read(string file, T obj){
if (props.TryGetValue(property, out PropertyInfo info)){ if (props.TryGetValue(property, out PropertyInfo info)){
if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){ if (!converters.TryGetValue(info.PropertyType, out ITypeConverter serializer)){
serializer = BasicSerializerObj; serializer = ClrTypeConverter.Instance;
} }
if (serializer.TryReadType(info.PropertyType, value, out object converted)){ if (serializer.TryReadType(info.PropertyType, value, out object? converted)){
info.SetValue(obj, converted); info.SetValue(obj, converted);
} }
else{ else{
@ -165,53 +164,5 @@ public void ReadIfExists(string file, T obj){
}catch(FileNotFoundException){ }catch(FileNotFoundException){
}catch(DirectoryNotFoundException){} }catch(DirectoryNotFoundException){}
} }
private sealed class BasicTypeConverter : ITypeConverter{
bool ITypeConverter.TryWriteType(Type type, object value, out string converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
converted = value.ToString();
return true;
case TypeCode.Int32:
converted = ((int)value).ToString(); // cast required for enums
return true;
case TypeCode.String:
converted = value?.ToString();
return true;
default:
converted = null;
return false;
}
}
bool ITypeConverter.TryReadType(Type type, string value, out object converted){
switch(Type.GetTypeCode(type)){
case TypeCode.Boolean:
if (bool.TryParse(value, out bool b)){
converted = b;
return true;
}
else goto default;
case TypeCode.Int32:
if (int.TryParse(value, out int i)){
converted = i;
return true;
}
else goto default;
case TypeCode.String:
converted = value;
return true;
default:
converted = null;
return false;
}
}
}
} }
} }

View File

@ -0,0 +1,8 @@
using System;
namespace TweetLib.Core.Serialization{
public interface ITypeConverter{
bool TryWriteType(Type type, object value, out string? converted);
bool TryReadType(Type type, string value, out object? converted);
}
}

View File

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

View File

@ -0,0 +1,39 @@
using System;
using System.IO;
namespace TweetLib.Core.Utils{
public static class FileUtils{
public static void CreateDirectoryForFile(string file){
string dir = Path.GetDirectoryName(file);
if (dir == null){
throw new ArgumentException("Invalid file path: "+file);
}
else if (dir.Length > 0){
Directory.CreateDirectory(dir);
}
}
public static bool CheckFolderWritePermission(string path){
string testFile = Path.Combine(path, ".test");
try{
Directory.CreateDirectory(path);
using(File.Create(testFile)){}
File.Delete(testFile);
return true;
}catch{
return false;
}
}
public static bool FileExistsAndNotEmpty(string path){
try{
return new FileInfo(path).Length > 0;
}catch{
return false;
}
}
}
}

View File

@ -3,8 +3,8 @@
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
namespace TweetDuck.Core.Utils{ namespace TweetLib.Core.Utils{
static class LocaleUtils{ public static class LocaleUtils{
// https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/ // https://cs.chromium.org/chromium/src/third_party/hunspell_dictionaries/
public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{ public static IEnumerable<Item> SpellCheckLanguages { get; } = new List<string>{
"af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE", "af-ZA", "bg-BG", "ca-ES", "cs-CZ", "da-DK", "de-DE",
@ -33,9 +33,9 @@ public sealed class Item : IComparable<Item>{
private string Name => info?.NativeName ?? Code; private string Name => info?.NativeName ?? Code;
private readonly CultureInfo info; private readonly CultureInfo? info;
public Item(string code, string alt = null){ public Item(string code, string? alt = null){
this.Code = code; this.Code = code;
try{ try{

View File

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

View File

@ -0,0 +1,29 @@
using System;
using System.IO;
namespace TweetLib.Core.Utils{
public static class UrlUtils{
private const string TwitterTrackingUrl = "t.co";
public enum CheckResult{
Invalid, Tracking, Fine
}
public static CheckResult Check(string url){
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)){
string scheme = uri.Scheme;
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto){
return uri.Host == TwitterTrackingUrl ? CheckResult.Tracking : CheckResult.Fine;
}
}
return CheckResult.Invalid;
}
public static string? GetFileNameFromUrl(string url){
string file = Path.GetFileName(new Uri(url).AbsolutePath);
return string.IsNullOrEmpty(file) ? null : file;
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
namespace TweetLib.Core.Utils{
public static class WebUtils{
private static bool HasMicrosoftBeenBroughtTo2008Yet;
private static void EnsureTLS12(){
if (!HasMicrosoftBeenBroughtTo2008Yet){
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
HasMicrosoftBeenBroughtTo2008Yet = true;
}
}
public static WebClient NewClient(string userAgent){
EnsureTLS12();
WebClient client = new WebClient{ Proxy = null };
client.Headers[HttpRequestHeader.UserAgent] = userAgent;
return client;
}
public static AsyncCompletedEventHandler FileDownloadCallback(string file, Action? onSuccess, Action<Exception>? onFailure){
return (sender, args) => {
if (args.Cancelled){
try{
File.Delete(file);
}catch{
// didn't want it deleted anyways
}
}
else if (args.Error != null){
onFailure?.Invoke(args.Error);
}
else{
onSuccess?.Invoke();
}
};
}
}
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
using System; using System;
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]

View File

@ -1,79 +0,0 @@
namespace TweetTest.Core.BrowserUtils
open Xunit
open TweetDuck.Core.Utils
module CheckUrl =
type Result = BrowserUtils.UrlCheckResult
[<Fact>]
let ``accepts HTTP protocol`` () =
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://example.com"))
[<Fact>]
let ``accepts HTTPS protocol`` () =
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("https://example.com"))
[<Fact>]
let ``accepts FTP protocol`` () =
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("ftp://example.com"))
[<Fact>]
let ``accepts MAILTO protocol`` () =
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("mailto://someone@example.com"))
[<Fact>]
let ``accepts URL with port, path, query, and hash`` () =
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://www.example.co.uk:80/path?key=abc&array[]=5#hash"))
[<Fact>]
let ``accepts IPv4 address`` () =
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://127.0.0.1"))
[<Fact>]
let ``accepts IPv6 address`` () =
Assert.Equal(Result.Fine, BrowserUtils.CheckUrl("http://[2001:db8:0:0:0:ff00:42:8329]"))
[<Fact>]
let ``recognizes t.co as tracking URL`` () =
Assert.Equal(Result.Tracking, BrowserUtils.CheckUrl("http://t.co/12345"))
[<Fact>]
let ``rejects empty URL`` () =
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl(""))
[<Fact>]
let ``rejects missing protocol`` () =
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("www.example.com"))
[<Fact>]
let ``rejects banned protocol`` () =
Assert.Equal(Result.Invalid, BrowserUtils.CheckUrl("file://example.com"))
module GetFileNameFromUrl =
[<Fact>]
let ``simple file URL returns file name`` () =
Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html"))
[<Fact>]
let ``file URL with query returns file name`` () =
Assert.Equal("index.html", BrowserUtils.GetFileNameFromUrl("http://example.com/index.html?version=2"))
[<Fact>]
let ``file URL w/o extension returns file name`` () =
Assert.Equal("index", BrowserUtils.GetFileNameFromUrl("http://example.com/index"))
[<Fact>]
let ``file URL with trailing dot returns file name with dot`` () =
Assert.Equal("index.", BrowserUtils.GetFileNameFromUrl("http://example.com/index."))
[<Fact>]
let ``root URL returns null`` () =
Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com"))
[<Fact>]
let ``path URL returns null`` () =
Assert.Null(BrowserUtils.GetFileNameFromUrl("http://example.com/path/"))

View File

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

View File

@ -0,0 +1,79 @@
namespace TweetTest.Core.UrlUtils
open Xunit
open TweetLib.Core.Utils
module Check =
type Result = UrlUtils.CheckResult
[<Fact>]
let ``accepts HTTP protocol`` () =
Assert.Equal(Result.Fine, UrlUtils.Check("http://example.com"))
[<Fact>]
let ``accepts HTTPS protocol`` () =
Assert.Equal(Result.Fine, UrlUtils.Check("https://example.com"))
[<Fact>]
let ``accepts FTP protocol`` () =
Assert.Equal(Result.Fine, UrlUtils.Check("ftp://example.com"))
[<Fact>]
let ``accepts MAILTO protocol`` () =
Assert.Equal(Result.Fine, UrlUtils.Check("mailto://someone@example.com"))
[<Fact>]
let ``accepts URL with port, path, query, and hash`` () =
Assert.Equal(Result.Fine, UrlUtils.Check("http://www.example.co.uk:80/path?key=abc&array[]=5#hash"))
[<Fact>]
let ``accepts IPv4 address`` () =
Assert.Equal(Result.Fine, UrlUtils.Check("http://127.0.0.1"))
[<Fact>]
let ``accepts IPv6 address`` () =
Assert.Equal(Result.Fine, UrlUtils.Check("http://[2001:db8:0:0:0:ff00:42:8329]"))
[<Fact>]
let ``recognizes t.co as tracking URL`` () =
Assert.Equal(Result.Tracking, UrlUtils.Check("http://t.co/12345"))
[<Fact>]
let ``rejects empty URL`` () =
Assert.Equal(Result.Invalid, UrlUtils.Check(""))
[<Fact>]
let ``rejects missing protocol`` () =
Assert.Equal(Result.Invalid, UrlUtils.Check("www.example.com"))
[<Fact>]
let ``rejects banned protocol`` () =
Assert.Equal(Result.Invalid, UrlUtils.Check("file://example.com"))
module GetFileNameFromUrl =
[<Fact>]
let ``simple file URL returns file name`` () =
Assert.Equal("index.html", UrlUtils.GetFileNameFromUrl("http://example.com/index.html"))
[<Fact>]
let ``file URL with query returns file name`` () =
Assert.Equal("index.html", UrlUtils.GetFileNameFromUrl("http://example.com/index.html?version=2"))
[<Fact>]
let ``file URL w/o extension returns file name`` () =
Assert.Equal("index", UrlUtils.GetFileNameFromUrl("http://example.com/index"))
[<Fact>]
let ``file URL with trailing dot returns file name with dot`` () =
Assert.Equal("index.", UrlUtils.GetFileNameFromUrl("http://example.com/index."))
[<Fact>]
let ``root URL returns null`` () =
Assert.Null(UrlUtils.GetFileNameFromUrl("http://example.com"))
[<Fact>]
let ``path URL returns null`` () =
Assert.Null(UrlUtils.GetFileNameFromUrl("http://example.com/path/"))

View File

@ -1,7 +1,7 @@
namespace TweetTest.Data.CommandLineArgs namespace TweetTest.Data.CommandLineArgs
open Xunit open Xunit
open TweetDuck.Data open TweetLib.Core.Collections
type _TestData = type _TestData =

View File

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

View File

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

View File

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

View File

@ -50,9 +50,9 @@
</Choose> </Choose>
<Import Project="$(FSharpTargetsPath)" /> <Import Project="$(FSharpTargetsPath)" />
<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" />