1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-09-14 19:32:10 +02:00

Compare commits

...

47 Commits
1.4.3 ... 1.5.1

Author SHA1 Message Date
04cd662d78 Release 1.5.1 2016-11-23 05:03:49 +01:00
da597f076f Fix quote escaping in updater arguments 2016-11-23 04:57:13 +01:00
fab3efdcf5 Fix update checker running outside of TweetDeck website 2016-11-23 04:55:58 +01:00
a55509a34d Add an old TweetDeck profile detection & warning message to the full installer 2016-11-23 03:51:58 +01:00
84fb1c5b2b Make update installer use TweetDuck's initial command line arguments 2016-11-23 03:20:38 +01:00
391a90e1df Add a -debugupdates command line argument to allow prereleases in update checker 2016-11-23 02:08:33 +01:00
e0fe39195d Add HasValue method to CommandLineArgs 2016-11-23 02:06:41 +01:00
385fead81a Fix updater calling onUpdateCheckFinished when eventID parameter is undefined 2016-11-23 02:05:44 +01:00
648d1b9aa9 Rewrite lock system to be more reliable and handle exceptions better 2016-11-19 05:57:55 +01:00
3f0028913d Move unhandled exception handler from Program to Reporter class 2016-11-19 03:11:37 +01:00
45e6ec8b0f Fix FormMessage fonts 2016-11-18 20:28:00 +01:00
a3fbaa0b34 Make program restarts as reliable as possible
Closes #80
2016-11-18 19:59:21 +01:00
7102cbfb3b Add a retry button to the warning message when TweetDuck takes too long to restart 2016-11-18 19:35:02 +01:00
cb61dc742f Push minor tweak in ExecuteScriptAsync in image pasting code 2016-11-16 18:29:35 +01:00
cd53f6e757 Disable 'Show Error Log' button if the logging failed 2016-11-16 14:51:47 +01:00
c64f7daa8d Cleanup browser subprocess path code 2016-11-16 14:25:52 +01:00
e70d792654 Fix plugin status not updating from new config after importing profile
Closes #79
2016-11-16 04:10:17 +01:00
9ae533f907 Remove TweetDick config file compatibility 2016-11-15 19:26:23 +01:00
cfe92f18e3 Remove all TweetDeck and other migration code 2016-11-15 19:20:43 +01:00
e2a34ea28e Remove original CheckFolderPermission and replace it with the lazy workaround 2016-11-15 18:10:25 +01:00
ec8000360e Windows file permissions can go to hell 2016-11-15 01:01:41 +01:00
57b0821e19 Revert "Rewrite folder write permission check to hopefully make it more reliable"
This reverts commit 1f9db3bda6.
2016-11-15 00:47:15 +01:00
09d39df15a Release 1.5 2016-11-15 00:19:24 +01:00
1f9db3bda6 Rewrite folder write permission check to hopefully make it more reliable 2016-11-14 23:32:45 +01:00
b7104c8828 Remove privilege requirement from update & portable installer, handle updater privileges within TweetDuck 2016-11-14 20:53:56 +01:00
5da02b4092 Make the portable installer fully autonomous 2016-11-14 20:52:11 +01:00
802f1e3042 Refactor Process.Start uses (missing using statement, use WindowsUtils for elevation) 2016-11-14 19:39:26 +01:00
66db0df45a Add WindowsUtils.StartProcess for easier elevated process starting 2016-11-14 19:38:36 +01:00
650c2e2eb7 Remove redundant null check from WindowsUtils 2016-11-14 18:54:58 +01:00
6ab3754129 Update write permission check to use the storage folder 2016-11-14 14:06:15 +01:00
7dca0a8cab Add plugin config migration to the new data folder 2016-11-14 14:01:22 +01:00
7cd0b4ad54 Fix highlighted tweets staying in context menu after logging out of TweetDeck 2016-11-14 10:35:58 +01:00
97acb41eee Fix console errors caused by running browser scripts even outside of TweetDeck website 2016-11-14 10:35:42 +01:00
b916b9726e Add a method to check if a frame has a TweetDeck URL to BrowserUtils 2016-11-14 10:34:52 +01:00
32d3990ace Rewrite plugin data export and combined file stream identifiers, add missing plugin warning 2016-11-14 10:15:21 +01:00
cb1fd109cc Prevent missing plugin folders from causing a crash 2016-11-14 10:12:22 +01:00
0e68dd6185 Fix Configure button in PluginControl causing issues with mixed slash types 2016-11-14 09:47:56 +01:00
fb6502bc65 Rename plugin data folder to TD_Plugins for consistency 2016-11-14 09:39:48 +01:00
c7e7403781 Update plugin config to use the data folder instead of plugin root 2016-11-14 06:14:38 +01:00
bf224408a3 Rewrite PluginBridge to accommodate the functions to the new plugin folder handling 2016-11-14 06:14:14 +01:00
84b7078873 Assign a data folder to each plugin and add new folder handling functions 2016-11-14 06:09:05 +01:00
89e5943d8f Add a PluginFolder enum and a plugin data root path to Program 2016-11-14 05:43:30 +01:00
b78c4cb8f0 Move PluginEnvironment and PluginGroup to a separate Enums package 2016-11-14 05:08:18 +01:00
976ba074a8 Add a warning for outdated config in reply-account plugin 2016-11-13 15:18:10 +01:00
5205d59b96 Rewrite custom selector in reply-account plugin to fix and futureproof TweetDeck changes 2016-11-13 15:06:51 +01:00
e8394b9c08 Add browser console logging to debug output 2016-11-13 13:45:10 +01:00
9cd00239f9 Fix update installer creating Start Menu entry in portable mode (applied to 1.4.3) 2016-10-23 00:48:23 +02:00
40 changed files with 510 additions and 768 deletions

View File

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

View File

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

View File

@@ -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(){

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
namespace TweetDck.Plugins{ namespace TweetDck.Plugins.Enums{
enum PluginGroup{ enum PluginGroup{
Official, Custom Official, Custom
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
namespace TweetDck.Updates{
class UpdaterSettings{
public bool AllowPreReleases { get; set; }
}
}

View File

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

View File

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

View File

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