diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a771a56..114f0e17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ Changelog ========= +v1.1.17 +------- + + - Don't store Syncthing's API key in config, and don't log it + - Fix filesystem notifications when the file contained non-ASCII characters (#400) + - Don't show device connected/disconnected notifications if a device is reconnecting a lot + - Don't watch / raise notifications about new folders if no existing folders are watched / have notifications (#393) + - Don't write to the disk as much by default (#370) + - Fix crash on the settings screen + - Be more reslient to weird registry permissions, fixing crash (#378) + - Fix crash when calculating data transfer stats (#380) + - Be more reslient when trying to find a free port for Syncthing to use (#381) + - Add installer command-line flags (for system administrators) (#371, #402) + - Add an exit poll to the uninstaller + v1.1.16 ------- diff --git a/README.md b/README.md index 8907c226..395c7368 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ Features include: - Syncthing on its own has to poll your folders, in order to see if any files have changed. - SyncTrayzor will watch your folders for changes, and alert Syncthing the second anything changes. - This means you can increase the polling interval in Syncthing, avoiding the resource usage of high-frequency polling, but still have any changes propagated straight away. - - Folder watching respects the ignores configured in Syncthing. - Has a tool to help you resolve file conflicts - Can pause devices on metered networks, to stop Syncthing transferring data on e.g. a mobile connection or wifi hotspot. - Contains translations for many languages @@ -203,5 +202,19 @@ Building from Source -------------------- You'll need [Visual Studio 2017](https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx). +Make sure you install "Blend for Visual Studio SDK for .NET". Clone/download the repository, open `src\SyncTrayzor.sln`, and compile. You'll also need to [download syncthing.exe](https://github.com/syncthing/syncthing/releases) and place it in the `bin\x86\Debug`, `bin\x64\Debug`, `bin\x86\Release`, or `bin\x64\Release` folder as appropriate. + + +Notes for System Administrators +------------------------------- + +The installer is built using Inno Setup, and has various command-line options, [documented here](http://www.jrsoftware.org/ishelp/index.php?topic=setupcmdline). +If you pass the `/silent` command-line flag when SyncTrayzor won't be launched when the installer completes: add `/StartSyncTrayzor` to override this (which also causes SyncTrayzor to be launched minimized). + +There are various parameters inside the file `SyncTrayzor.exe.config` which can be customised by system administrators, including the default SyncTrayzor configuration (used to create the user's SyncTrayzor config file when SyncTrayzor is first launched). +To override these, pass the flag `/SyncTrayzorExeConfig="Path\To\Customized\SyncTrayzor.exe.config"` to the installer -- the specified `SyncTrayzor.exe.config` will overwrite the default. + +Note that the contents / structure of `SyncTrayzor.exe.config` may change between releases. +Using the wrong version may cause a crash, or incorrect behaviour. \ No newline at end of file diff --git a/installer/common.iss b/installer/common.iss index 322a9a0c..df49fbee 100644 --- a/installer/common.iss +++ b/installer/common.iss @@ -10,12 +10,14 @@ #define AppDataFolder "SyncTrayzor" #define RunRegKey "Software\Microsoft\Windows\CurrentVersion\Run" #define DotNetInstallerExe "dotNet451Setup.exe" -#define DonateUrl = "https://synctrayzor.antonymale.co.uk/donate" +#define DonateUrl "https://synctrayzor.antonymale.co.uk/donate" +#define SurveyUrl "https://synctrayzor.antonymale.co.uk/survey.php" [Setup] AppId={{#AppId} AppName={#AppName} ({#Arch}) AppVersion={#AppVersion} +VersionInfoVersion={#AppVersion} ;AppVerName={#AppName} {#AppVersion} AppPublisher={#AppPublisher} AppPublisherURL={#AppURL} @@ -29,8 +31,8 @@ OutputDir="." OutputBaseFilename={#AppName}Setup-{#Arch} SetupIconFile={#AppSrc}\Icons\default.ico WizardSmallImageFile=..\icon.bmp -;Compression=lzma2/max -Compression=None +Compression=lzma2/max +;Compression=None SolidCompression=yes PrivilegesRequired=admin CloseApplications=yes @@ -79,11 +81,13 @@ Name: "{group}\{cm:UninstallProgram,{#AppName}}"; Filename: "{uninstallexe}" Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon [Run] -Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent +Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall; Parameters: {code:SyncTrayzorStartFlags}; Check: ShouldStartSyncTrayzor [Code] var GlobalRestartRequired: boolean; + UninstallPollPage: TNewNotebookPage; + UninstallNextButton: TNewButton; function DotNetIsMissing(): Boolean; var @@ -187,6 +191,7 @@ var FindRec: TFindRec; FolderPath: String; FilePath: String; + ExeConfig: String; begin if CurStep = ssInstall then begin @@ -219,6 +224,26 @@ begin end; end; end + else if CurStep = ssPostInstall then + begin + ExeConfig := ExpandConstant('{param:SyncTrayzorExeConfig}'); + if ExeConfig <> '' then + begin + if FileExists(ExeConfig) then + begin + FileCopy(ExeConfig, ExpandConstant('{app}\SyncTrayzor.exe.config'), false); + end + else + begin + MsgBox('Could not find SyncTrayzorExeConfig file: ' + ExeConfig + '. Using default.', mbError, MB_OK); + end + end + end +end; + +procedure CurPageChanged(CurPageID: Integer); +begin + end; function PrepareToInstall(var NeedsRestart: Boolean): String; @@ -238,10 +263,193 @@ begin Result := GlobalRestartRequired; end; +function ShouldStartSyncTrayzor(): Boolean; +var + flagPassed: Boolean; + i: Integer; +begin + // Can't use {param}, as it doesn't match flags with no value + flagPassed := False; + for i := 0 to ParamCount do begin + if ParamStr(i) = '/StartSyncTrayzor' then begin + flagPassed := True; + break; + end; + end; + Result := (not WizardSilent()) or flagPassed; +end; + +function SyncTrayzorStartFlags(param: String): String; +begin + if WizardSilent() then begin + Result := '-minimized' + end else begin + Result := '' + end; +end; + +function SerializeBool(value: Boolean): String; +begin + if value then begin + Result := 'true'; + end else begin + Result := 'false'; + end +end; + +function EscapeJsonString(value: String): String; +var + c: Char; + i: Integer; +begin + // http://www.jrsoftware.org/ishelp/index.php?topic=isxfunc_charlength + i := 1; + while i <= Length(value) do + begin + c := value[i]; + if c = #10 then begin + Result := Result + '\n'; + end else if c = #13 then begin + Result := Result + '\r'; + end else if c = #9 then begin + Result := Result + '\t'; + end else if Ord(c) < 32 then begin + Result := Result + Format('\x%.4x', [Ord(c)]); + end else if c = '"' then begin + Result := Result + '\"'; + end else if c = '\' then begin + Result := Result + '\\' + end else begin + Result := Result + c; + end; + i := i + CharLength(value, i); + end; +end; + +// See https://stackoverflow.com/a/42550055/1086121 + +procedure UpdateUninstallWizard; +begin + if UninstallProgressForm.InnerNotebook.ActivePage = UninstallPollPage then + begin + UninstallProgressForm.PageNameLabel.Caption := 'Please Tell Us Why You''re Leaving'; + UninstallProgressForm.PageDescriptionLabel.Caption := ''; + end; + + UninstallNextButton.Caption := 'Uninstall'; + // Make the "Uninstall" button break the ShowModal loop + UninstallNextButton.ModalResult := mrOK; +end; + +procedure InitializeUninstallProgressForm(); +var + PageText: TNewStaticText; + PageNameLabel: string; + PageDescriptionLabel: string; + CancelButtonEnabled: Boolean; + CancelButtonModalResult: Integer; + Checklist: TNewCheckListBox; + CommentsText: TNewStaticText; + CommentsBox: TNewMemo; + WinHttpReq: Variant; +begin + if not UninstallSilent then + begin + // Create the poll page and make it active + UninstallPollPage := TNewNotebookPage.Create(UninstallProgressForm); + UninstallPollPage.Notebook := UninstallProgressForm.InnerNotebook; + UninstallPollPage.Parent := UninstallProgressForm.InnerNotebook; + UninstallPollPage.Align := alClient; + + PageText := TNewStaticText.Create(UninstallProgressForm); + PageText.Parent := UninstallPollPage; + PageText.AutoSize := True; + PageText.WordWrap := True; + PageText.SetBounds(UninstallProgressForm.StatusLabel.Left, UninstallProgressForm.StatusLabel.Top, UninstallProgressForm.StatusLabel.Width, UninstallProgressForm.StatusLabel.Height); + PageText.ShowAccelChar := False; + PageText.Caption := 'Sorry you''re leaving! Please tell us what you didn''t like so we can improve it.' + #13#10 + + 'No personal data will be sent. You can skip this step if you want.'; + + Checklist := TNewCheckListBox.Create(UninstallProgressForm); + Checklist.Parent := UninstallPollPage; + Checklist.SetBounds(PageText.Left, PageText.Top + PageText.Height + ScaleY(10), PageText.Width, ScaleY(20) * 5); + Checklist.BorderStyle := bsNone; + Checklist.Color := clBtnFace; + Checklist.WantTabs := True; + Checklist.MinItemHeight := ScaleY(20); + + Checklist.AddCheckBox('I couldn''t get Syncthing to work', '', 0, False, True, False, False, nil); + Checklist.AddCheckBox('Syncthing doesn''t do what I need', '', 0, False, True, False, False, nil); + Checklist.AddCheckBox('I prefer another sync tool (please say which below)', '', 0, False, True, False, False, nil); + Checklist.AddCheckBox('I don''t like SyncTrayzor - I''m going to use another wrapper', '', 0, False, True, False, False, nil); + Checklist.AddCheckBox('Other (please expand below)', '', 0, False, True, False, False, nil); + + CommentsText := TNewStaticText.Create(UninstallProgressForm); + CommentsText.Parent := UninstallPollPage; + CommentsText.AutoSize := True; + CommentsText.WordWrap := True; + CommentsText.SetBounds(PageText.Left, Checklist.Top + Checklist.Height + ScaleY(10), PageText.Width, ScaleY(15)); + CommentsText.AutoSize := False; + CommentsText.ShowAccelChar := False; + CommentsText.Caption := 'More Details / Complaints:'; + + CommentsBox := TNewMemo.Create(UninstallProgressForm); + CommentsBox.Parent := UninstallPollPage; + CommentsBox.SetBounds(PageText.Left, CommentsText.Top + CommentsText.Height + ScaleY(5), PageText.Width, ScaleY(60)); + CommentsBox.ScrollBars := ssVertical; + + UninstallProgressForm.InnerNotebook.ActivePage := UninstallPollPage; + + PageNameLabel := UninstallProgressForm.PageNameLabel.Caption; + PageDescriptionLabel := UninstallProgressForm.PageDescriptionLabel.Caption; + + UninstallNextButton := TNewButton.Create(UninstallProgressForm); + UninstallNextButton.Parent := UninstallProgressForm; + UninstallNextButton.Left := UninstallProgressForm.CancelButton.Left - UninstallProgressForm.CancelButton.Width - ScaleX(10); + UninstallNextButton.Top := UninstallProgressForm.CancelButton.Top; + UninstallNextButton.Width := UninstallProgressForm.CancelButton.Width; + UninstallNextButton.Height := UninstallProgressForm.CancelButton.Height; + + UninstallProgressForm.CancelButton.TabOrder := UninstallNextButton.TabOrder + 1; + + // Run our wizard pages + UpdateUninstallWizard; + CancelButtonEnabled := UninstallProgressForm.CancelButton.Enabled + UninstallProgressForm.CancelButton.Enabled := True; + CancelButtonModalResult := UninstallProgressForm.CancelButton.ModalResult; + UninstallProgressForm.CancelButton.ModalResult := mrCancel; + + if UninstallProgressForm.ShowModal = mrCancel then Abort; + + if Checklist.Checked[0] or Checklist.Checked[1] or Checklist.Checked[2] or Checklist.Checked[3] or Checklist.Checked[4] or (CommentsBox.Text <> '') then begin + WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1'); + WinHttpReq.Open('POST', '{#SurveyUrl}', false); + WinHttpReq.Send('{' + + ' "version": "{#AppVersion}"' + + ', "comment": "' + EscapeJsonString(CommentsBox.Text) + '"' + + ', "checklist": {' + + ' "wontWork": '+ SerializeBool(Checklist.Checked[0]) + + ', "notWhatINeed": '+ SerializeBool(Checklist.Checked[1]) + + ', "preferAnotherSyncTool": '+ SerializeBool(Checklist.Checked[2]) + + ', "dontLikeSyncTrayzor": '+ SerializeBool(Checklist.Checked[3]) + + ', "other": '+ SerializeBool(Checklist.Checked[4]) + + ' }' + + ' }'); + end; + + // Restore the standard page payout + UninstallProgressForm.CancelButton.Enabled := CancelButtonEnabled; + UninstallProgressForm.CancelButton.ModalResult := CancelButtonModalResult; + + UninstallProgressForm.PageNameLabel.Caption := PageNameLabel; + UninstallProgressForm.PageDescriptionLabel.Caption := PageDescriptionLabel; + + UninstallProgressForm.InnerNotebook.ActivePage := UninstallProgressForm.InstallingPage; + end; +end; + [UninstallDelete] Type: files; Name: "{app}\ProcessRunner.exe.old" Type: files; Name: "{app}\InstallCount.txt" Type: filesandordirs; Name: "{userappdata}\{#AppDataFolder}" -Type: filesandordirs; Name: "{localappdata}\{#AppDataFolder}" - - +Type: filesandordirs; Name: "{localappdata}\{#AppDataFolder}" \ No newline at end of file diff --git a/server/survey.php b/server/survey.php new file mode 100644 index 00000000..4eb43637 --- /dev/null +++ b/server/survey.php @@ -0,0 +1,63 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->exec('PRAGMA foreign_keys = ON;'); + + if (!$exists) + { + $db->exec("CREATE TABLE IF NOT EXISTS responses ( + id INTEGER PRIMARY KEY, + date TEXT NOT NULL, + version TEXT NOT NULL, + ip TEXT NOT NULL, + comment TEXT + );"); + $db->exec("CREATE TABLE IF NOT EXISTS checklist ( + id INTEGER PRIMARY KEY, + response_id INTEGER NOT NULL REFERENCES responses(id), + key TEXT NOT NULL + );"); + } + + $data = json_decode(file_get_contents('php://input'), true); + $stmt = $db->prepare("INSERT INTO responses(date, ip, version, comment) VALUES (CURRENT_TIMESTAMP, :ip, :version, :comment);"); + $stmt->execute(array( + 'ip' => $_SERVER['REMOTE_ADDR'], + 'version' => $data['version'], + 'comment' => $data['comment'])); + $responseId = $db->lastInsertId(); + + $stmt = $db->prepare("INSERT INTO CHECKLIST (response_id, key) VALUES (:response_id, :key);"); + + foreach ($data['checklist'] as $key => $value) + { + if ($value) + { + $stmt->execute(array('response_id' => $responseId, 'key' => $key)); + } + } +} +catch (Exception $e) +{ + $loggable_error = $e->getMessage() . "\n" . $e->getTraceAsString(); + file_put_contents("survey_errors.txt", $loggable_error, FILE_APPEND); +} \ No newline at end of file diff --git a/server/version_check.php b/server/version_check.php index 0588f132..f7b84cca 100644 --- a/server/version_check.php +++ b/server/version_check.php @@ -65,7 +65,7 @@ function get_with_wildcard($src, $value, $default = null) } $versions = [ - '1.1.16' => [ + '1.1.17' => [ 'installed' => [ 'direct_download_url' => [ 'x64' => 'https://github.com/canton7/SyncTrayzor/releases/download/v{version}/SyncTrayzorSetup-x64.exe', @@ -81,11 +81,12 @@ function get_with_wildcard($src, $value, $default = null) 'sha1sum_download_url' => 'https://github.com/canton7/SyncTrayzor/releases/download/v{version}/sha1sum.txt.asc', 'sha512sum_download_url' => 'https://github.com/canton7/SyncTrayzor/releases/download/v{version}/sha512sum.txt.asc', 'release_page_url' => 'https://github.com/canton7/SyncTrayzor/releases/tag/v{version}', - 'release_notes' => "- Fix some crashes on startup\n- Fix bug where 'show logs' link on the crash screen would cause another crash\n- Reduce how often SyncTrayzor checks for updates", + 'release_notes' => "- Don't store Syncthing's API key in config, and don't log it\n- Fix filesystem notifications when the file contained non-ASCII characters\n- Don't show device connected/disconnected notifications if a device is reconnecting a lot\n- Don't watch / raise notifications about new folders if no existing folders are watched / have notifications\n- Don't write to the disk as much by default\n- Fix crash on the settings screen\n- Be more reslient to weird registry permissions, fixing crash\n- Fix crash when calculating data transfer stats\n- Be more reslient when trying to find a free port for Syncthing to use", ] ]; $upgrades = [ + '1.1.16' => ['to' => 'latest', 'formatter' => '5'], '1.1.15' => ['to' => 'latest', 'formatter' => '5'], '1.1.14' => ['to' => 'latest', 'formatter' => '5'], '1.1.13' => ['to' => 'latest', 'formatter' => '5'], diff --git a/src/SyncTrayzor/Bootstrapper.cs b/src/SyncTrayzor/Bootstrapper.cs index ff304303..f9634da2 100644 --- a/src/SyncTrayzor/Bootstrapper.cs +++ b/src/SyncTrayzor/Bootstrapper.cs @@ -100,7 +100,7 @@ protected override void Configure() var logger = LogManager.GetCurrentClassLogger(); var assembly = this.Container.Get(); - logger.Debug("SyncTrazor version {0} ({1}) started at {2} (.NET version: {3})", assembly.FullVersion, assembly.ProcessorArchitecture, assembly.Location, DotNetVersionFinder.FindDotNetVersion()); + logger.Info("SyncTrazor version {0} ({1}) started at {2} (.NET version: {3})", assembly.FullVersion, assembly.ProcessorArchitecture, assembly.Location, DotNetVersionFinder.FindDotNetVersion()); // This needs to happen before anything which might cause the unhandled exception stuff to be shown, as that wants to know // where to find the log file. diff --git a/src/SyncTrayzor/NotifyIcon/ConnectedEventDebouncer.cs b/src/SyncTrayzor/NotifyIcon/ConnectedEventDebouncer.cs index 18c160b2..f0cced10 100644 --- a/src/SyncTrayzor/NotifyIcon/ConnectedEventDebouncer.cs +++ b/src/SyncTrayzor/NotifyIcon/ConnectedEventDebouncer.cs @@ -16,7 +16,7 @@ public interface IConnectedEventDebouncer public class ConnectedEventDebouncer : IConnectedEventDebouncer { - private static readonly TimeSpan debounceTime = TimeSpan.FromSeconds(1); + private static readonly TimeSpan debounceTime = TimeSpan.FromSeconds(10); private readonly object syncRoot = new object(); diff --git a/src/SyncTrayzor/Pages/Settings/SettingsView.xaml b/src/SyncTrayzor/Pages/Settings/SettingsView.xaml index 5b2d0e12..e030b711 100644 --- a/src/SyncTrayzor/Pages/Settings/SettingsView.xaml +++ b/src/SyncTrayzor/Pages/Settings/SettingsView.xaml @@ -213,11 +213,17 @@ -