Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
disk.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Andrea Purracchio.
4 Copyright (C) 2010-2011, 2013 Henrik Andersson.
5 Copyright (C) 2010-2013 johannes hanika.
6 Copyright (C) 2010 Stuart Henderson.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Moritz Lipp.
9 Copyright (C) 2011 Olivier Tribout.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2011-2018, 2020 Tobias Ellinghaus.
12 Copyright (C) 2012 Christian Tellefsen.
13 Copyright (C) 2012 Richard Wonka.
14 Copyright (C) 2013 Jake Probst.
15 Copyright (C) 2013-2015 Jérémy Rosen.
16 Copyright (C) 2013-2015, 2018-2021 Pascal Obry.
17 Copyright (C) 2013-2016 Roman Lebedev.
18 Copyright (C) 2014 Pascal de Bruijn.
19 Copyright (C) 2015 Pedro Côrte-Real.
20 Copyright (C) 2017 parafin.
21 Copyright (C) 2018 Dan Torop.
22 Copyright (C) 2019, 2023, 2025 Aurélien PIERRE.
23 Copyright (C) 2019 Denis Dyakov.
24 Copyright (C) 2019 Edgardo Hoszowski.
25 Copyright (C) 2019, 2022 Philippe Weyland.
26 Copyright (C) 2020-2021 Chris Elston.
27 Copyright (C) 2020-2021 Diederik Ter Rahe.
28 Copyright (C) 2020 Marco.
29 Copyright (C) 2021 Hanno Schwalm.
30 Copyright (C) 2021 Hubert Kowalski.
31 Copyright (C) 2021 Marco Carrarini.
32 Copyright (C) 2021 Mark-64.
33 Copyright (C) 2021 Ralf Brown.
34 Copyright (C) 2022 Martin Bařinka.
35 Copyright (C) 2023 Ricky Moon.
36
37 darktable is free software: you can redistribute it and/or modify
38 it under the terms of the GNU General Public License as published by
39 the Free Software Foundation, either version 3 of the License, or
40 (at your option) any later version.
41
42 darktable is distributed in the hope that it will be useful,
43 but WITHOUT ANY WARRANTY; without even the implied warranty of
44 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45 GNU General Public License for more details.
46
47 You should have received a copy of the GNU General Public License
48 along with darktable. If not, see <http://www.gnu.org/licenses/>.
49*/
50
51#include "bauhaus/bauhaus.h"
52#include "common/darktable.h"
53#include "common/exif.h"
54#include "common/image.h"
55#include "common/image_cache.h"
56#include "common/imageio.h"
58#include "common/utility.h"
59#include "common/variables.h"
60#include "control/conf.h"
61#include "control/control.h"
62#include "dtgtk/button.h"
63#include "dtgtk/paint.h"
64#include "gui/gtk.h"
65#include "gui/gtkentry.h"
67#ifdef GDK_WINDOWING_QUARTZ
68#include "osx/osx.h"
69#endif
70#include <glib.h>
71#include <glib/gstdio.h>
72#include <stdio.h>
73#include <stdlib.h>
74
75DT_MODULE(3)
76
83
84// gui data
85typedef struct disk_t
86{
87 GtkEntry *entry;
90
91// saved params
98
99
100const char *name(const struct dt_imageio_module_storage_t *self)
101{
102 return _("File on disk");
103}
104
105void *legacy_params(dt_imageio_module_storage_t *self, const void *const old_params,
106 const size_t old_params_size, const int old_version, const int new_version,
107 size_t *new_size)
108{
109 if(old_version == 1 && new_version == 3)
110 {
111 typedef struct dt_imageio_disk_v1_t
112 {
113 char filename[1024];
115 gboolean overwrite;
116 } dt_imageio_disk_v1_t;
117
119 dt_imageio_disk_v1_t *o = (dt_imageio_disk_v1_t *)old_params;
120
121 g_strlcpy(n->filename, o->filename, sizeof(n->filename));
123
124 *new_size = self->params_size(self);
125 return n;
126 }
127 if(old_version == 2 && new_version == 3)
128 {
129 typedef struct dt_imageio_disk_v2_t
130 {
131 char filename[DT_MAX_PATH_FOR_PARAMS];
132 gboolean overwrite;
134 } dt_imageio_disk_v2_t;
135
137 dt_imageio_disk_v2_t *o = (dt_imageio_disk_v2_t *)old_params;
138
139 g_strlcpy(n->filename, o->filename, sizeof(n->filename));
141
142 *new_size = self->params_size(self);
143 return n;
144 }
145 return NULL;
146}
147
149{
150 disk_t *d = (disk_t *)self->gui_data;
152 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
153 _("select directory"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
154 _("_select as output destination"), _("_cancel"));
155
156 gchar *old = g_strdup(gtk_entry_get_text(d->entry));
157 char *c = g_strstr_len(old, -1, "$");
158 if(c) *c = '\0';
159 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), old);
160 dt_free(old);
161 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
162 {
163 gchar *dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
164 char *composed = g_build_filename(dir, "$(FILE_NAME)", NULL);
165
166 // composed can now contain '\': on Windows it's the path separator,
167 // on other platforms it can be part of a regular folder name.
168 // This would later clash with variable substitution, so we have to escape them
169 gchar *escaped = dt_util_str_replace(composed, "\\", "\\\\");
170
171 gtk_entry_set_text(GTK_ENTRY(d->entry), escaped); // the signal handler will write this to conf
172 gtk_editable_set_position(GTK_EDITABLE(d->entry), strlen(escaped));
173 dt_free(dir);
174 dt_free(composed);
175 dt_free(escaped);
176 }
177 g_object_unref(filechooser);
178}
179
180static void entry_changed_callback(GtkEntry *entry, gpointer user_data)
181{
182 dt_conf_set_string("plugins/imageio/storage/disk/file_directory", gtk_entry_get_text(entry));
183}
184
185static void onsave_action_toggle_callback(GtkWidget *widget, gpointer user_data)
186{
187 dt_conf_set_int("plugins/imageio/storage/disk/overwrite", dt_bauhaus_combobox_get(widget));
188}
189
191{
192 disk_t *d = (disk_t *)malloc(sizeof(disk_t));
193 self->gui_data = (void *)d;
194 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
195 GtkWidget *widget;
196
197 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
198 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(hbox), TRUE, FALSE, 0);
199
200 widget = gtk_entry_new();
202 gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
203 const char *dir = dt_conf_get_string_const("plugins/imageio/storage/disk/file_directory");
204 if(dir)
205 {
206 gtk_entry_set_text(GTK_ENTRY(widget), dir);
207 gtk_editable_set_position(GTK_EDITABLE(widget), strlen(dir));
208 }
209
211
212 d->entry = GTK_ENTRY(widget);
213 gtk_entry_set_width_chars(GTK_ENTRY(widget), 0);
214 gtk_widget_set_tooltip_text(widget,
215 _("enter the path where to put exported images\nvariables support bash like string manipulation\n"
216 "type '$(' to activate the completion and see the list of variables"));
217 g_signal_connect(G_OBJECT(widget), "changed", G_CALLBACK(entry_changed_callback), self);
218
220 gtk_widget_set_tooltip_text(widget, _("select directory"));
221 gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
222 g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(button_clicked), self);
223
225 dt_bauhaus_widget_set_label(d->onsave_action, N_("on conflict"));
226 gtk_widget_set_tooltip_text(d->onsave_action, _("Expected behaviour if the current naming pattern\n"
227 "produces a filename that already exists."));
228 dt_bauhaus_combobox_add(d->onsave_action, _("create unique filename"));
229 dt_bauhaus_combobox_add(d->onsave_action, _("overwrite"));
230 dt_bauhaus_combobox_add(d->onsave_action, _("skip"));
231 gtk_box_pack_start(GTK_BOX(self->widget), d->onsave_action, TRUE, TRUE, 0);
232 g_signal_connect(G_OBJECT(d->onsave_action), "value-changed", G_CALLBACK(onsave_action_toggle_callback), self);
233 dt_bauhaus_combobox_set(d->onsave_action, dt_conf_get_int("plugins/imageio/storage/disk/overwrite"));
234}
235
237{
238 dt_free(self->gui_data);
239}
240
242{
243 disk_t *d = (disk_t *)self->gui_data;
244 // global default can be annoying:
245 // gtk_entry_set_text(GTK_ENTRY(d->entry), "$(FILE_FOLDER)/darktable_exported/$(FILE_NAME)");
246 gtk_entry_set_text(d->entry, dt_confgen_get("plugins/imageio/storage/disk/file_directory", DT_DEFAULT));
247 dt_bauhaus_combobox_set(d->onsave_action, dt_confgen_get_int("plugins/imageio/storage/disk/overwrite", DT_DEFAULT));
248 dt_conf_set_string("plugins/imageio/storage/disk/file_directory", gtk_entry_get_text(d->entry));
249 dt_conf_set_int("plugins/imageio/storage/disk/overwrite", dt_bauhaus_combobox_get(d->onsave_action));
250}
251
252int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int32_t imgid,
253 dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total,
254 const gboolean high_quality, const gboolean export_masks,
255 dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent,
256 dt_export_metadata_t *metadata)
257{
259
260 char filename[PATH_MAX] = { 0 };
261 char input_dir[PATH_MAX] = { 0 };
262 char pattern[DT_MAX_PATH_FOR_PARAMS];
263 g_strlcpy(pattern, d->filename, sizeof(pattern));
264 gboolean from_cache = FALSE;
265 dt_image_full_path(imgid, input_dir, sizeof(input_dir), &from_cache, __FUNCTION__);
266 // set variable values to expand them afterwards in darktable variables
268
269 gboolean fail = FALSE;
270 // we're potentially called in parallel. have sequence number synchronized:
272 {
273try_again:
274 // avoid braindead export which is bound to overwrite at random:
275 if(total > 1 && !g_strrstr(pattern, "$"))
276 {
277 snprintf(pattern + strlen(pattern), sizeof(pattern) - strlen(pattern), "_$(SEQUENCE)");
278 }
279
280 gchar *fixed_path = dt_util_fix_path(pattern);
281 g_strlcpy(pattern, fixed_path, sizeof(pattern));
282 dt_free(fixed_path);
283
284 d->vp->filename = input_dir;
285 d->vp->jobcode = "export";
286 d->vp->imgid = imgid;
287 d->vp->sequence = num;
288
289 gchar *result_filename = dt_variables_expand(d->vp, pattern, TRUE);
290 g_strlcpy(filename, result_filename, sizeof(filename));
291 dt_free(result_filename);
292
293 // if filenamepattern is a directory just add ${FILE_NAME} as default..
294 // this can happen if the filename component of the pattern is an empty variable
295 char last_char = *(filename + strlen(filename) - 1);
296 if(last_char == '/' || last_char == '\\')
297 {
298 // add to the end of the original pattern without caring about a
299 // potentially added "_$(SEQUENCE)"
300 if (snprintf(pattern, sizeof(pattern), "%s" G_DIR_SEPARATOR_S "$(FILE_NAME)", d->filename) < sizeof(pattern))
301 goto try_again;
302 }
303
304 char *output_dir = g_path_get_dirname(filename);
305
306 if(g_mkdir_with_parents(output_dir, 0755))
307 {
308 fprintf(stderr, "[imageio_storage_disk] could not create directory: `%s'!\n", output_dir);
309 dt_control_log(_("could not create directory `%s'!"), output_dir);
310 fail = TRUE;
311 goto failed;
312 }
313 if(g_access(output_dir, W_OK | X_OK) != 0)
314 {
315 fprintf(stderr, "[imageio_storage_disk] could not write to directory: `%s'!\n", output_dir);
316 dt_control_log(_("could not write to directory `%s'!"), output_dir);
317 fail = TRUE;
318 goto failed;
319 }
320
321 const char *ext = format->extension(fdata);
322 char *c = filename + strlen(filename);
323 size_t filename_free_space = sizeof(filename) - (c - filename);
324 snprintf(c, filename_free_space, ".%s", ext);
325
326 /* prevent overwrite of files */
327 failed:
328 dt_free(output_dir);
329
330 if(!fail && d->onsave_action == DT_EXPORT_ONCONFLICT_UNIQUEFILENAME)
331 {
332 int seq = 1;
333 while(g_file_test(filename, G_FILE_TEST_EXISTS))
334 {
335 snprintf(c, filename_free_space, "_%.2d.%s", seq, ext);
336 seq++;
337 }
338 }
339
340 if(!fail && d->onsave_action == DT_EXPORT_ONCONFLICT_SKIP)
341 {
342 if(g_file_test(filename, G_FILE_TEST_EXISTS))
343 {
345 fprintf(stderr, "[export_job] skipping `%s'\n", filename);
346 dt_control_log(ngettext("%d/%d skipping `%s'", "%d/%d skipping `%s'", num),
347 num, total, filename);
348 return 0;
349 }
350 }
351 } // end of critical block
353 if(fail) return 1;
354
355 /* export image to file */
356 if(dt_imageio_export(imgid, filename, format, fdata, TRUE, TRUE, export_masks, icc_type,
357 icc_filename, icc_intent, self, sdata, num, total, metadata) != 0)
358 {
359 fprintf(stderr, "[imageio_storage_disk] could not export to file: `%s'!\n", filename);
360 dt_control_log(_("could not export to file `%s'!"), filename);
361 return 1;
362 }
363
364 fprintf(stderr, "[export_job] exported to `%s'\n", filename);
365 dt_control_log(ngettext("%d/%d exported to `%s'", "%d/%d exported to `%s'", num),
366 num, total, filename);
367 return 0;
368}
369
371{
372 return sizeof(dt_imageio_disk_t) - sizeof(void *);
373}
374
376{
377}
378
380{
382
383 const char *text = dt_conf_get_string_const("plugins/imageio/storage/disk/file_directory");
384 g_strlcpy(d->filename, text, sizeof(d->filename));
385
386 d->onsave_action = dt_conf_get_int("plugins/imageio/storage/disk/overwrite");
387
388 d->vp = NULL;
390
391 return d;
392}
393
395{
396 if(IS_NULL_PTR(params)) return;
399 dt_free(params);
400}
401
402int set_params(dt_imageio_module_storage_t *self, const void *params, const int size)
403{
405 disk_t *g = (disk_t *)self->gui_data;
406
407 if(size != self->params_size(self)) return 1;
408
409 gtk_entry_set_text(GTK_ENTRY(g->entry), d->filename);
410 gtk_editable_set_position(GTK_EDITABLE(g->entry), strlen(d->filename));
411 dt_bauhaus_combobox_set(g->onsave_action, d->onsave_action);
412 return 0;
413}
414
416{
417 disk_t *g = (disk_t *)self->gui_data;
418 if(dt_bauhaus_combobox_get(g->onsave_action) == DT_EXPORT_ONCONFLICT_OVERWRITE && dt_conf_get_bool("plugins/lighttable/export/ask_before_export_overwrite"))
419 {
420 return g_strdup(_("you are going to export on overwrite mode, this will overwrite any existing images\n\n"
421 "do you really want to continue?"));
422 }
423 else
424 {
425 return NULL;
426 }
427}
428
429// clang-format off
430// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
431// vim: shiftwidth=2 expandtab tabstop=2 cindent
432// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
433// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:134
dt_iop_color_intent_t
Definition colorspaces.h:63
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
char * name
@ DT_DEFAULT
Definition conf.h:96
int dt_conf_get_bool(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
int dt_confgen_get_int(const char *name, dt_confgen_value_kind_t kind)
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
const char * dt_confgen_get(const char *name, dt_confgen_value_kind_t kind)
void dt_control_log(const char *msg,...)
Definition control.c:761
darktable_t darktable
Definition darktable.c:181
#define DT_MODULE(MODVER)
Definition darktable.h:140
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MAX_PATH_FOR_PARAMS
Definition darktable.h:1071
#define PATH_MAX
Definition darktable.h:1062
#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:281
void * get_params(dt_imageio_module_storage_t *self)
Definition disk.c:379
char * ask_user_confirmation(dt_imageio_module_storage_t *self)
Definition disk.c:415
void gui_cleanup(dt_imageio_module_storage_t *self)
Definition disk.c:236
void free_params(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *params)
Definition disk.c:394
static void onsave_action_toggle_callback(GtkWidget *widget, gpointer user_data)
Definition disk.c:185
size_t params_size(dt_imageio_module_storage_t *self)
Definition disk.c:370
void gui_init(dt_imageio_module_storage_t *self)
Definition disk.c:190
void init(dt_imageio_module_storage_t *self)
Definition disk.c:375
void gui_reset(dt_imageio_module_storage_t *self)
Definition disk.c:241
dt_disk_onconflict_actions_t
Definition disk.c:78
@ DT_EXPORT_ONCONFLICT_SKIP
Definition disk.c:81
@ DT_EXPORT_ONCONFLICT_UNIQUEFILENAME
Definition disk.c:79
@ DT_EXPORT_ONCONFLICT_OVERWRITE
Definition disk.c:80
void * legacy_params(dt_imageio_module_storage_t *self, const void *const old_params, const size_t old_params_size, const int old_version, const int new_version, size_t *new_size)
Definition disk.c:105
int set_params(dt_imageio_module_storage_t *self, const void *params, const int size)
Definition disk.c:402
static void button_clicked(GtkWidget *widget, dt_imageio_module_storage_t *self)
Definition disk.c:148
static void entry_changed_callback(GtkEntry *entry, gpointer user_data)
Definition disk.c:180
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
void dtgtk_cairo_paint_directory(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_NONE
Definition dtgtk/paint.h:60
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3225
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
void dt_gtkentry_setup_completion(GtkEntry *entry, const dt_gtkentry_completion_spec *compl_list, const char *trigger_char)
Definition gtkentry.c:173
const dt_gtkentry_completion_spec * dt_gtkentry_get_default_path_compl_list()
Definition gtkentry.c:197
#define DT_GUI_MODULE(x)
int dt_imageio_export(const int32_t imgid, const char *filename, dt_imageio_module_format_t *format, dt_imageio_module_data_t *format_params, const gboolean high_quality, const gboolean copy_metadata, const gboolean export_masks, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent, dt_imageio_module_storage_t *storage, dt_imageio_module_data_t *storage_params, int num, int total, dt_export_metadata_t *metadata)
Definition imageio.c:765
size_t size
Definition mipmap_cache.c:3
struct _GtkWidget GtkWidget
Definition splash.h:29
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_set_max_width_height(dt_variables_params_t *params, int max_width, int max_height)
void dt_variables_params_init(dt_variables_params_t **params)
struct dt_gui_gtk_t * gui
Definition darktable.h:775
dt_pthread_mutex_t plugin_threadsafe
Definition darktable.h:793
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
Definition disk.c:86
GtkWidget * onsave_action
Definition disk.c:88
GtkEntry * entry
Definition disk.c:87
dt_ui_t * ui
Definition gtk.h:164
dt_variables_params_t * vp
Definition disk.c:96
dt_disk_onconflict_actions_t onsave_action
Definition disk.c:95
char filename[DT_MAX_PATH_FOR_PARAMS]
Definition disk.c:94
GModule *GtkWidget * widget
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:136
gchar * dt_util_fix_path(const gchar *path)
Definition utility.c:222