├── .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 |
--------------------------------------------------------------------------------
/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 | 
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
--------------------------------------------------------------------------------