Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
import_jobs.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2025-2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18#include "common/darktable.h"
19#include "import_jobs.h"
20#include "common/collection.h"
21#include "common/datetime.h"
22#include "common/exif.h"
24#include "common/metadata.h"
25#include "common/styles.h"
26#include "control/control.h"
27#include "common/image.h"
29#include "gui/gtk.h"
30
31#ifndef _WIN32
32#include <glob.h>
33#endif
34#include <string.h>
35#ifdef __APPLE__
36#include "osx/osx.h"
37#endif
38#ifdef _WIN32
39#include "win/dtwin.h"
40#endif
41
42
50gboolean _create_dir(const char *path)
51{
52 if(g_mkdir_with_parents(path, 0755) == -1)
53 {
54 fprintf(stderr, "failed to create directory %s.\n", path);
55 dt_control_log(_("Impossible to create directory %s.\nThe target may be full or read-only.\n"), path);
56 return TRUE;
57 }
58 return FALSE;
59}
60
68gchar *_path_cleanup(gchar *path_in)
69{
70 gchar *clean = dt_cleanup_separators(path_in);
71 gchar *path_out = dt_util_remove_whitespace(clean);
72 dt_free(clean);
73 return path_out;
74}
75
76gchar *dt_build_filename_from_pattern(const char *const filename, const int index, dt_image_t *img, dt_control_import_t *data)
77{
80 // Borrowed references, like every other dt_variables_params_t caller (see iop/watermark.c):
81 // dt_variables_params_destroy() does not own/free filename or jobcode.
82 params->filename = filename;
83 params->sequence = index;
84 params->jobcode = data->jobcode;
85 params->imgid = UNKNOWN_IMAGE;
86 params->img = img;
88
89 gchar *file_expand = dt_variables_expand(params, data->target_file_pattern, FALSE);
90 gchar *path_expand = dt_variables_expand(params, data->target_subfolder_pattern, FALSE);
91
92 // remove this if we decide to do the correction on user's settings directly
93 gchar *file = _path_cleanup(file_expand);
94 gchar *path = _path_cleanup(path_expand);
95 dt_free(file_expand);
96 dt_free(path_expand);
97
98 gchar *dir = g_build_path(G_DIR_SEPARATOR_S, data->base_folder, path, (char *) NULL);
100 gchar *res = g_build_path(G_DIR_SEPARATOR_S, data->target_dir, file, (char *) NULL);
101
102 dt_print(DT_DEBUG_PRINT, "[Import] Importing file to %s\n", res);
103
104 dt_free(file);
105 dt_free(path);
106 dt_free(dir);
108 return res;
109}
110
117gboolean _file_exist(const char *dest_file_path)
118{
119 return !IS_NULL_PTR(dest_file_path) && dest_file_path[0] && g_file_test(dest_file_path, G_FILE_TEST_EXISTS);
120}
121
129gboolean _copy_file(const char *filename, const char *dest_file_path)
130{
131 GFile *in = g_file_new_for_path(filename);
132 GFile *out = g_file_new_for_path(dest_file_path);
133
134 gboolean res = g_file_copy(in, out, G_FILE_COPY_NONE, 0, 0, 0, NULL);
135 if(!res) dt_print(DT_DEBUG_IMPORT, "[Import] Could not copy the file %s to %s\n", filename, dest_file_path);
136
137 g_object_unref(in);
138 g_object_unref(out);
139
140 return res;
141}
142
150const int32_t _import_job(dt_control_import_t *data, gchar *img_path_to_db)
151{
152 gchar *dirname = g_strdup(dt_util_path_get_dirname(img_path_to_db));
153
154 dt_film_t film;
155 const int32_t filmid = dt_film_new(&film, dirname);
156 // Regular imports skip the per-file DT_SIGNAL_IMAGE_IMPORT (large batches
157 // would otherwise raise it hundreds of times). Folder survey imports one or
158 // a few files at a time, and Studio Capture needs that signal to know which
159 // image to display as soon as it lands, so raise it for that case only.
160 const int32_t imgid = dt_image_import(filmid, img_path_to_db, data->folder_survey);
161 dt_free(dirname);
162 return imgid;
163}
164
177void dt_import_duplicate_get_dest_name(char *xmp_dest_name, const char *dest_file_path, const int counter)
178{
179 char *norm_dest_file = dt_util_normalize_path(dest_file_path);
180 char *ext = norm_dest_file + safe_strlen(norm_dest_file);
181 while(*ext != '.' && ext > norm_dest_file) ext--;
182 const size_t name_len = safe_strlen(norm_dest_file) - safe_strlen(ext);
183
184 if(counter == 0)
185 g_snprintf(xmp_dest_name, PATH_MAX, "%s.xmp", norm_dest_file);
186 else
187 g_snprintf(xmp_dest_name, PATH_MAX, "%.*s_%.2d%s.xmp", (int)name_len, norm_dest_file, counter, ext);
188
189 dt_print(DT_DEBUG_IMPORT, "[Import] XMP destination name: %s\n", xmp_dest_name);
190
191 dt_free(norm_dest_file);
192}
193
201int _import_copy_xmp(const char *const filename, gchar *dest_file_path)
202{
203 int xmp_cntr = 0;
204
205 GList *xmp_files = dt_image_find_xmps(filename); // the first xmp will be the original
206 if(g_list_length(xmp_files) > 0)
207 {
208 for(GList *current_xmp = xmp_files; current_xmp; current_xmp = g_list_next(current_xmp))
209 {
210 char *xmp_source = g_strdup((char*) current_xmp->data);
211 gchar xmp_dest_name[PATH_MAX] = { 0 };
212 dt_import_duplicate_get_dest_name(xmp_dest_name, dest_file_path, xmp_cntr);
213
214 // folder already created and writable, just copy.
215 int success = _copy_file(xmp_source, xmp_dest_name);
216 dt_print(DT_DEBUG_IMPORT, "[Import] copying %s to %s %s\n", xmp_source, xmp_dest_name,
217 (success) ? "succeeded" : "failed");
218 if(success) xmp_cntr++;
219 dt_free(xmp_source);
220 }
221 }
222 g_list_free(xmp_files);
223 xmp_files = NULL;
224 return xmp_cntr;
225}
226
227int _import_copy_txt(const char *const filename, const char *dest_file_path)
228{
229 char *txt_source = dt_image_get_text_path_from_path(filename);
230 if(IS_NULL_PTR(txt_source)) return 0;
231
232 char *txt_dest = dt_image_build_text_path_from_path(dest_file_path);
233 int success = 0;
234
235 if(!IS_NULL_PTR(txt_dest))
236 {
237 success = _copy_file(txt_source, txt_dest);
238 dt_print(DT_DEBUG_IMPORT, "[Import] copying %s to %s %s\n", txt_source, txt_dest,
239 (success) ? "succeeded" : "failed");
240 }
241
242 dt_free(txt_dest);
243 dt_free(txt_source);
244 return success ? 1 : 0;
245}
246
257int _import_copy_file(const char *const filename, const int index, dt_control_import_t *data, gchar *img_path_to_db, size_t pathname_len, GList **discarded)
258{
259 dt_image_t *img = malloc(sizeof(dt_image_t));
260 dt_image_init(img);
261
262 // Generate file I/O only if the pattern is using EXIF variables.
263 // Otherwise, discard it since it's really expensive if the file is on external/remote storage.
264 // This is mandatory BEFORE expanding variables in pattern
265 if(strstr(data->target_file_pattern, "$(EXIF") != NULL
266 || strstr(data->target_subfolder_pattern, "$(EXIF") != NULL )
267 {
268 dt_print(DT_DEBUG_IMPORT, "[Import] EXIF will be read for %s because the pattern needs it (performance penalty)\n", filename);
269 dt_exif_read(img, filename);
270 }
271
272 gchar *dest_file_path = dt_build_filename_from_pattern(filename, index, img, data);
273 dt_print(DT_DEBUG_IMPORT, "[Import] Image %s will be copied into %s\n", filename, dest_file_path);
274 dt_free(img);
275
276 int process = TRUE;
277 int copied = 0;
278
279 gboolean exists = _file_exist(dest_file_path);
280
281 // Resolve a name collision according to the requested policy. UNIQUE rewrites
282 // the destination to a free "<stem>_NN.<ext>" so the copy proceeds normally.
283 if(exists && data->on_conflict == DT_IMPORT_ONCONFLICT_UNIQUE)
284 {
285 const char *dot = strrchr(dest_file_path, '.');
286 const int stem_len = dot ? (int)(dot - dest_file_path) : (int)strlen(dest_file_path);
287 const char *ext = dot ? dot : "";
288 char *unique = NULL;
289 for(int seq = 1; seq < 10000; seq++)
290 {
291 dt_free(unique);
292 unique = g_strdup_printf("%.*s_%02d%s", stem_len, dest_file_path, seq, ext);
293 if(!_file_exist(unique)) break;
294 }
295 dt_free(dest_file_path);
296 dest_file_path = unique;
297 exists = FALSE;
298 }
299
300 // OVERWRITE deletes the stale destination so g_file_copy() (G_FILE_COPY_NONE)
301 // does not fail on an existing target.
302 if(exists && data->on_conflict == DT_IMPORT_ONCONFLICT_OVERWRITE)
303 {
304 g_unlink(dest_file_path);
305 exists = FALSE;
306 }
307
308 if(!exists)
309 {
310 if(!dt_util_dir_exist(data->target_dir))
312 else
313 dt_print(DT_DEBUG_PRINT, "[Import] target folder %s already exists. Nothing to do.\n", data->target_dir);
314
315 if(process)
317 else
318 fprintf(stdout, "[Import] Unable to create the target folder %s.\n", data->target_dir);
319
320 if(process)
321 {
322 process = _copy_file(filename, dest_file_path);
323 copied = process;
324 }
325 else
326 fprintf(stdout, "[Import] Not allowed to write in the %s folder.\n", data->target_dir);
327
328 if(process)
329 {
330 _import_copy_xmp(filename, dest_file_path);
331 _import_copy_txt(filename, dest_file_path);
332 }
333
334 if(process)
335 g_strlcpy(img_path_to_db, dest_file_path, pathname_len);
336 else
337 fprintf(stderr, "[Import] Unable to copy the file %s to %s.\n", img_path_to_db, dest_file_path);
338 }
339 else
340 {
341 // SKIP: keep and import the pre-existing destination file.
342 *discarded = g_list_prepend(*discarded, g_strdup(filename));
343 g_strlcpy(img_path_to_db, dest_file_path, pathname_len);
344 dt_print(DT_DEBUG_IMPORT, "[Import] File copy skipped, the target file %s already exists on the destination.\n", dest_file_path);
345 }
346
347 dt_free(dest_file_path);
348 return process ? copied : -1;
349}
350
351void _write_xmp_id(const char *filename, int32_t imgid)
352{
353 GList *res = dt_metadata_get(imgid, "Xmp.darktable.image_id", NULL);
354 if(!IS_NULL_PTR(res))
355 {
356 // Image ID is already set in metadata, don't overwrite it
357 g_list_free_full(res, dt_free_gpointer);
358 res = NULL;
359 return;
360 }
361 // else : init it
362 GError *error = NULL;
363 GFile *gfile = g_file_new_for_path(filename);
364 GFileInfo *info = g_file_query_info(gfile,
365 G_FILE_ATTRIBUTE_STANDARD_NAME ","
366 G_FILE_ATTRIBUTE_TIME_MODIFIED,
367 G_FILE_QUERY_INFO_NONE, NULL, &error);
368 const char *fn = g_file_info_get_name(info);
369
370 const time_t datetime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
371 char dt_txt[DT_DATETIME_EXIF_LENGTH];
372 dt_datetime_unix_to_exif(dt_txt, sizeof(dt_txt), &datetime);
373 const char *id = g_strconcat(fn, "-", dt_txt, NULL);
374 dt_metadata_set(imgid, "Xmp.darktable.image_id", id, FALSE);
375 g_object_unref(info);
376 g_object_unref(gfile);
377 g_clear_error(&error);
378}
379
388int32_t _import_image(const GList *img, dt_control_import_t *data, const int index, GList **discarded, int *xmps)
389{
390 const char *filename = (const char*) img->data;
391
392 gchar img_path_to_db[PATH_MAX] = { 0 };
393 gboolean process_error = FALSE;
394 int copy_status = 0;
395 int32_t imgid = UNKNOWN_IMAGE;
396
397 if(data->copy)
398 {
399 // Copy the file to destination folder, expanding variables internally
400 copy_status = _import_copy_file(filename, index, data, img_path_to_db, sizeof(img_path_to_db), discarded);
401 process_error = copy_status < 0;
402 }
403 else
404 // destination = origin, nothing to do
405 g_strlcpy(img_path_to_db, filename, sizeof(img_path_to_db));
406
407 if(process_error)
408 ;
409 else if(img_path_to_db[0] == 0)
410 fprintf(stderr, "[Import] Could not import file from disk: empty file path\n");
411 else
412 {
413 imgid = _import_job(data, img_path_to_db);
414
415 if(imgid == UNKNOWN_IMAGE)
416 {
417 dt_control_log(_("Error importing file in collection: %s"), img_path_to_db);
418 fprintf(stderr, "[Import] Error importing file in collection: %s", img_path_to_db);
419 }
420 else
421 {
422 // read all sidecar files (including the original one) and import them if not found in db.
423 *xmps = dt_image_read_duplicates(imgid, img_path_to_db, FALSE);
424 dt_print(DT_DEBUG_IMPORT, "[Import] Found and imported %i XMP for %s.\n", *xmps, img_path_to_db);
425 dt_print(DT_DEBUG_IMPORT, "[Import] successfully imported %s in DB at imgid %i\n", img_path_to_db, imgid);
426
427 if(data->delete_source && copy_status == 1)
428 {
429 // Compare the complete source and destination byte streams before
430 // deleting files from temporary ingest storage.
431 gboolean identical = FALSE;
432 GStatBuf source_stat;
433 GStatBuf destination_stat;
434 if(g_stat(filename, &source_stat) == 0
435 && g_stat(img_path_to_db, &destination_stat) == 0
436 && source_stat.st_size == destination_stat.st_size)
437 {
438 FILE *source = g_fopen(filename, "rb");
439 FILE *destination = g_fopen(img_path_to_db, "rb");
440 if(!IS_NULL_PTR(source) && !IS_NULL_PTR(destination))
441 {
442 const size_t buffer_size = 64 * 1024;
443 unsigned char *source_buffer = malloc(buffer_size);
444 unsigned char *destination_buffer = malloc(buffer_size);
445 identical = !IS_NULL_PTR(source_buffer) && !IS_NULL_PTR(destination_buffer);
446
447 while(identical)
448 {
449 const size_t source_read = fread(source_buffer, 1, buffer_size, source);
450 const size_t destination_read = fread(destination_buffer, 1, buffer_size, destination);
451 if(source_read != destination_read
452 || memcmp(source_buffer, destination_buffer, source_read))
453 identical = FALSE;
454
455 if(source_read < buffer_size)
456 {
457 if(ferror(source) || ferror(destination)) identical = FALSE;
458 break;
459 }
460 }
461
462 dt_free(source_buffer);
463 dt_free(destination_buffer);
464 }
465
466 if(!IS_NULL_PTR(source)) fclose(source);
467 if(!IS_NULL_PTR(destination)) fclose(destination);
468 }
469
470 if(identical)
471 {
472 if(g_unlink(filename) != 0)
473 dt_control_log(_("The imported file was verified but the original could not be deleted: %s"), filename);
474 }
475 else
476 dt_control_log(_("The imported file differs from the original, which was not deleted: %s"), filename);
477 }
478
479 // Studio capture auto-styling: replace the freshly imported default
480 // history with the first style, then stack the remaining styles in the
481 // user-defined order (source wins on conflicts).
482 if(!IS_NULL_PTR(data->styles))
483 {
484 dt_hm_batch_state_t batch = { 0 };
485 for(GList *s = data->styles; s; s = g_list_next(s))
486 {
487 const char *style_name = (const char *)s->data;
488 const int32_t style_id = dt_styles_get_id_by_name(style_name);
489 if(style_id <= 0) continue;
490 dt_styles_apply_to_image_merge(style_name, style_id, imgid, DT_HISTORY_MERGE_APPEND, &batch);
491 }
493
494 // The styles were written straight to DB: reload cached metadata, drop
495 // the stale mipmap and refresh thumbnails (lighttable + filmstrip).
497 }
498 }
499 }
500
501 return imgid;
502}
503
507static void _refresh_progress_counter(dt_job_t *job, const int elements, const int index,
508 const gboolean folder_survey)
509{
510 gchar message[128] = { 0 };
511 double fraction = (double)index / (double)elements;
512 if(folder_survey)
513 snprintf(message, sizeof(message),
514 ngettext("Capture: importing %i/%i image", "Capture: importing %i/%i images", index),
515 index, elements);
516 else
517 snprintf(message, sizeof(message),
518 ngettext("Importing %i/%i image", "Importing %i/%i images", index),
519 index, elements);
521 dt_control_job_set_progress(job, fraction);
522 g_usleep(100);
523}
524
526{
528 dt_control_import_t *data = params->data;
529
530 int index = 0;
531 int xmps = 0; // number of xmps imported in db.
532 int32_t imgid = UNKNOWN_IMAGE;
533 gint64 last_collection_refresh = 0;
534
535 for(GList *img = g_list_first(data->imgs); img; img = g_list_next(img))
536 {
537 dt_print(DT_DEBUG_IMPORT, "[Import] starting import of image #%i...\n", index);
538
539 _refresh_progress_counter(job, data->elements, index, data->folder_survey);
540 imgid = _import_image(img, data, index, &data->discarded, &xmps);
541 if(!IS_NULL_PTR(data->file_imported))
542 data->file_imported((const char *)img->data, imgid > UNKNOWN_IMAGE, data->callback_data);
543
544 if(imgid > UNKNOWN_IMAGE)
545 {
546 // On the first image, try to switch the current filmroll to the imported image's folder.
547 // dt_collection_load_filmroll() silently declines to do anything (no collection refresh)
548 // when it cannot switch folders, e.g. the collect module is not on the "Folders" tab. In
549 // that case a single imported image would never show up until the user reloads the
550 // collection by hand (issue #860). So always run a collection update afterwards: it
551 // re-runs the current query and makes newly-imported matching images appear.
552 if(index == 0)
554
555 // Throttled: dt_collection_update_query() fully rebuilds memory.collected_images and its
556 // DT_SIGNAL_COLLECTION_CHANGED triggers a full lighttable/thumbtable re-layout on the GUI
557 // thread. Firing it after every single image queued one such redraw per image -- for a
558 // large library each redraw is expensive enough that a multi-hundred-image import kept the
559 // GUI thread permanently busy processing the backlog, appearing fully frozen (no input
560 // handled, and even the background-job progress bar -- created and shown correctly -- never
561 // got an actual repaint) for the whole import.
562 const gint64 now = g_get_monotonic_time();
563 if(now - last_collection_refresh > 250000) // 250ms
564 {
566 last_collection_refresh = now;
567 }
568
569 index++;
570 }
571 }
572
573 // Guarantee the final state is reflected even if the last few images landed inside the throttle window.
574 if(index > 0)
576
577 if(index == 0)
578 {
579 if(data->folder_survey)
580 dt_control_log(_("Capture: No image imported!"));
581 else
582 dt_control_log(_("No image imported!"));
583 fprintf(stderr, "No image imported!\n\n");
584 }
585 // don't open picture in darkroom if more than 1 xmps (= duplicates) have been imported.
586 else if(index == 1 && xmps == 1)
587 {
588 if(data->folder_survey)
589 dt_control_log(_("Capture: imported 1 image."));
591 }
592 else
593 {
594 if(data->folder_survey)
595 dt_control_log(ngettext("Capture: imported %d image.",
596 "Capture: imported %d images.", index), index);
597 else
598 dt_control_log(ngettext("Imported %d image", "Imported %d images", index), index);
599 fprintf(stdout, "%d files imported in database.\n\n", index);
600 }
601
602 dt_conf_set_int("ui_last/nb_imported", index);
603
604 return index >= 1 ? 0 : 1;
605}
606
608{
609 g_date_time_unref(data->datetime);
610 dt_free(data->jobcode);
611 dt_free(data->base_folder);
614 dt_free(data->target_dir);
615 if(data->styles)
616 {
617 g_list_free_full(data->styles, dt_free_gpointer);
618 data->styles = NULL;
619 }
622 data->callback_data = NULL;
623
624 // GList of pathes stored as *char. We need to free the list and the *char
625 if(data->discarded)
626 {
627 g_list_free_full(data->discarded, dt_free_gpointer);
628 data->discarded = NULL;
629 }
630 if(data->imgs)
631 {
632 g_list_free_full(data->imgs, dt_free_gpointer);
633 data->imgs = NULL;
634 }
635}
636
638{
639 dt_control_import_t *data = params->data;
640
641 // Create the window
642 GtkWidget *dialog = gtk_dialog_new_with_buttons("Message",
643 GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)),
644 GTK_DIALOG_DESTROY_WITH_PARENT,
645 _("_OK"),
646 GTK_RESPONSE_NONE,
647 NULL);
648 gtk_window_set_title(GTK_WINDOW(dialog), _("Some files have not been copied"));
649 gtk_window_set_default_size(GTK_WINDOW(dialog), DT_PIXEL_APPLY_DPI(800), DT_PIXEL_APPLY_DPI(800));
650
651 // Create the label
652 GtkWidget *label = gtk_label_new(_("The following source files have not been copied "
653 "because similarly-named files already exist on the destination. "
654 "This may be because the files have already been imported "
655 "or the naming pattern leads to non-unique file names."));
656 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
657
658 // Create the scrolled window internal container
659 GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
660 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC,
661 GTK_POLICY_AUTOMATIC);
662 gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(scrolled_window), TRUE);
663
664 // Create the treeview model from the list of discarded file pathes
665 GtkListStore *store = gtk_list_store_new(1, G_TYPE_STRING);
666 GtkTreeIter iter;
667 for(GList *file = g_list_first(data->discarded); file; file = g_list_next(file))
668 {
669 if(file->data)
670 {
671 gtk_list_store_append(store, &iter);
672 gtk_list_store_set(store, &iter, 0, (char *)file->data, -1);
673 }
674 }
675
676 // Create the treeview view. Sooooo verbose... it's only a flat list.
677 GtkWidget *view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
678 GtkTreeViewColumn *col = gtk_tree_view_column_new();
679 gtk_tree_view_column_set_title(col, _("Origin path"));
680 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
681 gtk_tree_view_column_pack_start(col, renderer, TRUE);
682 gtk_tree_view_column_set_attributes(col, renderer, "text", 0, NULL);
683 gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
684 g_object_unref(store);
685
686 // Pack widgets to an unified box
687 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
688 gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
689 gtk_box_pack_start(GTK_BOX(box), scrolled_window, TRUE, TRUE, 0);
690 dt_gui_add_class(scrolled_window, "dt_recessed_scroll");
691 gtk_container_add(GTK_CONTAINER(scrolled_window), view);
692
693 // Pack the box to the dialog internal container
694 GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
695 gtk_container_add(GTK_CONTAINER(content_area), box);
696 gtk_widget_show_all(dialog);
697
698#ifdef GDK_WINDOWING_QUARTZ
700#endif
701
702 gtk_dialog_run(GTK_DIALOG(dialog));
703 gtk_widget_destroy(dialog);
704
706 dt_free(data);
708
709 return 0;
710}
711
713{
715 dt_control_import_t *data = params->data;
716
717 // Display a recap of files that weren't copied
718 if(g_list_length(data->discarded) > 0)
719 {
720 g_main_context_invoke(NULL, (GSourceFunc)_discarded_files_popup, (gpointer)params);
721 // we will free data and params from the function since it's run asynchronously
722 }
723 else
724 {
726 dt_free(data);
728 }
729}
730
732{
734 if(IS_NULL_PTR(params)) return NULL;
735
736 params->data = g_malloc0(sizeof(dt_control_import_t));
737 if(IS_NULL_PTR(params->data))
738 {
740 return NULL;
741 }
742 return params;
743}
744
746{
748 if(IS_NULL_PTR(job)) return NULL;
750 if(IS_NULL_PTR(params))
751 {
753 return NULL;
754 }
755 memcpy(params->data, &data, sizeof(dt_control_import_t));
756 params->index = NULL;
757 dt_control_job_add_progress(job, _("import"), FALSE);
759 return job;
760}
761
763{
765 if(IS_NULL_PTR(job))
766 {
767 // Report every source as failed so asynchronous clients can release their queued state.
768 for(GList *img = g_list_first(data.imgs); img; img = g_list_next(img))
769 if(!IS_NULL_PTR(data.file_imported))
770 data.file_imported((const char *)img->data, FALSE, data.callback_data);
771
773 return 1;
774 }
775
777}
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition ashift.c:3153
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
void dt_collection_load_filmroll(dt_collection_t *collection, const int32_t imgid, gboolean open_single_image)
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:142
@ DT_COLLECTION_CHANGE_NEW_QUERY
Definition collection.h:149
const dt_colormatrix_t dt_aligned_pixel_t out
int32_t dt_image_import(const int32_t film_id, const char *filename, gboolean raise_signals)
char * dt_image_get_text_path_from_path(const char *image_path)
void dt_image_init(dt_image_t *img)
GList * dt_image_find_xmps(const char *filename)
int dt_image_read_duplicates(const uint32_t id, const char *filename, const gboolean clear_selection)
char * dt_image_build_text_path_from_path(const char *image_path)
void dt_image_history_changed(const int32_t imgid, const gboolean refresh_filmstrip)
void dt_metadata_set(const int32_t imgid, const char *key, const char *value, const gboolean undo_on)
GList * dt_metadata_get(const int id, const char *key, uint32_t *count)
void dt_conf_set_int(const char *name, int val)
void dt_control_log(const char *msg,...)
Definition control.c:777
void * dt_control_image_enumerator_alloc()
void dt_control_image_enumerator_cleanup(void *p)
uint32_t view(const dt_view_t *self)
Definition darkroom.c:228
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define UNKNOWN_IMAGE
Definition darktable.h:194
@ DT_DEBUG_PRINT
Definition darktable.h:752
@ DT_DEBUG_IMPORT
Definition darktable.h:764
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
#define PATH_MAX
Definition darktable.h:1189
#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 darktable.h:293
gboolean dt_datetime_unix_to_exif(char *exif, const size_t exif_size, const time_t *unix)
Definition datetime.c:198
#define DT_DATETIME_EXIF_LENGTH
Definition datetime.h:38
int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int32_t imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality, const gboolean export_masks, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_export_metadata_t *metadata)
Definition disk.c:252
int dt_exif_read(dt_image_t *img, const char *path)
Definition exif.cc:1752
int dt_film_new(dt_film_t *film, const char *directory)
Definition film.c:161
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void dt_hm_batch_state_cleanup(dt_hm_batch_state_t *batch)
Release resources held by a batch state (the cached order). Safe to call on a zeroed state.
@ DT_HISTORY_MERGE_APPEND
int _import_copy_file(const char *const filename, const int index, dt_control_import_t *data, gchar *img_path_to_db, size_t pathname_len, GList **discarded)
copy a file to a destination path after checking if everything is allright.
static void * _control_import_alloc()
int _import_copy_xmp(const char *const filename, gchar *dest_file_path)
Attempt to find all sidecar XMP files along an image file and import (copy) it to destination.
gboolean _create_dir(const char *path)
Creates folders from path. Returns TRUE if success.
Definition import_jobs.c:50
static void _control_import_job_cleanup(void *p)
gchar * dt_build_filename_from_pattern(const char *const filename, const int index, dt_image_t *img, dt_control_import_t *data)
Build a full path for a given image file, given a pattern.
Definition import_jobs.c:76
gboolean _copy_file(const char *filename, const char *dest_file_path)
Just copy a file. Returns 1 if success.
void dt_import_duplicate_get_dest_name(char *xmp_dest_name, const char *dest_file_path, const int counter)
Gets the computed xmp file name with apropriate number for import copy. It computes a duplicate name ...
static int _discarded_files_popup(dt_control_image_enumerator_t *params)
int32_t _import_image(const GList *img, dt_control_import_t *data, const int index, GList **discarded, int *xmps)
process to copy (or not) and import an image to database.
int dt_control_import(dt_control_import_t data)
Process a list of images to import with or without copying the files on an arbitrary hard-drive.
static int32_t _control_import_job_run(dt_job_t *job)
static dt_job_t * _control_import_job_create(dt_control_import_t data)
static void _refresh_progress_counter(dt_job_t *job, const int elements, const int index, const gboolean folder_survey)
Update import progress with wording matching the import origin.
gboolean _file_exist(const char *dest_file_path)
Tests if file exist. Returns 1 if so.
const int32_t _import_job(dt_control_import_t *data, gchar *img_path_to_db)
Add an image entry in the database and returns its imgID.
void _write_xmp_id(const char *filename, int32_t imgid)
gchar * _path_cleanup(gchar *path_in)
Replaces separator depending of the current OS and removes whitespaces.
Definition import_jobs.c:68
int _import_copy_txt(const char *const filename, const char *dest_file_path)
void dt_control_import_data_free(dt_control_import_t *data)
@ DT_IMPORT_ONCONFLICT_UNIQUE
Definition import_jobs.h:39
@ DT_IMPORT_ONCONFLICT_OVERWRITE
Definition import_jobs.h:38
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:135
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:405
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:129
void dt_control_job_set_progress(dt_job_t *job, double value)
Definition jobs.c:626
void dt_control_job_add_progress(dt_job_t *job, const char *message, gboolean cancellable)
Definition jobs.c:612
void dt_control_job_set_progress_message(dt_job_t *job, const char *message)
Definition jobs.c:620
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:112
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:153
@ DT_JOB_QUEUE_USER_FG
Definition jobs.h:53
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
struct _GtkWidget GtkWidget
Definition splash.h:29
int dt_styles_apply_to_image_merge(const char *name, const int style_id, const int32_t newimgid, const dt_history_merge_strategy_t mode, dt_hm_batch_state_t *batch)
int32_t dt_styles_get_id_by_name(const char *name)
char * dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
void dt_variables_params_destroy(dt_variables_params_t *params)
void dt_variables_params_init(dt_variables_params_t **params)
void dt_variables_set_datetime(dt_variables_params_t *params, GDateTime *datetime)
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_collection_t * collection
Definition darktable.h:809
struct dt_control_t * control
Definition darktable.h:801
dt_import_onconflict_t on_conflict
Definition import_jobs.h:51
GDateTime * datetime
Definition import_jobs.h:45
GDestroyNotify callback_data_free
Definition import_jobs.h:89
void(* file_imported)(const char *source, gboolean success, gpointer user_data)
Optional per-file completion method.
Definition import_jobs.h:87
char * target_subfolder_pattern
Definition import_jobs.h:65
dt_ui_t * ui
Definition gtk.h:165
typedef double((*spd)(unsigned long int wavelength, double TempK))
gchar * dt_cleanup_separators(gchar *string)
Definition utility.c:1048
gboolean dt_util_dir_exist(const char *dir)
Definition utility.c:367
gchar * dt_util_path_get_dirname(const gchar *filename)
Definition utility.c:774
gchar * dt_util_normalize_path(const gchar *_input)
Definition utility.c:680
size_t safe_strlen(const char *str)
check if the string is empty or NULL before calling strlen()
Definition utility.c:90
gboolean dt_util_test_writable_dir(const char *path)
Definition utility.c:344
gchar * dt_util_remove_whitespace(const gchar *path)
Definition utility.c:1059