Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
film.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2013 johannes hanika.
4 Copyright (C) 2010-2012 Henrik Andersson.
5 Copyright (C) 2010-2018, 2020 Tobias Ellinghaus.
6 Copyright (C) 2012 James C. McPherson.
7 Copyright (C) 2012 Jesper Pedersen.
8 Copyright (C) 2012 José Carlos García Sogo.
9 Copyright (C) 2012 Richard Wonka.
10 Copyright (C) 2013 Dennis Gnad.
11 Copyright (C) 2013-2015 Jérémy Rosen.
12 Copyright (C) 2013-2014, 2020-2021 Pascal Obry.
13 Copyright (C) 2013 Simon Spannagel.
14 Copyright (C) 2014-2016 Roman Lebedev.
15 Copyright (C) 2017 luzpaz.
16 Copyright (C) 2018 Edgardo Hoszowski.
17 Copyright (C) 2018 parafin.
18 Copyright (C) 2019, 2022, 2025 Aurélien PIERRE.
19 Copyright (C) 2019-2020 Hanno Schwalm.
20 Copyright (C) 2019, 2021-2022 Philippe Weyland.
21 Copyright (C) 2020-2021 Aldric Renaudin.
22 Copyright (C) 2020 Heiko Bauke.
23 Copyright (C) 2020 Hubert Kowalski.
24 Copyright (C) 2020 JP Verrue.
25 Copyright (C) 2020 Nicolas Auffray.
26 Copyright (C) 2021-2022 HansBull.
27 Copyright (C) 2021 Ralf Brown.
28 Copyright (C) 2022 Martin Bařinka.
29
30 darktable is free software: you can redistribute it and/or modify
31 it under the terms of the GNU General Public License as published by
32 the Free Software Foundation, either version 3 of the License, or
33 (at your option) any later version.
34
35 darktable is distributed in the hope that it will be useful,
36 but WITHOUT ANY WARRANTY; without even the implied warranty of
37 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 GNU General Public License for more details.
39
40 You should have received a copy of the GNU General Public License
41 along with darktable. If not, see <http://www.gnu.org/licenses/>.
42*/
43#include "common/film.h"
44#include "database/database.h"
46#include "control/settings.h"
47#include "common/collection.h"
48#include "caches/mipmap_cache.h"
49#include "system/dtpthread.h"
50#include "caches/image_cache.h"
51#include "metadata/tags.h"
52#include "common/conf.h"
53#include "control/control.h"
55#include "control/jobs.h"
56#include "views/view.h"
57
58#include <assert.h>
59#include <errno.h>
60#include <limits.h>
61#include <math.h>
62#include <stdio.h>
63#include <stdlib.h>
64#include <string.h>
65#include <strings.h>
66#include <sys/stat.h>
67#include <sys/types.h>
68#include <unistd.h>
69#include "common/utility.h"
70#ifdef GDK_WINDOWING_QUARTZ
71#include "osx/osx.h"
72#endif
73
75{
77 film->last_loaded = film->num_images = 0;
78 film->dirname[0] = '\0';
79 film->dir = NULL;
80 film->id = -1;
81 film->ref = 0;
82}
83
85{
87 if(film->dir)
88 {
89 g_dir_close(film->dir);
90 film->dir = NULL;
91 }
92}
93
94void dt_film_set_query(const int32_t id)
95{
96 /* enable film id filter and set film id */
97 dt_conf_set_int("plugins/lighttable/collect/num_rules", 1);
98 dt_conf_set_int("plugins/lighttable/collect/item0", 0);
100 if(folder)
101 {
102 dt_conf_set_string("plugins/lighttable/collect/string0", folder);
104 }
106}
107
108int32_t dt_film_get_id(const char *folder)
109{
111}
112
113int dt_film_open(const int32_t id)
114{
116 if(folder)
117 {
120 }
121 // TODO: prefetch to cache using image_open
125 return 0;
126}
127
128
129int dt_film_new(dt_film_t *film, const char *directory)
130{
131 // Try open filmroll for folder if exists
132 film->id = -1;
133 g_strlcpy(film->dirname, directory, sizeof(film->dirname));
134
135 // remove a closing '/', unless it's also the start
136 char *last = &film->dirname[strlen(film->dirname) - 1];
137 if(*last == '/' && last != film->dirname) *last = '\0';
138
139 /* if we didn't find an id, lets instantiate a new filmroll */
140 film->id = dt_film_get_id(film->dirname);
141
142 /* if we didn't find an id, lets instantiate a new filmroll */
143 if(film->id <= 0)
144 {
145 // create a new filmroll
146 /* insert a new film roll into database */
148 fprintf(stderr, "[film_new] failed to insert film roll! %s\n", dt_database_get_last_error());
149 /* requery for filmroll and fetch new id */
150 film->id = dt_film_get_id(film->dirname);
151 if(film->id)
152 {
153 // add it to the table memory.film_folder
155 }
156 }
157#ifdef _WIN32
158 else
159 {
160 // Make sure we reuse the same path case.
161 //
162 // dt_film_get_id() matches case-insensitively on Windows (LIKE), so film->dirname can differ
163 // in case from what the roll is actually stored under; adopt the stored spelling so the two
164 // agree. This used to test `sqlite3_step(...) != SQLITE_ROW` and then read column 0 of a
165 // statement that had returned no row -- i.e. it could only ever have copied a NULL, and
166 // since film->id comes from dt_film_get_id() just above the row always exists, so the copy
167 // never ran at all. The test is inverted here to what it was plainly meant to be.
168 char *stored = dt_film_repository_get_folder(film->id);
169 if(stored)
170 {
171 g_strlcpy(film->dirname, stored, sizeof(film->dirname));
172 dt_free(stored);
173 }
174 }
175#endif
176
177 if(film->id <= 0)
178 dt_print(DT_DEBUG_IMPORT, "[Import] Could not create a new filmid for %s\n", directory);
179 else
180 dt_print(DT_DEBUG_IMPORT, "[Import] Reusing or creating filmid %i for %s\n", film->id, directory);
181
182 if(film->id <= 0) return 0;
183 film->last_loaded = 0;
184 return film->id;
185}
186
187int dt_film_import(const char *dirname)
188{
189 GError *error = NULL;
190
191 /* initialize a film object*/
192 dt_film_t *film = (dt_film_t *)malloc(sizeof(dt_film_t));
193 dt_film_init(film);
194
195 dt_film_new(film, dirname);
196
197 /* bail out if we got troubles */
198 if(film->id <= 0)
199 {
200 // if the film is empty => remove it again.
201 if(dt_film_is_empty(film->id))
202 {
203 dt_film_remove(film->id);
204 }
205 dt_film_cleanup(film);
206 dt_free(film);
207 return 0;
208 }
209
210 // when called without job system running the import will be done synchronously and destroy the film object
211 const int filmid = film->id;
212
213 /* at last put import film job on queue */
214 film->last_loaded = 0;
215 film->dir = g_dir_open(film->dirname, 0, &error);
216 if(error)
217 {
218 fprintf(stderr, "[film_import] failed to open directory %s: %s\n", film->dirname, error->message);
219 g_error_free(error);
220 dt_film_cleanup(film);
221 dt_free(film);
222 return 0;
223 }
224
225 // launch import job
227
228 return filmid;
229}
230
232
237
238/* Written once, at startup, before any film roll can change; read from whichever thread
239 * changes one. A lock would be guarding against a race nobody can create. */
241
246
248{
250 if(handler) handler();
251}
252
253void dt_film_remove_directories(const GList *dirs)
254{
255 for(const GList *iter = dirs; iter; iter = g_list_next(iter))
256 rmdir((char *)iter->data);
257}
258
259
260typedef struct _empty_rolls_t
261{
262 GList *ids;
263 GList *folders;
265
266static void _collect_empty_roll(void *user_data, const int32_t id, const char *folder)
267{
268 _empty_rolls_t *r = (_empty_rolls_t *)user_data;
269 r->ids = g_list_prepend(r->ids, GINT_TO_POINTER(id));
270 r->folders = g_list_prepend(r->folders, g_strdup(folder));
271}
272
273typedef struct _relocation_t
274{
275 const char *old_path;
276 const char *new_path;
277 GList *ids;
278 GList *folders;
280
281static void _collect_relocation(void *user_data, const int32_t id, const char *folder)
282{
283 _relocation_t *r = (_relocation_t *)user_data;
284 gchar *final = g_strcmp0(folder, r->old_path)
285 ? g_strdup_printf("%s/%s", r->new_path, folder + strlen(r->old_path) + 1)
286 : g_strdup(r->new_path);
287 r->ids = g_list_prepend(r->ids, GINT_TO_POINTER(id));
288 r->folders = g_list_prepend(r->folders, final);
289}
290
292{
293 // remove all empty film rolls from db:
294 GList *empty_dirs = NULL;
295 gboolean ask_before_rmdir = dt_conf_get_bool("ask_before_rmdir");
296 gboolean raise_signal = FALSE;
297 // Collect first: the original DELETEd rows while its own SELECT cursor was still walking
298 // main.film_rolls.
299 _empty_rolls_t rolls = { NULL, NULL };
301 rolls.ids = g_list_reverse(rolls.ids);
302 rolls.folders = g_list_reverse(rolls.folders);
303
304 for(GList *i = rolls.ids, *f = rolls.folders; i && f; i = g_list_next(i), f = g_list_next(f))
305 {
306 raise_signal = TRUE;
307 const gchar *folder = (const gchar *)f->data;
308 dt_film_repository_delete(GPOINTER_TO_INT(i->data));
309
311 {
312 if(ask_before_rmdir) empty_dirs = g_list_prepend(empty_dirs, g_strdup(folder));
313 else rmdir(folder);
314 }
315 }
316 g_list_free(rolls.ids);
317 g_list_free_full(rolls.folders, g_free);
319
320 // dispatch asking for deletion (and subsequent deletion) to the gui thread
321 if(empty_dirs)
322 {
323 empty_dirs = g_list_reverse(empty_dirs);
324 // Nobody to ask -> nothing is deleted. "ask before rmdir" cannot be honoured headless.
326 _confirm_rmdir_handler(empty_dirs); // takes ownership
327 else
328 g_list_free_full(empty_dirs, dt_free_gpointer);
329 empty_dirs = NULL;
330 }
331}
332
333gboolean dt_film_is_empty(const int id)
334{
336}
337
338// This is basically the same as dt_image_remove() from common/image.c.
339// It just does the iteration over all images in the SQL statement
340void dt_film_remove(const int id)
341{
342 // only allowed if local copies have their original accessible
343
344 gboolean remove_ok = TRUE;
345
346 GList *imgids = dt_film_repository_get_image_ids(id);
347
348 for(GList *l = imgids; l; l = g_list_next(l))
349 {
350 if(!dt_image_safe_remove(GPOINTER_TO_INT(l->data)))
351 {
352 remove_ok = FALSE;
353 break;
354 }
355 }
356
357 if(!remove_ok)
358 {
359 g_list_free(imgids);
360 dt_control_log(_("cannot remove film roll having local copies with non accessible originals"));
361 return;
362 }
363
364 // the same ids again, this time for the mipmap and image caches
365 for(GList *l = imgids; l; l = g_list_next(l))
366 {
367 const int32_t imgid = GPOINTER_TO_INT(l->data);
371 }
372 g_list_free(imgids);
373
375 // dt_control_update_recent_films();
376
378}
379
380void dt_film_relocate(const char *old_path, const char *new_path)
381{
382 if(IS_NULL_PTR(old_path) || IS_NULL_PTR(new_path)) return;
383
384 // Gather every film roll under old_path together with its remapped folder first, so we do
385 // not mutate the table while still iterating the SELECT.
386 _relocation_t reloc = { old_path, new_path, NULL, NULL };
388 reloc.ids = g_list_reverse(reloc.ids);
389 reloc.folders = g_list_reverse(reloc.folders);
390
391 for(GList *i = reloc.ids, *f = reloc.folders; i && f; i = g_list_next(i), f = g_list_next(f))
392 dt_film_repository_set_folder(GPOINTER_TO_INT(i->data), (const char *)f->data);
393
394 g_list_free(reloc.ids);
395 g_list_free_full(reloc.folders, g_free);
396}
397
398GList *dt_film_get_image_ids(const int filmid)
399{
401}
402
403static void _record_folder_status(void *user_data, const int32_t id, const char *folder)
404{
405 (void)user_data;
406 dt_film_repository_folder_status_set(id, g_file_test(folder, G_FILE_TEST_IS_DIR));
407}
408
410{
411 // Writing memory.film_folder while the main.film_rolls cursor is open is safe -- different
412 // tables -- and this is what the original did, one INSERT per row as it read them.
415}
416
417// clang-format off
418// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
419// vim: shiftwidth=2 expandtab tabstop=2 cindent
420// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
421// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
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
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:155
@ DT_COLLECTION_CHANGE_NEW_QUERY
Definition collection.h:162
const float f
int dt_conf_get_bool(const char *name)
void dt_conf_set_int(const char *name, int val)
void dt_conf_set_string(const char *name, const char *val)
int dt_image_local_copy_reset(const int32_t imgid)
gboolean dt_image_safe_remove(const int32_t imgid)
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
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
const char * dt_database_get_last_error(void)
Definition database.c:3776
gchar * directory
documentation root given after –doc, or NULL
GtkWidget * folder
destination folder chooser
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
void dt_film_init(dt_film_t *film)
Definition film.c:74
void dt_film_relocate(const char *old_path, const char *new_path)
Definition film.c:380
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_set_rolls_changed_handler(dt_film_rolls_changed_handler_t handler)
Install the handler, or NULL to remove it.
Definition film.c:242
void dt_film_remove_empty()
Definition film.c:291
void dt_film_remove(const int id)
Definition film.c:340
static void _record_folder_status(void *user_data, const int32_t id, const char *folder)
Definition film.c:403
int32_t dt_film_get_id(const char *folder)
Definition film.c:108
static void _collect_empty_roll(void *user_data, const int32_t id, const char *folder)
Definition film.c:266
void dt_film_set_query(const int32_t id)
Definition film.c:94
static dt_film_confirm_rmdir_handler_t _confirm_rmdir_handler
Definition film.c:231
static void _collect_relocation(void *user_data, const int32_t id, const char *folder)
Definition film.c:281
GList * dt_film_get_image_ids(const int filmid)
Definition film.c:398
void dt_film_remove_directories(const GList *dirs)
Definition film.c:253
int dt_film_open(const int32_t id)
Definition film.c:113
void dt_film_set_folder_status()
Definition film.c:409
static dt_film_rolls_changed_handler_t _rolls_changed_handler
Definition film.c:240
void dt_film_set_confirm_rmdir_handler(dt_film_confirm_rmdir_handler_t handler)
Definition film.c:233
int dt_film_import(const char *dirname)
Definition film.c:187
void dt_film_cleanup(dt_film_t *film)
Definition film.c:84
void dt_film_notify_rolls_changed(void)
State that the film-roll list changed.
Definition film.c:247
void(* dt_film_rolls_changed_handler_t)(void)
The film-roll list changed. Raised on whichever thread changed it; a handler that touches widgets get...
Definition film.h:111
void(* dt_film_confirm_rmdir_handler_t)(GList *empty_dirs)
Definition film.h:93
dt_job_t * dt_film_import1_create(dt_film_t *film)
Definition film_jobs.c:85
char * dt_film_repository_get_folder(const int32_t id)
gboolean dt_film_repository_insert(const char *folder)
void dt_film_repository_foreach_empty(dt_film_repository_row_cb cb, void *user_data)
gboolean dt_film_repository_has_images(const int32_t id)
void dt_film_repository_foreach_under(const char *path, dt_film_repository_row_cb cb, void *user_data)
int32_t dt_film_repository_find_by_folder(const char *folder)
gboolean dt_film_repository_touch_access(const int32_t id)
void dt_film_repository_foreach(dt_film_repository_row_cb cb, void *user_data)
gboolean dt_film_repository_set_folder(const int32_t id, const char *folder)
gboolean dt_film_repository_delete(const int32_t id)
void dt_film_repository_folder_status_set(const int32_t id, const gboolean present)
GList * dt_film_repository_get_image_ids(const int32_t filmid)
void dt_film_repository_folder_status_clear(void)
void dt_image_cache_remove(const int32_t imgid)
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:407
@ DT_JOB_QUEUE_USER_BG
Definition jobs.h:56
@ DT_DEBUG_IMPORT
Definition logging.h:77
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#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
void dt_mipmap_cache_remove(const int32_t imgid, const gboolean flush_disk)
#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_FILMROLLS_REMOVED
This signal is raised only when a filmroll is removed.
Definition signal.h:162
const float r
GList * ids
Definition film.c:262
GList * folders
Definition film.c:263
GList * folders
Definition film.c:278
const char * old_path
Definition film.c:275
const char * new_path
Definition film.c:276
GList * ids
Definition film.c:277
int32_t num_images
Definition film.h: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
int32_t last_loaded
Definition film.h:49
char dirname[512]
Definition film.h:46
gboolean dt_util_is_dir_empty(const char *dirname)
Definition utility.c:379
void dt_view_manager_reset(dt_view_manager_t *vm)
Definition view.c:491