mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
04cd662d78 | |||
da597f076f | |||
fab3efdcf5 | |||
a55509a34d | |||
84fb1c5b2b | |||
391a90e1df | |||
e0fe39195d | |||
385fead81a | |||
648d1b9aa9 | |||
3f0028913d | |||
45e6ec8b0f | |||
a3fbaa0b34 | |||
7102cbfb3b | |||
cb61dc742f | |||
cd53f6e757 | |||
c64f7daa8d | |||
e70d792654 | |||
9ae533f907 | |||
cfe92f18e3 | |||
e2a34ea28e | |||
ec8000360e | |||
57b0821e19 | |||
09d39df15a | |||
1f9db3bda6 | |||
b7104c8828 | |||
5da02b4092 | |||
802f1e3042 | |||
66db0df45a | |||
650c2e2eb7 | |||
6ab3754129 | |||
7dca0a8cab | |||
7cd0b4ad54 | |||
97acb41eee | |||
b916b9726e | |||
32d3990ace | |||
cb1fd109cc | |||
0e68dd6185 | |||
fb6502bc65 | |||
c7e7403781 | |||
bf224408a3 | |||
84b7078873 | |||
89e5943d8f | |||
b78c4cb8f0 | |||
976ba074a8 | |||
5205d59b96 | |||
e8394b9c08 | |||
9cd00239f9 |
@@ -5,6 +5,10 @@ using System.Threading;
|
|||||||
|
|
||||||
namespace TweetDck.Configuration{
|
namespace TweetDck.Configuration{
|
||||||
sealed class LockManager{
|
sealed class LockManager{
|
||||||
|
public enum Result{
|
||||||
|
Success, HasProcess, Fail
|
||||||
|
}
|
||||||
|
|
||||||
public Process LockingProcess { get; private set; }
|
public Process LockingProcess { get; private set; }
|
||||||
|
|
||||||
private readonly string file;
|
private readonly string file;
|
||||||
@@ -14,87 +18,97 @@ namespace TweetDck.Configuration{
|
|||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CreateLockFile(){
|
private void CreateLockFileStream(){
|
||||||
|
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
|
WriteIntToStream(lockStream, GetCurrentProcessId());
|
||||||
|
lockStream.Flush(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReleaseLockFileStream(){
|
||||||
|
if (lockStream != null){
|
||||||
|
lockStream.Dispose();
|
||||||
|
lockStream = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result TryCreateLockFile(){
|
||||||
if (lockStream != null){
|
if (lockStream != null){
|
||||||
throw new InvalidOperationException("Lock file already exists.");
|
throw new InvalidOperationException("Lock file already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
lockStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
|
CreateLockFileStream();
|
||||||
|
return Result.Success;
|
||||||
byte[] id = BitConverter.GetBytes(Process.GetCurrentProcess().Id);
|
}catch(DirectoryNotFoundException){
|
||||||
lockStream.Write(id, 0, id.Length);
|
|
||||||
lockStream.Flush();
|
|
||||||
|
|
||||||
if (LockingProcess != null){
|
|
||||||
LockingProcess.Close();
|
|
||||||
LockingProcess = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}catch(Exception){
|
|
||||||
if (lockStream != null){
|
|
||||||
lockStream.Close();
|
|
||||||
lockStream.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Lock(){
|
|
||||||
if (lockStream != null)return true;
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
byte[] bytes = new byte[4];
|
CreateLockFileStream();
|
||||||
|
return Result.Success;
|
||||||
|
}catch{
|
||||||
|
ReleaseLockFileStream();
|
||||||
|
return Result.Fail;
|
||||||
|
}
|
||||||
|
}catch(IOException){
|
||||||
|
return Result.HasProcess;
|
||||||
|
}catch{
|
||||||
|
ReleaseLockFileStream();
|
||||||
|
return Result.Fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result Lock(){
|
||||||
|
if (lockStream != null){
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result initialResult = TryCreateLockFile();
|
||||||
|
|
||||||
|
if (initialResult == Result.HasProcess){
|
||||||
|
try{
|
||||||
|
int pid;
|
||||||
|
|
||||||
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
using(FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)){
|
||||||
fileStream.Read(bytes, 0, 4);
|
pid = ReadIntFromStream(fileStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
int pid = BitConverter.ToInt32(bytes, 0);
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
Process foundProcess = Process.GetProcessById(pid);
|
Process foundProcess = Process.GetProcessById(pid);
|
||||||
|
|
||||||
using(Process currentProcess = Process.GetCurrentProcess()){
|
using(Process currentProcess = Process.GetCurrentProcess()){
|
||||||
if (foundProcess.ProcessName == currentProcess.ProcessName || foundProcess.MainWindowTitle == Program.BrandName){
|
if (foundProcess.MainModule.FileVersionInfo.InternalName == currentProcess.MainModule.FileVersionInfo.InternalName){
|
||||||
LockingProcess = foundProcess;
|
LockingProcess = foundProcess;
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
foundProcess.Close();
|
||||||
}
|
}
|
||||||
}catch(ArgumentException){}
|
|
||||||
|
|
||||||
return LockingProcess == null && CreateLockFile();
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
string dir = Path.GetDirectoryName(file);
|
|
||||||
|
|
||||||
if (dir != null){
|
|
||||||
Directory.CreateDirectory(dir);
|
|
||||||
return CreateLockFile();
|
|
||||||
}
|
}
|
||||||
}catch(FileNotFoundException){
|
}catch{
|
||||||
return CreateLockFile();
|
// GetProcessById throws ArgumentException if the process is missing
|
||||||
}catch(Exception){
|
// Process.MainModule can throw exceptions in some cases
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return LockingProcess == null ? Result.Fail : Result.HasProcess;
|
||||||
|
}catch{
|
||||||
|
return Result.Fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Unlock(){
|
public bool Unlock(){
|
||||||
bool result = true;
|
bool result = true;
|
||||||
|
|
||||||
if (lockStream != null){
|
if (ReleaseLockFileStream()){
|
||||||
lockStream.Dispose();
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.Log(e.ToString());
|
Program.Reporter.Log(e.ToString());
|
||||||
result = false;
|
result = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
lockStream = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -104,11 +118,9 @@ namespace TweetDck.Configuration{
|
|||||||
if (LockingProcess != null){
|
if (LockingProcess != null){
|
||||||
LockingProcess.CloseMainWindow();
|
LockingProcess.CloseMainWindow();
|
||||||
|
|
||||||
for(int waited = 0; waited < timeout && !LockingProcess.HasExited;){
|
for(int waited = 0; waited < timeout && !LockingProcess.HasExited; waited += 250){
|
||||||
LockingProcess.Refresh();
|
LockingProcess.Refresh();
|
||||||
|
Thread.Sleep(250);
|
||||||
Thread.Sleep(100);
|
|
||||||
waited += 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LockingProcess.HasExited){
|
if (LockingProcess.HasExited){
|
||||||
@@ -120,5 +132,24 @@ namespace TweetDck.Configuration{
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
|
||||||
|
private static void WriteIntToStream(Stream stream, int value){
|
||||||
|
byte[] id = BitConverter.GetBytes(value);
|
||||||
|
stream.Write(id, 0, id.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReadIntFromStream(Stream stream){
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
stream.Read(bytes, 0, 4);
|
||||||
|
return BitConverter.ToInt32(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetCurrentProcessId(){
|
||||||
|
using(Process process = Process.GetCurrentProcess()){
|
||||||
|
return process.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
@@ -12,17 +11,12 @@ using TweetDck.Plugins;
|
|||||||
namespace TweetDck.Configuration{
|
namespace TweetDck.Configuration{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
sealed class UserConfig{
|
sealed class UserConfig{
|
||||||
private static readonly IFormatter Formatter = new BinaryFormatter{
|
private static readonly IFormatter Formatter = new BinaryFormatter();
|
||||||
Binder = new SerializationCompatibilityHandler()
|
|
||||||
};
|
|
||||||
|
|
||||||
private const int CurrentFileVersion = 5;
|
private const int CurrentFileVersion = 5;
|
||||||
|
|
||||||
// START OF CONFIGURATION
|
// START OF CONFIGURATION
|
||||||
|
|
||||||
public bool IgnoreMigration { get; set; }
|
|
||||||
public bool IgnoreUninstallCheck { get; set; }
|
|
||||||
|
|
||||||
public WindowState BrowserWindow { get; set; }
|
public WindowState BrowserWindow { get; set; }
|
||||||
public bool DisplayNotificationTimer { get; set; }
|
public bool DisplayNotificationTimer { get; set; }
|
||||||
public bool NotificationTimerCountDown { get; set; }
|
public bool NotificationTimerCountDown { get; set; }
|
||||||
@@ -235,13 +229,5 @@ namespace TweetDck.Configuration{
|
|||||||
public static string GetBackupFile(string file){
|
public static string GetBackupFile(string file){
|
||||||
return file+".bak";
|
return file+".bak";
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SerializationCompatibilityHandler : SerializationBinder{
|
|
||||||
public override Type BindToType(string assemblyName, string typeName){
|
|
||||||
assemblyName = assemblyName.Replace("TweetDick", "TweetDuck");
|
|
||||||
typeName = typeName.Replace("TweetDick", "TweetDck");
|
|
||||||
return Type.GetType(string.Format(CultureInfo.CurrentCulture, "{0}, {1}", typeName, assemblyName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,8 +8,10 @@ using TweetDck.Core.Other;
|
|||||||
using TweetDck.Resources;
|
using TweetDck.Resources;
|
||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using TweetDck.Core.Utils;
|
||||||
using TweetDck.Updates;
|
using TweetDck.Updates;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
namespace TweetDck.Core{
|
||||||
@@ -33,14 +35,14 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
private FormWindowState prevState;
|
private FormWindowState prevState;
|
||||||
|
|
||||||
public FormBrowser(PluginManager pluginManager){
|
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Text = Program.BrandName;
|
Text = Program.BrandName;
|
||||||
|
|
||||||
this.plugins = pluginManager;
|
this.plugins = pluginManager;
|
||||||
this.plugins.Reloaded += plugins_Reloaded;
|
this.plugins.Reloaded += plugins_Reloaded;
|
||||||
this.plugins.Config.PluginChangedState += plugins_PluginChangedState;
|
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||||
|
|
||||||
FormNotification notification = CreateNotificationForm(true);
|
FormNotification notification = CreateNotificationForm(true);
|
||||||
notification.CanMoveWindow = () => false;
|
notification.CanMoveWindow = () => false;
|
||||||
@@ -52,6 +54,10 @@ namespace TweetDck.Core{
|
|||||||
LifeSpanHandler = new LifeSpanHandler()
|
LifeSpanHandler = new LifeSpanHandler()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
this.browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||||
|
#endif
|
||||||
|
|
||||||
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||||
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
|
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||||
@@ -67,7 +73,7 @@ namespace TweetDck.Core{
|
|||||||
|
|
||||||
UpdateTrayIcon();
|
UpdateTrayIcon();
|
||||||
|
|
||||||
this.updates = new UpdateHandler(browser, this);
|
this.updates = new UpdateHandler(browser, this, updaterSettings);
|
||||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +82,7 @@ namespace TweetDck.Core{
|
|||||||
form.Shown += (sender, args) => form.MoveToCenter(this);
|
form.Shown += (sender, args) => form.MoveToCenter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ForceClose(){
|
public void ForceClose(){
|
||||||
trayIcon.Visible = false; // checked in FormClosing event
|
trayIcon.Visible = false; // checked in FormClosing event
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
@@ -113,7 +119,7 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain){
|
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||||
|
|
||||||
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
|
if (plugins.HasAnyPlugin(PluginEnvironment.Browser)){
|
||||||
@@ -297,11 +303,11 @@ namespace TweetDck.Core{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void OnImagePasted(){
|
public void OnImagePasted(){
|
||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImage", new object[0]);
|
browser.ExecuteScriptAsync("TDGF_tryPasteImage()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnImagePastedFinish(){
|
public void OnImagePastedFinish(){
|
||||||
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish", new object[0]);
|
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadBrowser(){
|
public void ReloadBrowser(){
|
||||||
|
@@ -9,6 +9,7 @@ using TweetDck.Core.Handling;
|
|||||||
using TweetDck.Resources;
|
using TweetDck.Resources;
|
||||||
using TweetDck.Core.Utils;
|
using TweetDck.Core.Utils;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Core{
|
namespace TweetDck.Core{
|
||||||
sealed partial class FormNotification : Form{
|
sealed partial class FormNotification : Form{
|
||||||
@@ -100,6 +101,10 @@ namespace TweetDck.Core{
|
|||||||
LifeSpanHandler = new LifeSpanHandler()
|
LifeSpanHandler = new LifeSpanHandler()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
browser.ConsoleMessage += BrowserUtils.HandleConsoleMessage;
|
||||||
|
#endif
|
||||||
|
|
||||||
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
|
browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged;
|
||||||
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||||
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
|
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
|
||||||
|
@@ -37,6 +37,11 @@ namespace TweetDck.Core.Handling{
|
|||||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||||
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
lastHighlightedQuotedTweet = TweetDeckBridge.LastHighlightedQuotedTweet;
|
||||||
|
|
||||||
|
if (!BrowserUtils.IsTweetDeckWebsite(frame)){
|
||||||
|
lastHighlightedTweet = string.Empty;
|
||||||
|
lastHighlightedQuotedTweet = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
if (!string.IsNullOrEmpty(lastHighlightedTweet) && (parameters.TypeFlags & (ContextMenuType.Editable | ContextMenuType.Selection)) == 0){
|
||||||
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
|
model.AddItem((CefMenuCommand)MenuOpenTweetUrl, "Open tweet in browser");
|
||||||
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
model.AddItem((CefMenuCommand)MenuCopyTweetUrl, "Copy tweet address");
|
||||||
|
4
Core/Other/FormMessage.Designer.cs
generated
4
Core/Other/FormMessage.Designer.cs
generated
@@ -42,8 +42,8 @@
|
|||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.labelMessage.AutoSize = true;
|
this.labelMessage.AutoSize = true;
|
||||||
this.labelMessage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
this.labelMessage.Font = System.Drawing.SystemFonts.MessageBoxFont;
|
||||||
this.labelMessage.Location = new System.Drawing.Point(62, 33);
|
this.labelMessage.Location = new System.Drawing.Point(62, 34);
|
||||||
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
|
this.labelMessage.Margin = new System.Windows.Forms.Padding(53, 24, 27, 24);
|
||||||
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
|
this.labelMessage.MaximumSize = new System.Drawing.Size(600, 0);
|
||||||
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
|
this.labelMessage.MinimumSize = new System.Drawing.Size(0, 24);
|
||||||
|
@@ -53,6 +53,7 @@ namespace TweetDck.Core.Other{
|
|||||||
public Button AddButton(string title){
|
public Button AddButton(string title){
|
||||||
Button button = new Button{
|
Button button = new Button{
|
||||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
|
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
|
||||||
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(Width-112-buttonCount*96, 12),
|
Location = new Point(Width-112-buttonCount*96, 12),
|
||||||
Size = new Size(88, 26),
|
Size = new Size(88, 26),
|
||||||
TabIndex = buttonCount,
|
TabIndex = buttonCount,
|
||||||
|
@@ -6,6 +6,7 @@ using System.Windows.Forms;
|
|||||||
using TweetDck.Core.Controls;
|
using TweetDck.Core.Controls;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
using TweetDck.Plugins.Controls;
|
using TweetDck.Plugins.Controls;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other{
|
namespace TweetDck.Core.Other{
|
||||||
|
@@ -4,7 +4,7 @@ using System.Text;
|
|||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings.Export{
|
namespace TweetDck.Core.Other.Settings.Export{
|
||||||
class CombinedFileStream : IDisposable{
|
class CombinedFileStream : IDisposable{
|
||||||
public const char KeySeparator = '/';
|
public const char KeySeparator = '|';
|
||||||
|
|
||||||
private readonly Stream stream;
|
private readonly Stream stream;
|
||||||
|
|
||||||
@@ -12,6 +12,10 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteFile(string[] identifier, string path){
|
||||||
|
WriteFile(string.Join(KeySeparator.ToString(), identifier), path);
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteFile(string identifier, string path){
|
public void WriteFile(string identifier, string path){
|
||||||
byte[] name = Encoding.UTF8.GetBytes(identifier);
|
byte[] name = Encoding.UTF8.GetBytes(identifier);
|
||||||
|
|
||||||
@@ -77,6 +81,13 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string[] KeyValue{
|
||||||
|
get{
|
||||||
|
int index = Identifier.IndexOf(KeySeparator);
|
||||||
|
return index == -1 ? new string[0] : Identifier.Substring(index+1).Split(KeySeparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly byte[] contents;
|
private readonly byte[] contents;
|
||||||
|
|
||||||
public Entry(string identifier, byte[] contents){
|
public Entry(string identifier, byte[] contents){
|
||||||
|
@@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using TweetDck.Plugins;
|
using TweetDck.Plugins;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Core.Other.Settings.Export{
|
namespace TweetDck.Core.Other.Settings.Export{
|
||||||
sealed class ExportManager{
|
sealed class ExportManager{
|
||||||
@@ -26,33 +27,14 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))){
|
||||||
stream.WriteFile("config", Program.ConfigFilePath);
|
stream.WriteFile("config", Program.ConfigFilePath);
|
||||||
|
|
||||||
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathOfficialPlugins)){
|
foreach(Plugin plugin in plugins.Plugins){
|
||||||
string[] split = path.Relative.Split(CombinedFileStream.KeySeparator);
|
foreach(PathInfo path in EnumerateFilesRelative(plugin.GetPluginFolder(PluginFolder.Data))){
|
||||||
|
|
||||||
if (split.Length < 3){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (split.Length == 3){
|
|
||||||
if (split[2].Equals(".meta", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
split[2].Equals("browser.js", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
split[2].Equals("notification.js", StringComparison.OrdinalIgnoreCase)){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
stream.WriteFile("plugin.off"+path.Relative, path.Full);
|
stream.WriteFile(new string[]{ "plugin.data", plugin.Identifier, path.Relative }, path.Full);
|
||||||
}catch(ArgumentOutOfRangeException e){
|
}catch(ArgumentOutOfRangeException e){
|
||||||
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show("Could not include a plugin file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(PathInfo path in EnumerateFilesRelative(plugins.PathCustomPlugins)){
|
|
||||||
try{
|
|
||||||
stream.WriteFile("plugin.usr"+path.Relative, path.Full);
|
|
||||||
}catch(ArgumentOutOfRangeException e){
|
|
||||||
MessageBox.Show("Could not include a file in the export. "+e.Message, "Export Profile", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeSession){
|
if (includeSession){
|
||||||
@@ -71,7 +53,7 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
|
|
||||||
public bool Import(){
|
public bool Import(){
|
||||||
try{
|
try{
|
||||||
bool updatedPlugins = false;
|
HashSet<string> missingPlugins = new HashSet<string>();
|
||||||
|
|
||||||
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
using(CombinedFileStream stream = new CombinedFileStream(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))){
|
||||||
CombinedFileStream.Entry entry;
|
CombinedFileStream.Entry entry;
|
||||||
@@ -83,28 +65,21 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
Program.ReloadConfig();
|
Program.ReloadConfig();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plugin.off":
|
case "plugin.data":
|
||||||
string root = Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Split(CombinedFileStream.KeySeparator)[1]);
|
string[] value = entry.KeyValue;
|
||||||
|
|
||||||
if (Directory.Exists(root)){
|
entry.WriteToFile(Path.Combine(Program.PluginDataPath, value[0], value[1]), true);
|
||||||
entry.WriteToFile(Path.Combine(plugins.PathOfficialPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
|
|
||||||
updatedPlugins = true;
|
if (!plugins.IsPluginInstalled(value[0])){
|
||||||
|
missingPlugins.Add(value[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "plugin.usr":
|
|
||||||
entry.WriteToFile(Path.Combine(plugins.PathCustomPlugins, entry.Identifier.Substring(entry.KeyName.Length+1)), true);
|
|
||||||
updatedPlugins = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "cookies":
|
case "cookies":
|
||||||
if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
if (MessageBox.Show("Do you want to import the login session? This will restart "+Program.BrandName+".", "Importing "+Program.BrandName+" Profile", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||||
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
entry.WriteToFile(Path.Combine(Program.StoragePath, TempCookiesPath));
|
||||||
|
|
||||||
// okay to and restart, 'cookies' is always the last entry
|
|
||||||
IsRestarting = true;
|
IsRestarting = true;
|
||||||
Program.Restart(new string[]{ "-importcookies" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -112,7 +87,14 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedPlugins){
|
if (missingPlugins.Count > 0){
|
||||||
|
MessageBox.Show("Detected missing plugins when importing plugin data:"+Environment.NewLine+string.Join(Environment.NewLine, missingPlugins), "Importing "+Program.BrandName+" Profile", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsRestarting){
|
||||||
|
Program.Restart(new string[]{ "-importcookies" });
|
||||||
|
}
|
||||||
|
else{
|
||||||
plugins.Reload();
|
plugins.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,10 +120,10 @@ namespace TweetDck.Core.Other.Settings.Export{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
private static IEnumerable<PathInfo> EnumerateFilesRelative(string root){
|
||||||
return Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
|
return Directory.Exists(root) ? Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).Select(fullPath => new PathInfo{
|
||||||
Full = fullPath,
|
Full = fullPath,
|
||||||
Relative = fullPath.Substring(root.Length).Replace(Path.DirectorySeparatorChar, CombinedFileStream.KeySeparator) // includes leading separator character
|
Relative = fullPath.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) // strip leading separator character
|
||||||
});
|
}) : Enumerable.Empty<PathInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PathInfo{
|
private class PathInfo{
|
||||||
|
@@ -4,6 +4,7 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using CefSharp;
|
||||||
|
|
||||||
namespace TweetDck.Core.Utils{
|
namespace TweetDck.Core.Utils{
|
||||||
static class BrowserUtils{
|
static class BrowserUtils{
|
||||||
@@ -27,7 +28,7 @@ namespace TweetDck.Core.Utils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenExternalBrowser(string url){ // TODO implement mailto
|
public static void OpenExternalBrowser(string url){ // TODO implement mailto
|
||||||
Process.Start(url);
|
using(Process.Start(url)){}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetFileNameFromUrl(string url){
|
public static string GetFileNameFromUrl(string url){
|
||||||
@@ -47,5 +48,15 @@ namespace TweetDck.Core.Utils{
|
|||||||
|
|
||||||
client.DownloadFileAsync(new Uri(url), target);
|
client.DownloadFileAsync(new Uri(url), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsTweetDeckWebsite(IFrame frame){
|
||||||
|
return frame.Url.Contains("//tweetdeck.twitter.com/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
public static void HandleConsoleMessage(object sender, ConsoleMessageEventArgs e){
|
||||||
|
Debug.WriteLine("[Console] {0} ({1}:{2})", e.Message, e.Source, e.Line);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -57,6 +57,10 @@ namespace TweetDck.Core.Utils{
|
|||||||
values[key.ToLowerInvariant()] = value;
|
values[key.ToLowerInvariant()] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasValue(string key){
|
||||||
|
return values.ContainsKey(key.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
public string GetValue(string key, string defaultValue){
|
public string GetValue(string key, string defaultValue){
|
||||||
string val;
|
string val;
|
||||||
return values.TryGetValue(key.ToLowerInvariant(), out val) ? val : defaultValue;
|
return values.TryGetValue(key.ToLowerInvariant(), out val) ? val : defaultValue;
|
||||||
|
@@ -1,33 +1,33 @@
|
|||||||
using System.IO;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.IO;
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Security.Principal;
|
|
||||||
|
|
||||||
namespace TweetDck.Core.Utils{
|
namespace TweetDck.Core.Utils{
|
||||||
static class WindowsUtils{
|
static class WindowsUtils{
|
||||||
public static bool CheckFolderPermission(string path, FileSystemRights right){
|
public static bool CheckFolderWritePermission(string path){
|
||||||
|
string testFile = Path.Combine(path, ".test");
|
||||||
|
|
||||||
try{
|
try{
|
||||||
AuthorizationRuleCollection rules = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(SecurityIdentifier));
|
Directory.CreateDirectory(path);
|
||||||
WindowsIdentity identity = WindowsIdentity.GetCurrent();
|
|
||||||
|
|
||||||
if (identity == null || identity.Groups == null){
|
using(File.Create(testFile)){}
|
||||||
|
File.Delete(testFile);
|
||||||
|
return true;
|
||||||
|
}catch{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool accessAllow = false, accessDeny = false;
|
|
||||||
|
|
||||||
foreach(FileSystemAccessRule rule in rules.Cast<FileSystemAccessRule>().Where(rule => identity.Groups.Contains(rule.IdentityReference) && (right & rule.FileSystemRights) == right)){
|
|
||||||
switch(rule.AccessControlType){
|
|
||||||
case AccessControlType.Allow: accessAllow = true; break;
|
|
||||||
case AccessControlType.Deny: accessDeny = true; break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessAllow && !accessDeny;
|
public static Process StartProcess(string file, string arguments, bool runElevated){
|
||||||
}
|
ProcessStartInfo processInfo = new ProcessStartInfo{
|
||||||
catch{
|
FileName = file,
|
||||||
return false;
|
Arguments = arguments
|
||||||
|
};
|
||||||
|
|
||||||
|
if (runElevated){
|
||||||
|
processInfo.Verb = "runas";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Process.Start(processInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
77
Migration/FormBackgroundWork.Designer.cs
generated
77
Migration/FormBackgroundWork.Designer.cs
generated
@@ -1,77 +0,0 @@
|
|||||||
using TweetDck.Core.Controls;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration {
|
|
||||||
partial class FormBackgroundWork {
|
|
||||||
/// <summary>
|
|
||||||
/// Required designer variable.
|
|
||||||
/// </summary>
|
|
||||||
private System.ComponentModel.IContainer components = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clean up any resources being used.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
|
||||||
protected override void Dispose(bool disposing) {
|
|
||||||
if (disposing && (components != null)) {
|
|
||||||
components.Dispose();
|
|
||||||
}
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Windows Form Designer generated code
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Required method for Designer support - do not modify
|
|
||||||
/// the contents of this method with the code editor.
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeComponent() {
|
|
||||||
this.progressBarUseless = new System.Windows.Forms.ProgressBar();
|
|
||||||
this.labelDescription = new System.Windows.Forms.Label();
|
|
||||||
this.SuspendLayout();
|
|
||||||
//
|
|
||||||
// progressBarUseless
|
|
||||||
//
|
|
||||||
this.progressBarUseless.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
|
||||||
this.progressBarUseless.Location = new System.Drawing.Point(15, 52);
|
|
||||||
this.progressBarUseless.MarqueeAnimationSpeed = 10;
|
|
||||||
this.progressBarUseless.Name = "progressBarUseless";
|
|
||||||
this.progressBarUseless.Size = new System.Drawing.Size(477, 23);
|
|
||||||
this.progressBarUseless.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
|
|
||||||
this.progressBarUseless.TabIndex = 0;
|
|
||||||
//
|
|
||||||
// labelDescription
|
|
||||||
//
|
|
||||||
this.labelDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
|
||||||
this.labelDescription.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
|
||||||
this.labelDescription.Location = new System.Drawing.Point(12, 12);
|
|
||||||
this.labelDescription.Name = "labelDescription";
|
|
||||||
this.labelDescription.Size = new System.Drawing.Size(480, 37);
|
|
||||||
this.labelDescription.TabIndex = 1;
|
|
||||||
this.labelDescription.Text = "Please, watch this informationless progress bar showcase while some magic happens" +
|
|
||||||
" in the background...";
|
|
||||||
//
|
|
||||||
// FormBackgroundWork
|
|
||||||
//
|
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
|
||||||
this.ClientSize = new System.Drawing.Size(504, 87);
|
|
||||||
this.Controls.Add(this.labelDescription);
|
|
||||||
this.Controls.Add(this.progressBarUseless);
|
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
|
||||||
this.Name = "FormBackgroundWork";
|
|
||||||
this.ShowIcon = false;
|
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
|
||||||
this.Text = "TweetDeck Migration";
|
|
||||||
this.ResumeLayout(false);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private System.Windows.Forms.ProgressBar progressBarUseless;
|
|
||||||
private System.Windows.Forms.Label labelDescription;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration{
|
|
||||||
partial class FormBackgroundWork : Form{
|
|
||||||
public FormBackgroundWork(){
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowWorkDialog(Action onBegin){
|
|
||||||
Shown += (sender, args) => onBegin();
|
|
||||||
ShowDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
namespace TweetDck.Migration{
|
|
||||||
enum MigrationDecision{
|
|
||||||
/// <summary>
|
|
||||||
/// Copies the important files and then deletes the TweetDeck folder.
|
|
||||||
/// </summary>
|
|
||||||
Migrate,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does exactly what <see cref="Migrate"/> does, but also silently uninstalls TweetDeck.
|
|
||||||
/// </summary>
|
|
||||||
MigratePurge,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does not copy any files and does not ask the user about data migration again.
|
|
||||||
/// </summary>
|
|
||||||
Ignore,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Does not copy any files but asks the user again when the program is re-ran.
|
|
||||||
/// </summary>
|
|
||||||
AskLater
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,204 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
using TweetDck.Core.Other;
|
|
||||||
using System.Drawing;
|
|
||||||
using TweetDck.Core.Controls;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration{
|
|
||||||
static class MigrationManager{
|
|
||||||
private static readonly string TweetDeckPathParent = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "twitter");
|
|
||||||
private static readonly string TweetDeckPath = Path.Combine(TweetDeckPathParent, "TweetDeck");
|
|
||||||
|
|
||||||
private static readonly string TweetDickStorage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "TweetDick");
|
|
||||||
|
|
||||||
public static void Run(){
|
|
||||||
if (!Program.IsPortable && Directory.Exists(TweetDickStorage) && !Directory.Exists(Program.StoragePath)){
|
|
||||||
if (MessageBox.Show("Welcome to TweetDuck! Would you like to move your old TweetDick configuration and login data?", "TweetDick Migration", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes){
|
|
||||||
try{
|
|
||||||
Directory.Move(TweetDickStorage, Program.StoragePath);
|
|
||||||
MessageBox.Show("All done! You can now uninstall TweetDick.", "TweetDick Migration", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
||||||
}catch(Exception ex){
|
|
||||||
Program.Reporter.HandleException("Migration Error", "An unexpected error occurred during the migration process.", true, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Program.UserConfig.IgnoreMigration && Directory.Exists(TweetDeckPath)){
|
|
||||||
MigrationDecision decision;
|
|
||||||
|
|
||||||
const string prompt = "Hey there, I found some TweetDeck data! Do you want to »Migrate« it and delete the old data folder, »Ignore« the prompt, or try "+Program.BrandName+" out first? You may also »Migrate && Purge« which uninstalls TweetDeck too!";
|
|
||||||
|
|
||||||
using(FormMessage formQuestion = new FormMessage("TweetDeck Migration", prompt, MessageBoxIcon.Question)){
|
|
||||||
formQuestion.AddButton("Ask Later");
|
|
||||||
Button btnIgnore = formQuestion.AddButton("Ignore");
|
|
||||||
Button btnMigrate = formQuestion.AddButton("Migrate");
|
|
||||||
Button btnMigrateAndPurge = formQuestion.AddButton("Migrate && Purge");
|
|
||||||
|
|
||||||
btnMigrateAndPurge.Location = new Point(btnMigrateAndPurge.Location.X-20, btnMigrateAndPurge.Location.Y);
|
|
||||||
btnMigrateAndPurge.Width += 20;
|
|
||||||
btnMigrateAndPurge.SetElevated();
|
|
||||||
|
|
||||||
if (formQuestion.ShowDialog() == DialogResult.OK){
|
|
||||||
decision = formQuestion.ClickedButton == btnMigrateAndPurge ? MigrationDecision.MigratePurge :
|
|
||||||
formQuestion.ClickedButton == btnMigrate ? MigrationDecision.Migrate :
|
|
||||||
formQuestion.ClickedButton == btnIgnore ? MigrationDecision.Ignore : MigrationDecision.AskLater;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
decision = MigrationDecision.AskLater;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(decision){
|
|
||||||
case MigrationDecision.MigratePurge:
|
|
||||||
case MigrationDecision.Migrate:
|
|
||||||
FormBackgroundWork formWait = new FormBackgroundWork();
|
|
||||||
|
|
||||||
formWait.ShowWorkDialog(() => {
|
|
||||||
if (!BeginMigration(decision, ex => formWait.Invoke(new Action(() => {
|
|
||||||
formWait.Close();
|
|
||||||
|
|
||||||
if (ex != null){
|
|
||||||
Program.Reporter.HandleException("Migration Error", "An unexpected error occurred during the migration process.", true, ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.UserConfig.IgnoreMigration = true;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
})))){
|
|
||||||
formWait.Close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MigrationDecision.Ignore:
|
|
||||||
Program.UserConfig.IgnoreMigration = true;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!Program.UserConfig.IgnoreUninstallCheck){
|
|
||||||
string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck");
|
|
||||||
|
|
||||||
if (guid != null){
|
|
||||||
const string prompt = "TweetDeck is still installed on your computer, do you want to uninstall it?";
|
|
||||||
|
|
||||||
using(FormMessage formQuestion = new FormMessage("Uninstall TweetDeck", prompt, MessageBoxIcon.Question)){
|
|
||||||
formQuestion.AddButton("No");
|
|
||||||
|
|
||||||
Button btnYes = formQuestion.AddButton("Yes");
|
|
||||||
btnYes.SetElevated();
|
|
||||||
|
|
||||||
if (formQuestion.ShowDialog() == DialogResult.OK && formQuestion.ClickedButton == btnYes && MigrationUtils.RunUninstaller(guid, 0)){
|
|
||||||
CleanupTweetDeck();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.UserConfig.IgnoreUninstallCheck = true;
|
|
||||||
Program.UserConfig.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool BeginMigration(MigrationDecision decision, Action<Exception> onFinished){
|
|
||||||
if (decision != MigrationDecision.MigratePurge && decision != MigrationDecision.Migrate){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task task = new Task(() => {
|
|
||||||
Directory.CreateDirectory(Program.StoragePath);
|
|
||||||
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "localStorage"));
|
|
||||||
Directory.CreateDirectory(Path.Combine(Program.StoragePath, "Local Storage"));
|
|
||||||
|
|
||||||
CopyFile("Cookies");
|
|
||||||
CopyFile("Cookies-journal");
|
|
||||||
CopyFile("localStorage"+Path.DirectorySeparatorChar+"qrc__0.localstorage");
|
|
||||||
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage");
|
|
||||||
CopyFile("Local Storage"+Path.DirectorySeparatorChar+"https_tweetdeck.twitter.com_0.localstorage-journal");
|
|
||||||
|
|
||||||
if (decision == MigrationDecision.Migrate || decision == MigrationDecision.MigratePurge){
|
|
||||||
// kill process if running
|
|
||||||
Process runningProcess = null;
|
|
||||||
|
|
||||||
try{
|
|
||||||
runningProcess = Process.GetProcessesByName("TweetDeck").FirstOrDefault(process => process.MainWindowHandle != IntPtr.Zero);
|
|
||||||
}catch(Exception){
|
|
||||||
// process not found
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runningProcess != null){
|
|
||||||
runningProcess.CloseMainWindow();
|
|
||||||
|
|
||||||
for(int wait = 0; wait < 100 && !runningProcess.HasExited; wait++){ // 10 seconds
|
|
||||||
runningProcess.Refresh();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
runningProcess.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete folders
|
|
||||||
for(int wait = 0; wait < 50; wait++){
|
|
||||||
try{
|
|
||||||
Directory.Delete(TweetDeckPath, true);
|
|
||||||
break;
|
|
||||||
}catch(Exception){
|
|
||||||
// browser subprocess not ended yet, wait
|
|
||||||
Thread.Sleep(300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
Directory.Delete(TweetDeckPathParent, false);
|
|
||||||
}catch(IOException){
|
|
||||||
// most likely not empty, ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decision == MigrationDecision.MigratePurge){
|
|
||||||
// uninstall in the background
|
|
||||||
string guid = MigrationUtils.FindProgramGuidByDisplayName("TweetDeck");
|
|
||||||
|
|
||||||
if (guid != null && !MigrationUtils.RunUninstaller(guid, 5000)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// registry cleanup
|
|
||||||
CleanupTweetDeck();
|
|
||||||
|
|
||||||
// migration finished like a boss
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
task.ContinueWith(originalTask => onFinished(originalTask.Exception), TaskContinuationOptions.ExecuteSynchronously);
|
|
||||||
task.Start();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CopyFile(string relativePath){
|
|
||||||
try{
|
|
||||||
File.Copy(Path.Combine(TweetDeckPath, relativePath), Path.Combine(Program.StoragePath, relativePath), true);
|
|
||||||
}catch(FileNotFoundException){
|
|
||||||
}catch(DirectoryNotFoundException){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CleanupTweetDeck(){
|
|
||||||
try{
|
|
||||||
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Twitter\TweetDeck", true);
|
|
||||||
Registry.CurrentUser.DeleteSubKey(@"Software\Twitter"); // only if empty
|
|
||||||
}catch(Exception){
|
|
||||||
// not found or too bad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,66 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
|
|
||||||
namespace TweetDck.Migration{
|
|
||||||
static class MigrationUtils{
|
|
||||||
public static bool RunUninstaller(string guid, int timeout){
|
|
||||||
try{
|
|
||||||
Process uninstaller = Process.Start(new ProcessStartInfo{
|
|
||||||
FileName = "msiexec.exe",
|
|
||||||
Arguments = "/x "+guid+" /quiet /qn",
|
|
||||||
Verb = "runas"
|
|
||||||
});
|
|
||||||
|
|
||||||
if (uninstaller != null){
|
|
||||||
if (timeout > 0){
|
|
||||||
uninstaller.WaitForExit(timeout); // it appears that the process is restarted or something that triggers this, but it shouldn't be a problem
|
|
||||||
}
|
|
||||||
|
|
||||||
uninstaller.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}catch(Exception){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string FindProgramGuidByDisplayName(string displayName){
|
|
||||||
Predicate<RegistryKey> predicate = key => displayName.Equals(key.GetValue("DisplayName") as string, StringComparison.OrdinalIgnoreCase);
|
|
||||||
string guid;
|
|
||||||
|
|
||||||
return FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
|
|
||||||
FindMatchingSubKey(Registry.LocalMachine, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid) ||
|
|
||||||
FindMatchingSubKey(Registry.CurrentUser, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", predicate, out guid)
|
|
||||||
? guid : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool FindMatchingSubKey(RegistryKey keyHandle, string path, Predicate<RegistryKey> predicate, out string guid){
|
|
||||||
string outputId = null;
|
|
||||||
|
|
||||||
try{
|
|
||||||
RegistryKey parentKey = keyHandle.OpenSubKey(path, false);
|
|
||||||
if (parentKey == null)throw new InvalidOperationException();
|
|
||||||
|
|
||||||
foreach(RegistryKey subKey in parentKey.GetSubKeyNames().Select(subName => parentKey.OpenSubKey(subName, false)).Where(subKey => subKey != null)){
|
|
||||||
if (predicate(subKey)){
|
|
||||||
outputId = subKey.Name.Substring(subKey.Name.LastIndexOf('\\')+1);
|
|
||||||
subKey.Close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
subKey.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
parentKey.Close();
|
|
||||||
}catch(Exception){
|
|
||||||
guid = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (guid = outputId) != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -50,7 +50,7 @@ namespace TweetDck.Plugins.Controls{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void btnOpenConfig_Click(object sender, EventArgs e){
|
private void btnOpenConfig_Click(object sender, EventArgs e){
|
||||||
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath+"\"")){}
|
using(Process.Start("explorer.exe", "/select,\""+plugin.ConfigPath.Replace('/', '\\')+"\"")){}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnToggleState_Click(object sender, EventArgs e){
|
private void btnToggleState_Click(object sender, EventArgs e){
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins.Enums{
|
||||||
[Flags]
|
[Flags]
|
||||||
enum PluginEnvironment{
|
enum PluginEnvironment{
|
||||||
None = 0,
|
None = 0,
|
5
Plugins/Enums/PluginFolder.cs
Normal file
5
Plugins/Enums/PluginFolder.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace TweetDck.Plugins.Enums{
|
||||||
|
enum PluginFolder{
|
||||||
|
Root, Data
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins.Enums{
|
||||||
enum PluginGroup{
|
enum PluginGroup{
|
||||||
Official, Custom
|
Official, Custom
|
||||||
}
|
}
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
class Plugin{
|
class Plugin{
|
||||||
@@ -26,29 +27,30 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
public bool HasConfig{
|
public bool HasConfig{
|
||||||
get{
|
get{
|
||||||
return ConfigFile.Length > 0 && GetFullPathIfSafe(ConfigFile).Length > 0;
|
return ConfigFile.Length > 0 && GetFullPathIfSafe(PluginFolder.Data, ConfigFile).Length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ConfigPath{
|
public string ConfigPath{
|
||||||
get{
|
get{
|
||||||
return HasConfig ? Path.Combine(path, ConfigFile) : string.Empty;
|
return HasConfig ? Path.Combine(GetPluginFolder(PluginFolder.Data), ConfigFile) : string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasDefaultConfig{
|
public bool HasDefaultConfig{
|
||||||
get{
|
get{
|
||||||
return ConfigDefault.Length > 0 && GetFullPathIfSafe(ConfigDefault).Length > 0;
|
return ConfigDefault.Length > 0 && GetFullPathIfSafe(PluginFolder.Root, ConfigDefault).Length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string DefaultConfigPath{
|
public string DefaultConfigPath{
|
||||||
get{
|
get{
|
||||||
return HasDefaultConfig ? Path.Combine(path, ConfigDefault) : string.Empty;
|
return HasDefaultConfig ? Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigDefault) : string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly string path;
|
private readonly string pathRoot;
|
||||||
|
private readonly string pathData;
|
||||||
private readonly string identifier;
|
private readonly string identifier;
|
||||||
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
|
private readonly Dictionary<string, string> metadata = new Dictionary<string, string>(4){
|
||||||
{ "NAME", "" },
|
{ "NAME", "" },
|
||||||
@@ -64,8 +66,13 @@ namespace TweetDck.Plugins{
|
|||||||
private bool? canRun;
|
private bool? canRun;
|
||||||
|
|
||||||
private Plugin(string path, PluginGroup group){
|
private Plugin(string path, PluginGroup group){
|
||||||
this.path = path;
|
string name = Path.GetFileName(path);
|
||||||
this.identifier = group.GetIdentifierPrefix()+Path.GetFileName(path);
|
System.Diagnostics.Debug.Assert(name != null);
|
||||||
|
|
||||||
|
this.pathRoot = path;
|
||||||
|
this.pathData = Path.Combine(Program.PluginDataPath, group.GetIdentifierPrefix(), name);
|
||||||
|
|
||||||
|
this.identifier = group.GetIdentifierPrefix()+name;
|
||||||
this.Group = group;
|
this.Group = group;
|
||||||
this.Environments = PluginEnvironment.None;
|
this.Environments = PluginEnvironment.None;
|
||||||
}
|
}
|
||||||
@@ -74,7 +81,26 @@ namespace TweetDck.Plugins{
|
|||||||
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
|
string configPath = ConfigPath, defaultConfigPath = DefaultConfigPath;
|
||||||
|
|
||||||
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
if (configPath.Length > 0 && defaultConfigPath.Length > 0 && !File.Exists(configPath) && File.Exists(defaultConfigPath)){
|
||||||
|
string dataFolder = GetPluginFolder(PluginFolder.Data);
|
||||||
|
|
||||||
|
if (!Directory.Exists(dataFolder)){ // config migration
|
||||||
|
string originalFile = Path.Combine(GetPluginFolder(PluginFolder.Root), ConfigFile);
|
||||||
|
|
||||||
|
if (File.Exists(originalFile)){
|
||||||
try{
|
try{
|
||||||
|
Directory.CreateDirectory(dataFolder);
|
||||||
|
File.Copy(originalFile, configPath, false);
|
||||||
|
File.Delete(originalFile); // will fail without write perms in program folder, ignore if so
|
||||||
|
}catch{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
Directory.CreateDirectory(dataFolder);
|
||||||
File.Copy(defaultConfigPath, configPath, false);
|
File.Copy(defaultConfigPath, configPath, false);
|
||||||
}catch(Exception e){
|
}catch(Exception e){
|
||||||
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e);
|
Program.Reporter.HandleException("Plugin Loading Error", "Could not generate a configuration file for '"+identifier+"' plugin.", true, e);
|
||||||
@@ -85,18 +111,27 @@ namespace TweetDck.Plugins{
|
|||||||
public string GetScriptPath(PluginEnvironment environment){
|
public string GetScriptPath(PluginEnvironment environment){
|
||||||
if (Environments.HasFlag(environment)){
|
if (Environments.HasFlag(environment)){
|
||||||
string file = environment.GetScriptFile();
|
string file = environment.GetScriptFile();
|
||||||
return file != null ? Path.Combine(path, file) : string.Empty;
|
return file != null ? Path.Combine(pathRoot, file) : string.Empty;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFullPathIfSafe(string relativePath){
|
public string GetPluginFolder(PluginFolder folder){
|
||||||
string fullPath = Path.Combine(path, relativePath);
|
switch(folder){
|
||||||
|
case PluginFolder.Root: return pathRoot;
|
||||||
|
case PluginFolder.Data: return pathData;
|
||||||
|
default: return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFullPathIfSafe(PluginFolder folder, string relativePath){
|
||||||
|
string rootFolder = GetPluginFolder(folder);
|
||||||
|
string fullPath = Path.Combine(rootFolder, relativePath);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
string folderPathName = new DirectoryInfo(path).FullName;
|
string folderPathName = new DirectoryInfo(rootFolder).FullName;
|
||||||
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
|
DirectoryInfo currentInfo = new DirectoryInfo(fullPath);
|
||||||
|
|
||||||
while(currentInfo.Parent != null){
|
while(currentInfo.Parent != null){
|
||||||
@@ -124,7 +159,7 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
public override bool Equals(object obj){
|
public override bool Equals(object obj){
|
||||||
Plugin plugin = obj as Plugin;
|
Plugin plugin = obj as Plugin;
|
||||||
return plugin != null && plugin.path.Equals(path);
|
return plugin != null && plugin.identifier.Equals(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
|
public static Plugin CreateFromFolder(string path, PluginGroup group, out string error){
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
@@ -18,35 +19,26 @@ namespace TweetDck.Plugins{
|
|||||||
fileCache.Clear();
|
fileCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFullPathIfSafe(int token, string path){
|
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||||
Plugin plugin = manager.GetPluginFromToken(token);
|
Plugin plugin = manager.GetPluginFromToken(token);
|
||||||
return plugin == null ? string.Empty : plugin.GetFullPathIfSafe(path);
|
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteFile(int token, string path, string contents){
|
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
|
||||||
|
|
||||||
if (fullPath == string.Empty){
|
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once AssignNullToNotNullAttribute
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
|
||||||
|
|
||||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
|
||||||
fileCache[fullPath] = contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReadFile(int token, string path, bool cache){
|
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
|
||||||
|
|
||||||
if (fullPath.Length == 0){
|
if (fullPath.Length == 0){
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
switch(folder){
|
||||||
|
case PluginFolder.Data: throw new Exception("File path has to be relative to the plugin data folder.");
|
||||||
|
case PluginFolder.Root: throw new Exception("File path has to be relative to the plugin root folder.");
|
||||||
|
default: throw new Exception("Invalid folder type "+folder+", this is a "+Program.BrandName+" error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string ReadFileUnsafe(string fullPath, bool readCached){
|
||||||
string cachedContents;
|
string cachedContents;
|
||||||
|
|
||||||
if (cache && fileCache.TryGetValue(fullPath, out cachedContents)){
|
if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
|
||||||
return cachedContents;
|
return cachedContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,25 +51,39 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFile(int token, string path){
|
// Public methods
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
|
||||||
|
|
||||||
if (fullPath.Length == 0){
|
public void WriteFile(int token, string path, string contents){
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
|
// ReSharper disable once AssignNullToNotNullAttribute
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
||||||
|
|
||||||
|
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||||
|
fileCache[fullPath] = contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ReadFile(int token, string path, bool cache){
|
||||||
|
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteFile(int token, string path){
|
||||||
|
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||||
|
|
||||||
fileCache.Remove(fullPath);
|
fileCache.Remove(fullPath);
|
||||||
File.Delete(fullPath);
|
File.Delete(fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckFileExists(int token, string path){
|
public bool CheckFileExists(int token, string path){
|
||||||
string fullPath = GetFullPathIfSafe(token, path);
|
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Data, path));
|
||||||
|
|
||||||
if (fullPath.Length == 0){
|
|
||||||
throw new Exception("File path has to be relative to the plugin folder.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return File.Exists(fullPath);
|
public string ReadFileRoot(int token, string path){
|
||||||
|
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckFileExistsRoot(int token, string path){
|
||||||
|
return File.Exists(GetFullPathOrThrow(token, PluginFolder.Root, path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ namespace TweetDck.Plugins{
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
sealed class PluginConfig{
|
sealed class PluginConfig{
|
||||||
[field:NonSerialized]
|
[field:NonSerialized]
|
||||||
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
public event EventHandler<PluginChangedStateEventArgs> InternalPluginChangedState; // should only be accessed from PluginManager
|
||||||
|
|
||||||
public IEnumerable<string> DisabledPlugins{
|
public IEnumerable<string> DisabledPlugins{
|
||||||
get{
|
get{
|
||||||
@@ -24,8 +24,8 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
public void SetEnabled(Plugin plugin, bool enabled){
|
public void SetEnabled(Plugin plugin, bool enabled){
|
||||||
if ((enabled && Disabled.Remove(plugin.Identifier)) || (!enabled && Disabled.Add(plugin.Identifier))){
|
if ((enabled && Disabled.Remove(plugin.Identifier)) || (!enabled && Disabled.Add(plugin.Identifier))){
|
||||||
if (PluginChangedState != null){
|
if (InternalPluginChangedState != null){
|
||||||
PluginChangedState(this, new PluginChangedStateEventArgs(plugin, enabled));
|
InternalPluginChangedState(this, new PluginChangedStateEventArgs(plugin, enabled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CefSharp;
|
using CefSharp;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
using TweetDck.Resources;
|
using TweetDck.Resources;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ namespace TweetDck.Plugins{
|
|||||||
public PluginBridge Bridge { get; private set; }
|
public PluginBridge Bridge { get; private set; }
|
||||||
|
|
||||||
public event EventHandler<PluginLoadEventArgs> Reloaded;
|
public event EventHandler<PluginLoadEventArgs> Reloaded;
|
||||||
|
public event EventHandler<PluginChangedStateEventArgs> PluginChangedState;
|
||||||
|
|
||||||
private readonly string rootPath;
|
private readonly string rootPath;
|
||||||
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
private readonly HashSet<Plugin> plugins = new HashSet<Plugin>();
|
||||||
@@ -30,10 +32,25 @@ namespace TweetDck.Plugins{
|
|||||||
|
|
||||||
public PluginManager(string path, PluginConfig config){
|
public PluginManager(string path, PluginConfig config){
|
||||||
this.rootPath = path;
|
this.rootPath = path;
|
||||||
this.Config = config;
|
this.SetConfig(config);
|
||||||
this.Bridge = new PluginBridge(this);
|
this.Bridge = new PluginBridge(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetConfig(PluginConfig config){
|
||||||
|
this.Config = config;
|
||||||
|
this.Config.InternalPluginChangedState += Config_InternalPluginChangedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Config_InternalPluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||||
|
if (PluginChangedState != null){
|
||||||
|
PluginChangedState(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPluginInstalled(string identifier){
|
||||||
|
return plugins.Any(plugin => plugin.Identifier.Equals(identifier));
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){
|
public IEnumerable<Plugin> GetPluginsByGroup(PluginGroup group){
|
||||||
return plugins.Where(plugin => plugin.Group == group);
|
return plugins.Where(plugin => plugin.Group == group);
|
||||||
}
|
}
|
||||||
@@ -103,6 +120,10 @@ namespace TweetDck.Plugins{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
private IEnumerable<Plugin> LoadPluginsFrom(string path, PluginGroup group){
|
||||||
|
if (!Directory.Exists(path)){
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
foreach(string fullDir in Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)){
|
||||||
string error;
|
string error;
|
||||||
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error);
|
Plugin plugin = Plugin.CreateFromFolder(fullDir, group, out error);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using TweetDck.Plugins.Enums;
|
||||||
|
|
||||||
namespace TweetDck.Plugins{
|
namespace TweetDck.Plugins{
|
||||||
static class PluginScriptGenerator{
|
static class PluginScriptGenerator{
|
||||||
|
98
Program.cs
98
Program.cs
@@ -5,7 +5,6 @@ using System.Windows.Forms;
|
|||||||
using CefSharp;
|
using CefSharp;
|
||||||
using TweetDck.Configuration;
|
using TweetDck.Configuration;
|
||||||
using TweetDck.Core;
|
using TweetDck.Core;
|
||||||
using TweetDck.Migration;
|
|
||||||
using TweetDck.Core.Utils;
|
using TweetDck.Core.Utils;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -13,7 +12,8 @@ using TweetDck.Plugins;
|
|||||||
using TweetDck.Plugins.Events;
|
using TweetDck.Plugins.Events;
|
||||||
using TweetDck.Core.Other.Settings.Export;
|
using TweetDck.Core.Other.Settings.Export;
|
||||||
using TweetDck.Core.Handling;
|
using TweetDck.Core.Handling;
|
||||||
using System.Security.AccessControl;
|
using TweetDck.Core.Other;
|
||||||
|
using TweetDck.Updates;
|
||||||
|
|
||||||
[assembly: CLSCompliant(true)]
|
[assembly: CLSCompliant(true)]
|
||||||
namespace TweetDck{
|
namespace TweetDck{
|
||||||
@@ -21,10 +21,8 @@ namespace TweetDck{
|
|||||||
public const string BrandName = "TweetDuck";
|
public const string BrandName = "TweetDuck";
|
||||||
public const string Website = "https://tweetduck.chylex.com";
|
public const string Website = "https://tweetduck.chylex.com";
|
||||||
|
|
||||||
public const string BrowserSubprocess = BrandName+".Browser.exe";
|
public const string VersionTag = "1.5.1";
|
||||||
|
public const string VersionFull = "1.5.1.0";
|
||||||
public const string VersionTag = "1.4.3";
|
|
||||||
public const string VersionFull = "1.4.3.0";
|
|
||||||
|
|
||||||
public static readonly Version Version = new Version(VersionTag);
|
public static readonly Version Version = new Version(VersionTag);
|
||||||
|
|
||||||
@@ -35,6 +33,7 @@ namespace TweetDck{
|
|||||||
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
|
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataStoragePath();
|
||||||
public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6));
|
public static readonly string TemporaryPath = IsPortable ? Path.Combine(ProgramPath, "portable", "tmp") : Path.Combine(Path.GetTempPath(), BrandName+'_'+Path.GetRandomFileName().Substring(0, 6));
|
||||||
|
|
||||||
|
public static readonly string PluginDataPath = Path.Combine(StoragePath, "TD_Plugins");
|
||||||
public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
public static readonly string ConfigFilePath = Path.Combine(StoragePath, "TD_UserConfig.cfg");
|
||||||
private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
|
private static readonly string LogFilePath = Path.Combine(StoragePath, "TD_Log.txt");
|
||||||
|
|
||||||
@@ -56,48 +55,55 @@ namespace TweetDck{
|
|||||||
|
|
||||||
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
WindowRestoreMessage = NativeMethods.RegisterWindowMessage("TweetDuckRestore");
|
||||||
|
|
||||||
if (!WindowsUtils.CheckFolderPermission(ProgramPath, FileSystemRights.WriteData)){
|
if (!WindowsUtils.CheckFolderWritePermission(StoragePath)){
|
||||||
MessageBox.Show(BrandName+" does not have write permissions to the program folder. If it is installed in Program Files, please run it as Administrator.", "Administrator Required", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show(BrandName+" does not have write permissions to the storage folder: "+StoragePath, "Permission Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reporter = new Reporter(LogFilePath);
|
Reporter = new Reporter(LogFilePath);
|
||||||
|
Reporter.SetupUnhandledExceptionHandler(BrandName+" Has Failed :(");
|
||||||
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
|
||||||
Exception ex = args.ExceptionObject as Exception;
|
|
||||||
|
|
||||||
if (ex != null){
|
|
||||||
Reporter.HandleException(BrandName+" Has Failed :(", "An unhandled exception has occurred.", false, ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Args.HasFlag("-restart")){
|
if (Args.HasFlag("-restart")){
|
||||||
for(int attempt = 0; attempt < 41; attempt++){
|
for(int attempt = 0; attempt < 21; attempt++){
|
||||||
if (LockManager.Lock()){
|
LockManager.Result lockResult = LockManager.Lock();
|
||||||
|
|
||||||
|
if (lockResult == LockManager.Result.Success){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (attempt == 40){
|
else if (lockResult == LockManager.Result.Fail){
|
||||||
MessageBox.Show(BrandName+" is taking too long to close, please wait and then start the application again manually.", BrandName+" Cannot Restart", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (attempt == 20){
|
||||||
|
using(FormMessage form = new FormMessage(BrandName+" Cannot Restart", BrandName+" is taking too long to close.", MessageBoxIcon.Warning)){
|
||||||
|
form.AddButton("Exit");
|
||||||
|
Button btnRetry = form.AddButton("Retry");
|
||||||
|
|
||||||
|
if (form.ShowDialog() == DialogResult.OK){
|
||||||
|
if (form.ClickedButton == btnRetry){
|
||||||
|
attempt /= 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
else{
|
else{
|
||||||
Thread.Sleep(500);
|
Thread.Sleep(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReloadConfig();
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
ReloadConfig();
|
LockManager.Result lockResult = LockManager.Lock();
|
||||||
MigrationManager.Run();
|
|
||||||
|
|
||||||
if (!LockManager.Lock()){
|
if (lockResult == LockManager.Result.HasProcess){
|
||||||
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray
|
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray
|
||||||
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero);
|
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
else if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||||
if (!LockManager.CloseLockingProcess(20000)){
|
if (!LockManager.CloseLockingProcess(30000)){
|
||||||
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -106,7 +112,13 @@ namespace TweetDck{
|
|||||||
}
|
}
|
||||||
else return;
|
else return;
|
||||||
}
|
}
|
||||||
|
else if (lockResult != LockManager.Result.Success){
|
||||||
|
MessageBox.Show("An unknown error occurred accessing the data folder. Please, make sure "+BrandName+" is not already running. If the problem persists, try restarting your system.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReloadConfig();
|
||||||
|
|
||||||
if (Args.HasFlag("-importcookies")){
|
if (Args.HasFlag("-importcookies")){
|
||||||
ExportManager.ImportCookies();
|
ExportManager.ImportCookies();
|
||||||
@@ -119,9 +131,9 @@ namespace TweetDck{
|
|||||||
UserAgent = BrowserUtils.HeaderUserAgent,
|
UserAgent = BrowserUtils.HeaderUserAgent,
|
||||||
Locale = Args.GetValue("-locale", "en"),
|
Locale = Args.GetValue("-locale", "en"),
|
||||||
CachePath = StoragePath,
|
CachePath = StoragePath,
|
||||||
BrowserSubprocessPath = File.Exists(BrowserSubprocess) ? BrowserSubprocess : "CefSharp.BrowserSubprocess.exe",
|
|
||||||
LogFile = Path.Combine(StoragePath, "TD_Console.txt"),
|
LogFile = Path.Combine(StoragePath, "TD_Console.txt"),
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
|
BrowserSubprocessPath = BrandName+".Browser.exe",
|
||||||
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
|
LogSeverity = Args.HasFlag("-log") ? LogSeverity.Info : LogSeverity.Disable
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
@@ -139,16 +151,22 @@ namespace TweetDck{
|
|||||||
|
|
||||||
PluginManager plugins = new PluginManager(PluginPath, UserConfig.Plugins);
|
PluginManager plugins = new PluginManager(PluginPath, UserConfig.Plugins);
|
||||||
plugins.Reloaded += plugins_Reloaded;
|
plugins.Reloaded += plugins_Reloaded;
|
||||||
plugins.Config.PluginChangedState += (sender, args) => UserConfig.Save();
|
plugins.PluginChangedState += (sender, args) => UserConfig.Save();
|
||||||
plugins.Reload();
|
plugins.Reload();
|
||||||
|
|
||||||
FormBrowser mainForm = new FormBrowser(plugins);
|
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
|
||||||
|
AllowPreReleases = Args.HasFlag("-debugupdates")
|
||||||
|
});
|
||||||
|
|
||||||
Application.Run(mainForm);
|
Application.Run(mainForm);
|
||||||
|
|
||||||
if (mainForm.UpdateInstallerPath != null){
|
if (mainForm.UpdateInstallerPath != null){
|
||||||
ExitCleanup();
|
ExitCleanup();
|
||||||
|
|
||||||
Process.Start(mainForm.UpdateInstallerPath, "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\""+(IsPortable ? " /PORTABLE=1" : "")); // ProgramPath has a trailing backslash
|
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+GetArgsClean().ToString().Replace("\"", "^\"")+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||||
|
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||||
|
|
||||||
|
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated); // ProgramPath has a trailing backslash
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,6 +175,8 @@ namespace TweetDck{
|
|||||||
if (!e.Success){
|
if (!e.Success){
|
||||||
MessageBox.Show("The following plugins will not be available until the issues are resolved:\n"+string.Join("\n", e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
MessageBox.Show("The following plugins will not be available until the issues are resolved:\n"+string.Join("\n", e.Errors), "Error Loading Plugins", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
((PluginManager)sender).SetConfig(UserConfig.Plugins);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetDataStoragePath(){
|
private static string GetDataStoragePath(){
|
||||||
@@ -193,17 +213,31 @@ namespace TweetDck{
|
|||||||
ReloadConfig();
|
ReloadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CommandLineArgs GetArgsClean(){
|
||||||
|
CommandLineArgs args = Args.Clone();
|
||||||
|
args.RemoveFlag("-importcookies");
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
public static void Restart(){
|
public static void Restart(){
|
||||||
Restart(new string[0]);
|
Restart(new string[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Restart(string[] extraArgs){
|
public static void Restart(string[] extraArgs){
|
||||||
CommandLineArgs args = Args.Clone();
|
FormBrowser browserForm = Application.OpenForms.OfType<FormBrowser>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (browserForm == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandLineArgs args = GetArgsClean();
|
||||||
args.AddFlag("-restart");
|
args.AddFlag("-restart");
|
||||||
args.RemoveFlag("-importcookies");
|
|
||||||
|
|
||||||
CommandLineArgs.ReadStringArray('-', extraArgs, args);
|
CommandLineArgs.ReadStringArray('-', extraArgs, args);
|
||||||
|
|
||||||
|
browserForm.ForceClose();
|
||||||
|
ExitCleanup();
|
||||||
|
|
||||||
Process.Start(Application.ExecutablePath, args.ToString());
|
Process.Start(Application.ExecutablePath, args.ToString());
|
||||||
Application.Exit();
|
Application.Exit();
|
||||||
}
|
}
|
||||||
|
18
Reporter.cs
18
Reporter.cs
@@ -15,6 +15,16 @@ namespace TweetDck{
|
|||||||
this.logFile = logFile;
|
this.logFile = logFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetupUnhandledExceptionHandler(string caption){
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (sender, args) => {
|
||||||
|
Exception ex = args.ExceptionObject as Exception;
|
||||||
|
|
||||||
|
if (ex != null){
|
||||||
|
HandleException(caption, "An unhandled exception has occurred.", false, ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public bool Log(string data){
|
public bool Log(string data){
|
||||||
StringBuilder build = new StringBuilder();
|
StringBuilder build = new StringBuilder();
|
||||||
|
|
||||||
@@ -34,7 +44,7 @@ namespace TweetDck{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
public void HandleException(string caption, string message, bool canIgnore, Exception e){
|
||||||
Log(e.ToString());
|
bool loggedSuccessfully = Log(e.ToString());
|
||||||
|
|
||||||
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
FormMessage form = new FormMessage(caption, message+"\r\nError: "+e.Message, canIgnore ? MessageBoxIcon.Warning : MessageBoxIcon.Error);
|
||||||
|
|
||||||
@@ -43,6 +53,8 @@ namespace TweetDck{
|
|||||||
|
|
||||||
Button btnOpenLog = new Button{
|
Button btnOpenLog = new Button{
|
||||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
|
||||||
|
Enabled = loggedSuccessfully,
|
||||||
|
Font = SystemFonts.MessageBoxFont,
|
||||||
Location = new Point(12, 12),
|
Location = new Point(12, 12),
|
||||||
Margin = new Padding(0, 0, 48, 0),
|
Margin = new Padding(0, 0, 48, 0),
|
||||||
Size = new Size(88, 26),
|
Size = new Size(88, 26),
|
||||||
@@ -50,7 +62,9 @@ namespace TweetDck{
|
|||||||
UseVisualStyleBackColor = true
|
UseVisualStyleBackColor = true
|
||||||
};
|
};
|
||||||
|
|
||||||
btnOpenLog.Click += (sender, args) => Process.Start(logFile);
|
btnOpenLog.Click += (sender, args) => {
|
||||||
|
using(Process.Start(logFile)){}
|
||||||
|
};
|
||||||
|
|
||||||
form.AddActionControl(btnOpenLog);
|
form.AddActionControl(btnOpenLog);
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ Custom reply account
|
|||||||
chylex
|
chylex
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
1.1
|
1.2
|
||||||
|
|
||||||
[website]
|
[website]
|
||||||
https://tweetduck.chylex.com
|
https://tweetduck.chylex.com
|
||||||
|
@@ -14,8 +14,25 @@ enabled(){
|
|||||||
|
|
||||||
if (configuration.useAdvancedSelector){
|
if (configuration.useAdvancedSelector){
|
||||||
if (configuration.customSelector){
|
if (configuration.customSelector){
|
||||||
var column = TD.controller.columnManager.get(data.element.closest("section.column").attr("data-column"));
|
if (configuration.customSelector.toString().startsWith("function (column){")){
|
||||||
query = configuration.customSelector(column);
|
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector needs to be updated due to TweetDeck changes, please read the default configuration file for the updated guide");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var section = data.element.closest("section.column");
|
||||||
|
|
||||||
|
var column = TD.controller.columnManager.get(section.attr("data-column"));
|
||||||
|
var header = $("h1.column-title", section);
|
||||||
|
|
||||||
|
var columnTitle = header.children(".column-head-title").text();
|
||||||
|
var columnAccount = header.children(".attribution").text();
|
||||||
|
|
||||||
|
try{
|
||||||
|
query = configuration.customSelector(column.getColumnType(), columnTitle, columnAccount, column);
|
||||||
|
}catch(e){
|
||||||
|
$TD.alert("warning", "Plugin reply-account has invalid configuration: customSelector threw an error: "+e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
$TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing");
|
$TD.alert("warning", "Plugin reply-account has invalid configuration: useAdvancedSelector is true, but customSelector function is missing");
|
||||||
|
@@ -34,42 +34,43 @@
|
|||||||
* The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
|
* The 'customSelector' function should return a string in one of the formats supported by 'defaultAccount'.
|
||||||
* If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
|
* If it returns anything else (for example, false or undefined), it falls back to 'defaultAccount' behavior.
|
||||||
*
|
*
|
||||||
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object, open your browser, nagivate to TweetDeck,
|
|
||||||
* log in, and run the following code in your browser console, which will return an object containing all of the column objects mapped to their IDs:
|
|
||||||
* TD.controller.columnManager.getAll()
|
|
||||||
*
|
|
||||||
* The example below shows how to extract the column type, title, and account from the object.
|
|
||||||
* Column type is prefixed with col_, and may be one of the following:
|
|
||||||
*
|
*
|
||||||
|
* The 'type' parameter is TweetDeck column type. Here is the full list of column types, note that some are
|
||||||
|
* unused and have misleading names (for example, Home columns are 'col_timeline' instead of 'col_home'):
|
||||||
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
|
* col_timeline, col_interactions, col_mentions, col_followers, col_search, col_list,
|
||||||
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
|
* col_customtimeline, col_messages, col_usertweets, col_favorites, col_activity,
|
||||||
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
|
* col_dataminr, col_home, col_me, col_inbox, col_scheduled, col_unknown
|
||||||
*
|
*
|
||||||
* Some of these appear to be unused (for example, Home columns are 'col_timeline' instead of 'col_home').
|
* If you want to see your current column types, run this in your browser console:
|
||||||
|
* TD.controller.columnManager.getAllOrdered().map(obj => obj.getColumnType());
|
||||||
*
|
*
|
||||||
* If you want to see your column types, run this in your browser console:
|
|
||||||
* Object.values(TD.controller.columnManager.getAll()).forEach(obj => console.log(obj.getColumnType()));
|
|
||||||
*
|
*
|
||||||
* You can also get the jQuery column object using: $("section.column[data-column='"+column.ui.state.columnKey+"']")
|
* The 'title' parameter is the column title. Some are fixed (such as 'Home' or 'Notifications'),
|
||||||
|
* some contain specific information (for example, Search columns contain the search query).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The 'account' parameter is the account name displayed next to the column title (including the @).
|
||||||
|
* This parameter is empty for some columns (such as Messages, or Notifications for all accounts) or can
|
||||||
|
* contain other text (for example, the Scheduled column contains the string 'All accounts').
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The 'column' parameter is a TweetDeck column object. If you want to see all properties of the object,
|
||||||
|
* run the following code in your browser console, which will return an array containing all of your
|
||||||
|
* current column objects in order:
|
||||||
|
* TD.controller.columnManager.getAllOrdered()
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
useAdvancedSelector: false,
|
useAdvancedSelector: false,
|
||||||
|
|
||||||
/*customSelector: function(column){
|
/*customSelector: function(type, title, account, column){
|
||||||
var titleObj = $(column.getTitleHTML());
|
if (type === "col_search" && title === "TweetDuck"){
|
||||||
|
|
||||||
var columnType = column.getColumnType(); // col_timeline
|
|
||||||
var columnTitle = titleObj.siblings(".column-head-title").text(); // Home
|
|
||||||
var columnAccount = titleObj.siblings(".attribution").text(); // @chylexmc
|
|
||||||
|
|
||||||
if (columnType === "col_search" && columnTitle === "TweetDuck"){
|
|
||||||
// This is a search column that looks for 'TweetDuck' in the tweets,
|
// This is a search column that looks for 'TweetDuck' in the tweets,
|
||||||
// search columns are normally linked to the preferred account
|
// search columns are normally linked to the preferred account
|
||||||
// so this forces the @TryTweetDuck account to be used instead.
|
// so this forces the @TryTweetDuck account to be used instead.
|
||||||
return "@TryTweetDuck";
|
return "@TryTweetDuck";
|
||||||
}
|
}
|
||||||
else if (columnType === "col_timeline" && columnAccount === "@chylexcz"){
|
else if (type === "col_timeline" && account === "@chylexcz"){
|
||||||
// This is a Home column of my test account @chylexcz,
|
// This is a Home column of my test account @chylexcz,
|
||||||
// but I want to reply to tweets from my official account.
|
// but I want to reply to tweets from my official account.
|
||||||
return "@chylexmc";
|
return "@chylexmc";
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
$TDP.checkFileExists(token, fileNameUser).then(exists => {
|
$TDP.checkFileExists(token, fileNameUser).then(exists => {
|
||||||
var fileName = exists ? fileNameUser : fileNameDefault;
|
var fileName = exists ? fileNameUser : fileNameDefault;
|
||||||
|
|
||||||
$TDP.readFile(token, fileName, true).then(contents => {
|
(exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
|
||||||
var obj;
|
var obj;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
@@ -12,7 +12,12 @@
|
|||||||
//
|
//
|
||||||
// Constant: Url that returns JSON data about latest version.
|
// Constant: Url that returns JSON data about latest version.
|
||||||
//
|
//
|
||||||
const updateCheckUrl = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases/latest";
|
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases/latest";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Constant: Url that returns JSON data about all versions, including prereleases.
|
||||||
|
//
|
||||||
|
const updateCheckUrlAll = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Function: Creates the update notification element. Removes the old one if already exists.
|
// Function: Creates the update notification element. Removes the old one if already exists.
|
||||||
@@ -135,18 +140,24 @@
|
|||||||
clearTimeout(updateCheckTimeoutID);
|
clearTimeout(updateCheckTimeoutID);
|
||||||
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour
|
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour
|
||||||
|
|
||||||
if (!$TDU.updateCheckEnabled && !force)return;
|
if (!$TDU.updateCheckEnabled && !force){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$.getJSON(updateCheckUrl, function(response){
|
var allowPre = $TDU.allowPreReleases;
|
||||||
var tagName = response.tag_name;
|
|
||||||
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && response.assets.length > 0;
|
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
|
||||||
|
var release = allowPre ? response[0] : response;
|
||||||
|
|
||||||
|
var tagName = release.tag_name;
|
||||||
|
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && release.assets.length > 0;
|
||||||
|
|
||||||
if (hasUpdate){
|
if (hasUpdate){
|
||||||
var obj = response.assets.find(asset => asset.name === updateFileName) || response.assets[0];
|
var obj = release.assets.find(asset => asset.name === updateFileName) || release.assets[0];
|
||||||
createUpdateNotificationElement(tagName, obj.browser_download_url);
|
createUpdateNotificationElement(tagName, obj.browser_download_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventID !== 0){
|
if (eventID){ // ignore undefined and 0
|
||||||
$TDU.onUpdateCheckFinished(eventID, hasUpdate, tagName);
|
$TDU.onUpdateCheckFinished(eventID, hasUpdate, tagName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -180,12 +180,6 @@
|
|||||||
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
|
<Compile Include="Core\Utils\CommandLineArgsParser.cs" />
|
||||||
<Compile Include="Core\Utils\WindowState.cs" />
|
<Compile Include="Core\Utils\WindowState.cs" />
|
||||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||||
<Compile Include="Migration\FormBackgroundWork.cs">
|
|
||||||
<SubType>Form</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Migration\FormBackgroundWork.Designer.cs">
|
|
||||||
<DependentUpon>FormBackgroundWork.cs</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Core\Handling\TweetDeckBridge.cs" />
|
<Compile Include="Core\Handling\TweetDeckBridge.cs" />
|
||||||
<Compile Include="Core\Other\FormSettings.cs">
|
<Compile Include="Core\Other\FormSettings.cs">
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
@@ -193,7 +187,6 @@
|
|||||||
<Compile Include="Core\Other\FormSettings.Designer.cs">
|
<Compile Include="Core\Other\FormSettings.Designer.cs">
|
||||||
<DependentUpon>FormSettings.cs</DependentUpon>
|
<DependentUpon>FormSettings.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Migration\MigrationUtils.cs" />
|
|
||||||
<Compile Include="Plugins\Controls\PluginControl.cs">
|
<Compile Include="Plugins\Controls\PluginControl.cs">
|
||||||
<SubType>UserControl</SubType>
|
<SubType>UserControl</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -206,12 +199,13 @@
|
|||||||
<Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs">
|
<Compile Include="Plugins\Controls\PluginListFlowLayout.Designer.cs">
|
||||||
<DependentUpon>PluginListFlowLayout.cs</DependentUpon>
|
<DependentUpon>PluginListFlowLayout.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Plugins\Enums\PluginFolder.cs" />
|
||||||
<Compile Include="Plugins\Plugin.cs" />
|
<Compile Include="Plugins\Plugin.cs" />
|
||||||
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
|
<Compile Include="Plugins\Events\PluginChangedStateEventArgs.cs" />
|
||||||
<Compile Include="Plugins\PluginBridge.cs" />
|
<Compile Include="Plugins\PluginBridge.cs" />
|
||||||
<Compile Include="Plugins\PluginConfig.cs" />
|
<Compile Include="Plugins\PluginConfig.cs" />
|
||||||
<Compile Include="Plugins\PluginEnvironment.cs" />
|
<Compile Include="Plugins\Enums\PluginEnvironment.cs" />
|
||||||
<Compile Include="Plugins\PluginGroup.cs" />
|
<Compile Include="Plugins\Enums\PluginGroup.cs" />
|
||||||
<Compile Include="Plugins\Events\PluginLoadEventArgs.cs" />
|
<Compile Include="Plugins\Events\PluginLoadEventArgs.cs" />
|
||||||
<Compile Include="Plugins\PluginManager.cs" />
|
<Compile Include="Plugins\PluginManager.cs" />
|
||||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||||
@@ -236,8 +230,6 @@
|
|||||||
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
|
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
|
||||||
<Compile Include="Updates\UpdateHandler.cs" />
|
<Compile Include="Updates\UpdateHandler.cs" />
|
||||||
<Compile Include="Updates\UpdateInfo.cs" />
|
<Compile Include="Updates\UpdateInfo.cs" />
|
||||||
<Compile Include="Migration\MigrationDecision.cs" />
|
|
||||||
<Compile Include="Migration\MigrationManager.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Properties\Resources.Designer.cs">
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
@@ -246,6 +238,7 @@
|
|||||||
<DependentUpon>Resources.resx</DependentUpon>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Resources\ScriptLoader.cs" />
|
<Compile Include="Resources\ScriptLoader.cs" />
|
||||||
|
<Compile Include="Updates\UpdaterSettings.cs" />
|
||||||
<None Include="Configuration\app.config" />
|
<None Include="Configuration\app.config" />
|
||||||
<None Include="Configuration\packages.config" />
|
<None Include="Configuration\packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -10,21 +10,24 @@ namespace TweetDck.Updates{
|
|||||||
class UpdateHandler{
|
class UpdateHandler{
|
||||||
private readonly ChromiumWebBrowser browser;
|
private readonly ChromiumWebBrowser browser;
|
||||||
private readonly FormBrowser form;
|
private readonly FormBrowser form;
|
||||||
|
private readonly UpdaterSettings settings;
|
||||||
|
|
||||||
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
||||||
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
||||||
|
|
||||||
private int lastEventId;
|
private int lastEventId;
|
||||||
|
|
||||||
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form){
|
public UpdateHandler(ChromiumWebBrowser browser, FormBrowser form, UpdaterSettings settings){
|
||||||
this.browser = browser;
|
this.browser = browser;
|
||||||
this.form = form;
|
this.form = form;
|
||||||
|
this.settings = settings;
|
||||||
|
|
||||||
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||||
browser.RegisterJsObject("$TDU", new Bridge(this));
|
browser.RegisterJsObject("$TDU", new Bridge(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||||
if (e.Frame.IsMain){
|
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||||
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,6 +74,12 @@ namespace TweetDck.Updates{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool AllowPreReleases{
|
||||||
|
get{
|
||||||
|
return owner.settings.AllowPreReleases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsSystemSupported{
|
public bool IsSystemSupported{
|
||||||
get{
|
get{
|
||||||
return Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
return Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
||||||
|
5
Updates/UpdaterSettings.cs
Normal file
5
Updates/UpdaterSettings.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace TweetDck.Updates{
|
||||||
|
class UpdaterSettings{
|
||||||
|
public bool AllowPreReleases { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -57,7 +57,6 @@ Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\Cache"
|
|||||||
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
var IsPortableInstallation: Boolean;
|
|
||||||
var UpdatePath: String;
|
var UpdatePath: String;
|
||||||
|
|
||||||
function TDGetNetFrameworkVersion: Cardinal; forward;
|
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||||
@@ -65,16 +64,8 @@ function TDGetNetFrameworkVersion: Cardinal; forward;
|
|||||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
|
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
|
||||||
function InitializeSetup: Boolean;
|
function InitializeSetup: Boolean;
|
||||||
begin
|
begin
|
||||||
IsPortableInstallation := ExpandConstant('{param:PORTABLEINSTALL}') = '1'
|
|
||||||
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
||||||
|
|
||||||
if IsPortableInstallation and (UpdatePath = '') then
|
|
||||||
begin
|
|
||||||
MsgBox('The /PORTABLEINSTALL flag requires the /UPDATEPATH parameter.', mbCriticalError, MB_OK);
|
|
||||||
Result := False;
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if TDGetNetFrameworkVersion() >= 379893 then
|
if TDGetNetFrameworkVersion() >= 379893 then
|
||||||
begin
|
begin
|
||||||
Result := True;
|
Result := True;
|
||||||
@@ -105,6 +96,18 @@ begin
|
|||||||
Result := (PageID = wpSelectDir) and (UpdatePath <> '')
|
Result := (PageID = wpSelectDir) and (UpdatePath <> '')
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ Check for an old TweetDeck profile and show a warning before installation. }
|
||||||
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
|
begin
|
||||||
|
if CurStep = ssInstall then
|
||||||
|
begin
|
||||||
|
if DirExists(ExpandConstant('{localappdata}\twitter\TweetDeck')) then
|
||||||
|
begin
|
||||||
|
MsgBox('Detected a profile from an old TweetDeck installation, you may uninstall the old client to free up some space.', mbInformation, MB_OK)
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
{ Ask user if they want to delete 'AppData\TweetDuck' and 'plugins' folders after uninstallation. }
|
{ Ask user if they want to delete 'AppData\TweetDuck' and 'plugins' folders after uninstallation. }
|
||||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||||
var ProfileDataFolder: String;
|
var ProfileDataFolder: String;
|
||||||
@@ -125,25 +128,10 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Create a 'makeportable' file if running in portable mode. }
|
{ Returns true if the installer should create uninstallation entries (i.e. not running in full update mode). }
|
||||||
procedure CurStepChanged(CurStep: TSetupStep);
|
|
||||||
begin
|
|
||||||
if (CurStep = ssPostInstall) and IsPortableInstallation then
|
|
||||||
begin
|
|
||||||
while not SaveStringToFile(ExpandConstant('{app}\makeportable'), '', False) do
|
|
||||||
begin
|
|
||||||
if MsgBox('Could not create a ''makeportable'' file in the installation folder. If the file is not present, the installation will not be fully portable.', mbCriticalError, MB_RETRYCANCEL) <> IDRETRY then
|
|
||||||
begin
|
|
||||||
break;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ Returns true if the installer should create uninstallation entries (i.e. not running in portable or full update mode). }
|
|
||||||
function TDIsUninstallable: Boolean;
|
function TDIsUninstallable: Boolean;
|
||||||
begin
|
begin
|
||||||
Result := (UpdatePath = '') and not IsPortableInstallation
|
Result := (UpdatePath = '')
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Return DWORD value containing the build version of .NET Framework. }
|
{ Return DWORD value containing the build version of .NET Framework. }
|
||||||
|
@@ -25,32 +25,28 @@ LicenseFile=.\Resources\LICENSE
|
|||||||
SetupIconFile=.\Resources\icon.ico
|
SetupIconFile=.\Resources\icon.ico
|
||||||
Uninstallable=no
|
Uninstallable=no
|
||||||
UsePreviousAppDir=no
|
UsePreviousAppDir=no
|
||||||
|
PrivilegesRequired=lowest
|
||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
InternalCompressLevel=max
|
InternalCompressLevel=max
|
||||||
MinVersion=0,6.1
|
MinVersion=0,6.1
|
||||||
|
|
||||||
#include <idp.iss>
|
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll"
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
|
||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
function TDGetNetFrameworkVersion: Cardinal; forward;
|
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||||
function TDGetAppVersionClean: String; forward;
|
|
||||||
procedure TDExecuteFullDownload; forward;
|
|
||||||
|
|
||||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2, and prepare full download package. }
|
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.5.2. }
|
||||||
function InitializeSetup: Boolean;
|
function InitializeSetup: Boolean;
|
||||||
begin
|
begin
|
||||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/{#MyAppName}.exe', ExpandConstant('{tmp}\{#MyAppName}.Full.exe'));
|
|
||||||
|
|
||||||
if TDGetNetFrameworkVersion() >= 379893 then
|
if TDGetNetFrameworkVersion() >= 379893 then
|
||||||
begin
|
begin
|
||||||
Result := True;
|
Result := True;
|
||||||
@@ -66,21 +62,6 @@ begin
|
|||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Prepare download plugin if there are any files to download, and set the installation path. }
|
|
||||||
procedure InitializeWizard();
|
|
||||||
begin
|
|
||||||
idpDownloadAfter(wpReady);
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ Remove uninstallation data and application to force them to be replaced with updated ones. }
|
|
||||||
procedure CurStepChanged(CurStep: TSetupStep);
|
|
||||||
begin
|
|
||||||
if CurStep = ssInstall then
|
|
||||||
begin
|
|
||||||
TDExecuteFullDownload();
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ Return DWORD value containing the build version of .NET Framework. }
|
{ Return DWORD value containing the build version of .NET Framework. }
|
||||||
function TDGetNetFrameworkVersion: Cardinal;
|
function TDGetNetFrameworkVersion: Cardinal;
|
||||||
var FrameworkVersion: Cardinal;
|
var FrameworkVersion: Cardinal;
|
||||||
@@ -95,56 +76,17 @@ begin
|
|||||||
Result := 0;
|
Result := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Return a cleaned up form of the app version string (removes all .0 suffixes). }
|
{ Create a 'makeportable' file if running in portable mode. }
|
||||||
function TDGetAppVersionClean: String;
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
var Substr: String;
|
|
||||||
var CleanVersion: String;
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
CleanVersion := '{#MyAppVersion}'
|
if CurStep = ssPostInstall then
|
||||||
|
|
||||||
while True do
|
|
||||||
begin
|
begin
|
||||||
Substr := Copy(CleanVersion, Length(CleanVersion)-1, 2);
|
while not SaveStringToFile(ExpandConstant('{app}\makeportable'), '', False) do
|
||||||
|
begin
|
||||||
if (CompareStr(Substr, '.0') <> 0) then
|
if MsgBox('Could not create a ''makeportable'' file in the installation folder. If the file is not present, the installation will not be fully portable.', mbCriticalError, MB_RETRYCANCEL) <> IDRETRY then
|
||||||
begin
|
begin
|
||||||
break;
|
break;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
CleanVersion := Copy(CleanVersion, 1, Length(CleanVersion)-2);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := CleanVersion;
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ Run the full package installer if downloaded. }
|
|
||||||
procedure TDExecuteFullDownload;
|
|
||||||
var InstallFile: String;
|
|
||||||
var ResultCode: Integer;
|
|
||||||
|
|
||||||
begin
|
|
||||||
InstallFile := ExpandConstant('{tmp}\{#MyAppName}.Full.exe')
|
|
||||||
WizardForm.ProgressGauge.Style := npbstMarquee;
|
|
||||||
|
|
||||||
try
|
|
||||||
if Exec(InstallFile, '/SP- /SILENT /MERGETASKS="!desktopicon" /UPDATEPATH="'+ExpandConstant('{app}\')+'" /PORTABLEINSTALL=1', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
|
|
||||||
if ResultCode <> 0 then
|
|
||||||
begin
|
|
||||||
DeleteFile(InstallFile);
|
|
||||||
Abort();
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
end else
|
|
||||||
begin
|
|
||||||
MsgBox('Could not run the full installer in portable mode. Error: '+SysErrorMessage(ResultCode), mbCriticalError, MB_OK);
|
|
||||||
|
|
||||||
DeleteFile(InstallFile);
|
|
||||||
Abort();
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
finally
|
|
||||||
WizardForm.ProgressGauge.Style := npbstNormal;
|
|
||||||
DeleteFile(InstallFile);
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
@@ -28,6 +28,7 @@ SetupIconFile=.\Resources\icon.ico
|
|||||||
Uninstallable=TDIsUninstallable
|
Uninstallable=TDIsUninstallable
|
||||||
UninstallDisplayName={#MyAppName}
|
UninstallDisplayName={#MyAppName}
|
||||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
|
PrivilegesRequired=lowest
|
||||||
Compression=lzma
|
Compression=lzma
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
InternalCompressLevel=max
|
InternalCompressLevel=max
|
||||||
@@ -43,10 +44,10 @@ Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignorever
|
|||||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.dll,*.pak,*.bin,*.dat"
|
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.dll,*.pak,*.bin,*.dat"
|
||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Parameters: "{param:RUNARGS}"; Flags: nowait postinstall shellexec
|
||||||
|
|
||||||
[InstallDelete]
|
[InstallDelete]
|
||||||
Type: files; Name: "{app}\*.xml"
|
Type: files; Name: "{app}\*.xml"
|
||||||
@@ -241,7 +242,8 @@ begin
|
|||||||
WizardForm.ProgressGauge.Style := npbstMarquee;
|
WizardForm.ProgressGauge.Style := npbstMarquee;
|
||||||
|
|
||||||
try
|
try
|
||||||
if Exec(InstallFile, '/SP- /SILENT /MERGETASKS="!desktopicon" /UPDATEPATH="'+UpdatePath+'"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
|
if Exec(InstallFile, '/SP- /SILENT /MERGETASKS="!desktopicon" /UPDATEPATH="'+UpdatePath+'"', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
|
||||||
|
begin
|
||||||
if ResultCode <> 0 then
|
if ResultCode <> 0 then
|
||||||
begin
|
begin
|
||||||
DeleteFile(InstallFile);
|
DeleteFile(InstallFile);
|
||||||
|
Reference in New Issue
Block a user