Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
film_jobs.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Henrik Andersson.
4 Copyright (C) 2010, 2012 johannes hanika.
5 Copyright (C) 2012 Richard Wonka.
6 Copyright (C) 2014 Jérémy Rosen.
7 Copyright (C) 2014, 2016 Tobias Ellinghaus.
8 Copyright (C) 2015-2016 Roman Lebedev.
9 Copyright (C) 2016 parafin.
10 Copyright (C) 2017 luzpaz.
11 Copyright (C) 2020 Hubert Kowalski.
12 Copyright (C) 2020-2021 Pascal Obry.
13 Copyright (C) 2021 Aldric Renaudin.
14 Copyright (C) 2021 Bill Ferguson.
15 Copyright (C) 2021 Hanno Schwalm.
16 Copyright (C) 2021 Philippe Weyland.
17 Copyright (C) 2021 Ralf Brown.
18 Copyright (C) 2022 Martin Bařinka.
19 Copyright (C) 2023, 2025 Aurélien PIERRE.
20
21 darktable is free software: you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation, either version 3 of the License, or
24 (at your option) any later version.
25
26 darktable is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
30
31 You should have received a copy of the GNU General Public License
32 along with darktable. If not, see <http://www.gnu.org/licenses/>.
33*/
35#include "control/settings.h"
37#include "control/control.h"
38#include "common/conf.h"
40#include "common/collection.h"
41#include "common/film.h"
42#include <stdlib.h>
43#include "common/utility.h"
44#include "gui/application.h"
45
51
52static void _film_import1(dt_job_t *job, dt_film_t *film, GList *images);
53
54static int32_t dt_film_import1_run(dt_job_t *job)
55{
57 _film_import1(job, params->film, NULL); // import the given film, collecting its images
58 dt_pthread_mutex_lock(&params->film->images_mutex);
59 params->film->ref--;
60 dt_pthread_mutex_unlock(&params->film->images_mutex);
61 if(params->film->ref <= 0)
62 {
63 if(dt_film_is_empty(params->film->id))
64 {
65 dt_film_remove(params->film->id);
66 }
67 }
68
69 // notify the user via the window manager
71
72 return 0;
73}
74
75static void dt_film_import1_cleanup(void *p)
76{
77 dt_film_import1_t *params = p;
78
79 dt_film_cleanup(params->film);
80 dt_free(params->film);
81
82 dt_free(params);
83}
84
86{
87 dt_job_t *job = dt_control_job_create(&dt_film_import1_run, "cache load raw images for preview");
88 if(IS_NULL_PTR(job)) return NULL;
89 dt_film_import1_t *params = (dt_film_import1_t *)calloc(1, sizeof(dt_film_import1_t));
90 if(IS_NULL_PTR(params))
91 {
93 return NULL;
94 }
95 dt_control_job_add_progress(job, _("import images"), FALSE);
97 params->film = film;
99 film->ref++;
101 return job;
102}
103
104static int32_t _pathlist_import_run(dt_job_t *job)
105{
107 _film_import1(job, NULL, params->imagelist); // import the specified images, creating filmrolls as needed
108 params->imagelist = NULL; // the import will have freed the image list
109
110 // notify the user via the window manager
112 return 0;
113}
114
115static void _pathlist_import_cleanup(void *p)
116{
117 dt_film_import1_t *params = p;
118 dt_free(params);
119}
120
121dt_job_t *dt_pathlist_import_create(int argc, char *argv[])
122{
123 dt_job_t *job = dt_control_job_create(&_pathlist_import_run, "import commandline images");
124 if(IS_NULL_PTR(job)) return NULL;
125 dt_film_import1_t *params = (dt_film_import1_t *)calloc(1, sizeof(dt_film_import1_t));
126 if(IS_NULL_PTR(params))
127 {
129 return NULL;
130 }
131 dt_control_job_add_progress(job, _("import images"), FALSE);
133 params->film = NULL;
134 // now collect all of the images to be imported
135 params->imagelist = NULL;
136 for(int i = 1; i < argc; i++)
137 {
138 char *path = dt_util_normalize_path(argv[i]);
139 if(!g_file_test(path, G_FILE_TEST_IS_DIR))
140 {
141 // add just the given name to the list of images to import
142 params->imagelist = g_list_prepend(params->imagelist, path);
143 }
144 else
145 {
146 // iterate over the directory, extracting image files
147 GDir *cdir = g_dir_open(path, 0, NULL);
148 if (cdir)
149 {
150 while(TRUE)
151 {
152 const gchar *fname = g_dir_read_name(cdir);
153 if(IS_NULL_PTR(fname)) break; // no more files in directory
154 if(fname[0] == '.') continue; // skip hidden files
155 gchar *fullname = g_build_filename(path, fname, NULL);
156 if(!g_file_test(fullname, G_FILE_TEST_IS_DIR) && dt_supported_image(fname))
157 params->imagelist = g_list_prepend(params->imagelist, fullname);
158 else
159 dt_free(fullname);
160 }
161 }
162 g_dir_close(cdir);
163 dt_free(path);
164 }
165 }
166 params->imagelist = g_list_reverse(params->imagelist);
167 return job;
168}
169
170static GList *_film_recursive_get_files(const gchar *path, gboolean recursive, GList **result)
171{
172 gchar *fullname;
173
174 /* let's try open current dir */
175 GDir *cdir = g_dir_open(path, 0, NULL);
176 if(IS_NULL_PTR(cdir)) return *result;
177
178 /* lets read all files in current dir, recurse
179 into directories if we should import recursive.
180 */
181 do
182 {
183 /* get the current filename */
184 const gchar *filename = g_dir_read_name(cdir);
185
186 /* return if no more files are in current dir */
187 if(IS_NULL_PTR(filename)) break;
188 if(filename[0] == '.') continue;
189
190 /* build full path for filename */
191 fullname = g_build_filename(path, filename, NULL);
192
193 /* recurse into directory if we hit one and we doing a recursive import */
194 if(recursive && g_file_test(fullname, G_FILE_TEST_IS_DIR))
195 {
196 *result = _film_recursive_get_files(fullname, recursive, result);
197 dt_free(fullname);
198 }
199 /* or test if we found a supported image format to import */
200 else if(!g_file_test(fullname, G_FILE_TEST_IS_DIR) && dt_supported_image(filename))
201 *result = g_list_prepend(*result, fullname);
202 else
203 dt_free(fullname);
204
205 } while(TRUE);
206
207 /* cleanup and return results */
208 g_dir_close(cdir);
209
210 return *result;
211}
212
213/* check if we can find a gpx data file to be auto applied
214 to images in the just imported filmroll
215*/
217{
218 if(!IS_NULL_PTR(cfr) && cfr->dir)
219 {
220 g_dir_rewind(cfr->dir);
221 const gchar *dfn = NULL;
222 while((dfn = g_dir_read_name(cfr->dir)) != NULL)
223 {
224 /* check if we have a gpx to be auto applied to filmroll */
225 const size_t len = strlen(dfn);
226 if(strcmp(dfn + len - 4, ".gpx") == 0 || strcmp(dfn + len - 4, ".GPX") == 0)
227 {
228 gchar *gpx_file = g_build_path(G_DIR_SEPARATOR_S, cfr->dirname, dfn, NULL);
229 gchar *tz = dt_conf_get_string("plugins/lighttable/geotagging/tz");
230 dt_control_gpx_apply(gpx_file, cfr->id, tz, NULL);
231 dt_free(gpx_file);
232 dt_free(tz);
233 }
234 }
235 }
236}
237
238/* compare used for sorting the list of files to import
239 only sort on basename of full path eg. the actually filename.
240*/
241static int _film_filename_cmp(gchar *a, gchar *b)
242{
243 gchar *a_basename = g_path_get_basename(a);
244 gchar *b_basename = g_path_get_basename(b);
245 const int ret = g_strcmp0(a_basename, b_basename);
246 dt_free(a_basename);
247 dt_free(b_basename);
248 return ret;
249}
250
251static void _film_import1(dt_job_t *job, dt_film_t *film, GList *images)
252{
253 // first, gather all images to import if not already given
254 if (IS_NULL_PTR(images))
255 {
256 const gboolean recursive = dt_conf_get_bool("ui_last/import_recursive");
257
258 images = _film_recursive_get_files(film->dirname, recursive, &images);
259 if(IS_NULL_PTR(images))
260 {
261 dt_control_log(_("no supported images were found to be imported"));
262 return;
263 }
264 }
265
266 if(IS_NULL_PTR(images))
267 {
268 // no error message, lua probably emptied the list on purpose
269 return;
270 }
271
272 /* we got ourself a list of images, lets sort and start import */
273 images = g_list_sort(images, (GCompareFunc)_film_filename_cmp);
274
275 /* let's start import of images */
276 gchar message[512] = { 0 };
277 double fraction = 0;
278 const guint total = g_list_length(images);
279 g_snprintf(message, sizeof(message) - 1, ngettext("Importing %d image", "Importing %d images", total), total);
281
282 GList *all_imgs = NULL;
283 gint64 last_collection_refresh = 0;
284
285 /* loop thru the images and import to current film roll */
286 dt_film_t *cfr = film;
287 int32_t imgid = UNKNOWN_IMAGE;
288 // dt_collection_load_filmroll() below must be pointed at the FIRST successfully imported
289 // image, like import_jobs.c's _control_import_job_run() does (its imgid there is the one from
290 // index==0). imgid alone can't be reused for that: the loop overwrites it every iteration, so
291 // by the end it holds the LAST image instead -- for a flat single-folder import both are the
292 // same directory, but for a recursive import spanning several sub-folders they can differ,
293 // making it unpredictable which sub-folder ends up displayed.
294 int32_t first_imgid = UNKNOWN_IMAGE;
295 for(GList *image = images; image; image = g_list_next(image))
296 {
297 gchar *cdn = g_path_get_dirname((const gchar *)image->data);
298
299 /* check if we need to initialize a new filmroll */
300 if(IS_NULL_PTR(cfr) || g_strcmp0(cfr->dirname, cdn) != 0)
301 {
303
304 /* cleanup previously imported filmroll*/
305 if(cfr && cfr != film)
306 {
307 if(dt_film_is_empty(cfr->id))
308 {
309 dt_film_remove(cfr->id);
310 }
311 dt_film_cleanup(cfr);
312 dt_free(cfr);
313 }
314
315 /* initialize and create a new film to import to */
316 cfr = malloc(sizeof(dt_film_t));
317 dt_film_init(cfr);
318 dt_film_new(cfr, cdn);
319 }
320
321 dt_free(cdn);
322
323 /* import image */
324 imgid = dt_image_import(cfr->id, (const gchar *)image->data, FALSE);
325 if(first_imgid <= UNKNOWN_IMAGE && imgid > UNKNOWN_IMAGE)
326 {
327 first_imgid = imgid;
328 // Point mouse_over_id at the first imported image as soon as we know it, not only once the
329 // whole (possibly long, recursive) import finishes -- darkroom's try_enter() reads this to
330 // pick its target image. The dt_collection_load_filmroll() call below is told (via its
331 // set_mouse_over=FALSE argument) not to redo this at the end, so a mouse-over change from
332 // the user during the import is not clobbered once the job completes.
333 dt_control_set_mouse_over_id(first_imgid);
334 }
335 fraction += 1.0 / total;
336 dt_control_job_set_progress(job, fraction);
337
338 // cfr->dirname is imgid's folder for free: this import path never copies files, so the film
339 // roll it was just inserted into (created/reused a few lines above from the source path's own
340 // dirname) already names its final location -- no need to make notify_imported() re-derive it.
341 dt_collection_notify_imported(imgid, cfr->dirname, &last_collection_refresh);
342
343 all_imgs = g_list_prepend(all_imgs, GINT_TO_POINTER(imgid));
344 }
345
346 g_list_free_full(images, dt_free_gpointer);
347 images = NULL;
348 all_imgs = g_list_reverse(all_imgs);
349
350 // only redraw at the end, to not spam the cpu with exposure events
352
353 // set_mouse_over = FALSE: already pointed at first_imgid as soon as it was known, above --
354 // do not force it back here and clobber whatever the user may be hovering by now.
356 (g_list_length(all_imgs) == 1) ? DT_COLLECTION_IMPORT_VIEW_IMAGE
358 FALSE);
359
360 // dt_collection_load_filmroll() silently declines to switch/refresh the collection when it
361 // cannot switch folders -- e.g. the collect module is not on the "Folders" tab (see
362 // dt_collection_load_filmroll() in common/collection.c). Without this, a recursive
363 // folder import (dt_film_import(), reached from CLI/D-Bus/macOS "open with" while Ansel is
364 // already running) never shows up in the lighttable grid or the Collect module until the user
365 // reloads the collection by hand -- the same reason control/jobs/import_jobs.c's import job
366 // does it too, which this legacy, separate import path does not share code with.
367 if(all_imgs)
369
370 //QUESTION: should this come after _apply_filmroll_gpx, since that can change geotags again?
372
374
375 /* cleanup previously imported filmroll*/
376 if(!IS_NULL_PTR(cfr) && cfr != film)
377 {
378 dt_film_cleanup(cfr);
379 dt_free(cfr);
380 }
381}
382
383// clang-format off
384// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
385// vim: shiftwidth=2 expandtab tabstop=2 cindent
386// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
387// clang-format on
void dt_ui_notify_user()
draw user's attention
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
Definition collection.c:837
dt_collection_t * dt_collection_get_global(void)
Definition collection.c:138
void dt_collection_notify_imported(const int32_t imgid, const gchar *known_image_folder, gint64 *last_refresh_us)
void dt_collection_load_filmroll(dt_collection_t *collection, const int32_t imgid, dt_collection_import_view_t view_policy, gboolean set_mouse_over)
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:155
@ DT_COLLECTION_CHANGE_NEW_QUERY
Definition collection.h:162
@ DT_COLLECTION_IMPORT_VIEW_GRID
Definition collection.h:275
@ DT_COLLECTION_IMPORT_VIEW_IMAGE
Definition collection.h:277
int dt_conf_get_bool(const char *name)
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:994
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_gpx_apply(const gchar *filename, int32_t filmid, const gchar *tz, GList *imgs)
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
void dt_film_init(dt_film_t *film)
Definition film.c:74
gboolean dt_film_is_empty(const int id)
Definition film.c:333
int dt_film_new(dt_film_t *film, const char *directory)
Definition film.c:129
void dt_film_remove(const int id)
Definition film.c:340
void dt_film_cleanup(dt_film_t *film)
Definition film.c:84
dt_job_t * dt_pathlist_import_create(int argc, char *argv[])
Definition film_jobs.c:121
static void _film_import1(dt_job_t *job, dt_film_t *film, GList *images)
Definition film_jobs.c:251
static void _pathlist_import_cleanup(void *p)
Definition film_jobs.c:115
static GList * _film_recursive_get_files(const gchar *path, gboolean recursive, GList **result)
Definition film_jobs.c:170
static int _film_filename_cmp(gchar *a, gchar *b)
Definition film_jobs.c:241
static int32_t dt_film_import1_run(dt_job_t *job)
Definition film_jobs.c:54
dt_job_t * dt_film_import1_create(dt_film_t *film)
Definition film_jobs.c:85
static int32_t _pathlist_import_run(dt_job_t *job)
Definition film_jobs.c:104
static void _apply_filmroll_gpx(dt_film_t *cfr)
Definition film_jobs.c:216
static void dt_film_import1_cleanup(void *p)
Definition film_jobs.c:75
#define UNKNOWN_IMAGE
Definition image.h:78
gboolean dt_supported_image(const gchar *filename)
Definition darktable.c:381
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:137
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:131
void dt_control_job_set_progress(dt_job_t *job, double value)
Definition jobs.c:628
void dt_control_job_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
Definition jobs.c:614
void dt_control_job_set_progress_message(dt_job_t *job, const char *message)
Definition jobs.c:622
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:114
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:155
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:139
dt_film_t * film
Definition film_jobs.c:48
GList * imagelist
Definition film_jobs.c:49
dt_pthread_mutex_t images_mutex
Definition film.h:47
int32_t id
Definition film.h:45
GDir * dir
Definition film.h:48
int32_t ref
Definition film.h:50
char dirname[512]
Definition film.h:46
gchar * dt_util_normalize_path(const gchar *_input)
Definition utility.c:683