int store(dt_imageio_module_storage_t *self, dt_imageio_module_data_t *sdata, const int 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 upscale, dt_colorspaces_color_profile_type_t icc_type, const gchar *icc_filename, dt_iop_color_intent_t icc_intent) { dt_imageio_email_t *d = (dt_imageio_email_t *)sdata; _email_attachment_t *attachment = (_email_attachment_t *)g_malloc(sizeof(_email_attachment_t)); attachment->imgid = imgid; /* construct a temporary file name */ char tmpdir[PATH_MAX] = { 0 }; dt_loc_get_tmp_dir(tmpdir, sizeof(tmpdir)); char dirname[PATH_MAX] = { 0 }; gboolean from_cache = FALSE; dt_image_full_path(imgid, dirname, sizeof(dirname), &from_cache); gchar *filename = g_path_get_basename(dirname); g_strlcpy(dirname, filename, sizeof(dirname)); dt_image_path_append_version(imgid, dirname, sizeof(dirname)); gchar *end = g_strrstr(dirname, ".") + 1; if(end) *end = '\0'; g_strlcat(dirname, format->extension(fdata), sizeof(dirname)); // set exported filename attachment->file = g_build_filename(tmpdir, dirname, (char *)NULL); if(dt_imageio_export(imgid, attachment->file, format, fdata, high_quality, upscale, FALSE, icc_type, icc_filename, icc_intent, self, sdata, num, total) != 0) { fprintf(stderr, "[imageio_storage_email] could not export to file: `%s'!\n", attachment->file); dt_control_log(_("could not export to file `%s'!"), attachment->file); g_free(attachment); g_free(filename); return 1; } char *trunc = attachment->file + strlen(attachment->file) - 32; if(trunc < attachment->file) trunc = attachment->file; dt_control_log(ngettext("%d/%d exported to `%s%s'", "%d/%d exported to `%s%s'", num), num, total, trunc != filename ? ".." : "", trunc); #ifdef _OPENMP // store can be called in parallel, so synch access to shared memory #pragma omp critical #endif d->images = g_list_append(d->images, attachment); g_free(filename); return 0; }
static int image_tostring(lua_State *L) { const dt_image_t * my_image=checkreadimage(L,-1); char image_name[PATH_MAX]; dt_image_full_path(my_image->id,image_name,PATH_MAX); dt_image_path_append_version(my_image->id,image_name,PATH_MAX); lua_pushstring(L,image_name); releasereadimage(L,my_image); return 1; }
static int image_tostring(lua_State *L) { const dt_image_t * my_image=checkreadimage(L,-1); char image_name[PATH_MAX]; gboolean from_cache = FALSE; dt_image_full_path(my_image->id, image_name, sizeof(image_name), &from_cache); dt_image_path_append_version(my_image->id, image_name, sizeof(image_name)); lua_pushstring(L,image_name); releasereadimage(L,my_image); return 1; }
static int sidecar_member(lua_State *L) { const dt_image_t *my_image = checkreadimage(L, 1); gboolean from_cache = TRUE; char filename[PATH_MAX] = { 0 }; dt_image_full_path(my_image->id, filename, sizeof(filename), &from_cache); dt_image_path_append_version(my_image->id, filename, sizeof(filename)); g_strlcat(filename, ".xmp", sizeof(filename)); lua_pushstring(L, filename); releasereadimage(L, my_image); return 1; }
void dt_image_write_sidecar_file(int imgid) { // TODO: compute hash and don't write if not needed! // write .xmp file if(imgid > 0 && dt_conf_get_bool("write_sidecar_files")) { char filename[DT_MAX_PATH_LEN+8]; dt_image_full_path(imgid, filename, DT_MAX_PATH_LEN); dt_image_path_append_version(imgid, filename, DT_MAX_PATH_LEN); char *c = filename + strlen(filename); sprintf(c, ".xmp"); dt_exif_xmp_write(imgid, filename); } }
int32_t dt_image_move(const int32_t imgid, const int32_t filmid) { //TODO: several places where string truncation could occur unnoticed int32_t result = -1; gchar oldimg[DT_MAX_PATH_LEN] = {0}; gchar newimg[DT_MAX_PATH_LEN] = {0}; dt_image_full_path(imgid, oldimg, DT_MAX_PATH_LEN); gchar *newdir = NULL; sqlite3_stmt *film_stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select folder from film_rolls where id = ?1", -1, &film_stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(film_stmt, 1, filmid); if(sqlite3_step(film_stmt) == SQLITE_ROW) newdir = g_strdup((gchar *) sqlite3_column_text(film_stmt, 0)); sqlite3_finalize(film_stmt); if(newdir) { gchar *imgbname = g_path_get_basename(oldimg); g_snprintf(newimg, DT_MAX_PATH_LEN, "%s%c%s", newdir, G_DIR_SEPARATOR, imgbname); g_free(imgbname); g_free(newdir); // statement for getting ids of the image to be moved and it's duplicates sqlite3_stmt *duplicates_stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images where filename in (select filename from images " "where id = ?1) and film_id in (select film_id from images where id = ?1)", -1, &duplicates_stmt, NULL); // move image // TODO: Use gio's' g_file_move instead of g_rename? if (!g_file_test(newimg, G_FILE_TEST_EXISTS) && (g_rename(oldimg, newimg) == 0)) { // first move xmp files of image and duplicates GList *dup_list = NULL; DT_DEBUG_SQLITE3_BIND_INT(duplicates_stmt, 1, imgid); while (sqlite3_step(duplicates_stmt) == SQLITE_ROW) { int32_t id = sqlite3_column_int(duplicates_stmt, 0); dup_list = g_list_append(dup_list, GINT_TO_POINTER(id)); gchar oldxmp[512], newxmp[512]; g_strlcpy(oldxmp, oldimg, 512); g_strlcpy(newxmp, newimg, 512); dt_image_path_append_version(id, oldxmp, 512); dt_image_path_append_version(id, newxmp, 512); g_strlcat(oldxmp, ".xmp", 512); g_strlcat(newxmp, ".xmp", 512); if (g_file_test(oldxmp, G_FILE_TEST_EXISTS)) (void)g_rename(oldxmp, newxmp); } sqlite3_reset(duplicates_stmt); sqlite3_clear_bindings(duplicates_stmt); // then update database and cache // if update was performed in above loop, dt_image_path_append_version() // would return wrong version! while (dup_list) { long int id = GPOINTER_TO_INT(dup_list->data); const dt_image_t *cimg = dt_image_cache_read_get(darktable.image_cache, id); dt_image_t *img = dt_image_cache_write_get(darktable.image_cache, cimg); img->film_id = filmid; // write through to db, but not to xmp dt_image_cache_write_release(darktable.image_cache, img, DT_IMAGE_CACHE_RELAXED); dt_image_cache_read_release(darktable.image_cache, img); dup_list = g_list_delete_link(dup_list, dup_list); } g_list_free(dup_list); result = 0; } } return result; }
uint32_t dt_image_import(const int32_t film_id, const char *filename, gboolean override_ignore_jpegs) { if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) return 0; const char *cc = filename + strlen(filename); for(; *cc!='.'&&cc>filename; cc--); if(!strcmp(cc, ".dt")) return 0; if(!strcmp(cc, ".dttags")) return 0; if(!strcmp(cc, ".xmp")) return 0; char *ext = g_ascii_strdown(cc+1, -1); if(override_ignore_jpegs == FALSE && (!strcmp(ext, "jpg") || !strcmp(ext, "jpeg")) && dt_conf_get_bool("ui_last/import_ignore_jpegs")) return 0; int supported = 0; char **extensions = g_strsplit(dt_supported_extensions, ",", 100); for(char **i=extensions; *i!=NULL; i++) if(!strcmp(ext, *i)) { supported = 1; break; } g_strfreev(extensions); if(!supported) { g_free(ext); return 0; } int rc; uint32_t id = 0; // select from images; if found => return gchar *imgfname; imgfname = g_path_get_basename((const gchar*)filename); sqlite3_stmt *stmt; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images where film_id = ?1 and filename = ?2", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, film_id); DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, imgfname, strlen(imgfname), SQLITE_STATIC); if(sqlite3_step(stmt) == SQLITE_ROW) { id = sqlite3_column_int(stmt, 0); g_free(imgfname); sqlite3_finalize(stmt); g_free(ext); return id; } sqlite3_finalize(stmt); // also need to set the no-legacy bit, to make sure we get the right presets (new ones) uint32_t flags = dt_conf_get_int("ui_last/import_initial_rating"); if(flags > 5) { flags = 1; dt_conf_set_int("ui_last/import_initial_rating", 1); } flags |= DT_IMAGE_NO_LEGACY_PRESETS; // insert dummy image entry in database DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "insert into images (id, film_id, filename, caption, description, " "license, sha1sum, flags) values (null, ?1, ?2, '', '', '', '', ?3)", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, film_id); DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, imgfname, strlen(imgfname), SQLITE_TRANSIENT); DT_DEBUG_SQLITE3_BIND_INT(stmt, 3, flags); rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) fprintf(stderr, "sqlite3 error %d\n", rc); sqlite3_finalize(stmt); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select id from images where film_id = ?1 and filename = ?2", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, film_id); DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, imgfname, strlen(imgfname), SQLITE_STATIC); if(sqlite3_step(stmt) == SQLITE_ROW) id = sqlite3_column_int(stmt, 0); sqlite3_finalize(stmt); // Try to find out if this should be grouped already. gchar *basename = g_strdup(imgfname); gchar *cc2 = basename + strlen(basename); for(; *cc2!='.'&&cc2>basename; cc2--); *cc2='\0'; gchar *sql_pattern = g_strconcat(basename, ".%", NULL); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select group_id from images where film_id = ?1 and filename like ?2 and id != ?3", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, film_id); DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 2, sql_pattern, -1, SQLITE_TRANSIENT); DT_DEBUG_SQLITE3_BIND_INT(stmt, 3, id); int group_id; if(sqlite3_step(stmt) == SQLITE_ROW) group_id = sqlite3_column_int(stmt, 0); else group_id = id; sqlite3_finalize(stmt); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "update images set group_id = ?1 where id = ?2", -1, &stmt, NULL); DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, group_id); DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, id); sqlite3_step(stmt); sqlite3_finalize(stmt); // printf("[image_import] importing `%s' to img id %d\n", imgfname, id); // lock as shortly as possible: const dt_image_t *cimg = dt_image_cache_read_get(darktable.image_cache, id); dt_image_t *img = dt_image_cache_write_get(darktable.image_cache, cimg); img->group_id = group_id; // read dttags and exif for database queries! (void) dt_exif_read(img, filename); char dtfilename[DT_MAX_PATH_LEN]; g_strlcpy(dtfilename, filename, DT_MAX_PATH_LEN); dt_image_path_append_version(id, dtfilename, DT_MAX_PATH_LEN); char *c = dtfilename + strlen(dtfilename); sprintf(c, ".xmp"); (void)dt_exif_xmp_read(img, dtfilename, 0); // write through to db, but not to xmp. dt_image_cache_write_release(darktable.image_cache, img, DT_IMAGE_CACHE_RELAXED); dt_image_cache_read_release(darktable.image_cache, img); // add a tag with the file extension guint tagid = 0; char tagname[512]; snprintf(tagname, 512, "darktable|format|%s", ext); g_free(ext); dt_tag_new(tagname, &tagid); dt_tag_attach(tagid,id); // Search for sidecar files and import them if found. glob_t *globbuf = g_malloc(sizeof(glob_t)); // Add version wildcard gchar *fname = g_strdup(filename); gchar pattern[DT_MAX_PATH_LEN]; g_snprintf(pattern, DT_MAX_PATH_LEN, "%s", filename); char *c1 = pattern + strlen(pattern); while(*c1 != '.' && c1 > pattern) c1--; snprintf(c1, pattern + DT_MAX_PATH_LEN - c1, "_*"); char *c2 = fname + strlen(fname); while(*c2 != '.' && c2 > fname) c2--; snprintf(c1+2, pattern + DT_MAX_PATH_LEN - c1 - 2, "%s.xmp", c2); if (!glob(pattern, 0, NULL, globbuf)) { for (int i=0; i < globbuf->gl_pathc; i++) { int newid = -1; newid = dt_image_duplicate(id); const dt_image_t *cimg = dt_image_cache_read_get(darktable.image_cache, newid); dt_image_t *img = dt_image_cache_write_get(darktable.image_cache, cimg); (void)dt_exif_xmp_read(img, globbuf->gl_pathv[i], 0); dt_image_cache_write_release(darktable.image_cache, img, DT_IMAGE_CACHE_RELAXED); dt_image_cache_read_release(darktable.image_cache, img); } globfree(globbuf); } g_free(imgfname); g_free(fname); g_free(basename); g_free(sql_pattern); g_free(globbuf); dt_control_signal_raise(darktable.signals,DT_SIGNAL_IMAGE_IMPORT,id); return id; }
int32_t dt_control_delete_images_job_run(dt_job_t *job) { long int imgid = -1; dt_control_image_enumerator_t *t1 = (dt_control_image_enumerator_t *)job->param; GList *t = t1->index; int total = g_list_length(t); char message[512]= {0}; double fraction=0; snprintf(message, 512, ngettext ("deleting %d image", "deleting %d images", total), total ); const guint *jid = dt_control_backgroundjobs_create(darktable.control, 0, message); sqlite3_stmt *stmt; char query[1024]; sprintf(query, "update images set flags = (flags | %d) where id in (select imgid from selected_images)",DT_IMAGE_REMOVE); DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), query, NULL, NULL, NULL); dt_collection_update(darktable.collection); // We need a list of files to regenerate .xmp files if there are duplicates GList *list = NULL; DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select distinct folder || '/' || filename from images, film_rolls where images.film_id = film_rolls.id and images.id in (select imgid from selected_images)", -1, &stmt, NULL); if(sqlite3_step(stmt) == SQLITE_ROW) { list = g_list_append(list, g_strdup((const gchar *)sqlite3_column_text(stmt, 0))); } sqlite3_finalize(stmt); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select count(id) from images where filename in (select filename from images where id = ?1) and film_id in (select film_id from images where id = ?1)", -1, &stmt, NULL); while(t) { imgid = (long int)t->data; char filename[DT_MAX_PATH_LEN]; dt_image_full_path(imgid, filename, DT_MAX_PATH_LEN); int duplicates = 0; DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); if(sqlite3_step(stmt) == SQLITE_ROW) duplicates = sqlite3_column_int(stmt, 0); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); // remove from disk: if(duplicates == 1) // don't remove the actual data if there are (other) duplicates using it (void)g_unlink(filename); dt_image_path_append_version(imgid, filename, DT_MAX_PATH_LEN); char *c = filename + strlen(filename); sprintf(c, ".xmp"); (void)g_unlink(filename); dt_image_remove(imgid); t = g_list_delete_link(t, t); fraction=1.0/total; dt_control_backgroundjobs_progress(darktable.control, jid, fraction); } sqlite3_finalize(stmt); char *imgname; while(list) { imgname = (char *)list->data; dt_image_synch_all_xmp(imgname); list = g_list_delete_link(list, list); } g_list_free(list); dt_control_backgroundjobs_destroy(darktable.control, jid); dt_film_remove_empty(); dt_control_queue_redraw_center(); return 0; }
static int store_wrapper(struct dt_imageio_module_storage_t *self, struct dt_imageio_module_data_t *self_data, const int 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 upscale) { /* construct a temporary file name */ char tmpdir[PATH_MAX] = { 0 }; gboolean from_cache = FALSE; dt_loc_get_tmp_dir(tmpdir, sizeof(tmpdir)); char dirname[PATH_MAX] = { 0 }; dt_image_full_path(imgid, dirname, sizeof(dirname), &from_cache); dt_image_path_append_version(imgid, dirname, sizeof(dirname)); gchar *filename = g_path_get_basename(dirname); gchar *end = g_strrstr(filename, ".") + 1; g_strlcpy(end, format->extension(fdata), sizeof(dirname) - (end - dirname)); gchar *complete_name = g_build_filename(tmpdir, filename, (char *)NULL); if(dt_imageio_export(imgid, complete_name, format, fdata, high_quality, upscale, FALSE, self, self_data, num, total) != 0) { fprintf(stderr, "[%s] could not export to file: `%s'!\n", self->name(self), complete_name); g_free(complete_name); g_free(filename); return 1; } lua_storage_t *d = (lua_storage_t *)self_data; dt_lua_lock(); lua_State *L = darktable.lua_state.state; push_lua_data(L,d); dt_lua_goto_subtable(L,"files"); luaA_push(L, dt_lua_image_t, &(imgid)); lua_pushstring(L, complete_name); lua_settable(L, -3); lua_pop(L,1); lua_getfield(L, LUA_REGISTRYINDEX, "dt_lua_storages"); lua_getfield(L, -1, self->plugin_name); lua_getfield(L, -1, "store"); if(lua_isnil(L, -1)) { lua_pop(L, 3); dt_lua_unlock(); g_free(filename); return 0; } luaA_push_type(L, self->parameter_lua_type, self_data); luaA_push(L, dt_lua_image_t, &imgid); luaA_push_type(L, format->parameter_lua_type, fdata); lua_pushstring(L, complete_name); lua_pushnumber(L, num); lua_pushnumber(L, total); lua_pushboolean(L, high_quality); push_lua_data(L,d); dt_lua_goto_subtable(L,"extra"); dt_lua_treated_pcall(L,8,0); lua_pop(L, 2); dt_lua_unlock(); g_free(filename); return false; }
static int store_wrapper(struct dt_imageio_module_storage_t *self,struct dt_imageio_module_data_t *self_data, const int imgid, dt_imageio_module_format_t *format, dt_imageio_module_data_t *fdata, const int num, const int total, const gboolean high_quality) { /* construct a temporary file name */ char tmpdir[PATH_MAX]= {0}; gboolean from_cache = FALSE; dt_loc_get_tmp_dir (tmpdir, sizeof(tmpdir)); char dirname[PATH_MAX]; dt_image_full_path(imgid, dirname, sizeof(dirname), &from_cache); dt_image_path_append_version(imgid, dirname, sizeof(dirname)); gchar * filename = g_path_get_basename( dirname ); gchar * end = g_strrstr( filename,".")+1; g_strlcpy( end, format->extension(fdata), sizeof(dirname)-(end-dirname)); gchar* complete_name = g_build_filename( tmpdir, filename, (char *)NULL ); if(dt_imageio_export(imgid, complete_name, format, fdata, high_quality,FALSE,self,self_data) != 0) { fprintf(stderr, "[%s] could not export to file: `%s'!\n", self->name(self),complete_name); g_free(complete_name); g_free(filename); return 1; } lua_storage_t *d = (lua_storage_t*) self_data; d->imgids = g_list_prepend(d->imgids,(void*)(intptr_t)imgid); d->file_names = g_list_prepend(d->file_names,complete_name); gboolean has_lock = dt_lua_lock(); if(!d->data_created) { lua_pushlightuserdata(darktable.lua_state.state,d); lua_newtable(darktable.lua_state.state); lua_settable(darktable.lua_state.state,LUA_REGISTRYINDEX); d->data_created = true; } lua_State *L =darktable.lua_state.state; lua_getfield(L,LUA_REGISTRYINDEX,"dt_lua_storages"); lua_getfield(L,-1,self->plugin_name); lua_getfield(L,-1,"store"); if(lua_isnil(L,-1)) { lua_pop(L,3); dt_lua_unlock(has_lock); g_free(filename); return 1; } luaA_push_typeid(L,self->parameter_lua_type,self_data); luaA_push(L,dt_lua_image_t,&imgid); luaA_push_typeid(L,format->parameter_lua_type,fdata); lua_pushstring(L,complete_name); lua_pushnumber(L,num); lua_pushnumber(L,total); lua_pushboolean(L,high_quality); lua_pushlightuserdata(L,self_data); lua_gettable(L,LUA_REGISTRYINDEX); dt_lua_do_chunk_silent(L,8,1); int result = lua_toboolean(L,-1); lua_pop(L,3); dt_lua_unlock(has_lock); g_free(filename); return result; }
int32_t dt_control_delete_images_job_run(dt_job_t *job) { int imgid = -1; dt_control_image_enumerator_t *t1 = (dt_control_image_enumerator_t *)job->param; GList *t = t1->index; char *imgs = _get_image_list(t); int total = g_list_length(t); char message[512]= {0}; double fraction=0; snprintf(message, 512, ngettext ("deleting %d image", "deleting %d images", total), total ); const guint *jid = dt_control_backgroundjobs_create(darktable.control, 0, message); sqlite3_stmt *stmt; _set_remove_flag(imgs); dt_collection_update(darktable.collection); // We need a list of files to regenerate .xmp files if there are duplicates GList *list = _get_full_pathname(imgs); free(imgs); DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select count(id) from images where filename in (select filename from images where id = ?1) and film_id in (select film_id from images where id = ?1)", -1, &stmt, NULL); while(t) { imgid = GPOINTER_TO_INT(t->data); char filename[DT_MAX_PATH_LEN]; gboolean from_cache = FALSE; dt_image_full_path(imgid, filename, DT_MAX_PATH_LEN, &from_cache); int duplicates = 0; DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); if(sqlite3_step(stmt) == SQLITE_ROW) duplicates = sqlite3_column_int(stmt, 0); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); // remove from disk: if(duplicates == 1) { // there are no further duplicates so we can remove the source data file (void)g_unlink(filename); dt_image_remove(imgid); // all sidecar files - including left-overs - can be deleted; // left-overs can result when previously duplicates have been REMOVED; // no need to keep them as the source data file is gone. const int len = DT_MAX_PATH_LEN + 30; gchar pattern[len]; // NULL terminated list of glob patterns; should include "" and can be extended if needed static const gchar *glob_patterns[] = { "", "_[0-9][0-9]", "_[0-9][0-9][0-9]", "_[0-9][0-9][0-9][0-9]", NULL }; const gchar **glob_pattern = glob_patterns; GList *files = NULL; while(*glob_pattern) { snprintf(pattern, len, "%s", filename); gchar *c1 = pattern + strlen(pattern); while(*c1 != '.' && c1 > pattern) c1--; snprintf(c1, pattern + len - c1, "%s", *glob_pattern); const gchar *c2 = filename + strlen(filename); while(*c2 != '.' && c2 > filename) c2--; snprintf(c1+strlen(*glob_pattern), pattern + len - c1 - strlen(*glob_pattern), "%s.xmp", c2); #ifdef __WIN32__ WIN32_FIND_DATA data; HANDLE handle = FindFirstFile(pattern, &data); if(handle != INVALID_HANDLE_VALUE) { do files = g_list_append(files, g_strdup(data.cFileName)); while(FindNextFile(handle, &data)); } #else glob_t globbuf; if(!glob(pattern, 0, NULL, &globbuf)) { for(size_t i=0; i < globbuf.gl_pathc; i++) files = g_list_append(files, g_strdup(globbuf.gl_pathv[i])); globfree(&globbuf); } #endif glob_pattern++; } GList *file_iter = g_list_first(files); while(file_iter != NULL) { (void)g_unlink(file_iter->data); file_iter = g_list_next(file_iter); } g_list_free_full(files, g_free); } else { // don't remove the actual source data if there are further duplicates using it; // just delete the xmp file of the duplicate selected. dt_image_path_append_version(imgid, filename, DT_MAX_PATH_LEN); char *c = filename + strlen(filename); sprintf(c, ".xmp"); dt_image_remove(imgid); (void)g_unlink(filename); } t = g_list_delete_link(t, t); fraction=1.0/total; dt_control_backgroundjobs_progress(darktable.control, jid, fraction); } sqlite3_finalize(stmt); char *imgname; while(list) { imgname = (char *)list->data; dt_image_synch_all_xmp(imgname); list = g_list_delete_link(list, list); } g_list_free(list); dt_control_backgroundjobs_destroy(darktable.control, jid); dt_film_remove_empty(); dt_control_signal_raise(darktable.signals, DT_SIGNAL_FILMROLLS_CHANGED); dt_control_queue_redraw_center(); return 0; }