├── .gitignore ├── LICENSE ├── README.md ├── RoboBackup.sln ├── RoboBackupGUI ├── AboutForm.Designer.cs ├── AboutForm.cs ├── DirectoryTree.cs ├── IpcClient.cs ├── Lang.cs ├── MainForm.Designer.cs ├── MainForm.cs ├── OpenFolderDialog.cs ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── RoboBackup.ico ├── RoboBackupGUI.csproj ├── SettingsForm.Designer.cs ├── SettingsForm.cs ├── TaskForm.Designer.cs ├── TaskForm.cs └── lang │ ├── cs.txt │ └── en.txt ├── RoboBackupService ├── Config.cs ├── Credential.cs ├── IpcServer.cs ├── IpcService.cs ├── LogCleaner.cs ├── Logger.cs ├── Program.cs ├── ProjectInstaller.Designer.cs ├── ProjectInstaller.cs ├── Properties │ └── AssemblyInfo.cs ├── RoboBackupService.csproj ├── Service.Designer.cs ├── Service.cs ├── SysUtils.cs ├── Task.cs └── Unc.cs └── Setup └── Setup.vdproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | *RoboBackup* is a Windows service for orchestrating file backups via `robocopy` utility. The GUI aims to provide user-friendly interface for the backup tasks management without the need to understand the `robocopy` utility itself. The service runs with *Local System* privileges, allowing it to work independently on the GUI user or on any interactive session. The service manages scheduling, logging and retention of the backups. 4 | 5 | The goal of *RoboBackup* is not to be robust, secure and enterprise-ready, but to be small, simple and lower the difficulty of creating and scheduling `robocopy` commands. 6 | 7 | ## Usage 8 | 9 | Backups can be created in 3 modes: 10 | 11 | - **Incremental** means that there is a single destination directory. When the backup runs, files which are new or modified in the source directory are copied to the destination directory. Files which were deleted from the source directory are kept (i.e. not deleted) in the destination directory. 12 | - **Differential** creates timestamped subdirectories in `yyyy-MM-dd_HH-mm-ss` format under the destination directory for each backup run. Files are hardlinked from the subdirectory of the previous backup (if there is one) and then only the differences between the source directory and the previous backup are synchronized. This feature speeds up the whole backup process and reduces required filesystem space. In linux world, similar effect can be achieved using `rsync` tool with `--link-dest` parameter. 13 | - **Full** also creates timestamped subdirectories under the destination directory for each backup run, but the files are always fully copied from the source directory with no regard to the previous backups. 14 | 15 | Backup retention is set as a number of timestamped directories with previous backups. When the retention limit is exceeded, excessive subdirectories with the oldest timestamps are deleted. With retention set to 1, differential and full methods behave the same. They do not create timestamped directories and only differentially synchronize the contents of the source directory to the destination directory. 16 | 17 | **Note about network shares:** By default, Windows map drives only for interactive user sessions and open the mapping only when the drive is first used. This means that the mapped drives are not visible for the service account. GUI automatically resolves the mapped drives to UNC network paths, which *are* reachable for the service, but still requires you to enter the network credentials, so the service can open the resource to successfully run the backup. Both source and destination can be entered as UNC network paths and the service figures out when to use the credentials automatically, however they should not be network shares *both at the same time*. 18 | 19 | ## Acknowledgments 20 | - Toolbar icons are [Mark James' Silk icons](http://famfamfam.com/lab/icons/silk/), licensed under [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). 21 | - Main form icon is OpenIcon's one from [Pixabay](https://pixabay.com/en/hard-drive-disk-saving-data-add-97582/), licensed under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/). 22 | - [Simon Mourier](https://stackoverflow.com/a/15386992) for most of the folder selection dialog. 23 | - [PInvoke.net](https://pinvoke.net/) and [Microsoft](https://docs.microsoft.com/en-us/windows/desktop/api/index) for references on black magic with unmanaged API. 24 | - The idea for the tool is loosely based on [Create Synchronicity](https://sourceforge.net/projects/synchronicity/). 25 | -------------------------------------------------------------------------------- /RoboBackup.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.9 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoboBackupGUI", "RoboBackupGUI\RoboBackupGUI.csproj", "{7767BF21-EE40-448B-90ED-179A17E320CC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoboBackupService", "RoboBackupService\RoboBackupService.csproj", "{43C4F764-ABBD-46A5-A184-C24DA8D7B203}" 9 | EndProject 10 | Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Setup", "Setup\Setup.vdproj", "{67B792FA-B73D-4763-8030-82E09091B380}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {7767BF21-EE40-448B-90ED-179A17E320CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7767BF21-EE40-448B-90ED-179A17E320CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7767BF21-EE40-448B-90ED-179A17E320CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {7767BF21-EE40-448B-90ED-179A17E320CC}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {43C4F764-ABBD-46A5-A184-C24DA8D7B203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {43C4F764-ABBD-46A5-A184-C24DA8D7B203}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {43C4F764-ABBD-46A5-A184-C24DA8D7B203}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {43C4F764-ABBD-46A5-A184-C24DA8D7B203}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {67B792FA-B73D-4763-8030-82E09091B380}.Debug|Any CPU.ActiveCfg = Debug 27 | {67B792FA-B73D-4763-8030-82E09091B380}.Release|Any CPU.ActiveCfg = Release 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(ExtensibilityGlobals) = postSolution 33 | SolutionGuid = {B4D7F289-357D-443E-A9F6-8DEA052502AA} 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /RoboBackupGUI/AboutForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace RoboBackup { 2 | partial class AboutForm { 3 | private System.ComponentModel.IContainer components = null; 4 | 5 | protected override void Dispose(bool disposing) { 6 | if (disposing && (components != null)) { 7 | components.Dispose(); 8 | } 9 | base.Dispose(disposing); 10 | } 11 | 12 | #region Windows Form Designer generated code 13 | 14 | /// 15 | /// Required method for Designer support - do not modify 16 | /// the contents of this method with the code editor. 17 | /// 18 | private void InitializeComponent() { 19 | this.aboutNameLabel = new System.Windows.Forms.Label(); 20 | this.aboutVersionLabel = new System.Windows.Forms.Label(); 21 | this.aboutAuthorLabel = new System.Windows.Forms.Label(); 22 | this.aboutSourceCodeLabel = new System.Windows.Forms.Label(); 23 | this.sourceCodeLinkLabel = new System.Windows.Forms.LinkLabel(); 24 | this.authorLabel = new System.Windows.Forms.Label(); 25 | this.versionLabel = new System.Windows.Forms.Label(); 26 | this.SuspendLayout(); 27 | // 28 | // aboutNameLabel 29 | // 30 | this.aboutNameLabel.AutoSize = true; 31 | this.aboutNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 24F, System.Drawing.FontStyle.Bold); 32 | this.aboutNameLabel.ForeColor = System.Drawing.Color.Green; 33 | this.aboutNameLabel.Location = new System.Drawing.Point(12, 9); 34 | this.aboutNameLabel.Name = "aboutNameLabel"; 35 | this.aboutNameLabel.Size = new System.Drawing.Size(210, 37); 36 | this.aboutNameLabel.TabIndex = 0; 37 | this.aboutNameLabel.Text = "RoboBackup"; 38 | // 39 | // aboutVersionLabel 40 | // 41 | this.aboutVersionLabel.AutoSize = true; 42 | this.aboutVersionLabel.Location = new System.Drawing.Point(16, 56); 43 | this.aboutVersionLabel.Name = "aboutVersionLabel"; 44 | this.aboutVersionLabel.Size = new System.Drawing.Size(45, 13); 45 | this.aboutVersionLabel.TabIndex = 1; 46 | this.aboutVersionLabel.Text = "Version:"; 47 | // 48 | // aboutAuthorLabel 49 | // 50 | this.aboutAuthorLabel.AutoSize = true; 51 | this.aboutAuthorLabel.Location = new System.Drawing.Point(16, 82); 52 | this.aboutAuthorLabel.Name = "aboutAuthorLabel"; 53 | this.aboutAuthorLabel.Size = new System.Drawing.Size(41, 13); 54 | this.aboutAuthorLabel.TabIndex = 3; 55 | this.aboutAuthorLabel.Text = "Author:"; 56 | // 57 | // aboutSourceCodeLabel 58 | // 59 | this.aboutSourceCodeLabel.AutoSize = true; 60 | this.aboutSourceCodeLabel.Location = new System.Drawing.Point(16, 108); 61 | this.aboutSourceCodeLabel.Name = "aboutSourceCodeLabel"; 62 | this.aboutSourceCodeLabel.Size = new System.Drawing.Size(69, 13); 63 | this.aboutSourceCodeLabel.TabIndex = 5; 64 | this.aboutSourceCodeLabel.Text = "SourceCode:"; 65 | // 66 | // sourceCodeLinkLabel 67 | // 68 | this.sourceCodeLinkLabel.AutoSize = true; 69 | this.sourceCodeLinkLabel.Location = new System.Drawing.Point(93, 108); 70 | this.sourceCodeLinkLabel.Name = "sourceCodeLinkLabel"; 71 | this.sourceCodeLinkLabel.Size = new System.Drawing.Size(236, 13); 72 | this.sourceCodeLinkLabel.TabIndex = 6; 73 | this.sourceCodeLinkLabel.TabStop = true; 74 | this.sourceCodeLinkLabel.Text = "https://github.com/Disassembler0/RoboBackup"; 75 | this.sourceCodeLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.SourceCodeLinkLabel_LinkClicked); 76 | // 77 | // authorLabel 78 | // 79 | this.authorLabel.AutoSize = true; 80 | this.authorLabel.Location = new System.Drawing.Point(93, 82); 81 | this.authorLabel.Name = "authorLabel"; 82 | this.authorLabel.Size = new System.Drawing.Size(194, 13); 83 | this.authorLabel.TabIndex = 4; 84 | this.authorLabel.Text = "Disassembler "; 85 | // 86 | // versionLabel 87 | // 88 | this.versionLabel.AutoSize = true; 89 | this.versionLabel.Location = new System.Drawing.Point(93, 56); 90 | this.versionLabel.Name = "versionLabel"; 91 | this.versionLabel.Size = new System.Drawing.Size(40, 13); 92 | this.versionLabel.TabIndex = 2; 93 | this.versionLabel.Text = "0.0.0.0"; 94 | // 95 | // AboutForm 96 | // 97 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 98 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 99 | this.ClientSize = new System.Drawing.Size(344, 138); 100 | this.Controls.Add(this.versionLabel); 101 | this.Controls.Add(this.authorLabel); 102 | this.Controls.Add(this.sourceCodeLinkLabel); 103 | this.Controls.Add(this.aboutSourceCodeLabel); 104 | this.Controls.Add(this.aboutAuthorLabel); 105 | this.Controls.Add(this.aboutVersionLabel); 106 | this.Controls.Add(this.aboutNameLabel); 107 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 108 | this.MaximizeBox = false; 109 | this.MinimizeBox = false; 110 | this.Name = "AboutForm"; 111 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 112 | this.Text = "About"; 113 | this.ResumeLayout(false); 114 | this.PerformLayout(); 115 | 116 | } 117 | 118 | #endregion 119 | 120 | private System.Windows.Forms.Label aboutNameLabel; 121 | private System.Windows.Forms.Label aboutVersionLabel; 122 | private System.Windows.Forms.Label aboutAuthorLabel; 123 | private System.Windows.Forms.Label aboutSourceCodeLabel; 124 | private System.Windows.Forms.LinkLabel sourceCodeLinkLabel; 125 | private System.Windows.Forms.Label authorLabel; 126 | private System.Windows.Forms.Label versionLabel; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /RoboBackupGUI/AboutForm.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | using System.Windows.Forms; 4 | 5 | namespace RoboBackup { 6 | /// 7 | /// Form with brief information about the RoboBackup application and its author. 8 | /// 9 | public partial class AboutForm : Form { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public AboutForm() { 14 | InitializeComponent(); 15 | Localize(); 16 | versionLabel.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString(2); 17 | } 18 | 19 | /// 20 | /// Translates the form strings to the language set by 21 | /// 22 | private void Localize() { 23 | Text = Lang.Get("About"); 24 | aboutVersionLabel.Text = Lang.Get("Version", ":"); 25 | aboutAuthorLabel.Text = Lang.Get("Author", ":"); 26 | aboutSourceCodeLabel.Text = Lang.Get("SourceCode", ":"); 27 | } 28 | 29 | /// 30 | /// event handler. Opens a GitHub link in default web browser. 31 | /// 32 | private void SourceCodeLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { 33 | Process.Start(sourceCodeLinkLabel.Text); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RoboBackupGUI/DirectoryTree.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Windows.Forms; 4 | 5 | namespace RoboBackup { 6 | /// 7 | /// Node traversal helper class for 's object instance. 8 | /// 9 | public static class DirectoryTree { 10 | /// 11 | /// Locates and returns with given full path within a . 12 | /// 13 | /// to search in. 14 | /// Full node path of the searched . 15 | /// with given full path 16 | public static TreeNode FindNode(TreeNodeCollection nodes, string fullPath) { 17 | foreach (TreeNode child in nodes) { 18 | if (child.FullPath == fullPath) { 19 | return child; 20 | } 21 | TreeNode match = FindNode(child.Nodes, fullPath); 22 | if (match != null) { 23 | return match; 24 | } 25 | } 26 | return null; 27 | } 28 | 29 | /// 30 | /// Locates and returns collection of nodes with unchecked checkbox within a . 31 | /// 32 | /// to search in. 33 | /// of s with unchecked checkbox. 34 | public static List GetExcludedNodes(TreeNodeCollection nodes) { 35 | List list = new List(); 36 | foreach (TreeNode child in nodes) { 37 | if (!child.Checked) { 38 | list.Add(child); 39 | } else { 40 | list.AddRange(GetExcludedNodes(child.Nodes)); 41 | } 42 | } 43 | return list; 44 | } 45 | 46 | /// 47 | /// Traverses given filesystem path and creates an array of s corresponding to filesystem objects under that path. 48 | /// 49 | /// Filesystem path to traverse. 50 | /// Array of s corresponding to filesystem objects found under given path 51 | public static TreeNode[] LoadNodes(string path) { 52 | List nodes = new List(); 53 | IEnumerable directories = new string[] { }; 54 | IEnumerable files = new string[] { }; 55 | try { 56 | directories = Directory.EnumerateDirectories(path); 57 | files = Directory.EnumerateFiles(path); 58 | } catch { /* Ignore if the subdirectory is inaccessible */ } 59 | foreach (string dir in directories) { 60 | TreeNode node = new TreeNode(Path.GetFileName(dir)) { Checked = true, Tag = EntryType.Directory }; 61 | node.Nodes.AddRange(LoadNodes(dir)); 62 | nodes.Add(node); 63 | } 64 | foreach (string file in files) { 65 | nodes.Add(new TreeNode(Path.GetFileName(file)) { Checked = true, Tag = EntryType.File }); 66 | } 67 | return nodes.ToArray(); 68 | } 69 | 70 | /// 71 | /// Checks or unchecks checkboxes for all of the given . 72 | /// 73 | /// to descend to. 74 | /// state to set. 75 | public static void ToggleChildren(TreeNode node, bool check) { 76 | foreach (TreeNode child in node.Nodes) { 77 | child.Checked = check; 78 | ToggleChildren(child, check); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /RoboBackupGUI/IpcClient.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Remoting; 2 | using System.Runtime.Remoting.Channels; 3 | using System.Runtime.Remoting.Channels.Ipc; 4 | 5 | namespace RoboBackup { 6 | /// 7 | /// Client side for interprocess communication via named pipe between the GUI (client) and the service (server). 8 | /// 9 | public static class IpcClient { 10 | /// 11 | /// Interprocess communication client channel. 12 | /// 13 | private static IpcClientChannel _channel; 14 | 15 | /// 16 | /// Interprocess communication object proxy. 17 | /// 18 | private static IpcService _service; 19 | 20 | /// 21 | /// Tests connection via IPC and return object proxy. 22 | /// 23 | /// object proxy. 24 | public static IpcService GetService() { 25 | try { 26 | _service.Ping(); 27 | } catch { 28 | RegisterIpcClient(); 29 | } 30 | return _service; 31 | } 32 | 33 | /// 34 | /// Registers interprocess communication client channel and the object proxy. 35 | /// 36 | private static void RegisterIpcClient() { 37 | // Unregister the channel if it was previously registered but the underlying pipe has been closed (e.g. when the service was restarted but the GUI wasn't). 38 | if (_channel != null) { 39 | ChannelServices.UnregisterChannel(_channel); 40 | } 41 | _channel = new IpcClientChannel(); 42 | ChannelServices.RegisterChannel(_channel, false); 43 | if (_service == null) { 44 | WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry(typeof(IpcService), "ipc://RoboBackup/ipc"); 45 | RemotingConfiguration.RegisterWellKnownClientType(remoteType); 46 | _service = new IpcService(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RoboBackupGUI/Lang.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | 5 | namespace RoboBackup { 6 | /// 7 | /// Localization class providing translations and language-related methods for the GUI part. 8 | /// 9 | public static class Lang { 10 | /// 11 | /// Collection of available languages, mainly used in . Key is the language string (e.g. "English"), Value is the ISO 639-1 code of the language (e.g. "en"). 12 | /// 13 | public static Dictionary AvailableLocales { 14 | get { 15 | if (_availableLocales == null) { 16 | _availableLocales = GetAvailableLocales(); 17 | } 18 | return _availableLocales; 19 | } 20 | } 21 | 22 | /// 23 | /// Private store for property. 24 | /// 25 | private static Dictionary _availableLocales; 26 | 27 | /// 28 | /// Private store for translation strings for the currently set language. 29 | /// 30 | private static Dictionary _translations = new Dictionary(); 31 | 32 | /// 33 | /// Translates given placeholder string. Optionally appends culturally neutral string, e.g. ":" (colon). 34 | /// 35 | /// Placeholder to look up in translations. 36 | /// Optional to append to the translated string. 37 | /// A string translated into the currently set language. 38 | public static string Get(string key, string append = null) { 39 | if (_translations.ContainsKey(key)) { 40 | string translation = _translations[key]; 41 | if (append != null) { 42 | return string.Format("{0}{1}", translation, append); 43 | } 44 | return translation; 45 | } 46 | return key; 47 | } 48 | 49 | /// 50 | /// Loads corresponding translation string from language under directory. 51 | /// 52 | public static void SetLang() { 53 | string langFile = Path.Combine(Config.LangRoot, string.Format("{0}.txt", Config.Language)); 54 | if (!File.Exists(langFile)) { 55 | return; 56 | } 57 | // Read the lang file, trim whitespaces from lines, skip empty lines, and split the rest to key=value dictionary 58 | _translations = File.ReadAllLines(langFile).Select(line => line.Trim()).Where(line => !string.IsNullOrEmpty(line)).Select(line => line.Split(new char[] { '=' }, 2)).ToDictionary(line => line[0], line => line[1]); 59 | } 60 | 61 | /// 62 | /// Scans "lang" directory and compiles a collection of languages and translation files present in it. 63 | /// 64 | /// Collection of available languages, mainly used in . Key is the language string (e.g. "English"), Value is the ISO 639-1 code of the language (e.g. "en"). 65 | private static Dictionary GetAvailableLocales() { 66 | Dictionary locales = new Dictionary(); 67 | foreach (string file in Directory.EnumerateFiles(Config.LangRoot)) { 68 | try { 69 | // Get the value of line from lang file which starts with '=' 70 | string langName = File.ReadAllLines(file).First(line => line.StartsWith("=")).Trim().Substring(1); 71 | string langCode = Path.GetFileNameWithoutExtension(file); 72 | locales.Add(langName, langCode); 73 | } catch { /* Ignore if the file doesn't contain what we expect */ } 74 | } 75 | return locales; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /RoboBackupGUI/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace RoboBackup 2 | { 3 | partial class MainForm 4 | { 5 | private System.ComponentModel.IContainer components = null; 6 | 7 | protected override void Dispose(bool disposing) 8 | { 9 | if (disposing && (components != null)) 10 | { 11 | components.Dispose(); 12 | } 13 | base.Dispose(disposing); 14 | } 15 | 16 | #region Windows Form Designer generated code 17 | 18 | /// 19 | /// Required method for Designer support - do not modify 20 | /// the contents of this method with the code editor. 21 | /// 22 | private void InitializeComponent() 23 | { 24 | this.toolStrip = new System.Windows.Forms.ToolStrip(); 25 | this.newTaskToolStripButton = new System.Windows.Forms.ToolStripButton(); 26 | this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); 27 | this.editTaskToolStripButton = new System.Windows.Forms.ToolStripButton(); 28 | this.deleteTaskToolStripButton = new System.Windows.Forms.ToolStripButton(); 29 | this.aboutToolStripButton = new System.Windows.Forms.ToolStripButton(); 30 | this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); 31 | this.showTaskLogsToolStripButton = new System.Windows.Forms.ToolStripButton(); 32 | this.runTaskToolStripButton = new System.Windows.Forms.ToolStripButton(); 33 | this.settingsToolStripButton = new System.Windows.Forms.ToolStripButton(); 34 | this.taskListView = new System.Windows.Forms.ListView(); 35 | this.titleColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 36 | this.scheduleColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 37 | this.sourceColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 38 | this.destinationColumnHeader = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 39 | this.toolStrip.SuspendLayout(); 40 | this.SuspendLayout(); 41 | // 42 | // toolStrip 43 | // 44 | this.toolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; 45 | this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 46 | this.newTaskToolStripButton, 47 | this.toolStripSeparator1, 48 | this.editTaskToolStripButton, 49 | this.deleteTaskToolStripButton, 50 | this.aboutToolStripButton, 51 | this.toolStripSeparator2, 52 | this.showTaskLogsToolStripButton, 53 | this.runTaskToolStripButton, 54 | this.settingsToolStripButton}); 55 | this.toolStrip.Location = new System.Drawing.Point(0, 0); 56 | this.toolStrip.Name = "toolStrip"; 57 | this.toolStrip.Size = new System.Drawing.Size(726, 25); 58 | this.toolStrip.TabIndex = 1; 59 | // 60 | // newTaskToolStripButton 61 | // 62 | this.newTaskToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 63 | this.newTaskToolStripButton.Image = global::RoboBackup.Properties.Resources.NewIcon; 64 | this.newTaskToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 65 | this.newTaskToolStripButton.Name = "newTaskToolStripButton"; 66 | this.newTaskToolStripButton.Size = new System.Drawing.Size(23, 22); 67 | this.newTaskToolStripButton.Text = "NewTask"; 68 | this.newTaskToolStripButton.Click += new System.EventHandler(this.NewTaskToolStripButton_Click); 69 | // 70 | // toolStripSeparator1 71 | // 72 | this.toolStripSeparator1.Name = "toolStripSeparator1"; 73 | this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25); 74 | // 75 | // editTaskToolStripButton 76 | // 77 | this.editTaskToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 78 | this.editTaskToolStripButton.Enabled = false; 79 | this.editTaskToolStripButton.Image = global::RoboBackup.Properties.Resources.EditIcon; 80 | this.editTaskToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 81 | this.editTaskToolStripButton.Name = "editTaskToolStripButton"; 82 | this.editTaskToolStripButton.Size = new System.Drawing.Size(23, 22); 83 | this.editTaskToolStripButton.Text = "EditTask"; 84 | this.editTaskToolStripButton.Click += new System.EventHandler(this.EditTaskToolStripButton_Click); 85 | // 86 | // deleteTaskToolStripButton 87 | // 88 | this.deleteTaskToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 89 | this.deleteTaskToolStripButton.Enabled = false; 90 | this.deleteTaskToolStripButton.Image = global::RoboBackup.Properties.Resources.DeleteIcon; 91 | this.deleteTaskToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 92 | this.deleteTaskToolStripButton.Name = "deleteTaskToolStripButton"; 93 | this.deleteTaskToolStripButton.Size = new System.Drawing.Size(23, 22); 94 | this.deleteTaskToolStripButton.Text = "DeleteTask"; 95 | this.deleteTaskToolStripButton.Click += new System.EventHandler(this.DeleteTaskToolStripButton_Click); 96 | // 97 | // aboutToolStripButton 98 | // 99 | this.aboutToolStripButton.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; 100 | this.aboutToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 101 | this.aboutToolStripButton.Image = global::RoboBackup.Properties.Resources.HelpIcon; 102 | this.aboutToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 103 | this.aboutToolStripButton.Name = "aboutToolStripButton"; 104 | this.aboutToolStripButton.Size = new System.Drawing.Size(23, 22); 105 | this.aboutToolStripButton.Text = "About"; 106 | this.aboutToolStripButton.Click += new System.EventHandler(this.AboutToolStripButton_Click); 107 | // 108 | // toolStripSeparator2 109 | // 110 | this.toolStripSeparator2.Name = "toolStripSeparator2"; 111 | this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25); 112 | // 113 | // showTaskLogsToolStripButton 114 | // 115 | this.showTaskLogsToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 116 | this.showTaskLogsToolStripButton.Enabled = false; 117 | this.showTaskLogsToolStripButton.Image = global::RoboBackup.Properties.Resources.LogsIcon; 118 | this.showTaskLogsToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 119 | this.showTaskLogsToolStripButton.Name = "showTaskLogsToolStripButton"; 120 | this.showTaskLogsToolStripButton.Size = new System.Drawing.Size(23, 22); 121 | this.showTaskLogsToolStripButton.Text = "ShowTaskLogs"; 122 | this.showTaskLogsToolStripButton.Click += new System.EventHandler(this.ShowTaskLogsToolStripButton_Click); 123 | // 124 | // runTaskToolStripButton 125 | // 126 | this.runTaskToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 127 | this.runTaskToolStripButton.Enabled = false; 128 | this.runTaskToolStripButton.Image = global::RoboBackup.Properties.Resources.RunIcon; 129 | this.runTaskToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 130 | this.runTaskToolStripButton.Name = "runTaskToolStripButton"; 131 | this.runTaskToolStripButton.Size = new System.Drawing.Size(23, 22); 132 | this.runTaskToolStripButton.Text = "RunTask"; 133 | this.runTaskToolStripButton.Click += new System.EventHandler(this.RunTaskToolStripButton_Click); 134 | // 135 | // settingsToolStripButton 136 | // 137 | this.settingsToolStripButton.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; 138 | this.settingsToolStripButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; 139 | this.settingsToolStripButton.Image = global::RoboBackup.Properties.Resources.SettingsIcon; 140 | this.settingsToolStripButton.ImageTransparentColor = System.Drawing.Color.Magenta; 141 | this.settingsToolStripButton.Name = "settingsToolStripButton"; 142 | this.settingsToolStripButton.Size = new System.Drawing.Size(23, 22); 143 | this.settingsToolStripButton.Text = "Settings"; 144 | this.settingsToolStripButton.Click += new System.EventHandler(this.SettingsToolStripButton_Click); 145 | // 146 | // taskListView 147 | // 148 | this.taskListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 149 | this.titleColumnHeader, 150 | this.scheduleColumnHeader, 151 | this.sourceColumnHeader, 152 | this.destinationColumnHeader}); 153 | this.taskListView.Dock = System.Windows.Forms.DockStyle.Fill; 154 | this.taskListView.FullRowSelect = true; 155 | this.taskListView.GridLines = true; 156 | this.taskListView.Location = new System.Drawing.Point(0, 25); 157 | this.taskListView.Name = "taskListView"; 158 | this.taskListView.Size = new System.Drawing.Size(726, 289); 159 | this.taskListView.Sorting = System.Windows.Forms.SortOrder.Ascending; 160 | this.taskListView.TabIndex = 2; 161 | this.taskListView.UseCompatibleStateImageBehavior = false; 162 | this.taskListView.View = System.Windows.Forms.View.Details; 163 | this.taskListView.SelectedIndexChanged += new System.EventHandler(this.TaskListView_SelectedIndexChanged); 164 | this.taskListView.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.TaskListView_MouseDoubleClick); 165 | // 166 | // titleColumnHeader 167 | // 168 | this.titleColumnHeader.Text = "Title"; 169 | this.titleColumnHeader.Width = 160; 170 | // 171 | // scheduleColumnHeader 172 | // 173 | this.scheduleColumnHeader.Text = "Schedule"; 174 | this.scheduleColumnHeader.Width = 160; 175 | // 176 | // sourceColumnHeader 177 | // 178 | this.sourceColumnHeader.Text = "Source"; 179 | this.sourceColumnHeader.Width = 200; 180 | // 181 | // destinationColumnHeader 182 | // 183 | this.destinationColumnHeader.Text = "Destination"; 184 | this.destinationColumnHeader.Width = 200; 185 | // 186 | // MainForm 187 | // 188 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 189 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 190 | this.ClientSize = new System.Drawing.Size(726, 314); 191 | this.Controls.Add(this.taskListView); 192 | this.Controls.Add(this.toolStrip); 193 | this.Name = "MainForm"; 194 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 195 | this.Text = "RoboBackup"; 196 | this.toolStrip.ResumeLayout(false); 197 | this.toolStrip.PerformLayout(); 198 | this.ResumeLayout(false); 199 | this.PerformLayout(); 200 | 201 | } 202 | 203 | #endregion 204 | 205 | private System.Windows.Forms.ToolStrip toolStrip; 206 | private System.Windows.Forms.ToolStripButton newTaskToolStripButton; 207 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; 208 | private System.Windows.Forms.ToolStripButton editTaskToolStripButton; 209 | private System.Windows.Forms.ToolStripButton deleteTaskToolStripButton; 210 | private System.Windows.Forms.ToolStripButton aboutToolStripButton; 211 | private System.Windows.Forms.ListView taskListView; 212 | private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; 213 | private System.Windows.Forms.ToolStripButton showTaskLogsToolStripButton; 214 | private System.Windows.Forms.ToolStripButton runTaskToolStripButton; 215 | private System.Windows.Forms.ColumnHeader titleColumnHeader; 216 | private System.Windows.Forms.ColumnHeader scheduleColumnHeader; 217 | private System.Windows.Forms.ColumnHeader sourceColumnHeader; 218 | private System.Windows.Forms.ColumnHeader destinationColumnHeader; 219 | private System.Windows.Forms.ToolStripButton settingsToolStripButton; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /RoboBackupGUI/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Windows.Forms; 5 | using System.Drawing; 6 | 7 | namespace RoboBackup { 8 | /// 9 | /// Main form with overview of scheduled backup tasks. 10 | /// 11 | public partial class MainForm : Form { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public MainForm() { 16 | InitializeComponent(); 17 | Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); 18 | Localize(); 19 | RedrawTasks(); 20 | } 21 | 22 | /// 23 | /// Formats and localizes a to fit . 24 | /// 25 | /// to be formatted. 26 | /// Localized representation of the . 27 | private static ListViewItem DrawTask(Task task) { 28 | string method = Lang.Get("IncrementalAbbr"); 29 | if (task.Method == Method.Differential) { 30 | method = Lang.Get("DifferentialAbbr"); 31 | } else if (task.Method == Method.Full) { 32 | method = Lang.Get("FullAbbr"); 33 | } 34 | string schedule = Lang.Get("DailyAbbr"); 35 | if (task.Period == Period.Weekly) { 36 | schedule = string.Format("{0}, {1}", Lang.Get("WeeklyAbbr"), Lang.Get(task.DayOfWeek.ToString())); 37 | } else if (task.Period == Period.Monthly) { 38 | schedule = string.Format("{0}, {1}.", Lang.Get("MonthlyAbbr"), task.DayOfMonth); 39 | } 40 | schedule = string.Format("{0}, {1} @ {2:D2}:{3:D2}", method, schedule, task.Hour, task.Minute); 41 | return new ListViewItem(new string[] { task.Title, schedule, task.Source, task.Destination }) { Tag = task.Guid }; 42 | } 43 | 44 | /// 45 | /// Saves the configuration to file and sends a request for conf reload to the service via . 46 | /// 47 | private static void SaveConfig() { 48 | Config.Save(); 49 | try { 50 | IpcClient.GetService().ReloadConfig(); 51 | } catch { 52 | MessageBox.Show(Lang.Get("UnableToConnectService"), Lang.Get("Error"), MessageBoxButtons.OK, MessageBoxIcon.Error); 53 | } 54 | } 55 | 56 | /// 57 | /// event handler. Opens a new window. 58 | /// 59 | private void AboutToolStripButton_Click(object sender, EventArgs e) { 60 | AboutForm about = new AboutForm(); 61 | about.ShowDialog(); 62 | } 63 | 64 | /// 65 | /// event handler. Removes a task from the configuration. 66 | /// 67 | private void DeleteTaskToolStripButton_Click(object sender, EventArgs e) { 68 | if (MessageBox.Show(Lang.Get("DeleteConfirmation"), Lang.Get("Question"), MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { 69 | string guid = (string)taskListView.SelectedItems[0].Tag; 70 | Config.Tasks.Remove(guid); 71 | SaveConfig(); 72 | RedrawTasks(); 73 | } 74 | } 75 | 76 | /// 77 | /// event handler. Opens a new and updates the selected task in the configuration. 78 | /// 79 | private void EditTaskToolStripButton_Click(object sender, EventArgs e) { 80 | string guid = (string)taskListView.SelectedItems[0].Tag; 81 | TaskForm editForm = new TaskForm(Config.Tasks[guid]); 82 | if (editForm.ShowDialog() == DialogResult.OK) { 83 | Config.Tasks[guid] = editForm.ResultTask; 84 | SaveConfig(); 85 | RedrawTasks(); 86 | } 87 | } 88 | 89 | /// 90 | /// Translates the form strings to the language set by . 91 | /// 92 | private void Localize() { 93 | newTaskToolStripButton.Text = Lang.Get("NewTask"); 94 | editTaskToolStripButton.Text = Lang.Get("EditTask"); 95 | deleteTaskToolStripButton.Text = Lang.Get("DeleteTask"); 96 | showTaskLogsToolStripButton.Text = Lang.Get("ShowTaskLogs"); 97 | runTaskToolStripButton.Text = Lang.Get("RunTask"); 98 | settingsToolStripButton.Text = Lang.Get("Settings"); 99 | aboutToolStripButton.Text = Lang.Get("About"); 100 | titleColumnHeader.Text = Lang.Get("Title"); 101 | scheduleColumnHeader.Text = Lang.Get("Schedule"); 102 | sourceColumnHeader.Text = Lang.Get("Source"); 103 | destinationColumnHeader.Text = Lang.Get("Destination"); 104 | } 105 | 106 | /// 107 | /// event handler. Opens a new and inserts the new task into the configuration. 108 | /// 109 | private void NewTaskToolStripButton_Click(object sender, EventArgs e) { 110 | TaskForm newForm = new TaskForm(); 111 | if (newForm.ShowDialog() == DialogResult.OK) { 112 | Task t = newForm.ResultTask; 113 | Config.Tasks[t.Guid] = t; 114 | SaveConfig(); 115 | RedrawTasks(); 116 | } 117 | } 118 | 119 | /// 120 | /// Redraws items using the current locale set by . 121 | /// 122 | private void RedrawTasks() { 123 | taskListView.Items.Clear(); 124 | foreach (Task task in Config.Tasks.Values) { 125 | taskListView.Items.Add(DrawTask(task)); 126 | } 127 | taskListView.SelectedIndices.Clear(); 128 | TaskListView_SelectedIndexChanged(null, null); 129 | } 130 | 131 | /// 132 | /// event handler. Sends a request for immediate backup start to the service via . 133 | /// 134 | private void RunTaskToolStripButton_Click(object sender, EventArgs e) { 135 | string guid = (string)taskListView.SelectedItems[0].Tag; 136 | try { 137 | IpcClient.GetService().RunTask(guid); 138 | MessageBox.Show(Lang.Get("BackupStarted"), Lang.Get("Information"), MessageBoxButtons.OK, MessageBoxIcon.Information); 139 | } catch { 140 | MessageBox.Show(Lang.Get("UnableToConnectService"), Lang.Get("Error"), MessageBoxButtons.OK, MessageBoxIcon.Error); 141 | } 142 | } 143 | 144 | /// 145 | /// event handler. Opens a and updates the configuration for log retention and language. 146 | /// 147 | private void SettingsToolStripButton_Click(object sender, EventArgs e) { 148 | SettingsForm settings = new SettingsForm(); 149 | if (settings.ShowDialog() == DialogResult.OK) { 150 | bool langChanged = settings.Language != Config.Language; 151 | Config.LogRetention = settings.LogRetention; 152 | Config.Language = settings.Language; 153 | SaveConfig(); 154 | if (langChanged) { 155 | Lang.SetLang(); 156 | Localize(); 157 | RedrawTasks(); 158 | } 159 | } 160 | } 161 | 162 | /// 163 | /// handler. Opens a directory with logs for the selected . 164 | /// 165 | private void ShowTaskLogsToolStripButton_Click(object sender, EventArgs e) { 166 | string guid = (string)taskListView.SelectedItems[0].Tag; 167 | string logDir = Path.Combine(Config.LogRoot, guid); 168 | if (!Directory.Exists(logDir)) { 169 | MessageBox.Show(Lang.Get("NoTaskLogs"), Lang.Get("Information"), MessageBoxButtons.OK, MessageBoxIcon.Information); 170 | return; 171 | } 172 | Process.Start(logDir); 173 | } 174 | 175 | /// 176 | /// event handler. Calls . 177 | /// 178 | private void TaskListView_MouseDoubleClick(object sender, MouseEventArgs e) { 179 | EditTaskToolStripButton_Click(sender, null); 180 | } 181 | 182 | /// 183 | /// event handler. Changes the state of the buttons according to the selection. 184 | /// 185 | private void TaskListView_SelectedIndexChanged(object sender, EventArgs e) { 186 | bool selected = taskListView.SelectedItems.Count != 0; 187 | editTaskToolStripButton.Enabled = selected; 188 | deleteTaskToolStripButton.Enabled = selected; 189 | showTaskLogsToolStripButton.Enabled = selected; 190 | runTaskToolStripButton.Enabled = selected; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /RoboBackupGUI/OpenFolderDialog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Runtime.InteropServices; 4 | using System.Windows.Forms; 5 | 6 | namespace RoboBackup { 7 | /// 8 | /// Windows Vista style folder selection dialog. Allows for selection of Libraries or UNC paths. Also allows copypasting the directory path. 9 | /// 10 | /// The class heavily relies on P/Invoke, COM imports and black magic. Courtesy of Simon Mourier - https://stackoverflow.com/a/15386992 11 | public class OpenFolderDialog : Component { 12 | /// 13 | /// Selected directory full path. 14 | /// 15 | public string SelectedPath { get; set; } 16 | 17 | /// 18 | /// Runs a common dialog box with a default owner. 19 | /// 20 | /// if the user clicks OK in the dialog box; otherwise, . 21 | public DialogResult ShowDialog() { 22 | return ShowDialog(IntPtr.Zero); 23 | } 24 | 25 | /// 26 | /// Runs a common dialog box with the specified owner. 27 | /// 28 | /// Any object that implements IWin32Window that represents the top-level window that will own the modal dialog box. 29 | /// if the user clicks OK in the dialog box; otherwise, . 30 | public DialogResult ShowDialog(IntPtr hwndOwner) { 31 | IFileOpenDialog dialog = (IFileOpenDialog)new FileOpenDialog(); 32 | try { 33 | IShellItem item; 34 | if (!string.IsNullOrEmpty(SelectedPath)) { 35 | uint atts = 0; 36 | if (SHILCreateFromPath(SelectedPath, out IntPtr idl, ref atts) == 0) { 37 | if (SHCreateShellItem(IntPtr.Zero, IntPtr.Zero, idl, out item) == 0) { 38 | dialog.SetFolder(item); 39 | } 40 | Marshal.FreeCoTaskMem(idl); 41 | } 42 | } 43 | dialog.SetOptions(0x20 | 0x40); // FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM 44 | uint hresult = dialog.Show(hwndOwner); 45 | if (hresult == 0x800704C7) // ERROR_CANCELLED 46 | return DialogResult.Cancel; 47 | if (hresult != 0) 48 | return DialogResult.Abort; 49 | dialog.GetResult(out item); 50 | item.GetDisplayName(0x80058000, out string path); // SIGDN_FILESYSPATH 51 | SelectedPath = path; 52 | return DialogResult.OK; 53 | } finally { 54 | Marshal.ReleaseComObject(dialog); 55 | } 56 | } 57 | 58 | /// 59 | /// Creates a pointer to an item identifier list (PIDL) from a path. 60 | /// 61 | /// A pointer to a null-terminated string of maximum length MAX_PATH containing the path to be converted. 62 | /// The path in expressed as a PIDL. 63 | /// A pointer to a DWORD value that, on entry, indicates any attributes of the folder named in that the calling application would like to retrieve along with the PIDL. On exit, this value contains those requested attributes. 64 | /// If this function succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. 65 | /// https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shilcreatefrompath 66 | [DllImport("shell32.dll", CharSet = CharSet.Unicode)] 67 | private static extern int SHILCreateFromPath(string pszPath, out IntPtr ppIdl, ref uint rgflnOut); 68 | 69 | /// 70 | /// Creates and initializes a Shell item object from a pointer to a parent item identifier list (PIDL). The resulting shell item object supports the interface. 71 | /// 72 | /// A PIDL to the parent. This value can be NULL. 73 | /// A pointer to the parent . This value can be NULL. 74 | /// A PIDL to the requested item. If parent information is not included in pidlParent or psfParent, this must be an absolute PIDL. 75 | /// When this method returns, contains the interface pointer to the new . 76 | /// If this function succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code. 77 | /// https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shcreateshellitem 78 | [DllImport("shell32.dll")] 79 | private static extern int SHCreateShellItem(IntPtr pidlParent, IntPtr psfParent, IntPtr pidl, out IShellItem ppsi); 80 | 81 | /// 82 | /// Basic Vista-style file open dialog. Allows for selection of Libraries or UNC paths. Also allows copypasting the directory path. 83 | /// 84 | [ComImport, Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")] 85 | private class FileOpenDialog { } 86 | 87 | /// 88 | /// Extends the IFileDialog interface by adding methods specific to the open dialog. 89 | /// 90 | /// https://docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nn-shobjidl_core-ifileopendialog 91 | [ComImport, Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 92 | private interface IFileOpenDialog { 93 | [PreserveSig] 94 | uint Show([In] IntPtr parent); // IModalWindow 95 | void SetFileTypes(); // not fully defined 96 | void SetFileTypeIndex([In] uint iFileType); 97 | void GetFileTypeIndex(out uint piFileType); 98 | void Advise(); // not fully defined 99 | void Unadvise(); 100 | void SetOptions([In] int fos); 101 | void GetOptions(out int pfos); 102 | void SetDefaultFolder(IShellItem psi); 103 | void SetFolder(IShellItem psi); 104 | void GetFolder(out IShellItem ppsi); 105 | void GetCurrentSelection(out IShellItem ppsi); 106 | void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); 107 | void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); 108 | void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); 109 | void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); 110 | void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); 111 | void GetResult(out IShellItem ppsi); 112 | void AddPlace(IShellItem psi, int alignment); 113 | void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); 114 | void Close(int hr); 115 | void SetClientGuid(); // not fully defined 116 | void ClearClientData(); 117 | void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); 118 | void GetResults([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenum); // not fully defined 119 | void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppsai); // not fully defined 120 | } 121 | 122 | /// 123 | /// Exposes methods that retrieve information about a Shell item. 124 | /// 125 | /// https://docs.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nn-shobjidl_core-ishellitem 126 | [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 127 | private interface IShellItem { 128 | void BindToHandler(); // not fully defined 129 | void GetParent(); // not fully defined 130 | void GetDisplayName([In] uint sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); 131 | void GetAttributes(); // not fully defined 132 | void Compare(); // not fully defined 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /RoboBackupGUI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace RoboBackup { 5 | public static class Program { 6 | /// 7 | /// Main entry point to the GUI application. 8 | /// 9 | [STAThread] 10 | public static void Main() { 11 | Config.Load(); 12 | Lang.SetLang(); 13 | Application.EnableVisualStyles(); 14 | Application.SetCompatibleTextRenderingDefault(false); 15 | Application.Run(new MainForm()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RoboBackupGUI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("RoboBackup GUI")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("RoboBackup GUI")] 9 | [assembly: AssemblyCopyright("Copyright © Disassembler 2018")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | [assembly: Guid("7767bf21-ee40-448b-90ed-179a17e320cc")] 15 | 16 | [assembly: AssemblyVersion("1.0.0.0")] 17 | [assembly: AssemblyFileVersion("1.0.0.0")] 18 | -------------------------------------------------------------------------------- /RoboBackupGUI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace RoboBackup.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | public class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | public static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RoboBackup.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | public static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | public static System.Drawing.Bitmap DeleteIcon { 67 | get { 68 | object obj = ResourceManager.GetObject("DeleteIcon", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Bitmap. 75 | /// 76 | public static System.Drawing.Bitmap EditIcon { 77 | get { 78 | object obj = ResourceManager.GetObject("EditIcon", resourceCulture); 79 | return ((System.Drawing.Bitmap)(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Drawing.Bitmap. 85 | /// 86 | public static System.Drawing.Bitmap HelpIcon { 87 | get { 88 | object obj = ResourceManager.GetObject("HelpIcon", resourceCulture); 89 | return ((System.Drawing.Bitmap)(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Drawing.Bitmap. 95 | /// 96 | public static System.Drawing.Bitmap LogsIcon { 97 | get { 98 | object obj = ResourceManager.GetObject("LogsIcon", resourceCulture); 99 | return ((System.Drawing.Bitmap)(obj)); 100 | } 101 | } 102 | 103 | /// 104 | /// Looks up a localized resource of type System.Drawing.Bitmap. 105 | /// 106 | public static System.Drawing.Bitmap NewIcon { 107 | get { 108 | object obj = ResourceManager.GetObject("NewIcon", resourceCulture); 109 | return ((System.Drawing.Bitmap)(obj)); 110 | } 111 | } 112 | 113 | /// 114 | /// Looks up a localized resource of type System.Drawing.Bitmap. 115 | /// 116 | public static System.Drawing.Bitmap RunIcon { 117 | get { 118 | object obj = ResourceManager.GetObject("RunIcon", resourceCulture); 119 | return ((System.Drawing.Bitmap)(obj)); 120 | } 121 | } 122 | 123 | /// 124 | /// Looks up a localized resource of type System.Drawing.Bitmap. 125 | /// 126 | public static System.Drawing.Bitmap SettingsIcon { 127 | get { 128 | object obj = ResourceManager.GetObject("SettingsIcon", resourceCulture); 129 | return ((System.Drawing.Bitmap)(obj)); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /RoboBackupGUI/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | 123 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 124 | wwAADsMBx2+oZAAAAl9JREFUOE+tk/1LU2Ecxe/fMotggQTlD0UIFUmbL3ttuty8ya2LmxmLME3ZHaYZ 125 | lBhq9EOopetVMyGmmUFkzgRz9KKodFdMbbc2am3ubt4t5bStG2bOIOjA+e18zsPzfb4P8d81X1GR4TGb 126 | re9pmnWTZPhdwnOkkZ2hiq1TJYUZYiy9FsvLNazZzH1ta4Yw+hSxqUnEXjrBO3qwYKHxpkTDuQwKjRhf 127 | ryTsLivjl/p6sOqeQ3z4AeL2FsQ7LuH7vXasjA/DV2vBmCqPn9DnrC+ZN5kkbpr2hu/YscpOI37tAoIt 128 | NoSaayBcsSHWykBoOgNh8C4WT1IY1xz0jqoPSEScIDwmE+Ovt2Jl2pU6cbmNQeBiJaINJ9Y5fJaC0H8T 129 | rxXZeK7NZkScID4kBhbpv4V4bweCTVX4QmrTeqmKAt9cB5+tGs9Ue1gRJ4hpiorGHvcj3mZD8LwFm8mn 130 | z0fAXIJQx1UM5UijIp4sIKPLvV0QGi2InisT4xuVLPDTBoTa/yiYJYvYQFMdllsYhE8bU8G0LlYgWF+L 131 | hdpKDMszf7sCWciwulxEe7sRPKZDgFZvgLmiPHBH1IjYr8Op3IUhuXRtiG9L1ZJXRo13sbwUkftdqYH5 132 | DcqfoL4AnC4B65Tguzsxc1SLAZnU68jduvaMSbn0eZoxlYz3HDcgcrsL3xoZ+KhifDLqEWiwgr/RiVmj 133 | Cn1yCT8g25Z+G8cTG/bi8H5uUr0Xn2sqEWi9nPLH6lMYyd+Jh/Lt3KbwLzkL92WMKHZbn6iyWIc8K+yQ 134 | 7wg7CjLZR7lS6+ChLX//TP8ugvgBZP3SiZ3Tf+kAAAAASUVORK5CYII= 135 | 136 | 137 | 138 | 139 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 140 | wwAADsMBx2+oZAAAAU9JREFUOE9joBt4dMyC8/Ziv1f7J9v/PTHB7/+l2dr3Hyw2koRKEwY3VwT8v7E0 141 | 6//3hyf//3h8+v+pqU7/DzYZrINK4wdPdun//3Jl+f8fD4/+//n4DBh/v733//4J9n+hSnADkOYfbw// 142 | //Foxf+763L+fzm/FIxBBpzu9/8OVYYdwDT/+bL5/9ebGf8/n8//f2Fu5P+7q4v/n5zo/x+qDDtA0Xwn 143 | 4f+Xay7/f73r+/9sS+z/m/3WNNS8f4I6+Zp3dqr9//RsH+map8fLK4A0Pzy94f+Jlemk2zw5V+7/p4cn 144 | /v96fvH/1lqT/4dnGv6/cyT3//k5Xv+PN5vg1wwCIAN+AlPXm1WJ/1/McPh/KF30/6oEsf9bynQIawaB 145 | SCeh/1fmxP7/en7Z/zfnV/4/2O1BvGYQiHAQ/h/nJPp/TqYqWOPOLsu1UClaAwYGAIUsKp6HS3QnAAAA 146 | AElFTkSuQmCC 147 | 148 | 149 | 150 | 151 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 152 | wwAADsMBx2+oZAAAAqJJREFUOE+tk91LU3Ecxs/fov9BIBXUhQRdCRG76FJ0+dZaqBhCtplIhitmkTM1 153 | NcXcRi4R3XTu1F50c5pT9+7ezLntTHcc29w5O1oGT3OsTJAg6IHP3ffzwPf3Qvz3mANHpabAscjoY4Pq 154 | LZZRe1hmwc4E5+0Z0dxaprQ4dnH0WzneJ0cuHkp8w/Y+hwDFFAjt5eCN5aBcPogrzQlecfx8SFeW99GZ 155 | ZXfoI9i/pqAwRdE95UOXagtD5Das3gP44wz69CF2iAyfL1E7MyWzG2lqO8Fh2ZdER14aJEOIJjlEkjlM 156 | rcTQpnBCtxGHJ3II6WyQkqr8JUWdIFQrtNgdZbEeSkOs9KB11I7c8UmeH0VO0KF0oO39JpY8NBbdNDoV 157 | TnFRJ4hxUzzoz+86Qu7g4egmmkfWET3IQb0Wg8G1By5fYHBREPSb8UrthTOcQsvIarCoE8SA1s0FqCzE 158 | cgeahm24P2jND1swZ4sgxRxjP8Vh7LMf9b2LaHprgTeSAV+m5Yo6QfRMuzl/LItHEw4IBiy498aMhj4z 159 | stx37NIMhLJF1PTocTePUGaEJ5pGVfsfBZ1ye9C1m4ZM48f9fisaZEuoe21C7UtDAf5zEnzJAqolWnQp 160 | 1mAL0bjTqT5boXXYJl6wxWDx0mgasP4Wk4dHoDMcKrvnC/C7NSA3IpDn16lomzk7xMa+pZJaqYFa9dHQ 161 | rUch6DWi+oUOvdObBU7lqmcz+GAMwOiI4WbjJHWjUXV2jaeplOh5t9tJ1uzZg8FJ4al8FXVSLWokc3gy 162 | ZoHOFobeHsXV+kn2+gPlxa/xlkjDq3g8Ex8nt/AlkIAjnCyw4tvHkMaFKwJF/LJg4mL5V8pbpkrLmydF 163 | 14TyYJnwHVMmyFM7ESyrHxddqhn9+2f69xDETzT5CW1O8B7pAAAAAElFTkSuQmCC 164 | 165 | 166 | 167 | 168 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 169 | wwAADsMBx2+oZAAAAXRJREFUOE+Nk22LAWEUhu3+jt3a/0D+5yb5Bb5II+WDhEQhMj54S/KJiMgwxjv3 170 | nvvJzL4Y7KmrmZ4553rOeWbGc403wSv4n/AhvAo34d1sNsZgMDifTie4sdvtEAgEPiXXVeJn8Xq9hmVZ 171 | DsvlEovFApPJREnG4zGCweCN5EXwM2G73WK/36srMU0ThmFgOp0qgXQJbhIKhWyJCkfA4lwuh1QqhUQi 172 | gVgshkgkgnA4/Gscwppr7bfgeDw6Cbzn3PYos9lMdTIaje4LSKlUQj6fRzabRTKZVJ3E43FomoZoNIr5 173 | fP5Y8Be7E87PM1mtVmr9rkDXdVQqFRSLRRQKBXUmmUwG6XRanQ1FDwVusAseMGE3XLsraLfbaDQaqNfr 174 | qNVqqFarKJfLCnb1VGDDXcnhcLjhoaDf76PX66Hb7aLT6aDVaqHZbDpQ6ioYDocXW/IMyT1fBU745PVY 175 | fOBW8BPmyOdsSg3/XifeBZ9A639gsdR4PF9uQnJ8xr+yGQAAAABJRU5ErkJggg== 176 | 177 | 178 | 179 | 180 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 181 | wwAADsMBx2+oZAAAAnhJREFUOE+tk/1LU1EYx+/fMosgVpBoVCT4QpRgJF4qX1vkS+rSTV3k1LtpU+eu 182 | OfHtulFmQVu5TUa66ebcaIqZOF3pIvU6lGijCIJy+Ou3e+uaiRYEfeD57fl8zznPOYf479BeRVybW05p 183 | HBVs46hkS/Gcq2EJW22VUBVPr8UJbQdDe6pIrasq+mSxF/PRaYQ+B7H4aQ6usB1qdwXKTFeixYNZpNC+ 184 | F15WD5fGPOt2bH5bh/fDGEzsfTxaZWDfMGHm4wvQU0pImMxYvjFjb4jWIxc1O+WR8TUL1r+uYGClGz0L 185 | bdAHmsGE2tEXoqFf0mB80w7KLQVpuBDJYtJEgs4FuGSq/tkWvPuyxK3Yh95lGrpXauzQsqBEw7QUqlkZ 186 | nBs2XH14DhldySpBJwh+YGNhG7dVM+7NN6F1rgFKb6WgA+W2PNyZvI6aiXwY37Sje6YR5/VnWUEnCNVI 187 | 4bbv/Rj637Zzcp2g7UfuyoHKXwZLaACp6qPbgk4QdfbC7dHwEOgghabAbaF9P3wA5bsJa+gBkn4PUNgk 188 | rCFAg1nWoe6lFHJ/IYqtlwXtp8hXtTsP/YEWdPjqcVpzfPcIsqECVamVhCNsgdJfhCqf5MeZd+DlSmc2 189 | FOP5cKyZkdZ5CglN4t0hlplyRSWPyQjlLscI++zXwHhR5uSLl/MwumrCLUsuEu6KI8fUh3evkefG4CWy 190 | gLkYq3UUwcmFGAJaUJMlaJgogmG+FQ5OllqykaAWxU5QRw5+jTnGdJLsSY1mGlPQOUXB/JqBOchA56tF 191 | asdJThZH/yjvkNmVEpeuT6LS6DNssi5xK1Ebv5XIDSy+UUzF1x/6+2f6dwjiO8B3zyKE8PtmAAAAAElF 192 | TkSuQmCC 193 | 194 | 195 | 196 | 197 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 198 | wwAADsMBx2+oZAAAA2tJREFUOE9VkF9MU1ccx0+WLCbL9rhsL3vZg8v+JDyAzLmYRed4wPigD5u4uQyD 199 | bIJiKiidKLQ6tAgM1nUFFLhsHTVeAb1Ya2TqrZeCtg5KqRBqsSwFLQxuaXutlAp8d85dH7Zv8s355pzz 200 | +f5yDmFCFXlJuU50z2zEG7cR95qdrFMP/iNFIG+wc3rPFbtB3klvU7iFvBzpJueiD/JmUgsiZPfnc/d7 201 | tztLbyUdZbeWZGaWB4WdUtxTsPB8XCtHuohLvkLeUgtmL5KKqGvvk+XZ6wjfy190cRsUg3MBpgdzaBsJ 202 | q2b5RymIkc71iahPn1BGSv+e7SRDbDgJceRkfEgrh/vzFwe5t18Y74VwyfsUVukvmHvGYLw8ho7bQVg9 203 | M2i/K0Ls/WQ1+PDySvjqu/6ZVvqUx+fJ5gnzK36xdX3inBjERVcIPeIUJqdjWIgpmI89gz8Ug6VvEu39 204 | U7gk1OG+JTs53vLacNCc/ovStmu15d0+nHdOgafTmCLRKB49CWOJZmZlDWi2TcLsCKCSl3CAG6hWYaYv 205 | m/50HO3yofmGH+N0cuLFGpREAr+0c5ijRfFkEt0370B6FMVZfhTsLmPSOCE7GkVZYx1GTacXc89TiCRX 206 | sUynfq/T4/ceAeVVevQNuiCGAzhidoHdZUwapwVnBLmw1QUd50FISdGSVUxH4/ihrl6FJc8oTriPQNNf 207 | iEN39qHwt14wJo0TklPZ5djdIEJvGcLdQBRP6aOn4ynYHQMIyjHMrgDlAyWwTDahfliPkpsF2HHWNJTG 208 | Cdly9Fddru4qDl8YhIH3Y55+WCQFxFepaV6kBaViEdr9RrRR6wfKkc/nYXtTTrZasKm4OeujItNYzslr 209 | 0LRIqOEn4JlahEL/Q0mk4KV5v5CPprE6nPYcg/GhAWV9xdhWuxlqAVP2/vpvDxgsiW3aK/i67g9ofpZQ 210 | 9pMTmoZ+HGwU8ZU1D42jZ3DcXYJa7ykcshUgQ5exlMYJcTqdW3w+H26L0srHxa3YcNCKDw/3qGZ514Wd 211 | qBmugsFTiaLefdhUmZXKPJbx7xPsdvvrNptNCQQCEARBzthzQv/eFxWOD3ZrZWaWcxs+W9K7tfhO+AYb 212 | KzKx17Bnlwoz8Tz/akdHx4TJZFqm6/vp7f9pqyErktv4KTYez0S1sXqe47g3CSHkHz26c4uO0Rz4AAAA 213 | AElFTkSuQmCC 214 | 215 | 216 | 217 | 218 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 219 | wwAADsMBx2+oZAAAApJJREFUOE99U01LamEQPvQD3PsT+hmBC5eVEUEg2AcUaLUsRZRSiNBNi0AsSaQS 220 | +8IgpdSO+VWpxygzEtOFJgeFXLk9Z+7M2+lGcbsDh5cz78wzzzwzL/ebFQqF6Ww2y2cyGf7q6mpacf/f 221 | yuXy4O3trQoABm5ubrrtdhve3t7g6Oioa7PZBiKRiOrk5GRQCf9u9/f33uvraymfz4t4qlOplNhoNKBe 222 | r8Pp6al4fHysRiDx4OBA8vv9XiXtwzDZ8vr6Cu/v71Cr1QCT++FwWH5+foanpycIBAIyJvexLRBFEaLR 223 | KGxsbFiUdI67u7uzolMmEEqoVqsM6BOA/h8fH4EAsEVYWVmRl5aWrEr6h6FgTgouFouAgHB5eQnb29uA 224 | dIkR5HI5wNZY9fX1daeSxnGIKKTT6VYsFuu9vLyAIAgQj8dhdXU1t7y8rEHxNC6XK4cxDIju8b/ncDha 225 | GCNwPM9LnU6H0aXKpVIJ9vb2YG1tTaPU4DwejwY1ABwpY0ZsSFy9Xi8xgGazyXqkC6qAAsLm5uZfgK2t 226 | LU0wGGQMqIVkMsnix8bGJG53d1dwu92tnZ2dHk6DgZAOPp8vR5UJiFqg/qm1i4sLMBgMvfHx8ZZOpxOU 227 | GhyHFJ0oJBOKTlwkCIVCgHNnPtxGOD8/Z775+fkvEckODw+tWE2uVCosmcb18PDANCEgYkT+RCLB7hYW 228 | FuTZ2dmvMeKiWIgebd7+/j6g8n38ZBKN+jaZTPLc3Fyf7qh3u90Oo6OjX4tEhk7v1NSUNDMzI5rNZjVO 229 | QaRqxAD94uTkpHpiYkIcGhqStFrt91X+tMXFxUGspqKHg1+XKJ+dnQEms8eEp2p4ePjfj+mnGY3GaazI 230 | j4yM8Ej3l+fMcX8AvAUd7zJNh5cAAAAASUVORK5CYII= 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /RoboBackupGUI/RoboBackup.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Disassembler0/RoboBackup/cfba7a2168e85d2625ff7b1d26257db9273636ee/RoboBackupGUI/RoboBackup.ico -------------------------------------------------------------------------------- /RoboBackupGUI/RoboBackupGUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7767BF21-EE40-448B-90ED-179A17E320CC} 8 | WinExe 9 | RoboBackup 10 | RoboBackupGUI 11 | v4.6.1 12 | 512 13 | false 14 | 15 | publish\ 16 | true 17 | Disk 18 | false 19 | Foreground 20 | 7 21 | Days 22 | false 23 | false 24 | true 25 | 0 26 | 1.0.0.%2a 27 | false 28 | true 29 | 30 | 31 | AnyCPU 32 | true 33 | full 34 | false 35 | bin\Debug\ 36 | DEBUG;TRACE 37 | prompt 38 | 4 39 | AllRules.ruleset 40 | false 41 | 42 | 43 | AnyCPU 44 | pdbonly 45 | true 46 | bin\Release\ 47 | TRACE 48 | prompt 49 | 4 50 | 51 | 52 | 53 | 54 | 55 | RoboBackup.ico 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Form 67 | 68 | 69 | AboutForm.cs 70 | 71 | 72 | 73 | 74 | Component 75 | 76 | 77 | True 78 | True 79 | Resources.resx 80 | 81 | 82 | Form 83 | 84 | 85 | SettingsForm.cs 86 | 87 | 88 | Form 89 | 90 | 91 | MainForm.cs 92 | 93 | 94 | Form 95 | 96 | 97 | TaskForm.cs 98 | 99 | 100 | 101 | 102 | 103 | PublicResXFileCodeGenerator 104 | Resources.Designer.cs 105 | Designer 106 | 107 | 108 | 109 | 110 | False 111 | .NET Framework 3.5 SP1 112 | false 113 | 114 | 115 | 116 | 117 | {43c4f764-abbd-46a5-a184-c24da8d7b203} 118 | RoboBackupService 119 | 120 | 121 | 122 | 123 | PreserveNewest 124 | 125 | 126 | PreserveNewest 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /RoboBackupGUI/SettingsForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace RoboBackup { 2 | partial class SettingsForm { 3 | private System.ComponentModel.IContainer components = null; 4 | 5 | protected override void Dispose(bool disposing) { 6 | if (disposing && (components != null)) { 7 | components.Dispose(); 8 | } 9 | base.Dispose(disposing); 10 | } 11 | 12 | #region Windows Form Designer generated code 13 | 14 | /// 15 | /// Required method for Designer support - do not modify 16 | /// the contents of this method with the code editor. 17 | /// 18 | private void InitializeComponent() { 19 | this.languageLabel = new System.Windows.Forms.Label(); 20 | this.logRetentionLabel = new System.Windows.Forms.Label(); 21 | this.languageComboBox = new System.Windows.Forms.ComboBox(); 22 | this.logRetentionNumericUpDown = new System.Windows.Forms.NumericUpDown(); 23 | this.saveSettingsButton = new System.Windows.Forms.Button(); 24 | ((System.ComponentModel.ISupportInitialize)(this.logRetentionNumericUpDown)).BeginInit(); 25 | this.SuspendLayout(); 26 | // 27 | // languageLabel 28 | // 29 | this.languageLabel.AutoSize = true; 30 | this.languageLabel.Location = new System.Drawing.Point(12, 15); 31 | this.languageLabel.Name = "languageLabel"; 32 | this.languageLabel.Size = new System.Drawing.Size(58, 13); 33 | this.languageLabel.TabIndex = 0; 34 | this.languageLabel.Text = "Language:"; 35 | // 36 | // logRetentionLabel 37 | // 38 | this.logRetentionLabel.AutoSize = true; 39 | this.logRetentionLabel.Location = new System.Drawing.Point(12, 41); 40 | this.logRetentionLabel.Name = "logRetentionLabel"; 41 | this.logRetentionLabel.Size = new System.Drawing.Size(74, 13); 42 | this.logRetentionLabel.TabIndex = 2; 43 | this.logRetentionLabel.Text = "LogRetention:"; 44 | // 45 | // languageComboBox 46 | // 47 | this.languageComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 48 | this.languageComboBox.FormattingEnabled = true; 49 | this.languageComboBox.Location = new System.Drawing.Point(76, 12); 50 | this.languageComboBox.Name = "languageComboBox"; 51 | this.languageComboBox.Size = new System.Drawing.Size(116, 21); 52 | this.languageComboBox.TabIndex = 1; 53 | // 54 | // logRetentionNumericUpDown 55 | // 56 | this.logRetentionNumericUpDown.Location = new System.Drawing.Point(121, 39); 57 | this.logRetentionNumericUpDown.Maximum = new decimal(new int[] { 58 | 99999, 59 | 0, 60 | 0, 61 | 0}); 62 | this.logRetentionNumericUpDown.Minimum = new decimal(new int[] { 63 | 1, 64 | 0, 65 | 0, 66 | 0}); 67 | this.logRetentionNumericUpDown.Name = "logRetentionNumericUpDown"; 68 | this.logRetentionNumericUpDown.Size = new System.Drawing.Size(71, 20); 69 | this.logRetentionNumericUpDown.TabIndex = 3; 70 | this.logRetentionNumericUpDown.Value = new decimal(new int[] { 71 | 1, 72 | 0, 73 | 0, 74 | 0}); 75 | // 76 | // saveSettingsButton 77 | // 78 | this.saveSettingsButton.Location = new System.Drawing.Point(15, 65); 79 | this.saveSettingsButton.Name = "saveSettingsButton"; 80 | this.saveSettingsButton.Size = new System.Drawing.Size(177, 23); 81 | this.saveSettingsButton.TabIndex = 4; 82 | this.saveSettingsButton.Text = "SaveSettings"; 83 | this.saveSettingsButton.UseVisualStyleBackColor = true; 84 | this.saveSettingsButton.Click += new System.EventHandler(this.SaveSettingsButton_Click); 85 | // 86 | // SettingsForm 87 | // 88 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 89 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 90 | this.ClientSize = new System.Drawing.Size(205, 96); 91 | this.Controls.Add(this.saveSettingsButton); 92 | this.Controls.Add(this.logRetentionNumericUpDown); 93 | this.Controls.Add(this.languageComboBox); 94 | this.Controls.Add(this.logRetentionLabel); 95 | this.Controls.Add(this.languageLabel); 96 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 97 | this.MaximizeBox = false; 98 | this.MinimizeBox = false; 99 | this.Name = "SettingsForm"; 100 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 101 | this.Text = "Settings"; 102 | ((System.ComponentModel.ISupportInitialize)(this.logRetentionNumericUpDown)).EndInit(); 103 | this.ResumeLayout(false); 104 | this.PerformLayout(); 105 | 106 | } 107 | 108 | #endregion 109 | 110 | private System.Windows.Forms.Label languageLabel; 111 | private System.Windows.Forms.Label logRetentionLabel; 112 | private System.Windows.Forms.ComboBox languageComboBox; 113 | private System.Windows.Forms.NumericUpDown logRetentionNumericUpDown; 114 | private System.Windows.Forms.Button saveSettingsButton; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /RoboBackupGUI/SettingsForm.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Windows.Forms; 3 | 4 | namespace RoboBackup { 5 | /// 6 | /// Form with global settings - language and log retention 7 | /// 8 | public partial class SettingsForm : Form { 9 | /// 10 | /// Language returned by successfully completed form. 11 | /// 12 | public string Language { get; private set; } 13 | 14 | /// 15 | /// Log retention in days returned by successfully completed form. 16 | /// 17 | public ushort LogRetention { get; private set; } 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | public SettingsForm() { 23 | InitializeComponent(); 24 | Localize(); 25 | languageComboBox.Items.AddRange(Lang.AvailableLocales.Keys.ToArray()); 26 | languageComboBox.Text = Lang.Get(""); // Name of the language is stored in the empty string key 27 | logRetentionNumericUpDown.Value = Config.LogRetention; 28 | } 29 | 30 | /// 31 | /// Translates the form strings to the language set by . 32 | /// 33 | private void Localize() { 34 | Text = Lang.Get("Settings"); 35 | languageLabel.Text = Lang.Get("Language", ":"); 36 | logRetentionLabel.Text = Lang.Get("LogRetention", ":"); 37 | saveSettingsButton.Text = Lang.Get("SaveSettings"); 38 | } 39 | 40 | /// 41 | /// event handler. Populates and and closes the form. 42 | /// 43 | private void SaveSettingsButton_Click(object sender, System.EventArgs e) { 44 | Language = Lang.AvailableLocales[languageComboBox.Text]; 45 | LogRetention = (ushort)logRetentionNumericUpDown.Value; 46 | DialogResult = DialogResult.OK; 47 | Close(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RoboBackupGUI/TaskForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace RoboBackup 2 | { 3 | partial class TaskForm 4 | { 5 | private System.ComponentModel.IContainer components = null; 6 | 7 | protected override void Dispose(bool disposing) 8 | { 9 | if (disposing && (components != null)) 10 | { 11 | components.Dispose(); 12 | } 13 | base.Dispose(disposing); 14 | } 15 | 16 | #region Windows Form Designer generated code 17 | 18 | /// 19 | /// Required method for Designer support - do not modify 20 | /// the contents of this method with the code editor. 21 | /// 22 | private void InitializeComponent() 23 | { 24 | this.titleLabel = new System.Windows.Forms.Label(); 25 | this.titleTextBox = new System.Windows.Forms.TextBox(); 26 | this.sourceLabel = new System.Windows.Forms.Label(); 27 | this.destinationLabel = new System.Windows.Forms.Label(); 28 | this.fullRadioButton = new System.Windows.Forms.RadioButton(); 29 | this.incrementalRadioButton = new System.Windows.Forms.RadioButton(); 30 | this.sourceTreeView = new System.Windows.Forms.TreeView(); 31 | this.sourceTextBox = new System.Windows.Forms.TextBox(); 32 | this.sourceButton = new System.Windows.Forms.Button(); 33 | this.destinationTextBox = new System.Windows.Forms.TextBox(); 34 | this.destinationButton = new System.Windows.Forms.Button(); 35 | this.differentialRadioButton = new System.Windows.Forms.RadioButton(); 36 | this.retentionLabel = new System.Windows.Forms.Label(); 37 | this.retentionNumericUpDown = new System.Windows.Forms.NumericUpDown(); 38 | this.directoriesGroupBox = new System.Windows.Forms.GroupBox(); 39 | this.methodGroupBox = new System.Windows.Forms.GroupBox(); 40 | this.methodHelpButton = new System.Windows.Forms.Button(); 41 | this.saveTaskButton = new System.Windows.Forms.Button(); 42 | this.scheduleGroupBox = new System.Windows.Forms.GroupBox(); 43 | this.timeDateTimePicker = new System.Windows.Forms.DateTimePicker(); 44 | this.timeLabel = new System.Windows.Forms.Label(); 45 | this.monthdayNumericUpDown = new System.Windows.Forms.NumericUpDown(); 46 | this.dailyRadioButton = new System.Windows.Forms.RadioButton(); 47 | this.weeklyRadioButton = new System.Windows.Forms.RadioButton(); 48 | this.weekdayComboBox = new System.Windows.Forms.ComboBox(); 49 | this.monthlyRadioButton = new System.Windows.Forms.RadioButton(); 50 | this.networkCredentialsGroupBox = new System.Windows.Forms.GroupBox(); 51 | this.passwordTextBox = new System.Windows.Forms.TextBox(); 52 | this.usernameTextBox = new System.Windows.Forms.TextBox(); 53 | this.passwordLabel = new System.Windows.Forms.Label(); 54 | this.usernameLabel = new System.Windows.Forms.Label(); 55 | this.sourceFolderBrowserDialog = new RoboBackup.OpenFolderDialog(); 56 | this.destinationFolderBrowserDialog = new RoboBackup.OpenFolderDialog(); 57 | ((System.ComponentModel.ISupportInitialize)(this.retentionNumericUpDown)).BeginInit(); 58 | this.directoriesGroupBox.SuspendLayout(); 59 | this.methodGroupBox.SuspendLayout(); 60 | this.scheduleGroupBox.SuspendLayout(); 61 | ((System.ComponentModel.ISupportInitialize)(this.monthdayNumericUpDown)).BeginInit(); 62 | this.networkCredentialsGroupBox.SuspendLayout(); 63 | this.SuspendLayout(); 64 | // 65 | // titleLabel 66 | // 67 | this.titleLabel.AutoSize = true; 68 | this.titleLabel.Location = new System.Drawing.Point(18, 15); 69 | this.titleLabel.Name = "titleLabel"; 70 | this.titleLabel.Size = new System.Drawing.Size(30, 13); 71 | this.titleLabel.TabIndex = 0; 72 | this.titleLabel.Text = "Title:"; 73 | // 74 | // titleTextBox 75 | // 76 | this.titleTextBox.Location = new System.Drawing.Point(87, 12); 77 | this.titleTextBox.Name = "titleTextBox"; 78 | this.titleTextBox.Size = new System.Drawing.Size(262, 20); 79 | this.titleTextBox.TabIndex = 1; 80 | // 81 | // sourceLabel 82 | // 83 | this.sourceLabel.AutoSize = true; 84 | this.sourceLabel.Location = new System.Drawing.Point(6, 22); 85 | this.sourceLabel.Name = "sourceLabel"; 86 | this.sourceLabel.Size = new System.Drawing.Size(44, 13); 87 | this.sourceLabel.TabIndex = 0; 88 | this.sourceLabel.Text = "Source:"; 89 | // 90 | // destinationLabel 91 | // 92 | this.destinationLabel.AutoSize = true; 93 | this.destinationLabel.Location = new System.Drawing.Point(6, 173); 94 | this.destinationLabel.Name = "destinationLabel"; 95 | this.destinationLabel.Size = new System.Drawing.Size(63, 13); 96 | this.destinationLabel.TabIndex = 4; 97 | this.destinationLabel.Text = "Destination:"; 98 | // 99 | // fullRadioButton 100 | // 101 | this.fullRadioButton.AutoSize = true; 102 | this.fullRadioButton.Location = new System.Drawing.Point(6, 72); 103 | this.fullRadioButton.Name = "fullRadioButton"; 104 | this.fullRadioButton.Size = new System.Drawing.Size(41, 17); 105 | this.fullRadioButton.TabIndex = 2; 106 | this.fullRadioButton.Text = "Full"; 107 | this.fullRadioButton.UseVisualStyleBackColor = true; 108 | this.fullRadioButton.CheckedChanged += new System.EventHandler(this.FullRadioButton_CheckedChanged); 109 | // 110 | // incrementalRadioButton 111 | // 112 | this.incrementalRadioButton.AutoSize = true; 113 | this.incrementalRadioButton.Checked = true; 114 | this.incrementalRadioButton.Location = new System.Drawing.Point(6, 20); 115 | this.incrementalRadioButton.Name = "incrementalRadioButton"; 116 | this.incrementalRadioButton.Size = new System.Drawing.Size(80, 17); 117 | this.incrementalRadioButton.TabIndex = 0; 118 | this.incrementalRadioButton.TabStop = true; 119 | this.incrementalRadioButton.Text = "Incremental"; 120 | this.incrementalRadioButton.UseVisualStyleBackColor = true; 121 | this.incrementalRadioButton.CheckedChanged += new System.EventHandler(this.IncrementalRadioButton_CheckedChanged); 122 | // 123 | // sourceTreeView 124 | // 125 | this.sourceTreeView.CheckBoxes = true; 126 | this.sourceTreeView.FullRowSelect = true; 127 | this.sourceTreeView.Location = new System.Drawing.Point(75, 45); 128 | this.sourceTreeView.Name = "sourceTreeView"; 129 | this.sourceTreeView.Size = new System.Drawing.Size(255, 119); 130 | this.sourceTreeView.TabIndex = 3; 131 | this.sourceTreeView.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.SourceTreeView_AfterCheck); 132 | // 133 | // sourceTextBox 134 | // 135 | this.sourceTextBox.BackColor = System.Drawing.SystemColors.Window; 136 | this.sourceTextBox.Location = new System.Drawing.Point(75, 19); 137 | this.sourceTextBox.Name = "sourceTextBox"; 138 | this.sourceTextBox.ReadOnly = true; 139 | this.sourceTextBox.Size = new System.Drawing.Size(224, 20); 140 | this.sourceTextBox.TabIndex = 1; 141 | this.sourceTextBox.TextChanged += new System.EventHandler(this.SourceTextBox_TextChanged); 142 | // 143 | // sourceButton 144 | // 145 | this.sourceButton.Location = new System.Drawing.Point(305, 19); 146 | this.sourceButton.Name = "sourceButton"; 147 | this.sourceButton.Size = new System.Drawing.Size(25, 21); 148 | this.sourceButton.TabIndex = 2; 149 | this.sourceButton.Text = "..."; 150 | this.sourceButton.UseVisualStyleBackColor = true; 151 | this.sourceButton.Click += new System.EventHandler(this.SourceButton_Click); 152 | // 153 | // destinationTextBox 154 | // 155 | this.destinationTextBox.BackColor = System.Drawing.SystemColors.Window; 156 | this.destinationTextBox.Location = new System.Drawing.Point(75, 170); 157 | this.destinationTextBox.Name = "destinationTextBox"; 158 | this.destinationTextBox.ReadOnly = true; 159 | this.destinationTextBox.Size = new System.Drawing.Size(224, 20); 160 | this.destinationTextBox.TabIndex = 5; 161 | this.destinationTextBox.TextChanged += new System.EventHandler(this.DestinationTextBox_TextChanged); 162 | // 163 | // destinationButton 164 | // 165 | this.destinationButton.Location = new System.Drawing.Point(305, 170); 166 | this.destinationButton.Name = "destinationButton"; 167 | this.destinationButton.Size = new System.Drawing.Size(25, 21); 168 | this.destinationButton.TabIndex = 6; 169 | this.destinationButton.Text = "..."; 170 | this.destinationButton.UseVisualStyleBackColor = true; 171 | this.destinationButton.Click += new System.EventHandler(this.DestinationButton_Click); 172 | // 173 | // differentialRadioButton 174 | // 175 | this.differentialRadioButton.AutoSize = true; 176 | this.differentialRadioButton.Location = new System.Drawing.Point(6, 46); 177 | this.differentialRadioButton.Name = "differentialRadioButton"; 178 | this.differentialRadioButton.Size = new System.Drawing.Size(75, 17); 179 | this.differentialRadioButton.TabIndex = 1; 180 | this.differentialRadioButton.Text = "Differential"; 181 | this.differentialRadioButton.UseVisualStyleBackColor = true; 182 | this.differentialRadioButton.CheckedChanged += new System.EventHandler(this.DifferentialRadioButton_CheckedChanged); 183 | // 184 | // retentionLabel 185 | // 186 | this.retentionLabel.AutoSize = true; 187 | this.retentionLabel.Location = new System.Drawing.Point(6, 99); 188 | this.retentionLabel.Name = "retentionLabel"; 189 | this.retentionLabel.Size = new System.Drawing.Size(56, 13); 190 | this.retentionLabel.TabIndex = 3; 191 | this.retentionLabel.Text = "Retention:"; 192 | // 193 | // retentionNumericUpDown 194 | // 195 | this.retentionNumericUpDown.Enabled = false; 196 | this.retentionNumericUpDown.Location = new System.Drawing.Point(95, 97); 197 | this.retentionNumericUpDown.Maximum = new decimal(new int[] { 198 | 99999, 199 | 0, 200 | 0, 201 | 0}); 202 | this.retentionNumericUpDown.Minimum = new decimal(new int[] { 203 | 1, 204 | 0, 205 | 0, 206 | 0}); 207 | this.retentionNumericUpDown.Name = "retentionNumericUpDown"; 208 | this.retentionNumericUpDown.Size = new System.Drawing.Size(65, 20); 209 | this.retentionNumericUpDown.TabIndex = 4; 210 | this.retentionNumericUpDown.Value = new decimal(new int[] { 211 | 1, 212 | 0, 213 | 0, 214 | 0}); 215 | // 216 | // directoriesGroupBox 217 | // 218 | this.directoriesGroupBox.Controls.Add(this.sourceLabel); 219 | this.directoriesGroupBox.Controls.Add(this.destinationLabel); 220 | this.directoriesGroupBox.Controls.Add(this.sourceTreeView); 221 | this.directoriesGroupBox.Controls.Add(this.sourceTextBox); 222 | this.directoriesGroupBox.Controls.Add(this.destinationButton); 223 | this.directoriesGroupBox.Controls.Add(this.sourceButton); 224 | this.directoriesGroupBox.Controls.Add(this.destinationTextBox); 225 | this.directoriesGroupBox.Location = new System.Drawing.Point(12, 38); 226 | this.directoriesGroupBox.Name = "directoriesGroupBox"; 227 | this.directoriesGroupBox.Size = new System.Drawing.Size(339, 200); 228 | this.directoriesGroupBox.TabIndex = 2; 229 | this.directoriesGroupBox.TabStop = false; 230 | this.directoriesGroupBox.Text = "Directories"; 231 | // 232 | // methodGroupBox 233 | // 234 | this.methodGroupBox.Controls.Add(this.methodHelpButton); 235 | this.methodGroupBox.Controls.Add(this.retentionLabel); 236 | this.methodGroupBox.Controls.Add(this.differentialRadioButton); 237 | this.methodGroupBox.Controls.Add(this.fullRadioButton); 238 | this.methodGroupBox.Controls.Add(this.retentionNumericUpDown); 239 | this.methodGroupBox.Controls.Add(this.incrementalRadioButton); 240 | this.methodGroupBox.Location = new System.Drawing.Point(12, 324); 241 | this.methodGroupBox.Name = "methodGroupBox"; 242 | this.methodGroupBox.Size = new System.Drawing.Size(166, 125); 243 | this.methodGroupBox.TabIndex = 4; 244 | this.methodGroupBox.TabStop = false; 245 | this.methodGroupBox.Text = "Method"; 246 | // 247 | // methodHelpButton 248 | // 249 | this.methodHelpButton.Image = global::RoboBackup.Properties.Resources.HelpIcon; 250 | this.methodHelpButton.Location = new System.Drawing.Point(136, 13); 251 | this.methodHelpButton.Name = "methodHelpButton"; 252 | this.methodHelpButton.Size = new System.Drawing.Size(24, 24); 253 | this.methodHelpButton.TabIndex = 5; 254 | this.methodHelpButton.UseVisualStyleBackColor = true; 255 | this.methodHelpButton.Click += new System.EventHandler(this.MethodHelpButton_Click); 256 | // 257 | // saveTaskButton 258 | // 259 | this.saveTaskButton.Location = new System.Drawing.Point(12, 455); 260 | this.saveTaskButton.Name = "saveTaskButton"; 261 | this.saveTaskButton.Size = new System.Drawing.Size(339, 23); 262 | this.saveTaskButton.TabIndex = 6; 263 | this.saveTaskButton.Text = "SaveTask"; 264 | this.saveTaskButton.Click += new System.EventHandler(this.SaveTaskButton_Click); 265 | // 266 | // scheduleGroupBox 267 | // 268 | this.scheduleGroupBox.Controls.Add(this.timeDateTimePicker); 269 | this.scheduleGroupBox.Controls.Add(this.timeLabel); 270 | this.scheduleGroupBox.Controls.Add(this.monthdayNumericUpDown); 271 | this.scheduleGroupBox.Controls.Add(this.dailyRadioButton); 272 | this.scheduleGroupBox.Controls.Add(this.weeklyRadioButton); 273 | this.scheduleGroupBox.Controls.Add(this.weekdayComboBox); 274 | this.scheduleGroupBox.Controls.Add(this.monthlyRadioButton); 275 | this.scheduleGroupBox.Location = new System.Drawing.Point(185, 324); 276 | this.scheduleGroupBox.Name = "scheduleGroupBox"; 277 | this.scheduleGroupBox.Size = new System.Drawing.Size(166, 125); 278 | this.scheduleGroupBox.TabIndex = 5; 279 | this.scheduleGroupBox.TabStop = false; 280 | this.scheduleGroupBox.Text = "Schedule"; 281 | // 282 | // timeDateTimePicker 283 | // 284 | this.timeDateTimePicker.CustomFormat = "HH:mm"; 285 | this.timeDateTimePicker.Format = System.Windows.Forms.DateTimePickerFormat.Custom; 286 | this.timeDateTimePicker.Location = new System.Drawing.Point(106, 97); 287 | this.timeDateTimePicker.Name = "timeDateTimePicker"; 288 | this.timeDateTimePicker.ShowUpDown = true; 289 | this.timeDateTimePicker.Size = new System.Drawing.Size(51, 20); 290 | this.timeDateTimePicker.TabIndex = 6; 291 | this.timeDateTimePicker.Value = new System.DateTime(2018, 1, 1, 0, 0, 0, 0); 292 | // 293 | // timeLabel 294 | // 295 | this.timeLabel.AutoSize = true; 296 | this.timeLabel.Location = new System.Drawing.Point(6, 100); 297 | this.timeLabel.Name = "timeLabel"; 298 | this.timeLabel.Size = new System.Drawing.Size(43, 13); 299 | this.timeLabel.TabIndex = 5; 300 | this.timeLabel.Text = "AtTime:"; 301 | // 302 | // monthdayNumericUpDown 303 | // 304 | this.monthdayNumericUpDown.Location = new System.Drawing.Point(106, 72); 305 | this.monthdayNumericUpDown.Maximum = new decimal(new int[] { 306 | 31, 307 | 0, 308 | 0, 309 | 0}); 310 | this.monthdayNumericUpDown.Minimum = new decimal(new int[] { 311 | 1, 312 | 0, 313 | 0, 314 | 0}); 315 | this.monthdayNumericUpDown.Name = "monthdayNumericUpDown"; 316 | this.monthdayNumericUpDown.Size = new System.Drawing.Size(51, 20); 317 | this.monthdayNumericUpDown.TabIndex = 4; 318 | this.monthdayNumericUpDown.Value = new decimal(new int[] { 319 | 1, 320 | 0, 321 | 0, 322 | 0}); 323 | this.monthdayNumericUpDown.ValueChanged += new System.EventHandler(this.MonthdayNumericUpDown_ValueChanged); 324 | // 325 | // dailyRadioButton 326 | // 327 | this.dailyRadioButton.AutoSize = true; 328 | this.dailyRadioButton.Checked = true; 329 | this.dailyRadioButton.Location = new System.Drawing.Point(6, 20); 330 | this.dailyRadioButton.Name = "dailyRadioButton"; 331 | this.dailyRadioButton.Size = new System.Drawing.Size(48, 17); 332 | this.dailyRadioButton.TabIndex = 0; 333 | this.dailyRadioButton.TabStop = true; 334 | this.dailyRadioButton.Text = "Daily"; 335 | this.dailyRadioButton.UseVisualStyleBackColor = true; 336 | // 337 | // weeklyRadioButton 338 | // 339 | this.weeklyRadioButton.AutoSize = true; 340 | this.weeklyRadioButton.Location = new System.Drawing.Point(6, 46); 341 | this.weeklyRadioButton.Name = "weeklyRadioButton"; 342 | this.weeklyRadioButton.Size = new System.Drawing.Size(87, 17); 343 | this.weeklyRadioButton.TabIndex = 1; 344 | this.weeklyRadioButton.Text = "DayOfWeek:"; 345 | this.weeklyRadioButton.UseVisualStyleBackColor = true; 346 | // 347 | // weekdayComboBox 348 | // 349 | this.weekdayComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 350 | this.weekdayComboBox.Location = new System.Drawing.Point(106, 45); 351 | this.weekdayComboBox.Name = "weekdayComboBox"; 352 | this.weekdayComboBox.Size = new System.Drawing.Size(51, 21); 353 | this.weekdayComboBox.TabIndex = 2; 354 | this.weekdayComboBox.SelectedIndexChanged += new System.EventHandler(this.WeekdayComboBox_SelectedIndexChanged); 355 | // 356 | // monthlyRadioButton 357 | // 358 | this.monthlyRadioButton.AutoSize = true; 359 | this.monthlyRadioButton.Location = new System.Drawing.Point(6, 72); 360 | this.monthlyRadioButton.Name = "monthlyRadioButton"; 361 | this.monthlyRadioButton.Size = new System.Drawing.Size(88, 17); 362 | this.monthlyRadioButton.TabIndex = 3; 363 | this.monthlyRadioButton.Text = "DayOfMonth:"; 364 | this.monthlyRadioButton.UseVisualStyleBackColor = true; 365 | // 366 | // networkCredentialsGroupBox 367 | // 368 | this.networkCredentialsGroupBox.Controls.Add(this.passwordTextBox); 369 | this.networkCredentialsGroupBox.Controls.Add(this.usernameTextBox); 370 | this.networkCredentialsGroupBox.Controls.Add(this.passwordLabel); 371 | this.networkCredentialsGroupBox.Controls.Add(this.usernameLabel); 372 | this.networkCredentialsGroupBox.Location = new System.Drawing.Point(12, 244); 373 | this.networkCredentialsGroupBox.Name = "networkCredentialsGroupBox"; 374 | this.networkCredentialsGroupBox.Size = new System.Drawing.Size(339, 74); 375 | this.networkCredentialsGroupBox.TabIndex = 3; 376 | this.networkCredentialsGroupBox.TabStop = false; 377 | this.networkCredentialsGroupBox.Text = "NetworkCredentials"; 378 | // 379 | // passwordTextBox 380 | // 381 | this.passwordTextBox.Enabled = false; 382 | this.passwordTextBox.Location = new System.Drawing.Point(75, 45); 383 | this.passwordTextBox.Name = "passwordTextBox"; 384 | this.passwordTextBox.Size = new System.Drawing.Size(255, 20); 385 | this.passwordTextBox.TabIndex = 3; 386 | this.passwordTextBox.UseSystemPasswordChar = true; 387 | // 388 | // usernameTextBox 389 | // 390 | this.usernameTextBox.Enabled = false; 391 | this.usernameTextBox.Location = new System.Drawing.Point(75, 19); 392 | this.usernameTextBox.Name = "usernameTextBox"; 393 | this.usernameTextBox.Size = new System.Drawing.Size(255, 20); 394 | this.usernameTextBox.TabIndex = 1; 395 | // 396 | // passwordLabel 397 | // 398 | this.passwordLabel.AutoSize = true; 399 | this.passwordLabel.Location = new System.Drawing.Point(6, 48); 400 | this.passwordLabel.Name = "passwordLabel"; 401 | this.passwordLabel.Size = new System.Drawing.Size(56, 13); 402 | this.passwordLabel.TabIndex = 2; 403 | this.passwordLabel.Text = "Password:"; 404 | // 405 | // usernameLabel 406 | // 407 | this.usernameLabel.AutoSize = true; 408 | this.usernameLabel.Location = new System.Drawing.Point(6, 22); 409 | this.usernameLabel.Name = "usernameLabel"; 410 | this.usernameLabel.Size = new System.Drawing.Size(58, 13); 411 | this.usernameLabel.TabIndex = 0; 412 | this.usernameLabel.Text = "Username:"; 413 | // 414 | // sourceFolderBrowserDialog 415 | // 416 | this.sourceFolderBrowserDialog.SelectedPath = null; 417 | // 418 | // destinationFolderBrowserDialog 419 | // 420 | this.destinationFolderBrowserDialog.SelectedPath = null; 421 | // 422 | // TaskForm 423 | // 424 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 425 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 426 | this.ClientSize = new System.Drawing.Size(361, 485); 427 | this.Controls.Add(this.networkCredentialsGroupBox); 428 | this.Controls.Add(this.scheduleGroupBox); 429 | this.Controls.Add(this.saveTaskButton); 430 | this.Controls.Add(this.methodGroupBox); 431 | this.Controls.Add(this.directoriesGroupBox); 432 | this.Controls.Add(this.titleTextBox); 433 | this.Controls.Add(this.titleLabel); 434 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 435 | this.MaximizeBox = false; 436 | this.MinimizeBox = false; 437 | this.Name = "TaskForm"; 438 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 439 | this.Text = "Task"; 440 | ((System.ComponentModel.ISupportInitialize)(this.retentionNumericUpDown)).EndInit(); 441 | this.directoriesGroupBox.ResumeLayout(false); 442 | this.directoriesGroupBox.PerformLayout(); 443 | this.methodGroupBox.ResumeLayout(false); 444 | this.methodGroupBox.PerformLayout(); 445 | this.scheduleGroupBox.ResumeLayout(false); 446 | this.scheduleGroupBox.PerformLayout(); 447 | ((System.ComponentModel.ISupportInitialize)(this.monthdayNumericUpDown)).EndInit(); 448 | this.networkCredentialsGroupBox.ResumeLayout(false); 449 | this.networkCredentialsGroupBox.PerformLayout(); 450 | this.ResumeLayout(false); 451 | this.PerformLayout(); 452 | 453 | } 454 | 455 | #endregion 456 | 457 | private System.Windows.Forms.Label titleLabel; 458 | private System.Windows.Forms.TextBox titleTextBox; 459 | private System.Windows.Forms.Label sourceLabel; 460 | private System.Windows.Forms.Label destinationLabel; 461 | private System.Windows.Forms.RadioButton fullRadioButton; 462 | private System.Windows.Forms.RadioButton incrementalRadioButton; 463 | private System.Windows.Forms.TreeView sourceTreeView; 464 | private System.Windows.Forms.TextBox sourceTextBox; 465 | private System.Windows.Forms.Button sourceButton; 466 | private System.Windows.Forms.TextBox destinationTextBox; 467 | private System.Windows.Forms.Button destinationButton; 468 | private System.Windows.Forms.RadioButton differentialRadioButton; 469 | private System.Windows.Forms.Label retentionLabel; 470 | private System.Windows.Forms.NumericUpDown retentionNumericUpDown; 471 | private System.Windows.Forms.GroupBox directoriesGroupBox; 472 | private System.Windows.Forms.GroupBox methodGroupBox; 473 | private System.Windows.Forms.Button saveTaskButton; 474 | private System.Windows.Forms.GroupBox scheduleGroupBox; 475 | private System.Windows.Forms.Label timeLabel; 476 | private System.Windows.Forms.NumericUpDown monthdayNumericUpDown; 477 | private System.Windows.Forms.ComboBox weekdayComboBox; 478 | private System.Windows.Forms.RadioButton monthlyRadioButton; 479 | private System.Windows.Forms.RadioButton weeklyRadioButton; 480 | private System.Windows.Forms.RadioButton dailyRadioButton; 481 | private System.Windows.Forms.DateTimePicker timeDateTimePicker; 482 | private System.Windows.Forms.Button methodHelpButton; 483 | private OpenFolderDialog sourceFolderBrowserDialog; 484 | private OpenFolderDialog destinationFolderBrowserDialog; 485 | private System.Windows.Forms.GroupBox networkCredentialsGroupBox; 486 | private System.Windows.Forms.TextBox passwordTextBox; 487 | private System.Windows.Forms.TextBox usernameTextBox; 488 | private System.Windows.Forms.Label passwordLabel; 489 | private System.Windows.Forms.Label usernameLabel; 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /RoboBackupGUI/TaskForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace RoboBackup { 6 | /// 7 | /// Form to create or edit backup tasks details. 8 | /// 9 | public partial class TaskForm : Form { 10 | /// 11 | /// returned by successfully completed form. 12 | /// 13 | public Task ResultTask { get; private set; } 14 | 15 | /// 16 | /// to be reused when updating a . 17 | /// 18 | private string _guid; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public TaskForm() { 24 | InitializeComponent(); 25 | Localize(); 26 | } 27 | 28 | /// 29 | /// Initializes a new instance of the class and populates form controls with the properties. 30 | /// 31 | /// to be edited. 32 | public TaskForm(Task task) : this() { 33 | _guid = task.Guid; 34 | titleTextBox.Text = task.Title; 35 | sourceFolderBrowserDialog.SelectedPath = sourceTextBox.Text = task.Source; 36 | PopulateSourceTreeView(); 37 | foreach (string path in task.ExcludedDirs) { 38 | TreeNode node = DirectoryTree.FindNode(sourceTreeView.Nodes, path); 39 | if (node != null) { 40 | node.Checked = false; 41 | } 42 | } 43 | foreach (string path in task.ExcludedFiles) { 44 | TreeNode node = DirectoryTree.FindNode(sourceTreeView.Nodes, path); 45 | if (node != null) { 46 | node.Checked = false; 47 | } 48 | } 49 | destinationFolderBrowserDialog.SelectedPath = destinationTextBox.Text = task.Destination; 50 | if (!string.IsNullOrEmpty(task.Credential)) { 51 | usernameTextBox.Text = Lang.Get("ReenterCredentials"); 52 | } 53 | if (task.Method == Method.Differential) { 54 | differentialRadioButton.Checked = true; 55 | retentionNumericUpDown.Value = task.Retention; 56 | } else if (task.Method == Method.Full) { 57 | fullRadioButton.Checked = true; 58 | retentionNumericUpDown.Value = task.Retention; 59 | } 60 | if (task.Period == Period.Weekly) { 61 | weekdayComboBox.SelectedIndex = (int)task.DayOfWeek; 62 | } else if (task.Period == Period.Monthly) { 63 | monthdayNumericUpDown.Value = task.DayOfMonth; 64 | } 65 | timeDateTimePicker.Value = DateTime.Today.AddHours(task.Hour).AddMinutes(task.Minute); ; 66 | } 67 | 68 | /// 69 | /// Enables or disables network credentials controls based on the source and destination directory UNC conformance. 70 | /// 71 | private void CheckUncPath() { 72 | bool unc = Unc.IsUncPath(sourceTextBox.Text) || Unc.IsUncPath(destinationTextBox.Text); 73 | usernameTextBox.Enabled = unc; 74 | passwordTextBox.Enabled = unc; 75 | if (!unc) { 76 | usernameTextBox.Text = string.Empty; 77 | passwordTextBox.Text = string.Empty; 78 | } 79 | } 80 | 81 | /// 82 | /// Collects form control values and creates a form result. 83 | /// 84 | private void CreateTask() { 85 | _guid = _guid ?? Guid.NewGuid().ToString(); 86 | Task task = new Task() { Guid = _guid, Title = titleTextBox.Text, Source = sourceTextBox.Text, Destination = destinationTextBox.Text }; 87 | List excludedNodes = DirectoryTree.GetExcludedNodes(sourceTreeView.Nodes); 88 | List excludedDirs = new List(); 89 | List excludedFiles = new List(); 90 | foreach (TreeNode node in excludedNodes) { 91 | if ((EntryType)node.Tag == EntryType.Directory) { 92 | excludedDirs.Add(node.FullPath); 93 | } else { 94 | excludedFiles.Add(node.FullPath); 95 | } 96 | } 97 | task.ExcludedDirs = excludedDirs.ToArray(); 98 | task.ExcludedFiles = excludedFiles.ToArray(); 99 | task.Credential = Credential.Encrypt(_guid, new Credential() { Username = usernameTextBox.Text, Password = passwordTextBox.Text }); 100 | if (incrementalRadioButton.Checked) { 101 | task.Method = Method.Incremental; 102 | task.Retention = 1; 103 | } else if (differentialRadioButton.Checked) { 104 | task.Method = Method.Differential; 105 | task.Retention = (ushort)retentionNumericUpDown.Value; 106 | } else { 107 | task.Method = Method.Full; 108 | task.Retention = (ushort)retentionNumericUpDown.Value; 109 | } 110 | if (dailyRadioButton.Checked) { 111 | task.Period = Period.Daily; 112 | } else if (weeklyRadioButton.Checked) { 113 | task.Period = Period.Weekly; 114 | task.DayOfWeek = (DayOfWeek)weekdayComboBox.SelectedIndex; 115 | } else { 116 | task.Period = Period.Monthly; 117 | task.DayOfMonth = (byte)monthdayNumericUpDown.Value; 118 | } 119 | task.Hour = (byte)timeDateTimePicker.Value.Hour; 120 | task.Minute = (byte)timeDateTimePicker.Value.Minute; 121 | ResultTask = task; 122 | } 123 | 124 | /// 125 | /// event handler. Displays destination folder selection dialog. 126 | /// 127 | private void DestinationButton_Click(object sender, EventArgs e) { 128 | if (destinationFolderBrowserDialog.ShowDialog() == DialogResult.OK) { 129 | destinationTextBox.Text = destinationFolderBrowserDialog.SelectedPath = Unc.TranslatePath(destinationFolderBrowserDialog.SelectedPath); 130 | } 131 | } 132 | 133 | /// 134 | /// event handler. Enables control. 135 | /// 136 | private void DifferentialRadioButton_CheckedChanged(object sender, EventArgs e) { 137 | retentionNumericUpDown.Enabled = true; 138 | } 139 | 140 | /// 141 | /// event handler. Enables or disables network credentials input fields. 142 | /// 143 | private void DestinationTextBox_TextChanged(object sender, EventArgs e) { 144 | CheckUncPath(); 145 | } 146 | 147 | /// 148 | /// event handler. Enables control. 149 | /// 150 | private void FullRadioButton_CheckedChanged(object sender, EventArgs e) { 151 | retentionNumericUpDown.Enabled = true; 152 | } 153 | 154 | /// 155 | /// event handler. Disables control. 156 | /// 157 | private void IncrementalRadioButton_CheckedChanged(object sender, EventArgs e) { 158 | retentionNumericUpDown.Enabled = false; 159 | } 160 | 161 | /// 162 | /// Translates the form strings to the language set by . 163 | /// 164 | private void Localize() { 165 | Text = Lang.Get("Task"); 166 | titleLabel.Text = Lang.Get("Title", ":"); 167 | directoriesGroupBox.Text = Lang.Get("Directories"); 168 | sourceLabel.Text = Lang.Get("Source", ":"); 169 | destinationLabel.Text = Lang.Get("Destination", ":"); 170 | networkCredentialsGroupBox.Text = Lang.Get("NetworkCredentials"); 171 | usernameLabel.Text = Lang.Get("Username", ":"); 172 | passwordLabel.Text = Lang.Get("Password", ":"); 173 | methodGroupBox.Text = Lang.Get("Method"); 174 | incrementalRadioButton.Text = Lang.Get("Incremental"); 175 | differentialRadioButton.Text = Lang.Get("Differential"); 176 | fullRadioButton.Text = Lang.Get("Full"); 177 | retentionLabel.Text = Lang.Get("Retention", ":"); 178 | scheduleGroupBox.Text = Lang.Get("Schedule"); 179 | dailyRadioButton.Text = Lang.Get("Daily"); 180 | weeklyRadioButton.Text = Lang.Get("DayOfWeek", ":"); 181 | monthlyRadioButton.Text = Lang.Get("DayOfMonth", ":"); 182 | timeLabel.Text = Lang.Get("AtTime", ":"); 183 | saveTaskButton.Text = Lang.Get("SaveTask"); 184 | foreach (DayOfWeek dow in Enum.GetValues(typeof(DayOfWeek))) { 185 | weekdayComboBox.Items.Add(Lang.Get(dow.ToString())); 186 | } 187 | weekdayComboBox.Text = Lang.Get(DayOfWeek.Sunday.ToString()); 188 | dailyRadioButton.Checked = true; 189 | } 190 | 191 | /// 192 | /// event handler. Displays a with backup methods explanation. 193 | /// 194 | private void MethodHelpButton_Click(object sender, EventArgs e) { 195 | string message = string.Format("{0}\n\n{1}\n\n{2}\n\n{3}", Lang.Get("HelpIncremental"), Lang.Get("HelpDifferential"), Lang.Get("HelpFull"), Lang.Get("HelpRetention")); 196 | MessageBox.Show(message, Lang.Get("Help"), MessageBoxButtons.OK, MessageBoxIcon.Question); 197 | } 198 | 199 | /// 200 | /// monthdayNumericUpDown.ValueChanged event handler. Switches schedule selection to "monthly" when the day of month value changes. 201 | /// 202 | private void MonthdayNumericUpDown_ValueChanged(object sender, EventArgs e) { 203 | monthlyRadioButton.Checked = true; 204 | } 205 | 206 | /// 207 | /// Populates the with nodes corresponding to filesystem objects located under the directory selected by . 208 | /// 209 | private void PopulateSourceTreeView() { 210 | sourceTreeView.Nodes.Clear(); 211 | sourceTreeView.Nodes.AddRange(DirectoryTree.LoadNodes(sourceFolderBrowserDialog.SelectedPath)); 212 | } 213 | 214 | /// 215 | /// saveTaskButton.Click event handler. Populates and closes the form. 216 | /// 217 | private void SaveTaskButton_Click(object sender, EventArgs e) { 218 | if (string.IsNullOrEmpty(titleTextBox.Text) || string.IsNullOrEmpty(sourceTextBox.Text) || string.IsNullOrEmpty(destinationTextBox.Text)) { 219 | MessageBox.Show(Lang.Get("IncompleteTaskForm"), Lang.Get("Error"), MessageBoxButtons.OK, MessageBoxIcon.Error); 220 | return; 221 | } 222 | CreateTask(); 223 | DialogResult = DialogResult.OK; 224 | Close(); 225 | } 226 | 227 | /// 228 | /// event handler. Displays source folder selection dialog. 229 | /// 230 | private void SourceButton_Click(object sender, EventArgs e) { 231 | if (sourceFolderBrowserDialog.ShowDialog() == DialogResult.OK) { 232 | sourceTextBox.Text = sourceFolderBrowserDialog.SelectedPath = Unc.TranslatePath(sourceFolderBrowserDialog.SelectedPath); 233 | PopulateSourceTreeView(); 234 | } 235 | } 236 | 237 | /// 238 | /// event handler. Enables or disables network credentials input fields. 239 | /// 240 | private void SourceTextBox_TextChanged(object sender, EventArgs e) { 241 | CheckUncPath(); 242 | } 243 | 244 | /// 245 | /// event handler. Toggles for the of the node which received the click. 246 | /// 247 | private void SourceTreeView_AfterCheck(object sender, TreeViewEventArgs e) { 248 | DirectoryTree.ToggleChildren(e.Node, e.Node.Checked); 249 | } 250 | 251 | /// 252 | /// event handler. Switches schedule selection to "weekly" when the day of week value changes. 253 | /// 254 | private void WeekdayComboBox_SelectedIndexChanged(object sender, EventArgs e) { 255 | weeklyRadioButton.Checked = true; 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /RoboBackupGUI/lang/cs.txt: -------------------------------------------------------------------------------- 1 | =Česky 2 | About=O programu 3 | AtTime=V čase 4 | Author=Autor 5 | BackupStarted=Požadavek na okamžité spuštění zálohy byl odeslán službě. 6 | Daily=Denně 7 | DailyAbbr=Denně 8 | DayOfMonth=Den v měsíci 9 | DayOfWeek=Den v týdnu 10 | DeleteConfirmation=Skutečně chcete označenou úlohu smazat? 11 | DeleteTask=Smazat úlohu 12 | Destination=Cíl 13 | Differential=Rozdílová 14 | DifferentialAbbr=Roz. 15 | Directories=Adresáře 16 | EditTask=Upravit úlohu 17 | Friday=Pá 18 | Full=Plná 19 | FullAbbr=Plná 20 | Help=Nápověda 21 | HelpDifferential=Rozdílová = Každý běh vytvoří podadresář s časovým razítkem v cílovém adresáři. Soubory jsou "hardlinkovány" z podadresáře předchozí zálohy (pokud existuje) a následně jsou synchronizovány rozdíly se zdrojovým adresářem. 22 | HelpFull=Plná = Každý běh vytvoří podadresář s časovým razítkem v cílovém adresáři. Soubory jsou pokaždé kopírovány ze zdrojového adresáře. 23 | HelpIncremental=Přírůstková = Jediný cílový adresář. Nové a změněné soubory jsou ze zdrojového adresáře zkopírovány do cílového adresáře. Soubory smazané ve zdrojovém adresáři jsou ponechány (nejsou smazány) v cílovém adresáři. 24 | HelpRetention=Retence je uvedena jako počet podadresářů s časovými razítky. Při dosažení limitu bude nejstarší adresář smazán. S retencí nastavenou na 1 nevytváří rozdílová a plná metoda podadresáře s časovými razítky, ale pouze synchronizuje obsah zdrojového adresáře s cílovým adresářem. 25 | IncompleteTaskForm=Vyplňte prosím název, zdroj a cíl. 26 | Incremental=Přírůstková 27 | IncrementalAbbr=Pří. 28 | Information=Informace 29 | Language=Jazyk 30 | LogRetention=Retence logů (dny) 31 | Method=Způsob 32 | Monday=Po 33 | MonthlyAbbr=Měs. 34 | NetworkCredentials=Síťová pověření 35 | NewTask=Nová úloha 36 | NoTaskLogs=Tato úloha ještě nemá žádné záznamy. 37 | Password=Heslo 38 | Question=Otázka 39 | ReenterCredentials=Před uložením znovu zadejte pověření 40 | Retention=Retence 41 | RunTask=Ihned spustit úlohu 42 | Saturday=So 43 | SaveSettings=Uložit nastavení 44 | SaveTask=Uložit úlohu 45 | Schedule=Plán 46 | Settings=Nastavení 47 | ShowTaskLogs=Zobrazit logy úlohy 48 | Source=Zdroj 49 | SourceCode=Zdrojový kód 50 | Sunday=Ne 51 | Task=Úloha 52 | Thursday=Čt 53 | Title=Název 54 | Tuesday=Út 55 | UnableToConnectService=Nepodařilo se připojit ke službě. 56 | Username=Uživatel 57 | Version=Verze 58 | Wednesday=St 59 | WeeklyAbbr=Týdně 60 | -------------------------------------------------------------------------------- /RoboBackupGUI/lang/en.txt: -------------------------------------------------------------------------------- 1 | =English 2 | About=About 3 | AtTime=At time 4 | Author=Author 5 | BackupStarted=Request for immediate backup execution was sent to the service. 6 | Daily=Daily 7 | DailyAbbr=Daily 8 | DayOfMonth=Day of month 9 | DayOfWeek=Den of week 10 | DeleteConfirmation=Do you really want do delete selected task? 11 | DeleteTask=Delete task 12 | Destination=Destination 13 | Differential=Differential 14 | DifferentialAbbr=Diff 15 | Directories=Directories 16 | EditTask=Edit task 17 | Friday=Fri 18 | Full=Full 19 | FullAbbr=Full 20 | Help=Help 21 | HelpDifferential=Differential = Timestamped subdirectory under destination directory for each run. Files are hardlinked from the subdirectory of the previous backup (if there is one) and then the differences are synchronized from the source directory. 22 | HelpFull=Full = Timestamped subdirectory under destination directory for each run. Files are always fully copied from the source directory. 23 | HelpIncremental=Incremental = Single destination directory. New and modified files in the source directory are copied to the destination directory. Files deleted from the source directory are kept (not deleted) in the destination directory. 24 | HelpRetention=Retention is set as number of timestamped subdirectories. When the limit is reached, the oldest directory will be deleted. With retention set to 1, differential and full methods don't create timestamped directories and only synchronize the contents of the source directory to the destination directory. 25 | IncompleteTaskForm=Please fill in title, source and destination. 26 | Incremental=Incremental 27 | IncrementalAbbr=Inc 28 | Information=Information 29 | Language=Language 30 | LogRetention=Log retention (days) 31 | Method=Method 32 | Monday=Mon 33 | MonthlyAbbr=Monthly 34 | NetworkCredentials=Network credentials 35 | NewTask=New task 36 | NoTaskLogs=This task has no logs yet. 37 | Password=Password 38 | Question=Question 39 | ReenterCredentials=Re-enter credentials before saving 40 | Retention=Retention 41 | RunTask=Run task now 42 | Saturday=Sat 43 | SaveSettings=Save settings 44 | SaveTask=Save task 45 | Schedule=Schedule 46 | Settings=Settings 47 | ShowTaskLogs=Show task logs 48 | Source=Source 49 | SourceCode=Source code 50 | Sunday=Sun 51 | Task=Task 52 | Thursday=Thu 53 | Title=Title 54 | Tuesday=Tue 55 | UnableToConnectService=Unable to connect to the service. 56 | Username=Username 57 | Version=Version 58 | Wednesday=Wed 59 | WeeklyAbbr=Weekly 60 | -------------------------------------------------------------------------------- /RoboBackupService/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Xml.Serialization; 7 | 8 | namespace RoboBackup { 9 | /// 10 | /// Application configuration for both GUI and service parts. 11 | /// 12 | public static class Config { 13 | /// 14 | /// Full path to the language directory. 15 | /// 16 | public static string LangRoot { get; private set; } 17 | 18 | /// 19 | /// ISO 639-1 code of the selected language. 20 | /// 21 | public static string Language { get; set; } = "en"; 22 | 23 | /// 24 | /// Log retention set in days. 25 | /// 26 | public static ushort LogRetention { get; set; } = 30; 27 | 28 | /// 29 | /// Full path to the log directory. 30 | /// 31 | public static string LogRoot { get; private set; } 32 | 33 | /// 34 | /// Collection of all backup tasks. Key is the , Value is the . 35 | /// 36 | public static Dictionary Tasks { get; private set; } = new Dictionary(); 37 | 38 | /// 39 | /// Full path to the configuration file. 40 | /// 41 | private static readonly string _configFile; 42 | 43 | /// 44 | /// XML Serializer for serialization/deserialization for configuration saving/loading. 45 | /// 46 | private static XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SerializedConfig)); 47 | 48 | /// 49 | /// Static constructor to populate the file and directory paths according to the executable location. 50 | /// 51 | static Config() { 52 | string exeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 53 | string progData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); 54 | _configFile = Path.Combine(progData, "RoboBackup/config.xml"); 55 | LangRoot = Path.Combine(exeDir, "lang"); 56 | LogRoot = Path.Combine(progData, "RoboBackup/logs"); 57 | Directory.CreateDirectory(LogRoot); 58 | } 59 | 60 | /// 61 | /// Loads configuration from the default XML file. 62 | /// 63 | public static void Load() { 64 | Logger.LogConfigLoad(); 65 | if (File.Exists(_configFile)) { 66 | using (StreamReader reader = new StreamReader(_configFile)) { 67 | SerializedConfig serializedConfig = (SerializedConfig)_xmlSerializer.Deserialize(reader); 68 | Language = serializedConfig.Language; 69 | LogRetention = serializedConfig.LogRetention; 70 | Tasks = serializedConfig.Tasks.ToDictionary(task => task.Guid, task => task); 71 | } 72 | } 73 | Logger.LogConfig(); 74 | } 75 | 76 | /// 77 | /// saves configuration into the default XML file. 78 | /// 79 | public static void Save() { 80 | SerializedConfig serializedConfig = new SerializedConfig() { Language = Language, LogRetention = LogRetention, Tasks = Tasks.Values.ToArray() }; 81 | using (StreamWriter writer = new StreamWriter(_configFile)) { 82 | _xmlSerializer.Serialize(writer, serializedConfig); 83 | } 84 | } 85 | 86 | /// 87 | /// Helper struct for XML serialization/deserialization. 88 | /// 89 | [Serializable] 90 | public struct SerializedConfig { 91 | /// 92 | /// ISO 639-1 code of the selected language. 93 | /// 94 | public string Language; 95 | 96 | /// 97 | /// Log retention set in days. 98 | /// 99 | public ushort LogRetention; 100 | 101 | /// 102 | /// Array of all backup tasks. 103 | /// 104 | public Task[] Tasks; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /RoboBackupService/Credential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | 7 | namespace RoboBackup { 8 | /// 9 | /// Network credentials for use with UNC paths. 10 | /// 11 | [Serializable] 12 | public class Credential { 13 | /// 14 | /// Username associated with the credential. 15 | /// 16 | public string Username { get; set; } 17 | 18 | /// 19 | /// Password associated with the credential. 20 | /// 21 | public string Password { get; set; } 22 | 23 | /// 24 | /// Decrypts using AES128-CBC with as password for PBKDF2. 25 | /// 26 | /// to be used as PBKDF2 password. 27 | /// with encrypted . 28 | /// Decrypted . 29 | public static Credential Decrypt(string guid, string credential) { 30 | if (string.IsNullOrEmpty(credential)) { 31 | return null; 32 | } 33 | byte[] cipherbytes = Convert.FromBase64String(credential); 34 | byte[] salt = new byte[16]; 35 | byte[] iv = new byte[16]; 36 | Array.Copy(cipherbytes, salt, 16); 37 | Array.Copy(cipherbytes, 16, iv, 0, 16); 38 | using (AesManaged aes = new AesManaged() { KeySize = 128 } ) 39 | using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(guid, salt, 1024)) 40 | using (ICryptoTransform decryptor = aes.CreateDecryptor(pbkdf2.GetBytes(16), iv)) 41 | using (MemoryStream memoryStream = new MemoryStream()) 42 | using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write)) 43 | using (BinaryWriter writer = new BinaryWriter(cryptoStream)) { 44 | writer.Write(cipherbytes, 32, cipherbytes.Length - 32); 45 | cryptoStream.FlushFinalBlock(); 46 | return Deserialize(Encoding.UTF8.GetString(memoryStream.ToArray())); 47 | } 48 | } 49 | 50 | /// 51 | /// Encrypts using AES128-CBC with as password for PBKDF2. 52 | /// 53 | /// to be used as PBKDF2 password. 54 | /// to be encrypted. 55 | /// with encrypted . 56 | /// This is really more obfuscation than encryption as all compounds are in the config, but it's still good enough as a defense against prying eyes. 57 | /// Security-wise this isn't worse than using native Windows Credential Manager as anyone can read passwords from that one too. 58 | public static string Encrypt(string guid, Credential credential) { 59 | if (string.IsNullOrEmpty(credential.Username) && string.IsNullOrEmpty(credential.Password)) { 60 | return null; 61 | } 62 | using (AesManaged aes = new AesManaged() { KeySize = 128 } ) { 63 | aes.GenerateIV(); 64 | using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(guid, 16, 1024)) 65 | using (ICryptoTransform encryptor = aes.CreateEncryptor(pbkdf2.GetBytes(16), aes.IV)) 66 | using (MemoryStream memoryStream = new MemoryStream()) 67 | using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) 68 | using (BinaryWriter writer = new BinaryWriter(cryptoStream)) { 69 | writer.Write(Encoding.UTF8.GetBytes(Serialize(credential))); 70 | cryptoStream.FlushFinalBlock(); 71 | return Convert.ToBase64String(pbkdf2.Salt.Concat(aes.IV).Concat(memoryStream.ToArray()).ToArray()); 72 | } 73 | } 74 | } 75 | 76 | /// 77 | /// Deserializes into a . 78 | /// 79 | /// to be deserialized. 80 | /// Deserialized . 81 | private static Credential Deserialize(string credential) { 82 | string[] split = credential.Split(new char[] { '\n' }, 2); 83 | return new Credential() { Username = split[0], Password = split[1] }; 84 | } 85 | 86 | /// 87 | /// Serializes into a . 88 | /// 89 | /// The to be serialized. 90 | /// Serialized as . 91 | private static string Serialize(Credential credential) { 92 | return string.Format("{0}\n{1}", credential.Username, credential.Password); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /RoboBackupService/IpcServer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Runtime.Remoting; 3 | using System.Runtime.Remoting.Channels; 4 | using System.Runtime.Remoting.Channels.Ipc; 5 | 6 | namespace RoboBackup { 7 | /// 8 | /// Server side for interprocess communication via named pipe between the GUI (client) and the service (server). 9 | /// 10 | public static class IpcServer { 11 | /// 12 | /// Registers interprocess communication server channel and the object proxy. 13 | /// 14 | public static void Start() { 15 | Hashtable props = new Hashtable() { 16 | { "authorizedGroup", "Everyone" }, // Allow any user to connect 17 | { "exclusiveAddressUse", false }, // Allow to create channel if the client already has a handle on the named pipe 18 | { "portName", "RoboBackup" } }; 19 | IpcServerChannel channel = new IpcServerChannel(props, null); 20 | ChannelServices.RegisterChannel(channel, false); 21 | RemotingConfiguration.RegisterWellKnownServiceType(typeof(IpcService), "ipc", WellKnownObjectMode.Singleton); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RoboBackupService/IpcService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RoboBackup { 4 | /// 5 | /// Service object class proxied via IPC from the server to the client. 6 | /// 7 | public class IpcService : MarshalByRefObject { 8 | /// 9 | /// Method to test availability od the IPC channel. 10 | /// 11 | /// "pong". 12 | public string Ping() { 13 | return "pong"; 14 | } 15 | 16 | /// 17 | /// Reloads service configuration. 18 | /// 19 | public void ReloadConfig() { 20 | Logger.LogIpcReloadMessage(); 21 | Config.Load(); 22 | LogCleaner.Start(); 23 | } 24 | 25 | /// 26 | /// Requests service to immediately run backup task. 27 | /// 28 | /// GUID of the to be run. 29 | public void RunTask(string guid) { 30 | Logger.LogIpcRunTaskMessage(guid); 31 | if (Config.Tasks.ContainsKey(guid)) { 32 | Task task = Config.Tasks[guid]; 33 | task.Start(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /RoboBackupService/LogCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace RoboBackup { 5 | /// 6 | /// Backup log file cleaner class. 7 | /// 8 | public static class LogCleaner { 9 | /// 10 | /// of the next log file deletion run. 11 | /// 12 | public static DateTime NextRunDate { 13 | get { 14 | if (_nextRunDate == DateTime.MinValue) { 15 | RefreshNextRunDate(); 16 | } 17 | return _nextRunDate; 18 | } 19 | } 20 | 21 | /// 22 | /// used to execute the log file deletion process. 23 | /// 24 | private static Thread _cleanupThread; 25 | 26 | /// 27 | /// Private variable backing the property. 28 | /// 29 | private static DateTime _nextRunDate; 30 | 31 | /// 32 | /// Calculates of the next log file deletion run. 33 | /// 34 | public static void RefreshNextRunDate() { 35 | _nextRunDate = DateTime.Today.AddDays(1); 36 | Logger.LogCleanupNextRunDate(_nextRunDate); 37 | } 38 | 39 | /// 40 | /// Starts the file deletion in a separate thread. 41 | /// 42 | public static void Start() { 43 | Logger.LogCleanupStart(); 44 | _cleanupThread = new Thread(new ThreadStart(SysUtils.DeleteOldLogs)); 45 | _cleanupThread.Start(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RoboBackupService/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RoboBackup { 5 | /// 6 | /// Optional logging class for debugging purposes. Used when the is started with "/log" parameter. 7 | /// 8 | public static class Logger { 9 | /// 10 | /// Service log file stream writer. 11 | /// 12 | public static StreamWriter Writer { get; private set; } 13 | 14 | /// 15 | /// format used in logs. 16 | /// 17 | private const string _dateFormat = "yyyy-MM-dd HH:mm:ss.fff"; 18 | 19 | /// 20 | /// Logs next run date of the . 21 | /// 22 | /// Next run date of the . 23 | public static void LogCleanupNextRunDate(DateTime nextCleanupDate) { 24 | if (Writer != null) { 25 | Log(string.Format("[Cleanup] Next log cleanup scheduled at {0}", nextCleanupDate.ToString(_dateFormat))); 26 | } 27 | } 28 | 29 | /// 30 | /// Logs start of deletion thread. 31 | /// 32 | public static void LogCleanupStart() { 33 | if (Writer != null) { 34 | Log("[Cleanup] Log cleanup started"); 35 | } 36 | } 37 | 38 | /// 39 | /// Logs the values and tasks present in . 40 | /// 41 | public static void LogConfig() { 42 | if (Writer != null) { 43 | Log(string.Format("[Config] Got log retention period {0} days", Config.LogRetention)); 44 | LogCleaner.RefreshNextRunDate(); // Produces log cleaner log line 45 | foreach (Task task in Config.Tasks.Values) { 46 | task.RefreshNextRunDate(); // Produces task detail log line 47 | } 48 | } 49 | } 50 | 51 | /// 52 | /// Logs the beginning of file load. 53 | /// 54 | public static void LogConfigLoad() { 55 | if (Writer != null) { 56 | Log("[Config] Loading config"); 57 | } 58 | } 59 | 60 | /// 61 | /// Logs deletion of old backup directory. 62 | /// 63 | /// Deleted directory path. 64 | public static void LogDirectoryDeleted(string directory) { 65 | if (Writer != null) { 66 | Log(string.Format("[Task] Deleted old directory {0}", directory)); 67 | } 68 | } 69 | 70 | /// 71 | /// Logs the reception of reload message received via IPC. 72 | /// 73 | public static void LogIpcReloadMessage() { 74 | if (Writer != null) { 75 | Log("[Service] Received IPC request for config reload"); 76 | } 77 | } 78 | 79 | /// 80 | /// Logs the reception of immediate run message received via IPC. 81 | /// 82 | /// GUID of the to run. 83 | public static void LogIpcRunTaskMessage(string guid) { 84 | if (Writer != null) { 85 | Log(string.Format("[Service] Received IPC request for task GUID {0} run", guid)); 86 | } 87 | } 88 | 89 | /// 90 | /// Logs deletion of old log file. 91 | /// 92 | /// Deleted log file path. 93 | public static void LogLogFileDeleted(string logFile) { 94 | if (Writer != null) { 95 | Log(string.Format("[Cleanup] Deleted old log file {0}", logFile)); 96 | } 97 | } 98 | 99 | /// 100 | /// Logs the start 101 | /// 102 | public static void LogServiceStarted() { 103 | if (Writer != null) { 104 | Log("[Service] Service started"); 105 | } 106 | } 107 | 108 | /// 109 | /// Logs the stop. 110 | /// 111 | public static void LogServiceStopped() { 112 | if (Writer != null) { 113 | Log("[Service] Service stopped"); 114 | } 115 | } 116 | 117 | /// 118 | /// Logs next run date of a . 119 | /// 120 | /// The to be logged. 121 | public static void LogTaskNextRunDate(Task task) { 122 | if (Writer != null) { 123 | Log(string.Format("[Task] Next task GUID {0} run scheduled at {1}", task.Guid, task.NextRunDate.ToString(_dateFormat))); 124 | } 125 | } 126 | 127 | /// 128 | /// Logs the start of the run thread. 129 | /// 130 | /// The starting . 131 | public static void LogTaskStart(Task task) { 132 | if (Writer != null) { 133 | Log(string.Format("[Task] Task GUID {0} started", task.Guid)); 134 | } 135 | } 136 | 137 | /// 138 | /// Logs timer next tick delay. 139 | /// 140 | /// The delay of the next tick in milliseconds. 141 | public static void LogTickDelay(int delay) { 142 | if (Writer != null) { 143 | Log(string.Format("[Timer] Next tick in {0} ms", delay)); 144 | } 145 | } 146 | 147 | /// 148 | /// Logs the beginning of the timer tick. 149 | /// 150 | public static void LogTick() { 151 | if (Writer != null) { 152 | Log("[Timer] Tick"); 153 | } 154 | } 155 | 156 | /// 157 | /// Logs timer tick skew warning. 158 | /// 159 | public static void LogTickSkew() { 160 | if (Writer != null) { 161 | Log("[Timer] Last tick more than 90 seconds ago or in the future, refreshing schedules"); 162 | } 163 | } 164 | 165 | /// 166 | /// Opens a for the given log file name. 167 | /// 168 | /// Log file name. 169 | public static void Open(string path) { 170 | Writer = new StreamWriter(path, true) { AutoFlush = true }; 171 | } 172 | 173 | /// 174 | /// Opens a for the given . 175 | /// 176 | /// to write into. 177 | public static void Open(Stream stream) { 178 | Writer = new StreamWriter(stream) { AutoFlush = true }; 179 | } 180 | 181 | /// 182 | /// Logs a message using . 183 | /// 184 | /// The message to be logged. 185 | private static void Log(string message) { 186 | Writer.WriteLine(string.Format("{0} - {1}", DateTime.Now.ToString(_dateFormat), message)); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /RoboBackupService/Program.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceProcess; 2 | 3 | namespace RoboBackup { 4 | public static class Program { 5 | /// 6 | /// Main entry point to the service application. 7 | /// 8 | public static void Main() { 9 | ServiceBase.Run(new Service()); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RoboBackupService/ProjectInstaller.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace RoboBackup { 2 | partial class ProjectInstaller { 3 | private System.ComponentModel.IContainer components = null; 4 | 5 | protected override void Dispose(bool disposing) { 6 | if (disposing && (components != null)) { 7 | components.Dispose(); 8 | } 9 | base.Dispose(disposing); 10 | } 11 | 12 | #region Component Designer generated code 13 | 14 | /// 15 | /// Required method for Designer support - do not modify 16 | /// the contents of this method with the code editor. 17 | /// 18 | private void InitializeComponent() { 19 | this.serviceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller(); 20 | this.serviceInstaller = new System.ServiceProcess.ServiceInstaller(); 21 | // 22 | // serviceProcessInstaller 23 | // 24 | this.serviceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem; 25 | this.serviceProcessInstaller.Password = null; 26 | this.serviceProcessInstaller.Username = null; 27 | // 28 | // serviceInstaller 29 | // 30 | this.serviceInstaller.Description = "RoboBackup robocopy backup service"; 31 | this.serviceInstaller.DisplayName = "RoboBackup"; 32 | this.serviceInstaller.ServiceName = "RoboBackup"; 33 | this.serviceInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic; 34 | this.serviceInstaller.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.ServiceInstaller_AfterInstall); 35 | // 36 | // ProjectInstaller 37 | // 38 | this.Installers.AddRange(new System.Configuration.Install.Installer[] { 39 | this.serviceProcessInstaller, 40 | this.serviceInstaller}); 41 | 42 | } 43 | 44 | #endregion 45 | 46 | private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller; 47 | private System.ServiceProcess.ServiceInstaller serviceInstaller; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /RoboBackupService/ProjectInstaller.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Configuration.Install; 3 | using System.ServiceProcess; 4 | 5 | namespace RoboBackup { 6 | /// 7 | /// installer used by the Setup project. 8 | /// 9 | [RunInstaller(true)] 10 | public partial class ProjectInstaller : Installer { 11 | public ProjectInstaller() { 12 | InitializeComponent(); 13 | } 14 | 15 | /// 16 | /// event hadler. Starts the installed service. 17 | /// 18 | private void ServiceInstaller_AfterInstall(object sender, InstallEventArgs e) { 19 | using (ServiceController serviceController = new ServiceController(serviceInstaller.ServiceName)) { 20 | serviceController.Start(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RoboBackupService/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("RoboBackup Service")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("RoboBackup Service")] 9 | [assembly: AssemblyCopyright("Copyright © Disassembler 2018")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | [assembly: Guid("43c4f764-abbd-46a5-a184-c24da8d7b203")] 15 | 16 | [assembly: AssemblyVersion("1.0.0.0")] 17 | [assembly: AssemblyFileVersion("1.0.0.0")] 18 | -------------------------------------------------------------------------------- /RoboBackupService/RoboBackupService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {43C4F764-ABBD-46A5-A184-C24DA8D7B203} 8 | WinExe 9 | RoboBackup 10 | RoboBackupService 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | AllRules.ruleset 26 | false 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Component 53 | 54 | 55 | ProjectInstaller.cs 56 | 57 | 58 | 59 | Component 60 | 61 | 62 | Service.cs 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /RoboBackupService/Service.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace RoboBackup { 2 | partial class Service { 3 | private System.ComponentModel.IContainer components = null; 4 | 5 | protected override void Dispose(bool disposing) { 6 | if (disposing && (components != null)) { 7 | components.Dispose(); 8 | } 9 | base.Dispose(disposing); 10 | } 11 | 12 | #region Component Designer generated code 13 | 14 | /// 15 | /// Required method for Designer support - do not modify 16 | /// the contents of this method with the code editor. 17 | /// 18 | private void InitializeComponent() { 19 | // 20 | // Service 21 | // 22 | this.ServiceName = "RoboBackup"; 23 | 24 | } 25 | 26 | #endregion 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RoboBackupService/Service.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.ServiceProcess; 4 | using System.Threading; 5 | 6 | namespace RoboBackup { 7 | /// 8 | /// RoboBackup service class. 9 | /// 10 | public partial class Service : ServiceBase { 11 | /// 12 | /// Tick period in milliseconds. 13 | /// 14 | private const int _period = 60000; 15 | 16 | /// 17 | /// Threshold limit for tick skew. 18 | /// 19 | private static readonly TimeSpan _skewLimit = TimeSpan.FromSeconds(90); 20 | 21 | /// 22 | /// of the last tick. 23 | /// 24 | private static DateTime _lastTick; 25 | 26 | /// 27 | /// Main timer for schedulling and execution. 28 | /// 29 | private static Timer _timer = new Timer(OnTick, null, Timeout.Infinite, Timeout.Infinite); 30 | 31 | /// 32 | /// Initializes a new instance of the class. 33 | /// 34 | public Service() { 35 | InitializeComponent(); 36 | } 37 | 38 | /// 39 | /// handler. Opens (if configured), loads and starts the . 40 | /// 41 | /// Command line arguments. 42 | protected override void OnStart(string[] args) { 43 | if (args.Length == 1 && args[0] == "/log") { 44 | Logger.Open(Path.Combine(Config.LogRoot, "service.log")); 45 | } 46 | Logger.LogServiceStarted(); 47 | Config.Load(); 48 | LogCleaner.Start(); 49 | IpcServer.Start(); 50 | SetNextTick(DateTime.Now); 51 | } 52 | 53 | /// 54 | /// handler. Stops the and interrupts any currently running . 55 | /// 56 | protected override void OnStop() { 57 | _timer.Change(Timeout.Infinite, Timeout.Infinite); 58 | _timer = null; 59 | foreach (Task task in Config.Tasks.Values) { 60 | task.Stop(); 61 | } 62 | Logger.LogServiceStopped(); 63 | } 64 | 65 | /// 66 | /// Performs workload during normal tick. Runs or if they are scheduled to be run in the current tick. 67 | /// 68 | /// 69 | private static void DoTick(DateTime now) { 70 | foreach (Task task in Config.Tasks.Values) { 71 | if (task.NextRunDate <= now) { 72 | task.Start(); 73 | task.RefreshNextRunDate(); 74 | } 75 | } 76 | if (LogCleaner.NextRunDate <= now) { 77 | LogCleaner.Start(); 78 | LogCleaner.RefreshNextRunDate(); 79 | } 80 | } 81 | 82 | /// 83 | /// to be called every tick. Checks validity of the tick, performs workload and schedues the next tick. 84 | /// 85 | /// 86 | private static void OnTick(object state) { 87 | Logger.LogTick(); 88 | DateTime now = DateTime.Now; 89 | // Recalculate next run dates if the last tick was more than 90 seconds ago or if it is in the future. 90 | // Such clock skew implies manual time change, huge NTP leap, machine awakened from hibernation/suspension or some other potentially unsafe state. 91 | if (now - _lastTick > _skewLimit || now < _lastTick) { 92 | Logger.LogTickSkew(); 93 | foreach (Task task in Config.Tasks.Values) { 94 | task.RefreshNextRunDate(); 95 | } 96 | LogCleaner.Start(); 97 | LogCleaner.RefreshNextRunDate(); 98 | } else { 99 | DoTick(now); 100 | } 101 | // The next tick time has to be readjusted often to maintain the precision of the scheduled tasks. 102 | // System.Threading.Timer is ridiculously imprecise. The best case scenario skews the timer by 1 ms per minute (1 minute per ~100 days). 103 | // An alternative would be to create a custom timer implementaion based on high-precision multimedia timers, but 104 | // System.Threading.Timer is simple to use and guaranteed to be supported pretty much everywhere, so it's good enough. 105 | SetNextTick(now); 106 | } 107 | 108 | /// 109 | /// Sets the delay until the next tick. 110 | /// 111 | /// of the previous tick. 112 | private static void SetNextTick(DateTime now) { 113 | int delay = _period - (now.Second * 1000) - now.Millisecond; 114 | Logger.LogTickDelay(delay); 115 | _timer.Change(delay, _period); 116 | _lastTick = now; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /RoboBackupService/SysUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace RoboBackup { 10 | /// 11 | /// Filesystem entry type. 12 | /// 13 | public enum EntryType { 14 | File, 15 | Directory 16 | } 17 | 18 | /// 19 | /// Filesystem modification and robocopy process utilities. 20 | /// 21 | public static class SysUtils { 22 | /// 23 | /// Deletes logs older than days. 24 | /// 25 | public static void DeleteOldLogs() { 26 | DateTime threshold = DateTime.Now.AddDays(-Config.LogRetention); 27 | DirectoryInfo logRoot = new DirectoryInfo(Config.LogRoot); 28 | foreach (DirectoryInfo directory in logRoot.EnumerateDirectories()) { 29 | foreach (FileInfo file in directory.EnumerateFiles().Where(f => f.LastWriteTime < threshold)) { 30 | try { 31 | file.Delete(); 32 | Logger.LogLogFileDeleted(file.FullName); 33 | } catch { /* Ignore if the old log can't be deleted now */ } 34 | } 35 | } 36 | } 37 | 38 | /// 39 | /// Prepares and executes robocopy backup command. 40 | /// 41 | /// to run. 42 | public static void RunBackup(Task task) { 43 | string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); 44 | string logFile = GetLogName(task.Guid, timestamp); 45 | string destDir = Path.Combine(task.Destination, timestamp); 46 | string latestLink = Path.Combine(task.Destination, "latest"); 47 | try { 48 | if (task.Method == Method.Differential && task.Retention > 1) { 49 | try { 50 | CopyDirectoryStructure(latestLink, destDir); 51 | CopyHardlinkStructure(latestLink, destDir); 52 | } catch { /* Ignore if the latestLink doesn't exist or points to invalid directory */ } 53 | } 54 | string uncPath = Unc.IsUncPath(task.Source) ? task.Source : Unc.IsUncPath(task.Destination) ? task.Destination : null; 55 | using (Unc unc = new Unc(uncPath)) { 56 | unc.Connect(Credential.Decrypt(task.Guid, task.Credential)); 57 | RunRobocopy(task, logFile, destDir); 58 | } 59 | if (task.Retention > 1) { 60 | CreateLatestSymlink(destDir, latestLink); 61 | DeleteOldDirs(task.Destination, task.Retention); 62 | } 63 | } catch (Exception e) { 64 | using (StreamWriter streamWriter = new StreamWriter(logFile, true)) { 65 | streamWriter.Write(e.Message); 66 | streamWriter.Write(e.StackTrace); 67 | } 68 | } 69 | } 70 | 71 | /// 72 | /// Copies directory structure from source to destination. 73 | /// 74 | /// Source directory. 75 | /// Destination directory. 76 | private static void CopyDirectoryStructure(string sourceDir, string destDir) { 77 | string args = string.Format("{0} {1} /E /CREATE /DST /DCOPY:DAT /XF * /XJ /R:0 /W:0", EscapeArg(sourceDir), EscapeArg(destDir)); 78 | ProcessStartInfo psi = new ProcessStartInfo("robocopy", args) { WindowStyle = ProcessWindowStyle.Hidden }; 79 | Process robocopy = Process.Start(psi); 80 | robocopy.WaitForExit(); 81 | } 82 | 83 | /// 84 | /// Copies file structure from source to destination as hardlinks. 85 | /// 86 | /// Source directory path. 87 | /// Destination directory path. 88 | private static void CopyHardlinkStructure(string sourceDir, string destDir) { 89 | foreach (string dir in Directory.EnumerateDirectories(sourceDir)) { 90 | string dest = Path.Combine(destDir, Path.GetFileName(dir)); 91 | CopyHardlinkStructure(dir, dest); 92 | } 93 | foreach (string file in Directory.EnumerateFiles(sourceDir)) { 94 | string dest = Path.Combine(destDir, Path.GetFileName(file)); 95 | CreateHardlink(dest, file); 96 | } 97 | } 98 | 99 | /// 100 | /// Creates a hardlink. 101 | /// 102 | /// New hardlink file path. 103 | /// Existing file path to point the hardlink to. 104 | private static void CreateHardlink(string lpFileName, string lpExistingFileName) { 105 | CreateHardlink(lpFileName, lpExistingFileName, IntPtr.Zero); 106 | } 107 | 108 | /// 109 | /// Creates a hardlink. 110 | /// 111 | /// New hardlink file path. 112 | /// Existing file path to point the hardlink to. 113 | /// Security attributes - unused. 114 | /// 115 | [DllImport("kernel32.dll", EntryPoint = "CreateHardLinkW", CharSet = CharSet.Unicode)] 116 | private static extern int CreateHardlink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); 117 | 118 | /// 119 | /// Creates a symlink to the latest backup directory. 120 | /// 121 | /// Path of the latest backup directory. 122 | /// Symlink path. 123 | private static void CreateLatestSymlink(string destDir, string latestLink) { 124 | if (Directory.Exists(latestLink)) { 125 | Directory.Delete(latestLink); 126 | } 127 | CreateSymlink(latestLink, destDir, (int)EntryType.Directory); 128 | } 129 | 130 | /// 131 | /// Creates a symbolic link. 132 | /// 133 | /// New symlink file path. 134 | /// Existing file or directory path to point the symlink to. 135 | /// of the existing file or directory. 136 | /// 137 | [DllImport("kernel32.dll", EntryPoint = "CreateSymbolicLinkW", CharSet = CharSet.Unicode)] 138 | private static extern int CreateSymlink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); 139 | 140 | /// 141 | /// Recursively deletes a directory. 142 | /// 143 | /// Path of the directory to delete. 144 | /// Directory.Delete() is slow as it enumerates the content of the directory. Furthermore it doesn't allow to delete files with read-only attribute without unsetting it first. 145 | private static void DeleteDir(string path) { 146 | ProcessStartInfo psi = new ProcessStartInfo("cmd", string.Format("/C rmdir /s /q {0}", EscapeArg(path))) { WindowStyle = ProcessWindowStyle.Hidden }; 147 | Process rmdir = Process.Start(psi); 148 | rmdir.WaitForExit(); 149 | Logger.LogDirectoryDeleted(path); 150 | } 151 | 152 | /// 153 | /// Deletes old backups. Leaves the latest directories and deletes the rest. 154 | /// 155 | /// log directory. 156 | /// Number of the latest backups to retain. 157 | private static void DeleteOldDirs(string dir, ushort retention) { 158 | string[] subdirs = Directory.GetDirectories(dir); 159 | if (subdirs.Length > ++retention) { 160 | foreach (string subdir in subdirs.Take(subdirs.Length - retention)) { 161 | DeleteDir(subdir); 162 | } 163 | } 164 | } 165 | 166 | /// 167 | /// Escapes command line argument. 168 | /// 169 | /// Argument to escape. 170 | /// 171 | private static string EscapeArg(string arg) { 172 | return "\"" + Regex.Replace(arg, @"(\\+)$", @"$1$1") + "\""; 173 | } 174 | 175 | /// 176 | /// Generates a log file name for the current run of the . 177 | /// 178 | /// GUID of the . 179 | /// Formatted of the call. 180 | /// 181 | private static string GetLogName(string guid, string timestamp) { 182 | string logDir = Path.Combine(Config.LogRoot, guid); 183 | if (!Directory.Exists(logDir)) { 184 | Directory.CreateDirectory(logDir); 185 | } 186 | return Path.Combine(logDir, string.Format("{0}.log", timestamp)); 187 | } 188 | 189 | /// 190 | /// Compiles the robocopy backup arguments and executes the robocopy . 191 | /// 192 | /// to run. 193 | /// Robocopy log file path. 194 | /// Backup destination directory path. 195 | private static void RunRobocopy(Task task, string logFile, string destDir) { 196 | List args = new List() { EscapeArg(task.Source) }; 197 | if (task.Retention == 1) { 198 | args.Add(EscapeArg(task.Destination)); 199 | args.Add(task.Method == Method.Incremental ? "/E" : "/MIR"); 200 | } else { 201 | args.Add(EscapeArg(destDir)); 202 | args.Add("/MIR"); 203 | } 204 | if (task.ExcludedDirs.Length > 0) { 205 | args.Add("/XD"); 206 | args.AddRange(task.ExcludedDirs.Select(x => EscapeArg(Path.Combine(task.Source, x)))); 207 | } 208 | if (task.ExcludedFiles.Length > 0) { 209 | args.Add("/XF"); 210 | args.AddRange(task.ExcludedFiles.Select(x => EscapeArg(Path.Combine(task.Source, x)))); 211 | } 212 | args.Add(string.Format("/XJ /SL /DST /ZB /DCOPY:DAT /COPY:DAT /R:2 /W:3 /NP \"/UNILOG+:{0}\"", logFile)); 213 | ProcessStartInfo psi = new ProcessStartInfo("robocopy", string.Join(" ", args)) { WindowStyle = ProcessWindowStyle.Hidden }; 214 | Process robocopy = Process.Start(psi); 215 | robocopy.WaitForExit(); 216 | } 217 | 218 | /* 219 | * Usage :: ROBOCOPY source destination [file [file]...] [options] 220 | * /E :: copy subdirectories, including Empty ones. 221 | * /PURGE :: delete dest files/dirs that no longer exist in source. 222 | * /MIR :: MIRror a directory tree (equivalent to /E plus /PURGE). 223 | * /CREATE :: CREATE directory tree and zero-length files only. 224 | * /XF file [file]... :: eXclude Files matching given names/paths/wildcards. 225 | * /XD dirs [dirs]... :: eXclude Directories matching given names/paths. 226 | * /XJ :: eXclude Junction points and symbolic links. (normally included by default). 227 | * /SL :: copy symbolic links versus the target. 228 | * /DST :: compensate for one-hour DST time differences. 229 | * /ZB :: use restartable mode; if access denied use Backup mode. 230 | * /DCOPY:copyflag[s] :: what to COPY for directories (default is /DCOPY:DA). 231 | * (copyflags : D=Data, A=Attributes, T=Timestamps). 232 | * /COPY:copyflag[s] :: what to COPY for files (default is /COPY:DAT). 233 | * (copyflags : D=Data, A=Attributes, T=Timestamps). 234 | * (S=Security=NTFS ACLs, O=Owner info, U=aUditing info). 235 | * /R:n :: number of Retries on failed copies: default 1 million. 236 | * /W:n :: Wait time between retries: default is 30 seconds. 237 | * /NP :: No Progress - don't display percentage copied. 238 | * /UNILOG+:file :: output status to LOG file as UNICODE (append to existing log). 239 | */ 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /RoboBackupService/Task.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace RoboBackup { 5 | /// 6 | /// method. 7 | /// 8 | public enum Method { 9 | Incremental, 10 | Differential, 11 | Full 12 | } 13 | 14 | /// 15 | /// period. 16 | /// 17 | public enum Period { 18 | Daily, 19 | Weekly, 20 | Monthly 21 | } 22 | 23 | /// 24 | /// Backup task definition - source and destination directories, exclusions, schedule etc. 25 | /// 26 | [Serializable] 27 | public class Task { 28 | /// 29 | /// Encrypted credentials for use with network shares. 30 | /// 31 | public string Credential { get; set; } 32 | 33 | /// 34 | /// Day of month for monthly schedule. 35 | /// 36 | public byte DayOfMonth { get; set; } 37 | 38 | /// 39 | /// Day of week for weekly schedule. 40 | /// 41 | public DayOfWeek DayOfWeek { get; set; } 42 | 43 | /// 44 | /// Backup destination directory. 45 | /// 46 | public string Destination { get; set; } 47 | 48 | /// 49 | /// Directories to be excluded from the backup. 50 | /// 51 | public string[] ExcludedDirs { get; set; } 52 | 53 | /// 54 | /// Files to be excluded from the backup. 55 | /// 56 | public string[] ExcludedFiles { get; set; } 57 | 58 | /// 59 | /// Global unique identifier of the . 60 | /// 61 | public string Guid { get; set; } 62 | 63 | /// 64 | /// Scheduled hour when to run the . 65 | /// 66 | public byte Hour { get; set; } 67 | 68 | /// 69 | /// Backup method. 70 | /// 71 | public Method Method { get; set; } 72 | 73 | /// 74 | /// Scheduled minute when to run the . 75 | /// 76 | public byte Minute { get; set; } 77 | 78 | /// 79 | /// of the next run. 80 | /// 81 | public DateTime NextRunDate { 82 | get { 83 | if (_nextRunDate == DateTime.MinValue) { 84 | RefreshNextRunDate(); 85 | } 86 | return _nextRunDate; 87 | } 88 | } 89 | 90 | /// 91 | /// How often to run the . 92 | /// 93 | public Period Period { get; set; } 94 | 95 | /// 96 | /// How many backup directories to retain. 97 | /// 98 | public ushort Retention { get; set; } 99 | 100 | /// 101 | /// Backup source directory. 102 | /// 103 | public string Source { get; set; } 104 | 105 | /// 106 | /// Non-unique human readable identifier. 107 | /// 108 | public string Title { get; set; } 109 | 110 | /// 111 | /// Private variable backing the property. 112 | /// 113 | [NonSerialized] 114 | private DateTime _nextRunDate; 115 | 116 | /// 117 | /// used to execute the robocopy backup process. 118 | /// 119 | [NonSerialized] 120 | private Thread _thread; 121 | 122 | /// 123 | /// Calculates of the next run. 124 | /// 125 | public void RefreshNextRunDate() { 126 | DateTime now = DateTime.Now; 127 | DateTime nextRun = DateTime.Today.AddHours(Hour).AddMinutes(Minute); 128 | if (Period == Period.Daily) { 129 | if (nextRun < now) { 130 | nextRun = nextRun.AddDays(1); 131 | } 132 | } else if (Period == Period.Weekly) { 133 | nextRun = nextRun.AddDays(DayOfWeek - now.DayOfWeek); 134 | if (nextRun < now) { 135 | nextRun = nextRun.AddDays(7); 136 | } 137 | } else { 138 | while (DayOfMonth > DateTime.DaysInMonth(nextRun.Year, nextRun.Month)) { 139 | nextRun = nextRun.AddMonths(1); 140 | } 141 | nextRun = nextRun.AddDays(DayOfMonth - now.Day); 142 | if (nextRun < now) { 143 | nextRun = nextRun.AddMonths(1); 144 | } 145 | } 146 | _nextRunDate = nextRun; 147 | Logger.LogTaskNextRunDate(this); 148 | } 149 | 150 | /// 151 | /// Starts the robocopy backup process in a separate thread. 152 | /// 153 | public void Start() { 154 | Logger.LogTaskStart(this); 155 | _thread = new Thread(new ThreadStart(() => SysUtils.RunBackup(this))); 156 | _thread.Start(); 157 | } 158 | 159 | /// 160 | /// Interrupts the thread with the robocopy backup process. 161 | /// 162 | public void Stop() { 163 | if (_thread != null && _thread.ThreadState == ThreadState.Running) { 164 | _thread.Abort(); 165 | } 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /RoboBackupService/Unc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace RoboBackup { 7 | /// 8 | /// Provides methods for accessing UNC paths with shared network resources requiring credentials. 9 | /// 10 | public class Unc : IDisposable { 11 | /// 12 | /// Lifecycle state of the object. 13 | /// 14 | private bool _disposed = false; 15 | 16 | /// 17 | /// UNC path with shared network resource to be accessed. 18 | /// 19 | private readonly string _path; 20 | 21 | /// 22 | /// Prepare a new instance for connection to a shared network resource. 23 | /// 24 | /// Network path to be accessed. 25 | public Unc(string path) { 26 | _path = path; 27 | } 28 | 29 | /// 30 | /// Destroys instance for shared network resource access. 31 | /// 32 | ~Unc() { 33 | Dispose(); 34 | } 35 | 36 | /// 37 | /// Checks if resembles an UNC path. 38 | /// 39 | /// to be checked. 40 | /// if the resembles UNC path, otherwise . 41 | public static bool IsUncPath(string path) { 42 | return path.StartsWith(@"\\"); 43 | } 44 | 45 | /// 46 | /// Resolves mapped drive into UNC network path. 47 | /// 48 | /// Path to be resolved. 49 | /// Resolved UNC network path if the root of the is on mapped drive, unchaged otherwise. 50 | public static string TranslatePath(string path) { 51 | if (IsUncPath(path)) { 52 | return path; 53 | } 54 | string[] path_split = path.Split(new char[] { '\\' }, 2); 55 | StringBuilder stringBuilder = new StringBuilder(512); 56 | int size = stringBuilder.Capacity; 57 | var error = WNetGetConnectionW(path_split[0], stringBuilder, ref size); 58 | if (error != 0) 59 | return path; 60 | return Path.Combine(stringBuilder.ToString(), path_split[1]); 61 | } 62 | 63 | /// 64 | /// Accesses the UNC path with shared network resource using . 65 | /// 66 | /// Username and password required for access to the shared network resource. 67 | public void Connect(Credential credential) { 68 | if (credential == null) { 69 | _disposed = true; 70 | return; 71 | } 72 | string[] path_split = _path.Split(new char[] { '\\' }); 73 | string domain = path_split.Length > 2 ? path_split[2] : null; 74 | UseInfo2 useinfo = new UseInfo2 { 75 | ui2_remote = _path, 76 | ui2_domainname = domain, 77 | ui2_username = credential.Username, 78 | ui2_password = credential.Password, 79 | ui2_asg_type = 0, // USE_WILDCARD 80 | ui2_usecount = 1 81 | }; 82 | NetUseAdd(null, 2, ref useinfo, out uint parmErr); // 2 = useinfo is USE_INFO_2 83 | } 84 | 85 | /// 86 | /// Disconnects from the shared network resource and sets the object as disposed. 87 | /// 88 | public void Dispose() { 89 | if (!_disposed) { 90 | NetUseDel(null, _path, 2); // 2 = USE_LOTS_OF_FORCE 91 | _disposed = true; 92 | } 93 | GC.SuppressFinalize(this); 94 | } 95 | 96 | /// 97 | /// The NetUseAdd function establishes a connection between the local computer and a remote server. You can specify a local drive letter or a printer device to connect. If you do not specify a local drive letter or printer device, the function authenticates the client with the server for future connections. 98 | /// 99 | /// The UNC name of the computer on which to execute this function. If this parameter is , then the local computer is used. 100 | /// A value that specifies the information level of the data. 101 | /// A pointer to the buffer that specifies the data. The format of this data depends on the value of the parameter. 102 | /// A pointer to a value that receives the index of the first member of the information structure in error. If this parameter is , the index is not returned on error. 103 | /// If the function succeeds, the return value is NERR_Success. If the function fails, the return value is a system error code. 104 | /// https://docs.microsoft.com/en-us/windows/desktop/api/lmuse/nf-lmuse-netuseadd 105 | [DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 106 | private static extern uint NetUseAdd(string uncServerName, uint levelFlags, ref UseInfo2 buf, out uint parmErr); 107 | 108 | /// 109 | /// The NetUseDel function ends a connection to a shared resource. 110 | /// 111 | /// The UNC name of the computer on which to execute this function. If this is parameter is NULL, then the local computer is used. 112 | /// A pointer to a string that specifies the path of the connection to delete. 113 | /// The level of force to use in deleting the connection. 114 | /// If the function succeeds, the return value is NERR_Success. If the function fails, the return value is a system error code. 115 | /// https://docs.microsoft.com/en-us/windows/desktop/api/lmuse/nf-lmuse-netusedel 116 | [DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 117 | private static extern uint NetUseDel(string uncServerName, string useName, uint forceLevelFlags); 118 | 119 | /// 120 | /// Retrieves the name of the network resource associated with a local device. 121 | /// 122 | /// Pointer to a constant null-terminated string that specifies the name of the local device to get the network name for. 123 | /// Pointer to a null-terminated string that receives the remote name used to make the connection. 124 | /// Pointer to a variable that specifies the size of the buffer pointed to by the lpRemoteName parameter, in characters. If the function fails because the buffer is not large enough, this parameter returns the required buffer size. 125 | /// If the function succeeds, the return value is NO_ERROR. If the function fails, the return value is a system error code. 126 | [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)] 127 | private static extern int WNetGetConnectionW(string lpLocalName, StringBuilder lpRemoteName, ref int lpnLength); 128 | 129 | /// 130 | /// The USE_INFO_2 structure contains information about a connection between a local computer and a shared resource, including connection type, connection status, user name, and domain name. 131 | /// 132 | /// https://docs.microsoft.com/en-us/windows/desktop/api/lmuse/ns-lmuse-_use_info_2 133 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 134 | private struct UseInfo2 { 135 | internal string ui2_local; 136 | internal string ui2_remote; 137 | internal string ui2_password; 138 | internal uint ui2_status; 139 | internal uint ui2_asg_type; 140 | internal uint ui2_refcount; 141 | internal uint ui2_usecount; 142 | internal string ui2_username; 143 | internal string ui2_domainname; 144 | } 145 | } 146 | } 147 | --------------------------------------------------------------------------------