1+ --[[
2+
3+ dbmaint.lua - perform database maintenance
4+
5+ Copyright (C) 2024 Bill Ferguson <wpferguson.com>.
6+
7+ This program is free software: you can redistribute it and/or modify
8+ it under the terms of the GNU General Public License as published by
9+ the Free Software Foundation; either version 3 of the License, or
10+ (at your option) any later version.
11+
12+ This program is distributed in the hope that it will be useful,
13+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ GNU General Public License for more details.
16+
17+ You should have received a copy of the GNU General Public License
18+ along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ ]]
20+ --[[
21+ dbmaint - perform database maintenance
22+
23+ Perform database maintenance to clean up missing images and filmstrips.
24+
25+ ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
26+ None
27+
28+ USAGE
29+ * start dbmaint from script_manager
30+ * scan for missing film rolls or missing images
31+ * look at the results and choose to delete or not
32+
33+ BUGS, COMMENTS, SUGGESTIONS
34+ Bill Ferguson <wpferguson.com>
35+
36+ CHANGES
37+ ]]
38+
39+ local dt = require " darktable"
40+ local du = require " lib/dtutils"
41+ local df = require " lib/dtutils.file"
42+ local log = require " lib/dtutils.log"
43+
44+
45+ -- - - - - - - - - - - - - - - - - - - - - - - -
46+ -- C O N S T A N T S
47+ -- - - - - - - - - - - - - - - - - - - - - - - -
48+
49+ local MODULE <const> = " dbmaint"
50+ local DEFAULT_LOG_LEVEL <const> = log .error
51+ local PS <const> = dt .configuration .running_os == " windows" and " \\ " or " /"
52+
53+ -- - - - - - - - - - - - - - - - - - - - - - - -
54+ -- A P I C H E C K
55+ -- - - - - - - - - - - - - - - - - - - - - - - -
56+
57+ du .check_min_api_version (" 7.0.0" , MODULE ) -- choose the minimum version that contains the features you need
58+
59+
60+ -- - - - - - - - - - - - - - - - - - - - - - - - - -
61+ -- I 1 8 N
62+ -- - - - - - - - - - - - - - - - - - - - - - - - - -
63+
64+ local gettext = dt .gettext .gettext
65+
66+ dt .gettext .bindtextdomain (MODULE , dt .configuration .config_dir .. " /lua/locale/" )
67+
68+ local function _ (msgid )
69+ return gettext (MODULE , msgid )
70+ end
71+
72+
73+ -- - - - - - - - - - - - - - - - - - - - - - - - - -
74+ -- S C R I P T M A N A G E R I N T E G R A T I O N
75+ -- - - - - - - - - - - - - - - - - - - - - - - - - -
76+
77+ local script_data = {}
78+
79+ script_data .destroy = nil -- function to destory the script
80+ script_data .destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
81+ script_data .restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
82+ script_data .show = nil -- only required for libs since the destroy_method only hides them
83+
84+ script_data .metadata = {
85+ name = " dbmaint" ,
86+ purpose = _ (" perform database maintenance" ),
87+ author = " Bill Ferguson <wpferguson.com>" ,
88+ help = " https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/dbmaint/"
89+ }
90+
91+
92+ -- - - - - - - - - - - - - - - - - - - - - - - -
93+ -- L O G L E V E L
94+ -- - - - - - - - - - - - - - - - - - - - - - - -
95+
96+ log .log_level (DEFAULT_LOG_LEVEL )
97+
98+ -- - - - - - - - - - - - - - - - - - - - - - - -
99+ -- N A M E S P A C E
100+ -- - - - - - - - - - - - - - - - - - - - - - - -
101+
102+ local dbmaint = {}
103+
104+ -- - - - - - - - - - - - - - - - - - - - - - - -
105+ -- G L O B A L V A R I A B L E S
106+ -- - - - - - - - - - - - - - - - - - - - - - - -
107+
108+ dbmaint .main_widget = nil
109+
110+ -- - - - - - - - - - - - - - - - - - - - - - - -
111+ -- A L I A S E S
112+ -- - - - - - - - - - - - - - - - - - - - - - - -
113+
114+ local namespace = dbmaint
115+
116+ -- - - - - - - - - - - - - - - - - - - - - - - -
117+ -- F U N C T I O N S
118+ -- - - - - - - - - - - - - - - - - - - - - - - -
119+
120+ local function scan_film_rolls ()
121+ local missing_films = {}
122+
123+ for _ , filmroll in ipairs (dt .films ) do
124+ if not df .check_if_file_exists (filmroll .path ) then
125+ table.insert (missing_films , filmroll )
126+ end
127+ end
128+
129+ return missing_films
130+ end
131+
132+ local function scan_images (film )
133+ local missing_images = {}
134+
135+ if film then
136+ for i = 1 , # film do
137+ local image = film [i ]
138+ log .log_msg (log .debug , " checking " .. image .filename )
139+ if not df .check_if_file_exists (image .path .. PS .. image .filename ) then
140+ log .log_msg (log .info , image .filename .. " not found" )
141+ table.insert (missing_images , image )
142+ end
143+ end
144+ end
145+
146+ return missing_images
147+ end
148+
149+ local function remove_missing_film_rolls (list )
150+ for _ , filmroll in ipairs (list ) do
151+ filmroll :delete (true )
152+ end
153+ end
154+
155+ -- force the lighttable to reload
156+
157+ local function refresh_lighttable (film )
158+ local rules = dt .gui .libs .collect .filter ()
159+ dt .gui .libs .collect .filter (rules )
160+ end
161+
162+ local function remove_missing_images (list )
163+ local film = list [1 ].film
164+ for _ , image in ipairs (list ) do
165+ image :delete ()
166+ end
167+ refresh_lighttable (film )
168+ end
169+
170+ local function install_module ()
171+ if not namespace .module_installed then
172+ dt .register_lib (
173+ MODULE , -- Module name
174+ _ (" DB maintenance" ), -- Visible name
175+ true , -- expandable
176+ true , -- resetable
177+ {[dt .gui .views .lighttable ] = {" DT_UI_CONTAINER_PANEL_LEFT_CENTER" , 0 }}, -- containers
178+ namespace .main_widget ,
179+ nil ,-- view_enter
180+ nil -- view_leave
181+ )
182+ namespace .module_installed = true
183+ end
184+ end
185+
186+ -- - - - - - - - - - - - - - - - - - - - - - - -
187+ -- U S E R I N T E R F A C E
188+ -- - - - - - - - - - - - - - - - - - - - - - - -
189+
190+ dbmaint .list_widget = dt .new_widget (" text_view" ){
191+ editable = false ,
192+ reset_callback = function (this )
193+ this .text = " "
194+ end
195+ }
196+
197+ dbmaint .chooser = dt .new_widget (" combobox" ){
198+ label = " scan for" ,
199+ selected = 1 ,
200+ " film rolls" , " images" ,
201+ reset_callback = function (this )
202+ this .selected = 1
203+ end
204+ }
205+
206+ dbmaint .scan_button = dt .new_widget (" button" ){
207+ label = " scan" ,
208+ tooltip = " click to scan for missing film rolls/files" ,
209+ clicked_callback = function (this )
210+ local found = nil
211+ local found_text = " "
212+ if dbmaint .chooser .value == " film rolls" then
213+ found = scan_film_rolls ()
214+ if # found > 0 then
215+ for _ , film in ipairs (found ) do
216+ local dir_name = du .split (film .path , PS )
217+ found_text = found_text .. dir_name [# dir_name ] .. " \n "
218+ end
219+ end
220+ else
221+ log .log_msg (log .debug , " checking path " .. dt .collection [1 ].path .. " for missing files" )
222+ found = scan_images (dt .collection [1 ].film )
223+ if # found > 0 then
224+ for _ , image in ipairs (found ) do
225+ found_text = found_text .. image .filename .. " \n "
226+ end
227+ end
228+ end
229+ if # found > 0 then
230+ dbmaint .list_widget .text = found_text
231+ dbmaint .found = found
232+ dbmaint .remove_button .sensitive = true
233+ end
234+ end ,
235+ reset_callback = function (this )
236+ dbmaint .found = nil
237+ end
238+ }
239+
240+ dbmaint .remove_button = dt .new_widget (" button" ){
241+ label = " remove" ,
242+ tooltip = " remove missing film rolls/images" ,
243+ sensitive = false ,
244+ clicked_callback = function (this )
245+ if dbmaint .chooser .value == " film rolls" then
246+ remove_missing_film_rolls (dbmaint .found )
247+ else
248+ remove_missing_images (dbmaint .found )
249+ end
250+ dbmaint .found = nil
251+ dbmaint .list_widget .text = " "
252+ this .sensitive = false
253+ end ,
254+ reset_callback = function (this )
255+ this .sensitive = false
256+ end
257+ }
258+
259+ dbmaint .main_widget = dt .new_widget (" box" ){
260+ orientation = " vertical" ,
261+ dt .new_widget (" section_label" ){label = " missing items" },
262+ dbmaint .list_widget ,
263+ dt .new_widget (" label" ){label = " " },
264+ dbmaint .chooser ,
265+ dt .new_widget (" label" ){label = " " },
266+ dbmaint .scan_button ,
267+ dbmaint .remove_button
268+ }
269+
270+ -- - - - - - - - - - - - - - - - - - - - - - - -
271+ -- D A R K T A B L E I N T E G R A T I O N
272+ -- - - - - - - - - - - - - - - - - - - - - - - -
273+
274+ local function destroy ()
275+ dt .gui .libs [MODULE ].visible = false
276+
277+ if namespace .event_registered then
278+ dt .destroy_event (MODULE , " view-changed" )
279+ end
280+
281+ return
282+ end
283+
284+ local function restart ()
285+ dt .gui .libs [MODULE ].visible = true
286+
287+ return
288+ end
289+
290+ script_data .destroy = destroy
291+ script_data .restart = restart
292+ script_data .destroy_method = " hide"
293+ script_data .show = restart
294+
295+ -- - - - - - - - - - - - - - - - - - - - - - - -
296+ -- E V E N T S
297+ -- - - - - - - - - - - - - - - - - - - - - - - -
298+
299+ if dt .gui .current_view ().id == " lighttable" then
300+ install_module ()
301+ else
302+ if not namespace .event_registered then
303+ dt .register_event (MODULE , " view-changed" ,
304+ function (event , old_view , new_view )
305+ if new_view .name == " lighttable" and old_view .name == " darkroom" then
306+ install_module ()
307+ end
308+ end
309+ )
310+ namespace .event_registered = true
311+ end
312+ end
313+
314+ return script_data
0 commit comments