├── .gitattributes ├── .gitignore ├── InnoSetup.iss ├── LICENSE ├── LightroomSync.sln ├── LightroomSync ├── Alert.Designer.cs ├── Alert.cs ├── Alert.resx ├── Config.cs ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── LightroomSync.csproj ├── Program.cs ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── launchSettings.json ├── Status.cs ├── Utils.cs ├── camera.ico ├── camera.svg └── checkmark.png ├── README.md ├── Screenshot.png └── latestVersion.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Output/ 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | ## 6 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 7 | 8 | # User-specific files 9 | *.rsuser 10 | *.suo 11 | *.user 12 | *.userosscache 13 | *.sln.docstates 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Mono auto generated files 19 | mono_crash.* 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | [Ww][Ii][Nn]32/ 29 | [Aa][Rr][Mm]/ 30 | [Aa][Rr][Mm]64/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Ll]og/ 35 | [Ll]ogs/ 36 | 37 | # Visual Studio 2015/2017 cache/options directory 38 | .vs/ 39 | # Uncomment if you have tasks that create the project's static files in wwwroot 40 | #wwwroot/ 41 | 42 | # Visual Studio 2017 auto generated files 43 | Generated\ Files/ 44 | 45 | # MSTest test Results 46 | [Tt]est[Rr]esult*/ 47 | [Bb]uild[Ll]og.* 48 | 49 | # NUnit 50 | *.VisualState.xml 51 | TestResult.xml 52 | nunit-*.xml 53 | 54 | # Build Results of an ATL Project 55 | [Dd]ebugPS/ 56 | [Rr]eleasePS/ 57 | dlldata.c 58 | 59 | # Benchmark Results 60 | BenchmarkDotNet.Artifacts/ 61 | 62 | # .NET Core 63 | project.lock.json 64 | project.fragment.lock.json 65 | artifacts/ 66 | 67 | # ASP.NET Scaffolding 68 | ScaffoldingReadMe.txt 69 | 70 | # StyleCop 71 | StyleCopReport.xml 72 | 73 | # Files built by Visual Studio 74 | *_i.c 75 | *_p.c 76 | *_h.h 77 | *.ilk 78 | *.meta 79 | *.obj 80 | *.iobj 81 | *.pch 82 | *.pdb 83 | *.ipdb 84 | *.pgc 85 | *.pgd 86 | *.rsp 87 | *.sbr 88 | *.tlb 89 | *.tli 90 | *.tlh 91 | *.tmp 92 | *.tmp_proj 93 | *_wpftmp.csproj 94 | *.log 95 | *.tlog 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 300 | *.vbp 301 | 302 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 303 | *.dsw 304 | *.dsp 305 | 306 | # Visual Studio 6 technical files 307 | *.ncb 308 | *.aps 309 | 310 | # Visual Studio LightSwitch build output 311 | **/*.HTMLClient/GeneratedArtifacts 312 | **/*.DesktopClient/GeneratedArtifacts 313 | **/*.DesktopClient/ModelManifest.xml 314 | **/*.Server/GeneratedArtifacts 315 | **/*.Server/ModelManifest.xml 316 | _Pvt_Extensions 317 | 318 | # Paket dependency manager 319 | .paket/paket.exe 320 | paket-files/ 321 | 322 | # FAKE - F# Make 323 | .fake/ 324 | 325 | # CodeRush personal settings 326 | .cr/personal 327 | 328 | # Python Tools for Visual Studio (PTVS) 329 | __pycache__/ 330 | *.pyc 331 | 332 | # Cake - Uncomment if you are using it 333 | # tools/** 334 | # !tools/packages.config 335 | 336 | # Tabs Studio 337 | *.tss 338 | 339 | # Telerik's JustMock configuration file 340 | *.jmconfig 341 | 342 | # BizTalk build output 343 | *.btp.cs 344 | *.btm.cs 345 | *.odx.cs 346 | *.xsd.cs 347 | 348 | # OpenCover UI analysis results 349 | OpenCover/ 350 | 351 | # Azure Stream Analytics local run output 352 | ASALocalRun/ 353 | 354 | # MSBuild Binary and Structured Log 355 | *.binlog 356 | 357 | # NVidia Nsight GPU debugger configuration file 358 | *.nvuser 359 | 360 | # MFractors (Xamarin productivity tool) working folder 361 | .mfractor/ 362 | 363 | # Local History for Visual Studio 364 | .localhistory/ 365 | 366 | # Visual Studio History (VSHistory) files 367 | .vshistory/ 368 | 369 | # BeatPulse healthcheck temp database 370 | healthchecksdb 371 | 372 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 373 | MigrationBackup/ 374 | 375 | # Ionide (cross platform F# VS Code tools) working folder 376 | .ionide/ 377 | 378 | # Fody - auto-generated XML schema 379 | FodyWeavers.xsd 380 | 381 | # VS Code files for those working on multiple tools 382 | .vscode/* 383 | !.vscode/settings.json 384 | !.vscode/tasks.json 385 | !.vscode/launch.json 386 | !.vscode/extensions.json 387 | *.code-workspace 388 | 389 | # Local History for Visual Studio Code 390 | .history/ 391 | 392 | # Windows Installer files from build outputs 393 | *.cab 394 | *.msi 395 | *.msix 396 | *.msm 397 | *.msp 398 | 399 | # JetBrains Rider 400 | *.sln.iml 401 | -------------------------------------------------------------------------------- /InnoSetup.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "LightroomSync" 5 | #define MyAppVersion "1.0" 6 | #define MyAppPublisher "Anthony Bryan" 7 | #define MyAppURL "https://github.com/software-2/LightroomSync" 8 | #define MyAppExeName "LightroomSync.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{E517626F-1634-44A5-A111-1BB07C55E499} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | ;AppVerName={#MyAppName} {#MyAppVersion} 17 | AppPublisher={#MyAppPublisher} 18 | AppPublisherURL={#MyAppURL} 19 | AppSupportURL={#MyAppURL} 20 | AppUpdatesURL={#MyAppURL} 21 | DefaultDirName={autopf}\{#MyAppName} 22 | DefaultGroupName={#MyAppName} 23 | AllowNoIcons=yes 24 | LicenseFile=C:\Git\LightroomSync\LICENSE 25 | ; Remove the following line to run in administrative install mode (install for all users.) 26 | ;PrivilegesRequired=lowest 27 | PrivilegesRequiredOverridesAllowed=dialog 28 | OutputBaseFilename=LightroomSync Setup 29 | Compression=lzma 30 | SolidCompression=yes 31 | WizardStyle=modern 32 | 33 | [Languages] 34 | Name: "english"; MessagesFile: "compiler:Default.isl" 35 | 36 | [Tasks] 37 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 38 | 39 | [Files] 40 | Source: "C:\Git\LightroomSync\LightroomSync\bin\Release\net6.0-windows\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion 41 | Source: "C:\Git\LightroomSync\LightroomSync\bin\Release\net6.0-windows\LightroomSync.deps.json"; DestDir: "{app}"; Flags: ignoreversion 42 | Source: "C:\Git\LightroomSync\LightroomSync\bin\Release\net6.0-windows\LightroomSync.dll"; DestDir: "{app}"; Flags: ignoreversion 43 | Source: "C:\Git\LightroomSync\LightroomSync\bin\Release\net6.0-windows\LightroomSync.pdb"; DestDir: "{app}"; Flags: ignoreversion 44 | Source: "C:\Git\LightroomSync\LightroomSync\bin\Release\net6.0-windows\LightroomSync.runtimeconfig.json"; DestDir: "{app}"; Flags: ignoreversion 45 | Source: "C:\Git\LightroomSync\LightroomSync\bin\Release\net6.0-windows\Newtonsoft.Json.dll"; DestDir: "{app}"; Flags: ignoreversion 46 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 47 | 48 | [Icons] 49 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 50 | Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" 51 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 52 | 53 | [Run] 54 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 55 | 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anthony Bryan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LightroomSync.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33712.159 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LightroomSync", "LightroomSync\LightroomSync.csproj", "{FD159443-4A90-46B8-A81A-6A80E96DBB79}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {FD159443-4A90-46B8-A81A-6A80E96DBB79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {FD159443-4A90-46B8-A81A-6A80E96DBB79}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {FD159443-4A90-46B8-A81A-6A80E96DBB79}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {FD159443-4A90-46B8-A81A-6A80E96DBB79}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {A7D8F432-F246-410B-B401-2A0D58410309} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /LightroomSync/Alert.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace LightroomSync 2 | { 3 | partial class Alert 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | label_info = new Label(); 32 | button_KillLightroom = new Button(); 33 | button1 = new Button(); 34 | SuspendLayout(); 35 | // 36 | // label_info 37 | // 38 | label_info.Location = new Point(12, 9); 39 | label_info.Name = "label_info"; 40 | label_info.Size = new Size(776, 252); 41 | label_info.TabIndex = 0; 42 | label_info.Text = "This "; 43 | // 44 | // button_KillLightroom 45 | // 46 | button_KillLightroom.DialogResult = DialogResult.Yes; 47 | button_KillLightroom.Location = new Point(88, 359); 48 | button_KillLightroom.Name = "button_KillLightroom"; 49 | button_KillLightroom.Size = new Size(208, 51); 50 | button_KillLightroom.TabIndex = 1; 51 | button_KillLightroom.Text = "Kill Lightroom"; 52 | button_KillLightroom.UseVisualStyleBackColor = true; 53 | button_KillLightroom.Click += button_KillLightroom_Click; 54 | // 55 | // button1 56 | // 57 | button1.DialogResult = DialogResult.No; 58 | button1.Location = new Point(510, 359); 59 | button1.Name = "button1"; 60 | button1.Size = new Size(208, 51); 61 | button1.TabIndex = 2; 62 | button1.Text = "Override Catalogs"; 63 | button1.UseVisualStyleBackColor = true; 64 | // 65 | // Alert 66 | // 67 | AutoScaleDimensions = new SizeF(7F, 15F); 68 | AutoScaleMode = AutoScaleMode.Font; 69 | ClientSize = new Size(800, 450); 70 | ControlBox = false; 71 | Controls.Add(button1); 72 | Controls.Add(button_KillLightroom); 73 | Controls.Add(label_info); 74 | Name = "Alert"; 75 | StartPosition = FormStartPosition.CenterScreen; 76 | Text = "STOP USING LIGHTROOM"; 77 | TopMost = true; 78 | FormClosing += Alert_FormClosing; 79 | ResumeLayout(false); 80 | } 81 | 82 | #endregion 83 | 84 | private Label label_info; 85 | private Button button_KillLightroom; 86 | private Button button1; 87 | } 88 | } -------------------------------------------------------------------------------- /LightroomSync/Alert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Diagnostics; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Windows.Forms; 12 | 13 | namespace LightroomSync 14 | { 15 | public partial class Alert : Form 16 | { 17 | // Struct representing FLASHWINFO 18 | [StructLayout(LayoutKind.Sequential)] 19 | public struct FLASHWINFO 20 | { 21 | public uint cbSize; 22 | public IntPtr hwnd; 23 | public uint dwFlags; 24 | public uint uCount; 25 | public uint dwTimeout; 26 | } 27 | 28 | // Import the FlashWindowEx function from user32.dll 29 | [DllImport("user32.dll")] 30 | [return: MarshalAs(UnmanagedType.Bool)] 31 | private static extern bool FlashWindowEx(ref FLASHWINFO pwfi); 32 | 33 | private void FlashAppIcon() 34 | { 35 | // Create a new instance of FLASHWINFO 36 | FLASHWINFO flashInfo = new FLASHWINFO 37 | { 38 | cbSize = Convert.ToUInt32(Marshal.SizeOf(typeof(FLASHWINFO))), 39 | hwnd = Process.GetCurrentProcess().MainWindowHandle, 40 | dwFlags = 2 | 4, // Flash both the caption and taskbar button 41 | uCount = uint.MaxValue, // Flash indefinitely 42 | dwTimeout = 0 // Use the default flash rate 43 | }; 44 | 45 | // Call FlashWindowEx to make the application icon flash 46 | FlashWindowEx(ref flashInfo); 47 | } 48 | 49 | public Alert(string lastUser) 50 | { 51 | InitializeComponent(); 52 | FlashAppIcon(); 53 | 54 | label_info.Text = "Another machine (" + lastUser + ") has indicated it is not safe to use Lightroom!" + 55 | Environment.NewLine + Environment.NewLine + 56 | "This is either because Lightroom is already open on that machine, or the status file somehow got out of sync. " + 57 | Environment.NewLine + Environment.NewLine + 58 | "It is strongly recommended you kill Lightroom here and then gracefully close Lightroom on " + lastUser + "." + 59 | Environment.NewLine + Environment.NewLine + 60 | "If you are sure this machine has the most up to date version of your catalogs and it is not open on another machine, click \"Override Catalogs\" " + 61 | "to erase the status file on your network share. When you close Lightroom, we'll use this machine's catalogs as the new master record."; 62 | } 63 | 64 | private void button_KillLightroom_Click(object sender, EventArgs e) 65 | { 66 | 67 | } 68 | 69 | private void Alert_FormClosing(object sender, FormClosingEventArgs e) 70 | { 71 | 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /LightroomSync/Alert.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 | -------------------------------------------------------------------------------- /LightroomSync/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | 8 | namespace LightroomSync 9 | { 10 | internal class Config 11 | { 12 | public string LocalFolder { get; set; } 13 | public string NetworkFolder { get; set; } 14 | 15 | public bool AutoCheckForUpdates { get; set; } 16 | 17 | public Config() { 18 | this.LocalFolder = "C:\\Users\\" + System.Environment.UserName + "\\Pictures\\Lightroom"; 19 | this.NetworkFolder = "P:\\Lightroom"; 20 | this.AutoCheckForUpdates = true; 21 | } 22 | 23 | public string ToJson() 24 | { 25 | return JsonConvert.SerializeObject(this); 26 | } 27 | 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LightroomSync/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace LightroomSync 2 | { 3 | partial class Form1 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | components = new System.ComponentModel.Container(); 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); 33 | label1 = new Label(); 34 | localFolderTextBox = new TextBox(); 35 | label2 = new Label(); 36 | networkFolderTextBox = new TextBox(); 37 | label3 = new Label(); 38 | eventsTextBox = new TextBox(); 39 | buttonSelectLocalFolder = new Button(); 40 | buttonSelectNetworkFolder = new Button(); 41 | timer1 = new System.Windows.Forms.Timer(components); 42 | menuStrip1 = new MenuStrip(); 43 | fileToolStripMenuItem = new ToolStripMenuItem(); 44 | launchAtStartupToolStripMenuItem = new ToolStripMenuItem(); 45 | autoCheckForUpdatesToolStripMenuItem = new ToolStripMenuItem(); 46 | minimizeToTrayToolStripMenuItem = new ToolStripMenuItem(); 47 | exitToolStripMenuItem = new ToolStripMenuItem(); 48 | helpToolStripMenuItem = new ToolStripMenuItem(); 49 | submitABugToolStripMenuItem = new ToolStripMenuItem(); 50 | gitHubPageToolStripMenuItem = new ToolStripMenuItem(); 51 | checkForUpdatesToolStripMenuItem = new ToolStripMenuItem(); 52 | aboutToolStripMenuItem = new ToolStripMenuItem(); 53 | menuStrip1.SuspendLayout(); 54 | SuspendLayout(); 55 | // 56 | // label1 57 | // 58 | label1.AutoSize = true; 59 | label1.Location = new Point(14, 52); 60 | label1.Name = "label1"; 61 | label1.Size = new Size(90, 20); 62 | label1.TabIndex = 0; 63 | label1.Text = "Local Folder"; 64 | label1.Click += label1_Click; 65 | // 66 | // localFolderTextBox 67 | // 68 | localFolderTextBox.Location = new Point(14, 76); 69 | localFolderTextBox.Margin = new Padding(3, 4, 3, 4); 70 | localFolderTextBox.Name = "localFolderTextBox"; 71 | localFolderTextBox.Size = new Size(865, 27); 72 | localFolderTextBox.TabIndex = 1; 73 | localFolderTextBox.TextChanged += localFolderTextBox_TextChanged; 74 | // 75 | // label2 76 | // 77 | label2.AutoSize = true; 78 | label2.Location = new Point(14, 132); 79 | label2.Name = "label2"; 80 | label2.Size = new Size(111, 20); 81 | label2.TabIndex = 2; 82 | label2.Text = "Network Folder"; 83 | // 84 | // networkFolderTextBox 85 | // 86 | networkFolderTextBox.Location = new Point(14, 156); 87 | networkFolderTextBox.Margin = new Padding(3, 4, 3, 4); 88 | networkFolderTextBox.Name = "networkFolderTextBox"; 89 | networkFolderTextBox.Size = new Size(865, 27); 90 | networkFolderTextBox.TabIndex = 3; 91 | networkFolderTextBox.Text = "P:\\Lightroom"; 92 | networkFolderTextBox.TextChanged += networkFolderTextBox_TextChanged; 93 | // 94 | // label3 95 | // 96 | label3.AutoSize = true; 97 | label3.Location = new Point(14, 219); 98 | label3.Name = "label3"; 99 | label3.Size = new Size(51, 20); 100 | label3.TabIndex = 4; 101 | label3.Text = "Events"; 102 | // 103 | // eventsTextBox 104 | // 105 | eventsTextBox.Location = new Point(14, 243); 106 | eventsTextBox.Margin = new Padding(3, 4, 3, 4); 107 | eventsTextBox.Multiline = true; 108 | eventsTextBox.Name = "eventsTextBox"; 109 | eventsTextBox.ScrollBars = ScrollBars.Vertical; 110 | eventsTextBox.Size = new Size(886, 167); 111 | eventsTextBox.TabIndex = 5; 112 | // 113 | // buttonSelectLocalFolder 114 | // 115 | buttonSelectLocalFolder.Location = new Point(886, 76); 116 | buttonSelectLocalFolder.Margin = new Padding(3, 4, 3, 4); 117 | buttonSelectLocalFolder.Name = "buttonSelectLocalFolder"; 118 | buttonSelectLocalFolder.Size = new Size(24, 31); 119 | buttonSelectLocalFolder.TabIndex = 8; 120 | buttonSelectLocalFolder.UseVisualStyleBackColor = true; 121 | buttonSelectLocalFolder.Click += buttonSelectLocalFolder_Click; 122 | // 123 | // buttonSelectNetworkFolder 124 | // 125 | buttonSelectNetworkFolder.Location = new Point(886, 156); 126 | buttonSelectNetworkFolder.Margin = new Padding(3, 4, 3, 4); 127 | buttonSelectNetworkFolder.Name = "buttonSelectNetworkFolder"; 128 | buttonSelectNetworkFolder.Size = new Size(24, 31); 129 | buttonSelectNetworkFolder.TabIndex = 9; 130 | buttonSelectNetworkFolder.UseVisualStyleBackColor = true; 131 | buttonSelectNetworkFolder.Click += buttonSelectNetworkFolder_Click; 132 | // 133 | // timer1 134 | // 135 | timer1.Enabled = true; 136 | timer1.Interval = 5000; 137 | timer1.Tick += timer1_Tick; 138 | // 139 | // menuStrip1 140 | // 141 | menuStrip1.ImageScalingSize = new Size(20, 20); 142 | menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, helpToolStripMenuItem }); 143 | menuStrip1.Location = new Point(0, 0); 144 | menuStrip1.Name = "menuStrip1"; 145 | menuStrip1.Padding = new Padding(7, 3, 0, 3); 146 | menuStrip1.Size = new Size(914, 30); 147 | menuStrip1.TabIndex = 10; 148 | menuStrip1.Text = "menuStrip1"; 149 | // 150 | // fileToolStripMenuItem 151 | // 152 | fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { launchAtStartupToolStripMenuItem, autoCheckForUpdatesToolStripMenuItem, minimizeToTrayToolStripMenuItem, exitToolStripMenuItem }); 153 | fileToolStripMenuItem.Name = "fileToolStripMenuItem"; 154 | fileToolStripMenuItem.Size = new Size(46, 24); 155 | fileToolStripMenuItem.Text = "File"; 156 | // 157 | // launchAtStartupToolStripMenuItem 158 | // 159 | launchAtStartupToolStripMenuItem.Name = "launchAtStartupToolStripMenuItem"; 160 | launchAtStartupToolStripMenuItem.Size = new Size(251, 26); 161 | launchAtStartupToolStripMenuItem.Text = "Launch At Startup"; 162 | launchAtStartupToolStripMenuItem.Click += launchAtStartupToolStripMenuItem_Click; 163 | // 164 | // autoCheckForUpdatesToolStripMenuItem 165 | // 166 | autoCheckForUpdatesToolStripMenuItem.Name = "autoCheckForUpdatesToolStripMenuItem"; 167 | autoCheckForUpdatesToolStripMenuItem.Size = new Size(251, 26); 168 | autoCheckForUpdatesToolStripMenuItem.Text = "Auto Check For Updates"; 169 | autoCheckForUpdatesToolStripMenuItem.Click += autoCheckForUpdatesToolStripMenuItem_Click; 170 | // 171 | // minimizeToTrayToolStripMenuItem 172 | // 173 | minimizeToTrayToolStripMenuItem.Name = "minimizeToTrayToolStripMenuItem"; 174 | minimizeToTrayToolStripMenuItem.Size = new Size(251, 26); 175 | minimizeToTrayToolStripMenuItem.Text = "Minimize To Tray"; 176 | minimizeToTrayToolStripMenuItem.Click += minimizeToTrayToolStripMenuItem_Click; 177 | // 178 | // exitToolStripMenuItem 179 | // 180 | exitToolStripMenuItem.Name = "exitToolStripMenuItem"; 181 | exitToolStripMenuItem.Size = new Size(251, 26); 182 | exitToolStripMenuItem.Text = "E&xit"; 183 | exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; 184 | // 185 | // helpToolStripMenuItem 186 | // 187 | helpToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { submitABugToolStripMenuItem, gitHubPageToolStripMenuItem, checkForUpdatesToolStripMenuItem, aboutToolStripMenuItem }); 188 | helpToolStripMenuItem.Name = "helpToolStripMenuItem"; 189 | helpToolStripMenuItem.Size = new Size(55, 24); 190 | helpToolStripMenuItem.Text = "Help"; 191 | // 192 | // submitABugToolStripMenuItem 193 | // 194 | submitABugToolStripMenuItem.Name = "submitABugToolStripMenuItem"; 195 | submitABugToolStripMenuItem.Size = new Size(215, 26); 196 | submitABugToolStripMenuItem.Text = "Submit A Bug"; 197 | submitABugToolStripMenuItem.Click += submitABugToolStripMenuItem_Click; 198 | // 199 | // gitHubPageToolStripMenuItem 200 | // 201 | gitHubPageToolStripMenuItem.Name = "gitHubPageToolStripMenuItem"; 202 | gitHubPageToolStripMenuItem.Size = new Size(215, 26); 203 | gitHubPageToolStripMenuItem.Text = "GitHub Page"; 204 | gitHubPageToolStripMenuItem.Click += gitHubPageToolStripMenuItem_Click; 205 | // 206 | // checkForUpdatesToolStripMenuItem 207 | // 208 | checkForUpdatesToolStripMenuItem.Name = "checkForUpdatesToolStripMenuItem"; 209 | checkForUpdatesToolStripMenuItem.Size = new Size(215, 26); 210 | checkForUpdatesToolStripMenuItem.Text = "Check For Updates"; 211 | checkForUpdatesToolStripMenuItem.Click += checkForUpdatesToolStripMenuItem_Click; 212 | // 213 | // aboutToolStripMenuItem 214 | // 215 | aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; 216 | aboutToolStripMenuItem.Size = new Size(215, 26); 217 | aboutToolStripMenuItem.Text = "About"; 218 | aboutToolStripMenuItem.Click += aboutToolStripMenuItem_Click; 219 | // 220 | // Form1 221 | // 222 | AutoScaleDimensions = new SizeF(8F, 20F); 223 | AutoScaleMode = AutoScaleMode.Font; 224 | ClientSize = new Size(914, 435); 225 | Controls.Add(buttonSelectNetworkFolder); 226 | Controls.Add(buttonSelectLocalFolder); 227 | Controls.Add(eventsTextBox); 228 | Controls.Add(label3); 229 | Controls.Add(networkFolderTextBox); 230 | Controls.Add(label2); 231 | Controls.Add(localFolderTextBox); 232 | Controls.Add(label1); 233 | Controls.Add(menuStrip1); 234 | Icon = (Icon)resources.GetObject("$this.Icon"); 235 | MainMenuStrip = menuStrip1; 236 | Margin = new Padding(3, 4, 3, 4); 237 | Name = "Form1"; 238 | Text = "Lightroom Sync"; 239 | FormClosing += Form1_FormClosing; 240 | Load += Form1_Load; 241 | menuStrip1.ResumeLayout(false); 242 | menuStrip1.PerformLayout(); 243 | ResumeLayout(false); 244 | PerformLayout(); 245 | } 246 | 247 | #endregion 248 | 249 | private Label label1; 250 | private TextBox localFolderTextBox; 251 | private Label label2; 252 | private TextBox networkFolderTextBox; 253 | private Label label3; 254 | private TextBox eventsTextBox; 255 | private Button buttonSelectLocalFolder; 256 | private Button buttonSelectNetworkFolder; 257 | private System.Windows.Forms.Timer timer1; 258 | private MenuStrip menuStrip1; 259 | private ToolStripMenuItem fileToolStripMenuItem; 260 | private ToolStripMenuItem launchAtStartupToolStripMenuItem; 261 | private ToolStripMenuItem helpToolStripMenuItem; 262 | private ToolStripMenuItem minimizeToTrayToolStripMenuItem; 263 | private ToolStripMenuItem exitToolStripMenuItem; 264 | private ToolStripMenuItem submitABugToolStripMenuItem; 265 | private ToolStripMenuItem gitHubPageToolStripMenuItem; 266 | private ToolStripMenuItem aboutToolStripMenuItem; 267 | private ToolStripMenuItem checkForUpdatesToolStripMenuItem; 268 | private ToolStripMenuItem autoCheckForUpdatesToolStripMenuItem; 269 | } 270 | } -------------------------------------------------------------------------------- /LightroomSync/Form1.cs: -------------------------------------------------------------------------------- 1 | using LightroomSync.Properties; 2 | using Microsoft.Win32; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Diagnostics; 6 | using System.IO.Compression; 7 | using System.Reflection; 8 | using System.Reflection.Metadata; 9 | using System.Runtime.InteropServices; 10 | using System.Security.Policy; 11 | using System.Security.Principal; 12 | using static LightroomSync.Alert; 13 | using static System.Net.Mime.MediaTypeNames; 14 | 15 | namespace LightroomSync 16 | { 17 | public partial class Form1 : Form 18 | { 19 | public string currentVersion = "1.0.0"; // <----- Make sure you always update latestVersion.txt as well! 20 | // Yes, I'm too lazy to pipe this in. 21 | 22 | private Config config = new Config(); 23 | private Status status = new Status(); 24 | 25 | private bool hasDealtWithLightroomOpen = false; 26 | 27 | private bool timerBeingHandled = false; //Used to handle async events in the timer potentially firing multiple times 28 | 29 | private NotifyIcon trayIcon; 30 | private ContextMenuStrip trayMenu; 31 | 32 | 33 | // Struct representing FLASHWINFO 34 | [StructLayout(LayoutKind.Sequential)] 35 | public struct FLASHWINFO 36 | { 37 | public uint cbSize; 38 | public IntPtr hwnd; 39 | public uint dwFlags; 40 | public uint uCount; 41 | public uint dwTimeout; 42 | } 43 | 44 | // Import the FlashWindowEx function from user32.dll 45 | [DllImport("user32.dll")] 46 | [return: MarshalAs(UnmanagedType.Bool)] 47 | private static extern bool FlashWindowEx(ref FLASHWINFO pwfi); 48 | 49 | 50 | 51 | private void StopFlashing() 52 | { 53 | FLASHWINFO flashInfo = new FLASHWINFO 54 | { 55 | cbSize = Convert.ToUInt32(Marshal.SizeOf(typeof(FLASHWINFO))), 56 | hwnd = Process.GetCurrentProcess().MainWindowHandle, 57 | dwFlags = 0, // Stop flashing 58 | uCount = 0, 59 | dwTimeout = 0 60 | }; 61 | FlashWindowEx(ref flashInfo); 62 | } 63 | 64 | private void Log(string message) 65 | { 66 | if (eventsTextBox.InvokeRequired) 67 | { 68 | eventsTextBox.Invoke(new Action(Log), message + Environment.NewLine + eventsTextBox.Text); 69 | } 70 | else 71 | { 72 | eventsTextBox.Text = message + Environment.NewLine + eventsTextBox.Text; 73 | } 74 | } 75 | 76 | private async Task UpdateStatusFileOnNetwork() 77 | { 78 | await Task.Run(() => 79 | { 80 | try 81 | { 82 | string filePath = config.NetworkFolder + "\\status.txt"; 83 | File.WriteAllText(filePath, status.ToJson()); 84 | Log("Updated status file"); 85 | } 86 | catch (Exception ex) 87 | { 88 | throw new Exception("ERROR: Failed writing status file: " + ex.Message); 89 | } 90 | }); 91 | } 92 | 93 | static async Task ZipFilesAndFolders(string zipFilename, string[] filesToZip, string[] foldersToZip) 94 | { 95 | await Task.Run(() => 96 | { 97 | using (ZipArchive zipArchive = ZipFile.Open(zipFilename, ZipArchiveMode.Create)) 98 | { 99 | // Zip individual files 100 | foreach (string filePath in filesToZip) 101 | { 102 | if (File.Exists(filePath)) 103 | { 104 | string entryName = Path.GetFileName(filePath); 105 | zipArchive.CreateEntryFromFile(filePath, entryName); 106 | } 107 | else 108 | { 109 | throw new FileNotFoundException($"File not found: {filePath}"); 110 | } 111 | } 112 | 113 | // Zip folders 114 | foreach (string folderPath in foldersToZip) 115 | { 116 | if (Directory.Exists(folderPath)) 117 | { 118 | string folderName = Path.GetFileName(folderPath); 119 | 120 | // Zip files inside the folder 121 | string[] files = Directory.GetFiles(folderPath); 122 | foreach (string filePath in files) 123 | { 124 | string entryName = Path.Combine(folderName, Path.GetFileName(filePath)); 125 | zipArchive.CreateEntryFromFile(filePath, entryName); 126 | } 127 | } 128 | else 129 | { 130 | throw new DirectoryNotFoundException($"Folder not found: {folderPath}"); 131 | } 132 | } 133 | } 134 | }); 135 | } 136 | 137 | private Status? getNetworkStatus() 138 | { 139 | string networkStatusFile = config.NetworkFolder + "\\status.txt"; 140 | if (File.Exists(networkStatusFile)) 141 | { 142 | 143 | string jsonContent = File.ReadAllText(networkStatusFile); 144 | try 145 | { 146 | return JsonConvert.DeserializeObject(jsonContent); 147 | } 148 | catch (JsonException ex) 149 | { 150 | Log("JSON parsing error: " + ex.Message); 151 | } 152 | catch (Exception ex) 153 | { 154 | Log("Unexpected error: " + ex.Message); 155 | } 156 | } 157 | return null; 158 | } 159 | 160 | private async Task UploadCatalogs() 161 | { 162 | if (Status.LightroomIsOpen()) 163 | { 164 | Log("ERROR: Lightroom is open, cannot save to network drive"); 165 | return; 166 | } 167 | 168 | //Verify status file says it's safe to work (if it exists, otherwise, we can assume this is the first time) 169 | Status? loadedStatus = getNetworkStatus(); 170 | if (loadedStatus != null && loadedStatus.isSafeToOverride) 171 | { 172 | //Safe to proceed 173 | } 174 | else if (loadedStatus != null && loadedStatus.LastUser == Environment.MachineName) 175 | { 176 | //Safe to proceed since we're the ones who said it wasn't safe. 177 | } 178 | else 179 | { 180 | Log("Network status file says it's not safe to proceed! Something has gone wrong, or another catalog sync is happening!"); 181 | return; 182 | } 183 | 184 | status.isSafeToOverride = false; 185 | try 186 | { 187 | await UpdateStatusFileOnNetwork(); 188 | } 189 | catch (Exception ex) 190 | { 191 | Log(ex.Message); 192 | return; 193 | } 194 | 195 | 196 | string[] files = Directory.GetFiles(config.LocalFolder, "*.lrcat"); 197 | 198 | status.MostRecentVersions = new List(); 199 | 200 | foreach (string file in files) 201 | { 202 | string catName = Path.GetFileNameWithoutExtension(file); 203 | 204 | 205 | string[] filesToZip = { config.LocalFolder + "\\" + catName + ".lrcat" }; 206 | string[] foldersToZip = { config.LocalFolder + "\\" + catName + ".lrcat-data", config.LocalFolder + "\\" + catName + " Helper.lrdata" }; 207 | 208 | DateTime lastModified = File.GetLastWriteTime(file); 209 | string customFormat = catName + " - " + lastModified.ToString("yyyy-MM-dd HH-mm") + ".zip"; 210 | 211 | Log("Zipping " + catName); 212 | try 213 | { 214 | await ZipFilesAndFolders(customFormat, filesToZip, foldersToZip); 215 | status.MostRecentVersions.Add(customFormat); 216 | Log("Files and folders have been zipped successfully."); 217 | } 218 | catch (Exception ex) 219 | { 220 | Log("ERROR: zipping files and folders: " + ex.Message); 221 | return; 222 | } 223 | 224 | Log("Moving " + customFormat + " to " + config.NetworkFolder); 225 | try 226 | { 227 | await Task.Run(() => { File.Move(customFormat, config.NetworkFolder + "\\" + customFormat, true); }); 228 | Log(customFormat + " moved successfully."); 229 | } 230 | catch (IOException ex) 231 | { 232 | Log("Error moving: " + ex.Message); 233 | } 234 | } 235 | 236 | status.isSafeToOverride = true; 237 | try 238 | { 239 | await UpdateStatusFileOnNetwork(); 240 | } 241 | catch (Exception ex) 242 | { 243 | Log(ex.Message); 244 | return; 245 | } 246 | Log("Sucessfully updated all " + files.Length.ToString() + " catalog(s) to network share."); 247 | } 248 | 249 | public Form1(bool startMinimized) 250 | { 251 | InitializeComponent(); 252 | 253 | // Create the NotifyIcon instance 254 | trayIcon = new NotifyIcon(); 255 | trayIcon.Text = "LightroomSync"; 256 | trayIcon.Icon = new Icon(GetType(), "camera.ico"); 257 | 258 | // Create a context menu for the tray icon 259 | trayMenu = new ContextMenuStrip(); 260 | trayMenu.Items.Add("Restore", null, OnRestore); 261 | trayMenu.Items.Add("Exit", null, OnExit); 262 | 263 | // Assign the context menu to the tray icon 264 | trayIcon.ContextMenuStrip = trayMenu; 265 | 266 | // Handle the form's Resize event 267 | this.Resize += OnResize; 268 | trayIcon.Click += OnRestore; 269 | 270 | if (startMinimized) 271 | { 272 | this.WindowState = FormWindowState.Minimized; 273 | this.Hide(); 274 | this.ShowInTaskbar = false; 275 | trayIcon.Visible = true; 276 | } 277 | } 278 | 279 | private void OnRestore(object sender, EventArgs e) 280 | { 281 | // Restore the form from the system tray 282 | this.Show(); 283 | this.ShowInTaskbar = true; 284 | this.WindowState = FormWindowState.Normal; 285 | trayIcon.Visible = false; 286 | } 287 | 288 | private void OnExit(object sender, EventArgs e) 289 | { 290 | // Clean up resources and close the application 291 | trayIcon.Dispose(); 292 | System.Windows.Forms.Application.Exit(); 293 | } 294 | 295 | private void OnResize(object sender, EventArgs e) 296 | { 297 | // Minimize the form to the system tray when it's minimized 298 | if (FormWindowState.Minimized == this.WindowState) 299 | { 300 | this.Hide(); 301 | trayIcon.Visible = true; 302 | } 303 | } 304 | 305 | private async void label1_Click(object sender, EventArgs e) 306 | { 307 | await UploadCatalogs(); 308 | } 309 | 310 | private void Form1_Load(object sender, EventArgs e) 311 | { 312 | if (File.Exists("config.txt")) 313 | { 314 | 315 | string jsonContent = File.ReadAllText("config.txt"); 316 | try 317 | { 318 | Config? loadedConfig = JsonConvert.DeserializeObject(jsonContent); 319 | 320 | if (loadedConfig != null) 321 | { 322 | config = loadedConfig; 323 | } 324 | else 325 | { 326 | Log("JSON deserialization failed"); 327 | } 328 | } 329 | catch (JsonException ex) 330 | { 331 | Log("JSON parsing error: " + ex.Message); 332 | } 333 | catch (Exception ex) 334 | { 335 | Log("Unexpected error: " + ex.Message); 336 | } 337 | } 338 | 339 | localFolderTextBox.Text = config.LocalFolder; 340 | networkFolderTextBox.Text = config.NetworkFolder; 341 | 342 | status.LastUser = System.Environment.MachineName; 343 | 344 | if (Utils.ShortcutExistsInStartupFolder()) 345 | { 346 | launchAtStartupToolStripMenuItem.Image = Resources.checkmark; 347 | } 348 | 349 | if (config.AutoCheckForUpdates) 350 | { 351 | autoCheckForUpdatesToolStripMenuItem.Image = Resources.checkmark; 352 | CheckForUpdates(true); 353 | } 354 | } 355 | 356 | private void button1_Click(object sender, EventArgs e) 357 | { 358 | 359 | 360 | 361 | } 362 | 363 | private void Form1_FormClosing(object sender, FormClosingEventArgs e) 364 | { 365 | string filePath = "config.txt"; 366 | File.WriteAllText(filePath, config.ToJson()); 367 | } 368 | 369 | private void localFolderTextBox_TextChanged(object sender, EventArgs e) 370 | { 371 | if (Directory.Exists(localFolderTextBox.Text)) 372 | { 373 | localFolderTextBox.BackColor = Color.White; 374 | config.LocalFolder = localFolderTextBox.Text; 375 | } 376 | else 377 | { 378 | localFolderTextBox.BackColor = Color.LightPink; 379 | } 380 | } 381 | 382 | private void networkFolderTextBox_TextChanged(object sender, EventArgs e) 383 | { 384 | if (Directory.Exists(networkFolderTextBox.Text)) 385 | { 386 | networkFolderTextBox.BackColor = Color.White; 387 | config.NetworkFolder = networkFolderTextBox.Text; 388 | } 389 | else 390 | { 391 | networkFolderTextBox.BackColor = Color.LightPink; 392 | } 393 | } 394 | 395 | private void buttonSelectLocalFolder_Click(object sender, EventArgs e) 396 | { 397 | using (var folderBrowserDialog = new FolderBrowserDialog()) 398 | { 399 | // Show the folder browser dialog 400 | DialogResult result = folderBrowserDialog.ShowDialog(); 401 | 402 | // Check if the user selected a folder 403 | if (result == DialogResult.OK && !string.IsNullOrWhiteSpace(folderBrowserDialog.SelectedPath)) 404 | { 405 | // Update the text box with the selected folder path 406 | localFolderTextBox.Text = folderBrowserDialog.SelectedPath; 407 | } 408 | } 409 | } 410 | 411 | private void buttonSelectNetworkFolder_Click(object sender, EventArgs e) 412 | { 413 | using (var folderBrowserDialog = new FolderBrowserDialog()) 414 | { 415 | // Show the folder browser dialog 416 | DialogResult result = folderBrowserDialog.ShowDialog(); 417 | 418 | // Check if the user selected a folder 419 | if (result == DialogResult.OK && !string.IsNullOrWhiteSpace(folderBrowserDialog.SelectedPath)) 420 | { 421 | // Update the text box with the selected folder path 422 | networkFolderTextBox.Text = folderBrowserDialog.SelectedPath; 423 | } 424 | } 425 | } 426 | 427 | private void timer1_Tick(object sender, EventArgs e) 428 | { 429 | if (timerBeingHandled == true) 430 | { 431 | //return; 432 | } 433 | 434 | HandleTimerEvent(); 435 | } 436 | 437 | private async void HandleTimerEvent() 438 | { 439 | 440 | timerBeingHandled = true; 441 | 442 | if (Status.LightroomIsOpen() && hasDealtWithLightroomOpen == false) 443 | { 444 | timer1.Enabled = false; 445 | 446 | Status? loadedStatus = getNetworkStatus(); 447 | if (loadedStatus != null && loadedStatus.isSafeToOverride == false && loadedStatus.LastUser != Environment.MachineName) 448 | { 449 | Alert alert = new(loadedStatus.LastUser); 450 | DialogResult result = alert.ShowDialog(); 451 | if (result == DialogResult.Yes) 452 | { 453 | Process[] processes = Process.GetProcessesByName("Lightroom"); 454 | if (processes.Length > 0) 455 | { 456 | processes[0].Kill(); 457 | Log("Lightroom process killed. Please close Lightroom on " + loadedStatus.LastUser + " before trying again."); 458 | StopFlashing(); 459 | timer1.Enabled = true; 460 | timerBeingHandled = false; 461 | return; 462 | } 463 | else 464 | { 465 | Log("ERROR: Couldn't find Lightroom process to kill! Either this is a bug or you closed it manually. Please restart this application (and consider filing a bug report on GitHub)."); 466 | return; 467 | } 468 | } 469 | 470 | // Continue if user selected DialogResult.No, since that means they want to wipe the existing status 471 | Log("The status file on your network will be overwritten by this machine."); 472 | StopFlashing(); 473 | } 474 | 475 | Log("Detected Lightroom is open. Updating the status file to alert other machines."); 476 | hasDealtWithLightroomOpen = true; 477 | status.isSafeToOverride = false; 478 | status.LastUser = Environment.MachineName; 479 | await UpdateStatusFileOnNetwork(); 480 | timer1.Enabled = true; 481 | timerBeingHandled = false; 482 | } 483 | else if (Status.LightroomIsOpen() == false && hasDealtWithLightroomOpen == true) 484 | { 485 | //This means Lightroom WAS open, but isn't anymore. This is where we upload the catalogs. 486 | timer1.Enabled = false; 487 | await UploadCatalogs(); 488 | hasDealtWithLightroomOpen = false; 489 | timer1.Enabled = true; 490 | timerBeingHandled = false; 491 | } 492 | else if (Status.LightroomIsOpen() == false && hasDealtWithLightroomOpen == false) 493 | { 494 | // Lightroom has not been open, so there's a possibility the network has newer catalogs. 495 | timer1.Enabled = false; 496 | Status? loadedStatus = getNetworkStatus(); 497 | if (loadedStatus == null) 498 | { 499 | timer1.Enabled = true; 500 | timerBeingHandled = false; 501 | return; 502 | } 503 | 504 | string[] files = Directory.GetFiles(config.LocalFolder, "*.lrcat"); 505 | string[] timestamped = new string[files.Length]; 506 | for (int i = 0; i < files.Length; i++) 507 | { 508 | string catName = Path.GetFileNameWithoutExtension(files[i]); 509 | DateTime lastModified = File.GetLastWriteTime(files[i]); 510 | timestamped[i] = catName + " - " + lastModified.ToString("yyyy-MM-dd HH-mm") + ".zip"; 511 | } 512 | 513 | foreach (string catalog in loadedStatus.MostRecentVersions) 514 | { 515 | if (!timestamped.Contains(catalog)) 516 | { 517 | //We do not have the most recent version. 518 | Log("Newer catalog version detected! Copying " + catalog + " to local storage."); 519 | try 520 | { 521 | await Task.Run(() => { File.Copy(config.NetworkFolder + "\\" + catalog, catalog); }); 522 | Log(catalog + " copied successfully. Erasing existing catalog."); 523 | 524 | string catName = catalog.Substring(0, catalog.Length - 23); //23 is len(" - yyyy-MM-dd HH-mm.zip") 525 | string file1 = config.LocalFolder + "\\" + catName + ".lrcat"; 526 | string dir1 = config.LocalFolder + "\\" + catName + ".lrcat-data"; 527 | string dir2 = config.LocalFolder + "\\" + catName + " Helper.lrdata"; 528 | try 529 | { 530 | if (File.Exists(file1)) 531 | { 532 | File.Delete(file1); 533 | Log("Deleted " + file1); 534 | } 535 | if (Directory.Exists(dir1)) 536 | { 537 | Directory.Delete(dir1, recursive: true); 538 | Log("Deleted " + dir1); 539 | } 540 | if (Directory.Exists(dir2)) 541 | { 542 | Directory.Delete(dir2, recursive: true); 543 | Log("Deleted " + dir2); 544 | } 545 | } 546 | catch (IOException ex) 547 | { 548 | Log("An I/O error occurred: " + ex.Message); 549 | Log("YOU SHOULD MANUALLY EXTRACT THE ZIP TO RECOVER YOUR CATALOG"); 550 | return; 551 | } 552 | catch (UnauthorizedAccessException ex) 553 | { 554 | Log("Unauthorized access error occurred: " + ex.Message); 555 | Log("YOU SHOULD MANUALLY EXTRACT THE ZIP TO RECOVER YOUR CATALOG"); 556 | return; 557 | } 558 | catch (Exception ex) 559 | { 560 | Log("An error occurred: " + ex.Message); 561 | Log("YOU SHOULD MANUALLY EXTRACT THE ZIP TO RECOVER YOUR CATALOG"); 562 | return; 563 | } 564 | 565 | Log("Extracting zip"); 566 | try 567 | { 568 | await Task.Run(() => 569 | { 570 | ZipFile.ExtractToDirectory(catalog, config.LocalFolder); 571 | }); 572 | Log("Unzipped " + catalog + " - Now deleting the zip file locally."); 573 | } 574 | catch (Exception ex) 575 | { 576 | Log("An error occurred while unzipping the file: " + ex.Message); 577 | Log("YOU SHOULD MANUALLY EXTRACT THE ZIP TO RECOVER YOUR CATALOG"); 578 | } 579 | 580 | //You know what? If this file I just created has errors in deleting, I want someone to go file a bug report. 581 | //That's absurd, and I'm not adding another wall of error handling around it. 582 | File.Delete(catalog); 583 | Log("Zip file deleted. This catalog is up to date!"); 584 | } 585 | catch (IOException ex) 586 | { 587 | Log("Error copying: " + ex.Message); 588 | } 589 | } 590 | } 591 | 592 | timer1.Enabled = true; 593 | timerBeingHandled = false; 594 | } 595 | } 596 | 597 | private async void CheckForUpdates(bool silently) 598 | { 599 | string version = "ERR NOT SET"; 600 | 601 | using (HttpClient client = new HttpClient()) 602 | { 603 | try 604 | { 605 | version = await client.GetStringAsync("https://github.com/software-2/LightroomSync/raw/master/latestVersion.txt"); 606 | } 607 | catch (Exception ex) 608 | { 609 | Log($"Error checking for new version: {ex.Message}"); 610 | if (silently) 611 | { 612 | return; 613 | } 614 | var result = MessageBox.Show("Sorry, I couldn't find the version number. Do you want to go to the website to check?", "Error!", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); 615 | if (result == DialogResult.Yes) 616 | { 617 | Utils.OpenURL("https://github.com/software-2/LightroomSync/releases"); 618 | } 619 | return; 620 | } 621 | } 622 | 623 | var parsed = Version.Parse(version); 624 | 625 | if (parsed.CompareTo(Version.Parse(currentVersion)) != 0) 626 | { 627 | var dialog = "There is a new version! Want to go get it?" + Environment.NewLine + Environment.NewLine + "New Version: " + parsed.ToString() + Environment.NewLine + "Your Version: " + currentVersion.ToString(); 628 | var result = MessageBox.Show(dialog, "New Version!", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); 629 | if (result == DialogResult.Yes) 630 | { 631 | Utils.OpenURL("https://github.com/software-2/LightroomSync/releases"); 632 | } 633 | } 634 | else if (!silently) 635 | { 636 | MessageBox.Show("You expected an update, but it was me! Dio!", "Up To Date!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 637 | } 638 | } 639 | 640 | private void submitABugToolStripMenuItem_Click(object sender, EventArgs e) 641 | { 642 | Utils.OpenURL("https://github.com/software-2/LightroomSync/issues"); 643 | } 644 | 645 | private void gitHubPageToolStripMenuItem_Click(object sender, EventArgs e) 646 | { 647 | Utils.OpenURL("https://github.com/software-2/LightroomSync"); 648 | } 649 | 650 | private void minimizeToTrayToolStripMenuItem_Click(object sender, EventArgs e) 651 | { 652 | this.WindowState = FormWindowState.Minimized; 653 | } 654 | 655 | private void exitToolStripMenuItem_Click(object sender, EventArgs e) 656 | { 657 | this.Close(); 658 | } 659 | 660 | private void launchAtStartupToolStripMenuItem_Click(object sender, EventArgs e) 661 | { 662 | //Either remove or add from startup as a toggle. 663 | if (Utils.ShortcutExistsInStartupFolder()) 664 | { 665 | Utils.DeleteShortcutFromStartupFolder(); 666 | launchAtStartupToolStripMenuItem.Image = null; 667 | } 668 | else 669 | { 670 | string assemblyLocation = Assembly.GetEntryAssembly().Location; 671 | string executablePath = Path.GetDirectoryName(assemblyLocation); 672 | string appPath = executablePath + "\\LightroomSync.exe"; 673 | launchAtStartupToolStripMenuItem.Image = Resources.checkmark; 674 | 675 | Utils.CreateShortcutInStartupFolder(appPath); 676 | } 677 | } 678 | 679 | private void aboutToolStripMenuItem_Click(object sender, EventArgs e) 680 | { 681 | MessageBox.Show("LightroomSync" + Environment.NewLine + "Copyright 2023 Anthony Bryan" + Environment.NewLine + Environment.NewLine + "Version " + currentVersion); 682 | } 683 | 684 | private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) 685 | { 686 | CheckForUpdates(false); 687 | } 688 | 689 | private void autoCheckForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) 690 | { 691 | config.AutoCheckForUpdates = !config.AutoCheckForUpdates; 692 | if (config.AutoCheckForUpdates) 693 | { 694 | autoCheckForUpdatesToolStripMenuItem.Image = Resources.checkmark; 695 | } 696 | else 697 | { 698 | autoCheckForUpdatesToolStripMenuItem.Image = null; 699 | } 700 | } 701 | } 702 | } -------------------------------------------------------------------------------- /LightroomSync/Form1.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 | 17, 17 122 | 123 | 124 | 104, 17 125 | 126 | 127 | 128 | 129 | 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 275 | 276 | 277 | -------------------------------------------------------------------------------- /LightroomSync/LightroomSync.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | enable 7 | true 8 | enable 9 | LICENSE 10 | Copyright 2023 Anthony Bryan 11 | https://github.com/software-2/LightroomSync 12 | https://github.com/software-2/LightroomSync 13 | git 14 | 1.0.0 15 | 1.0.0 16 | 17 | camera.ico 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | tlbimp 28 | 0 29 | 1 30 | f935dc20-1cf0-11d0-adb9-00c04fd58a0b 31 | 0 32 | false 33 | true 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | True 49 | \ 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | True 60 | True 61 | Resources.resx 62 | 63 | 64 | 65 | 66 | 67 | ResXFileCodeGenerator 68 | Resources.Designer.cs 69 | 70 | 71 | 72 | 73 | 74 | True 75 | \ 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /LightroomSync/Program.cs: -------------------------------------------------------------------------------- 1 | namespace LightroomSync 2 | { 3 | internal static class Program 4 | { 5 | /// 6 | /// The main entry point for the application. 7 | /// 8 | [STAThread] 9 | static void Main(string[] args) 10 | { 11 | // To customize application configuration such as set high DPI settings or default font, 12 | // see https://aka.ms/applicationconfiguration. 13 | ApplicationConfiguration.Initialize(); 14 | 15 | if (!Directory.Exists(Utils.GetWorkingDir())) 16 | { 17 | Directory.CreateDirectory(Utils.GetWorkingDir()); 18 | } 19 | Directory.SetCurrentDirectory(Utils.GetWorkingDir()); 20 | 21 | bool startMinimized = false; 22 | foreach (string argument in args) 23 | { 24 | if (argument == "tray") 25 | { 26 | startMinimized = true; 27 | } 28 | } 29 | 30 | Application.Run(new Form1(startMinimized)); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LightroomSync/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 LightroomSync.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", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal 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 | internal 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("LightroomSync.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 | internal 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 | internal static System.Drawing.Bitmap checkmark { 67 | get { 68 | object obj = ResourceManager.GetObject("checkmark", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /LightroomSync/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 | ..\checkmark.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /LightroomSync/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "LightroomSync": { 4 | "commandName": "Project" 5 | }, 6 | "Launch To Tray": { 7 | "commandName": "Project", 8 | "commandLineArgs": "tray", 9 | "hotReloadEnabled": false 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /LightroomSync/Status.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using static System.Windows.Forms.VisualStyles.VisualStyleElement; 9 | 10 | namespace LightroomSync 11 | { 12 | internal class Status 13 | { 14 | public string LastUser { get; set; } 15 | public bool isSafeToOverride { get; set; } 16 | public List MostRecentVersions { get; set; } 17 | 18 | public Status() { 19 | LastUser = Environment.MachineName; 20 | isSafeToOverride = true; 21 | MostRecentVersions = new List(); 22 | } 23 | public static bool LightroomIsOpen() 24 | { 25 | Process[] processes = Process.GetProcessesByName("Lightroom"); 26 | return processes.Length > 0; 27 | } 28 | 29 | public string ToJson() 30 | { 31 | return JsonConvert.SerializeObject(this); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LightroomSync/Utils.cs: -------------------------------------------------------------------------------- 1 | using IWshRuntimeLibrary; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.IO; 8 | using System.Diagnostics; 9 | 10 | namespace LightroomSync 11 | { 12 | internal class Utils 13 | { 14 | public static void CreateShortcutInStartupFolder(string appPath) 15 | { 16 | string startupFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup); 17 | string shortcutPath = Path.Combine(startupFolderPath, "LightroomSync.lnk"); 18 | 19 | WshShell shell = new WshShell(); 20 | IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(shortcutPath); 21 | shortcut.TargetPath = appPath; 22 | shortcut.WorkingDirectory = Path.GetDirectoryName(appPath); 23 | shortcut.Description = "LightroomSync"; 24 | shortcut.Arguments = "tray"; 25 | shortcut.Save(); 26 | } 27 | 28 | public static bool ShortcutExistsInStartupFolder() 29 | { 30 | string startupFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup); 31 | string shortcutPath = Path.Combine(startupFolderPath, "LightroomSync.lnk"); 32 | 33 | return System.IO.File.Exists(shortcutPath); 34 | } 35 | 36 | public static void DeleteShortcutFromStartupFolder() 37 | { 38 | string startupFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup); 39 | string shortcutPath = Path.Combine(startupFolderPath, "LightroomSync.lnk"); 40 | 41 | System.IO.File.Delete(shortcutPath); 42 | } 43 | 44 | public static string GetWorkingDir() 45 | { 46 | return Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\LightroomSync"; 47 | } 48 | 49 | public static void OpenURL(string url) 50 | { 51 | ProcessStartInfo startInfo = new ProcessStartInfo 52 | { 53 | FileName = url, 54 | UseShellExecute = true 55 | }; 56 | Process.Start(startInfo); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /LightroomSync/camera.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-2/LightroomSync/08a7fd9a1ede0073800edf5f62998ab746a07c60/LightroomSync/camera.ico -------------------------------------------------------------------------------- /LightroomSync/camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LightroomSync/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-2/LightroomSync/08a7fd9a1ede0073800edf5f62998ab746a07c60/LightroomSync/checkmark.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LightroomSync 2 | 3 | A tool to manage your Lightroom Classic catalogs across multiple Windows computers. 4 | 5 | Lightroom catalogs are designed to be single-user, and Lightroom won't even let you run your catalog from a network folder, even though all your photos are on that NAS. But if you're like me, you want to work on your photos in multiple locations (such as your laptop on the couch for triaging, and your main workstation for more advanced editing). This means having to manually copy your catalog around, and that's a pain. Well no more! This program will save your catalog(s) to a network folder, and automatically update your local catalogs if a newer version is on the network. 6 | 7 | ![Screenshot](https://github.com/software-2/LightroomSync/blob/master/Screenshot.png?raw=true) 8 | 9 | ## Features 10 | - Automatically uploads a zip of your catalog to your network folder as soon as you close Lightroom. 11 | - Automatically replaces your catalogs with the newest version on the network. 12 | - Prevents you from having Lightroom open on multiple machines at once to avoid conflicts and overwriting work. 13 | - Can launch at startup in the system tray - never think about this problem again! 14 | 15 | ## Setup Instructions 16 | - Build from source, or use the latest installer on the [Releases](https://github.com/software-2/LightroomSync/releases) page. 17 | - On each machine, specify the directory where all your catalogs are stored locally. 18 | - Specify a common network location that all machines can access. This is where catalogs will be stored. 19 | - If you want this to run all the time, select File > Run At Startup. It will silently run in the system tray. 20 | - With this program running, open Lightroom Classic, then close it. After detecting Lightroom has closed, it will zip and upload your catalogs. 21 | - Launch this program on another machine. It will grab every catalog from the network. 22 | 23 | ## Troubleshooting 24 | 25 | ### My catalog is missing! Oh my God! 26 | 27 | If something went wrong, please [file a bug report](https://github.com/software-2/LightroomSync/issues) so I can try to fix it. But worry not, your catalogs should still be available on the network share, and a copy is also stored in %AppData% until after the zip is finished extracting. The zips in the network share are never deleted by this program as a safety measure. (And as a better backup solution than Adobe's.) 28 | 29 | ### The program crashed or isn't working. 30 | 31 | It works on my machine! That means I need you to file a bug report here on GitHub. In the app there's an event textbox. Please copy/paste that into your bug report, as it will likely give me a better idea of what's going on. 32 | 33 | ### Where is the Mac version! 34 | 35 | I don't use my Mac as anything more than a glorified build server. This program uses tons of Windows-specific calls, so it'd likely need to be rewritten. The config file is simple JSON though, so if you want to make a version that's compatible with this, it could certainly be done. If you want to work on that, reach out to me and I'll try to help. -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-2/LightroomSync/08a7fd9a1ede0073800edf5f62998ab746a07c60/Screenshot.png -------------------------------------------------------------------------------- /latestVersion.txt: -------------------------------------------------------------------------------- 1 | 1.0.0 --------------------------------------------------------------------------------