1 /*
2     This file is part of darktable,
3     Copyright (C) 2014-2021 darktable developers.
4 
5     darktable 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     darktable 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 darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <stdio.h>
20 
21 #include "common/film.h"
22 #include "common/import_session.h"
23 #include "common/variables.h"
24 #include "control/conf.h"
25 #include "control/control.h"
26 
27 /* TODO: Investigate if we can make one import session instance thread safe
28          eg. having several background jobs working with same instance.
29 */
30 
31 typedef struct dt_import_session_t
32 {
33   uint32_t ref;
34 
35   dt_film_t *film;
36   dt_variables_params_t *vp;
37 
38   const gchar *current_path;
39   const gchar *current_filename;
40 
41 } dt_import_session_t;
42 
43 
_import_session_cleanup_filmroll(dt_import_session_t * self)44 static void _import_session_cleanup_filmroll(dt_import_session_t *self)
45 {
46   if(self->film == NULL) return;
47   /* if current filmroll for session is empty, remove it */
48   if(dt_film_is_empty(self->film->id))
49   {
50     dt_film_remove(self->film->id);
51     if(self->current_path != NULL && g_file_test(self->current_path, G_FILE_TEST_IS_DIR) && dt_util_is_dir_empty(self->current_path))
52     {
53       // no need to ask for rmdir as it'll be re-created if it's needed
54       // by another import session with same path params
55       g_rmdir(self->current_path);
56       self->current_path = NULL;
57     }
58   }
59   dt_film_cleanup(self->film);
60 
61   g_free(self->film);
62   self->film = NULL;
63 }
64 
65 
_import_session_initialize_filmroll(dt_import_session_t * self,const char * path)66 static gboolean _import_session_initialize_filmroll(dt_import_session_t *self, const char *path)
67 {
68   int32_t film_id;
69 
70   /* cleanup of previously used filmroll */
71   _import_session_cleanup_filmroll(self);
72 
73   /* recursively create directories, abort if failed */
74   if(g_mkdir_with_parents(path, 0755) == -1)
75   {
76     fprintf(stderr, "failed to create session path %s.\n", path);
77     _import_session_cleanup_filmroll(self);
78     return TRUE;
79   }
80 
81   /* open one or initialize a filmroll for the session */
82   self->film = (dt_film_t *)g_malloc0(sizeof(dt_film_t));
83   film_id = dt_film_new(self->film, path);
84   if(film_id == 0)
85   {
86     fprintf(stderr, "[import_session] Failed to initialize film roll.\n");
87     _import_session_cleanup_filmroll(self);
88     return TRUE;
89   }
90 
91   /* every thing is good lets setup current path */
92   self->current_path = path;
93 
94   return FALSE;
95 }
96 
97 
_import_session_migrate_old_config()98 static void _import_session_migrate_old_config()
99 {
100   /* TODO: check if old config exists, migrate to new and remove old */
101 }
102 
103 
_import_session_path_pattern()104 static char *_import_session_path_pattern()
105 {
106   char *res;
107   char *base;
108   char *sub;
109 
110   res = NULL;
111   base = dt_conf_get_string("session/base_directory_pattern");
112   sub = dt_conf_get_string("session/sub_directory_pattern");
113 
114   if(!sub || !base)
115   {
116     fprintf(stderr, "[import_session] No base or subpath configured...\n");
117     goto bail_out;
118   }
119 
120 #ifdef WIN32
121   res = g_build_path("/", base, sub, (char *)NULL);
122 #else
123   res = g_build_path(G_DIR_SEPARATOR_S, base, sub, (char *)NULL);
124 #endif
125 
126 bail_out:
127   g_free(base);
128   g_free(sub);
129   return res;
130 }
131 
132 
_import_session_filename_pattern()133 static char *_import_session_filename_pattern()
134 {
135   char *name;
136 
137   name = dt_conf_get_string("session/filename_pattern");
138   if(!name)
139   {
140     fprintf(stderr, "[import_session] No name configured...\n");
141     return NULL;
142   }
143 
144   return name;
145 }
146 
147 
dt_import_session_new()148 struct dt_import_session_t *dt_import_session_new()
149 {
150   dt_import_session_t *is;
151 
152   is = (dt_import_session_t *)g_malloc0(sizeof(dt_import_session_t));
153 
154   dt_variables_params_init(&is->vp);
155 
156   /* migrate old configuration */
157   _import_session_migrate_old_config();
158   return is;
159 }
160 
161 
dt_import_session_destroy(struct dt_import_session_t * self)162 void dt_import_session_destroy(struct dt_import_session_t *self)
163 {
164   if(--self->ref != 0) return;
165 
166   /* cleanup of session import film roll */
167   _import_session_cleanup_filmroll(self);
168 
169   dt_variables_params_destroy(self->vp);
170 
171   g_free(self);
172 }
173 
dt_import_session_ready(struct dt_import_session_t * self)174 gboolean dt_import_session_ready(struct dt_import_session_t *self)
175 {
176   return (self->film && self->film->id);
177 }
178 
dt_import_session_ref(struct dt_import_session_t * self)179 void dt_import_session_ref(struct dt_import_session_t *self)
180 {
181   self->ref++;
182 }
183 
dt_import_session_unref(struct dt_import_session_t * self)184 void dt_import_session_unref(struct dt_import_session_t *self)
185 {
186   self->ref--;
187 }
188 
dt_import_session_import(struct dt_import_session_t * self)189 void dt_import_session_import(struct dt_import_session_t *self)
190 {
191   const int32_t id = dt_image_import(self->film->id, self->current_filename, TRUE, TRUE);
192   if(id)
193   {
194     DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, id);
195     dt_control_queue_redraw();
196   }
197 }
198 
199 
dt_import_session_set_name(struct dt_import_session_t * self,const char * name)200 void dt_import_session_set_name(struct dt_import_session_t *self, const char *name)
201 {
202   /* free previous jobcode name */
203   g_free((void *)self->vp->jobcode);
204 
205   self->vp->jobcode = g_strdup(name);
206 
207   /* setup new filmroll if path has changed */
208   dt_import_session_path(self, FALSE);
209 }
210 
211 
dt_import_session_set_time(struct dt_import_session_t * self,time_t time)212 void dt_import_session_set_time(struct dt_import_session_t *self, time_t time)
213 {
214   dt_variables_set_time(self->vp, time);
215 }
216 
217 
218 void
dt_import_session_set_exif_time(struct dt_import_session_t * self,time_t exif_time)219 dt_import_session_set_exif_time(struct dt_import_session_t *self, time_t exif_time)
220 {
221   dt_variables_set_exif_time(self->vp, exif_time);
222 }
223 
224 
dt_import_session_set_filename(struct dt_import_session_t * self,const char * filename)225 void dt_import_session_set_filename(struct dt_import_session_t *self, const char *filename)
226 {
227   self->vp->filename = filename;
228 }
229 
230 
dt_import_session_film_id(struct dt_import_session_t * self)231 int32_t dt_import_session_film_id(struct dt_import_session_t *self)
232 {
233   if(self->film) return self->film->id;
234 
235   return -1;
236 }
237 
238 
dt_import_session_name(struct dt_import_session_t * self)239 const char *dt_import_session_name(struct dt_import_session_t *self)
240 {
241   return self->vp->jobcode;
242 }
243 
244 /* This returns a unique filename using session path **and** the filename.
245    If current is true we will use the original filename otherwise use the pattern.
246 */
dt_import_session_filename(struct dt_import_session_t * self,gboolean use_filename)247 const char *dt_import_session_filename(struct dt_import_session_t *self, gboolean use_filename)
248 {
249   const char *path;
250   char *fname, *previous_fname;
251   char *pattern;
252   gchar *result_fname;
253 
254   /* expand next filename */
255   g_free((void *)self->current_filename);
256   self->current_filename = NULL;
257 
258   pattern = _import_session_filename_pattern();
259   if(pattern == NULL)
260   {
261     fprintf(stderr, "[import_session] Failed to get session filaname pattern.\n");
262     return NULL;
263   }
264 
265   /* verify that expanded path and filename yields a unique file */
266   path = dt_import_session_path(self, TRUE);
267 
268   if(use_filename)
269     result_fname = g_strdup(self->vp->filename);
270   else
271     result_fname = dt_variables_expand(self->vp, pattern, TRUE);
272 
273   previous_fname = fname = g_build_path(G_DIR_SEPARATOR_S, path, result_fname, (char *)NULL);
274   if(g_file_test(fname, G_FILE_TEST_EXISTS) == TRUE)
275   {
276     fprintf(stderr, "[import_session] File %s exists.\n", fname);
277     do
278     {
279       /* file exists, yield a new filename */
280       g_free(result_fname);
281       result_fname = dt_variables_expand(self->vp, pattern, TRUE);
282       fname = g_build_path(G_DIR_SEPARATOR_S, path, result_fname, (char *)NULL);
283 
284       fprintf(stderr, "[import_session] Testing %s.\n", fname);
285       /* check if same filename was yielded as before */
286       if(strcmp(previous_fname, fname) == 0)
287       {
288         g_free(previous_fname);
289         g_free(fname);
290         dt_control_log(_(
291             "couldn't expand to a unique filename for session, please check your import session settings."));
292         return NULL;
293       }
294 
295       g_free(previous_fname);
296       previous_fname = fname;
297 
298     } while(g_file_test(fname, G_FILE_TEST_EXISTS) == TRUE);
299   }
300 
301   g_free(previous_fname);
302   g_free(pattern);
303 
304   self->current_filename = result_fname;
305   fprintf(stderr, "[import_session] Using filename %s.\n", self->current_filename);
306 
307   return self->current_filename;
308 }
309 
_import_session_path(struct dt_import_session_t * self,gboolean current)310 static const char *_import_session_path(struct dt_import_session_t *self, gboolean current)
311 {
312   char *pattern;
313   char *new_path;
314   const gboolean currentok = dt_util_test_writable_dir(self->current_path);
315   fprintf(stderr, " _import_session_path testing `%s' %i", self->current_path, currentok);
316 
317   if(current && self->current_path != NULL)
318   {
319     // the current path might not be a writable directory so test for that
320     if(currentok) return self->current_path;
321     // the current path is not valid so we can't  cleanup
322     self->current_path = NULL;
323     return NULL;
324   }
325   /* check if expanded path differs from current */
326   pattern = _import_session_path_pattern();
327   if(pattern == NULL)
328   {
329     fprintf(stderr, "[import_session] Failed to get session path pattern.\n");
330     return NULL;
331   }
332 
333   new_path = dt_variables_expand(self->vp, pattern, FALSE);
334   g_free(pattern);
335 
336   /* did the session path change ? */
337   if(self->current_path && strcmp(self->current_path, new_path) == 0)
338   {
339     g_free(new_path);
340     if(currentok) return self->current_path;
341   }
342 
343   if(!currentok) self->current_path = NULL;
344   /* we need to initialize a new filmroll for the new path */
345   if(_import_session_initialize_filmroll(self, new_path) != 0)
346   {
347     g_free(new_path);
348     return NULL;
349   }
350   return self->current_path;
351 }
352 
dt_import_session_path(struct dt_import_session_t * self,gboolean current)353 const char *dt_import_session_path(struct dt_import_session_t *self, gboolean current)
354 {
355   const char *path = _import_session_path(self, current);
356   if(path == NULL)
357   {
358     fprintf(stderr, "[import_session] Failed to get session path.\n");
359     dt_control_log(_("requested session path not available. "
360                      "device not mounted?"));
361   }
362   return path;
363 }
364 
365 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
366 // vim: shiftwidth=2 expandtab tabstop=2 cindent
367 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
368