From 6e16800c2be351024c417db34fb67ebc8799e173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Tue, 5 Oct 2021 20:46:05 +0200 Subject: [PATCH 01/12] crawler: enable multiselection --- src/control/crawler.c | 54 +++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 72536f15fec0..def53bb9c46e 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -200,6 +200,7 @@ GList *dt_control_crawler_run() typedef struct dt_control_crawler_gui_t { + GtkTreeView *tree; GtkTreeModel *model; GtkWidget *select_all; gulong select_all_handler_id; @@ -222,19 +223,36 @@ static void _clear_select_all(dt_control_crawler_gui_t *gui) g_signal_handler_unblock(G_OBJECT(gui->select_all), gui->select_all_handler_id); } -// set the "selected" flag in the list model when an image gets (un)selected -static void _select_toggled_callback(GtkCellRendererToggle *cell_renderer, gchar *path_str, gpointer user_data) +static void _reset_selection(GtkTreeModel *model) { - dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + // Reset at the TreeViewToogleButton level + // This is because the crawler doesn't use the default TreeView selection methods + // but implements its own partial ones. + // To get the multiselection, we had to enable default TreeView selection methods too, + // but since all the internal circuitery uses the custom ToggleButton boolean, + // we need to maintain both pathes and sync them. + // This function is equivalent to gtk_tree_selection_unselect_all() but for the custom ToggleButton path GtkTreeIter iter; - GtkTreePath *path = gtk_tree_path_new_from_string(path_str); - gboolean selected; + gboolean valid = gtk_tree_model_get_iter_first(model, &iter); + while(valid) + { + gtk_list_store_set(GTK_LIST_STORE(model), &iter, DT_CONTROL_CRAWLER_COL_SELECTED, FALSE, -1); + valid = gtk_tree_model_iter_next(model, &iter); + } +} - gtk_tree_model_get_iter(gui->model, &iter, path); - gtk_tree_model_get(gui->model, &iter, DT_CONTROL_CRAWLER_COL_SELECTED, &selected, -1); - gtk_list_store_set(GTK_LIST_STORE(gui->model), &iter, DT_CONTROL_CRAWLER_COL_SELECTED, !selected, -1); +static void _commit_line_from_selection(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) +{ + gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_CONTROL_CRAWLER_COL_SELECTED, TRUE, -1); +} - gtk_tree_path_free(path); +static void _selection_changed(GtkTreeSelection *selection, dt_control_crawler_gui_t *gui) +{ + // Deselect everything + _reset_selection(gui->model); + + // Commit selection + gtk_tree_selection_selected_foreach(selection, _commit_line_from_selection, gui); // we also want to disable the "select all" thing _clear_select_all(gui); @@ -244,9 +262,9 @@ static void _select_toggled_callback(GtkCellRendererToggle *cell_renderer, gchar static void _select_all_callback(GtkToggleButton *togglebutton, gpointer user_data) { dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + const gboolean selected = gtk_toggle_button_get_active(togglebutton); - gboolean selected = gtk_toggle_button_get_active(togglebutton); - + // Select at the TreeViewToggleButton level GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(gui->model, &iter); while(valid) @@ -254,6 +272,14 @@ static void _select_all_callback(GtkToggleButton *togglebutton, gpointer user_da gtk_list_store_set(GTK_LIST_STORE(gui->model), &iter, DT_CONTROL_CRAWLER_COL_SELECTED, selected, -1); valid = gtk_tree_model_iter_next(gui->model, &iter); } + + // Select at the TreeView level + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); + + if(selected) + gtk_tree_selection_select_all(selection); + else + gtk_tree_selection_unselect_all(selection); } // reload xmp files of the selected images @@ -373,9 +399,13 @@ void dt_control_crawler_show_image_list(GList *images) g_list_free_full(images, g_free); GtkWidget *tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(_selection_changed), gui); + + gui->tree = GTK_TREE_VIEW(tree); // FIXME: do we need to free that later ? GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new(); - g_signal_connect(renderer, "toggled", G_CALLBACK(_select_toggled_callback), gui); column = gtk_tree_view_column_new_with_attributes(_("select"), renderer, "active", DT_CONTROL_CRAWLER_COL_SELECTED, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); From 40230e1c6b8bc894a45787f3906946e3524e0795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Tue, 5 Oct 2021 23:00:33 +0200 Subject: [PATCH 02/12] crawler: ellipsize pathes for readability --- src/control/crawler.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index def53bb9c46e..5f130295f7b1 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -410,9 +410,14 @@ void dt_control_crawler_show_image_list(GList *images) DT_CONTROL_CRAWLER_COL_SELECTED, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); - column = gtk_tree_view_column_new_with_attributes(_("path"), gtk_cell_renderer_text_new(), "text", + GtkCellRenderer *renderer_text = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("path"), renderer_text, "text", DT_CONTROL_CRAWLER_COL_IMAGE_PATH, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); + gtk_tree_view_column_set_expand(column, TRUE); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_min_width(column, DT_PIXEL_APPLY_DPI(200)); + g_object_set(renderer_text, "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL); column = gtk_tree_view_column_new_with_attributes(_("xmp timestamp"), gtk_cell_renderer_text_new(), "text", DT_CONTROL_CRAWLER_COL_TS_XMP, NULL); From 6fcfe4bf7fdf5bab5ffd7ac0c15c21d91513ff00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Fri, 8 Oct 2021 18:54:31 +0200 Subject: [PATCH 03/12] =?UTF-8?q?crawler.c=20:=C2=A0get=20rid=20of=20the?= =?UTF-8?q?=20togglebutton=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use native Gtk TreeView all along add options to select all, none or invert selection add options to keep newer or older versions rename previous versions more legibly --- src/control/crawler.c | 374 ++++++++++++++++++++++++++---------------- 1 file changed, 236 insertions(+), 138 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 5f130295f7b1..0f3f4394ca68 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -23,11 +23,12 @@ #include #include "common/darktable.h" -#include "common/debug.h" #include "common/database.h" +#include "common/debug.h" #include "common/history.h" #include "common/image.h" #include "control/conf.h" +#include "control/control.h" #include "crawler.h" #include "gui/gtk.h" #ifdef GDK_WINDOWING_QUARTZ @@ -37,13 +38,13 @@ typedef enum dt_control_crawler_cols_t { - DT_CONTROL_CRAWLER_COL_SELECTED = 0, - DT_CONTROL_CRAWLER_COL_ID, + DT_CONTROL_CRAWLER_COL_ID = 0, DT_CONTROL_CRAWLER_COL_IMAGE_PATH, DT_CONTROL_CRAWLER_COL_XMP_PATH, DT_CONTROL_CRAWLER_COL_TS_XMP, DT_CONTROL_CRAWLER_COL_TS_DB, - DT_CONTROL_CRAWLER_COL_TS, // new timestamp to db + DT_CONTROL_CRAWLER_COL_TS_XMP_INT, // new timestamp to db + DT_CONTROL_CRAWLER_COL_TS_DB_INT, DT_CONTROL_CRAWLER_NUM_COLS } dt_control_crawler_cols_t; @@ -192,7 +193,7 @@ GList *dt_control_crawler_run() sqlite3_finalize(stmt); sqlite3_finalize(inner_stmt); - return g_list_reverse(result); // list was built in reverse order, so un-reverse it + return g_list_reverse(result); // list was built in reverse order, so un-reverse it } @@ -202,8 +203,6 @@ typedef struct dt_control_crawler_gui_t { GtkTreeView *tree; GtkTreeModel *model; - GtkWidget *select_all; - gulong select_all_handler_id; } dt_control_crawler_gui_t; // close the window and clean up @@ -215,141 +214,240 @@ static void dt_control_crawler_response_callback(GtkWidget *dialog, gint respons free(gui); } -// unselect the "select all" toggle -static void _clear_select_all(dt_control_crawler_gui_t *gui) -{ - g_signal_handler_block(G_OBJECT(gui->select_all), gui->select_all_handler_id); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui->select_all), FALSE); - g_signal_handler_unblock(G_OBJECT(gui->select_all), gui->select_all_handler_id); -} -static void _reset_selection(GtkTreeModel *model) +static void _delete_selected_rows(GList *rr_list, GtkTreeModel *model) { - // Reset at the TreeViewToogleButton level - // This is because the crawler doesn't use the default TreeView selection methods - // but implements its own partial ones. - // To get the multiselection, we had to enable default TreeView selection methods too, - // but since all the internal circuitery uses the custom ToggleButton boolean, - // we need to maintain both pathes and sync them. - // This function is equivalent to gtk_tree_selection_unselect_all() but for the custom ToggleButton path - GtkTreeIter iter; - gboolean valid = gtk_tree_model_get_iter_first(model, &iter); - while(valid) + // Remove TreeView rows from rr_list. It needs to be populated before + GList *node; + for(node = rr_list; node != NULL; node = g_list_next(node)) { - gtk_list_store_set(GTK_LIST_STORE(model), &iter, DT_CONTROL_CRAWLER_COL_SELECTED, FALSE, -1); - valid = gtk_tree_model_iter_next(model, &iter); + GtkTreePath *path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data); + + if(path) + { + GtkTreeIter iter; + if (gtk_tree_model_get_iter(model, &iter, path)) + gtk_list_store_remove(GTK_LIST_STORE(model), &iter); + } } } -static void _commit_line_from_selection(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) -{ - gtk_list_store_set(GTK_LIST_STORE(model), iter, DT_CONTROL_CRAWLER_COL_SELECTED, TRUE, -1); -} -static void _selection_changed(GtkTreeSelection *selection, dt_control_crawler_gui_t *gui) +static void _select_all_callback(GtkButton *button, gpointer user_data) { - // Deselect everything - _reset_selection(gui->model); + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); + gtk_tree_selection_select_all(selection); +} - // Commit selection - gtk_tree_selection_selected_foreach(selection, _commit_line_from_selection, gui); - // we also want to disable the "select all" thing - _clear_select_all(gui); +static void _select_none_callback(GtkButton *button, gpointer user_data) +{ + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); + gtk_tree_selection_unselect_all(selection); } -// (un)select all images in the list -static void _select_all_callback(GtkToggleButton *togglebutton, gpointer user_data) + +static void _select_invert_callback(GtkButton *button, gpointer user_data) { dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; - const gboolean selected = gtk_toggle_button_get_active(togglebutton); + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); - // Select at the TreeViewToggleButton level GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(gui->model, &iter); while(valid) { - gtk_list_store_set(GTK_LIST_STORE(gui->model), &iter, DT_CONTROL_CRAWLER_COL_SELECTED, selected, -1); + if(gtk_tree_selection_iter_is_selected(selection, &iter)) + gtk_tree_selection_unselect_iter(selection, &iter); + else + gtk_tree_selection_select_iter(selection, &iter); + valid = gtk_tree_model_iter_next(gui->model, &iter); } +} - // Select at the TreeView level - GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); - if(selected) - gtk_tree_selection_select_all(selection); +static void _db_update_timestamp(const int id, const time_t timestamp) +{ + // Update DB writing timestamp with XMP file timestamp + sqlite3_stmt *stmt; + DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), + "UPDATE main.images SET write_timestamp = ?2 WHERE id = ?1", -1, &stmt, NULL); + DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id); + DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, timestamp); + sqlite3_step(stmt); + sqlite3_finalize(stmt); +} + + +static void _get_crawler_entry_from_model(GtkTreeModel *model, GtkTreeIter *iter, + dt_control_crawler_result_t *entry) +{ + gtk_tree_model_get(model, iter, DT_CONTROL_CRAWLER_COL_IMAGE_PATH, &entry->image_path, DT_CONTROL_CRAWLER_COL_ID, + &entry->id, DT_CONTROL_CRAWLER_COL_XMP_PATH, &entry->xmp_path, + DT_CONTROL_CRAWLER_COL_TS_DB_INT, &entry->timestamp_db, DT_CONTROL_CRAWLER_COL_TS_XMP_INT, + &entry->timestamp_xmp, -1); +} + + +static void _append_row_to_remove(GtkTreeModel *model, GtkTreePath *path, gpointer user_data) +{ + // append TreeModel rows to the list to remove + GList **rowref_list = (GList **)user_data; + GtkTreeRowReference *rowref = gtk_tree_row_reference_new(model, path); + *rowref_list = g_list_append(*rowref_list, rowref); +} + + +static void _cleanup_GList(GList *list) +{ + g_list_foreach(list, (GFunc) gtk_tree_row_reference_free, NULL); + g_list_free(list); +} + + +static void sync_xmp_to_db(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) +{ + dt_control_crawler_result_t entry = { 0 }; + _get_crawler_entry_from_model(model, iter, &entry); + _db_update_timestamp(entry.id, entry.timestamp_xmp); + const int success = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); // success = 0, fail = 1 + + if(!success) _append_row_to_remove(model, path, user_data); + fprintf(stdout, "%s synced XMP -> DB\n", entry.image_path); + + int *return_code = (int *)user_data; + *return_code |= success; + + g_free(entry.xmp_path); + g_free(entry.image_path); +} + + +static void sync_db_to_xmp(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) +{ + dt_control_crawler_result_t entry = { 0 }; + _get_crawler_entry_from_model(model, iter, &entry); + dt_image_write_sidecar_file(entry.id); + + fprintf(stdout, "%s synced DB -> XMP\n", entry.image_path); + _append_row_to_remove(model, path, user_data); + + g_free(entry.xmp_path); + g_free(entry.image_path); + + // no return code, dt_image_write_sidecar_file always succeeds… ¯\_(ツ)_/¯ + // FIXME ? +} + +static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) +{ + dt_control_crawler_result_t entry = { 0 }; + _get_crawler_entry_from_model(model, iter, &entry); + + if(entry.timestamp_xmp > entry.timestamp_db) + { + // WRITE XMP in DB + _db_update_timestamp(entry.id, entry.timestamp_xmp); + dt_history_load_and_apply(entry.id, entry.xmp_path, 0); + fprintf(stdout, "%s synced XMP (new) -> DB (old)\n", entry.image_path); + // don't track the return code since the other path doesn't anyway + } + else if(entry.timestamp_xmp < entry.timestamp_db) + { + // WRITE DB in XMP + dt_image_write_sidecar_file(entry.id); + fprintf(stdout, "%s synced DB (new) -> XMP (old)\n", entry.image_path); + } else - gtk_tree_selection_unselect_all(selection); + { + // we should never reach that part of the code + // if both timestamps are equal, they should not be in this list in the first place + fprintf(stderr, "editing timestamps may be corrupted\n"); + } + + _append_row_to_remove(model, path, user_data); + + g_free(entry.xmp_path); + g_free(entry.image_path); } -// reload xmp files of the selected images -static void _reload_button_clicked(GtkButton *button, gpointer user_data) + +static void sync_oldest_to_newest(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { - dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + dt_control_crawler_result_t entry = { 0 }; + _get_crawler_entry_from_model(model, iter, &entry); - GtkTreeIter iter; - gboolean valid = gtk_tree_model_get_iter_first(gui->model, &iter); - while(valid) + if(entry.timestamp_xmp < entry.timestamp_db) { - gboolean selected; - int id; - gchar *xmp_path = NULL; - time_t timestamp; - gtk_tree_model_get(gui->model, &iter, - DT_CONTROL_CRAWLER_COL_SELECTED, &selected, - DT_CONTROL_CRAWLER_COL_ID, &id, - DT_CONTROL_CRAWLER_COL_XMP_PATH, &xmp_path, - DT_CONTROL_CRAWLER_COL_TS, ×tamp, - -1); - if(selected) - { - // align db write timestamp on xmp file timestamp - sqlite3_stmt *stmt; - DT_DEBUG_SQLITE3_PREPARE_V2 - (dt_database_get(darktable.db), - "UPDATE main.images SET write_timestamp = ?2 WHERE id = ?1", - -1, &stmt, NULL); - DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, id); - DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, timestamp); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - - dt_history_load_and_apply(id, xmp_path, 0); - valid = gtk_list_store_remove(GTK_LIST_STORE(gui->model), &iter); - } - else - valid = gtk_tree_model_iter_next(gui->model, &iter); - g_free(xmp_path); + // WRITE XMP in DB + _db_update_timestamp(entry.id, entry.timestamp_xmp); + dt_history_load_and_apply(entry.id, entry.xmp_path, 0); + fprintf(stdout, "%s synced XMP (old) -> DB (new)\n", entry.image_path); + // don't track the return code since the other path doesn't anyway } - // we also want to disable the "select all" thing - _clear_select_all(gui); + else if(entry.timestamp_xmp > entry.timestamp_db) + { + // WRITE DB in XMP + dt_image_write_sidecar_file(entry.id); + fprintf(stdout, "%s synced DB (old) -> XMP (new)\n", entry.image_path); + } + else + { + // we should never reach that part of the code + // if both timestamps are equal, they should not be in this list in the first place + fprintf(stderr, "editing timestamps may be corrupted\n"); + } + + _append_row_to_remove(model, path, user_data); + + g_free(entry.xmp_path); + g_free(entry.image_path); } -// overwrite xmp files of the selected images +// overwrite database with xmp +static void _reload_button_clicked(GtkButton *button, gpointer user_data) +{ + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); + GList *rows_to_remove = NULL; + gtk_tree_selection_selected_foreach(selection, sync_xmp_to_db, &rows_to_remove); + _delete_selected_rows(rows_to_remove, gui->model); + _cleanup_GList(rows_to_remove); +} + +// overwrite xmp with database void _overwrite_button_clicked(GtkButton *button, gpointer user_data) { dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); + GList *rows_to_remove = NULL; + gtk_tree_selection_selected_foreach(selection, sync_db_to_xmp, &rows_to_remove); + _delete_selected_rows(rows_to_remove, gui->model); + _cleanup_GList(rows_to_remove); +} - GtkTreeIter iter; - gboolean valid = gtk_tree_model_get_iter_first(gui->model, &iter); - while(valid) - { - gboolean selected; - int id; - gtk_tree_model_get(gui->model, &iter, - DT_CONTROL_CRAWLER_COL_SELECTED, &selected, - DT_CONTROL_CRAWLER_COL_ID, &id, - -1); - if(selected) - { - dt_image_write_sidecar_file(id); - valid = gtk_list_store_remove(GTK_LIST_STORE(gui->model), &iter); - } - else - valid = gtk_tree_model_iter_next(gui->model, &iter); - } - // we also want to disable the "select all" thing - _clear_select_all(gui); +// overwrite the oldest with the newest +static void _newest_button_clicked(GtkButton *button, gpointer user_data) +{ + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); + GList *rows_to_remove = NULL; + gtk_tree_selection_selected_foreach(selection, sync_newest_to_oldest, &rows_to_remove); + _delete_selected_rows(rows_to_remove, gui->model); + _cleanup_GList(rows_to_remove); +} + +// overwrite the newest with the oldest +static void _oldest_button_clicked(GtkButton *button, gpointer user_data) +{ + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; + GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); + GList *rows_to_remove = NULL; + gtk_tree_selection_selected_foreach(selection, sync_oldest_to_newest, &rows_to_remove); + _delete_selected_rows(rows_to_remove, gui->model); + _cleanup_GList(rows_to_remove); } // show a popup window with a list of updated images/xmp files and allow the user to tell dt what to do about them @@ -364,14 +462,13 @@ void dt_control_crawler_show_image_list(GList *images) GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); gtk_widget_set_vexpand(scroll, TRUE); GtkListStore *store = gtk_list_store_new(DT_CONTROL_CRAWLER_NUM_COLS, - G_TYPE_BOOLEAN, // selection toggle - G_TYPE_INT, // id - G_TYPE_STRING, // image path - G_TYPE_STRING, // xmp path - G_TYPE_STRING, // timestamp from xmp - G_TYPE_STRING, // timestamp from db - G_TYPE_INT // timestamp to db - ); + G_TYPE_INT, // id + G_TYPE_STRING, // image path + G_TYPE_STRING, // xmp path + G_TYPE_STRING, // timestamp from xmp + G_TYPE_STRING, // timestamp from db + G_TYPE_INT, // timestamp to db + G_TYPE_INT); gui->model = GTK_TREE_MODEL(store); @@ -384,15 +481,11 @@ void dt_control_crawler_show_image_list(GList *images) strftime(timestamp_db, sizeof(timestamp_db), "%c", localtime_r(&item->timestamp_db, &tm_stamp)); strftime(timestamp_xmp, sizeof(timestamp_xmp), "%c", localtime_r(&item->timestamp_xmp, &tm_stamp)); gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, - DT_CONTROL_CRAWLER_COL_SELECTED, 0, - DT_CONTROL_CRAWLER_COL_ID, item->id, - DT_CONTROL_CRAWLER_COL_IMAGE_PATH, item->image_path, - DT_CONTROL_CRAWLER_COL_XMP_PATH, item->xmp_path, - DT_CONTROL_CRAWLER_COL_TS_XMP, timestamp_xmp, - DT_CONTROL_CRAWLER_COL_TS_DB, timestamp_db, - DT_CONTROL_CRAWLER_COL_TS, item->timestamp_xmp, - -1); + gtk_list_store_set(store, &iter, DT_CONTROL_CRAWLER_COL_ID, item->id, DT_CONTROL_CRAWLER_COL_IMAGE_PATH, + item->image_path, DT_CONTROL_CRAWLER_COL_XMP_PATH, item->xmp_path, + DT_CONTROL_CRAWLER_COL_TS_XMP, timestamp_xmp, DT_CONTROL_CRAWLER_COL_TS_DB, timestamp_db, + DT_CONTROL_CRAWLER_COL_TS_XMP_INT, item->timestamp_xmp, DT_CONTROL_CRAWLER_COL_TS_DB_INT, + item->timestamp_db, -1); g_free(item->image_path); g_free(item->xmp_path); } @@ -401,15 +494,9 @@ void dt_control_crawler_show_image_list(GList *images) GtkWidget *tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); - g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(_selection_changed), gui); gui->tree = GTK_TREE_VIEW(tree); // FIXME: do we need to free that later ? - GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new(); - column = gtk_tree_view_column_new_with_attributes(_("select"), renderer, "active", - DT_CONTROL_CRAWLER_COL_SELECTED, NULL); - gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); - GtkCellRenderer *renderer_text = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("path"), renderer_text, "text", DT_CONTROL_CRAWLER_COL_IMAGE_PATH, NULL); @@ -423,8 +510,8 @@ void dt_control_crawler_show_image_list(GList *images) DT_CONTROL_CRAWLER_COL_TS_XMP, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); - column = gtk_tree_view_column_new_with_attributes(_("database timestamp"), gtk_cell_renderer_text_new(), - "text", DT_CONTROL_CRAWLER_COL_TS_DB, NULL); + column = gtk_tree_view_column_new_with_attributes(_("database timestamp"), gtk_cell_renderer_text_new(), "text", + DT_CONTROL_CRAWLER_COL_TS_DB, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); gtk_container_add(GTK_CONTAINER(scroll), tree); @@ -433,8 +520,8 @@ void dt_control_crawler_show_image_list(GList *images) // build a dialog window that contains the list of images GtkWidget *win = dt_ui_main_window(darktable.gui->ui); GtkWidget *dialog = gtk_dialog_new_with_buttons(_("updated xmp sidecar files found"), GTK_WINDOW(win), - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, - _("_close"), GTK_RESPONSE_CLOSE, NULL); + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, _("_close"), + GTK_RESPONSE_CLOSE, NULL); #ifdef GDK_WINDOWING_QUARTZ dt_osx_disallow_fullscreen(dialog); #endif @@ -449,19 +536,30 @@ void dt_control_crawler_show_image_list(GList *images) GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(content_box), box, FALSE, FALSE, 0); - GtkWidget *select_all = gtk_check_button_new_with_label(_("select all")); + GtkWidget *select_all = gtk_button_new_with_label(_("select all")); + GtkWidget *select_none = gtk_button_new_with_label(_("select none")); + GtkWidget *select_invert = gtk_button_new_with_label(_("invert selection")); gtk_box_pack_start(GTK_BOX(box), select_all, FALSE, FALSE, 0); - gui->select_all_handler_id = g_signal_connect(select_all, "toggled", G_CALLBACK(_select_all_callback), gui); - gui->select_all = select_all; + gtk_box_pack_start(GTK_BOX(box), select_none, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), select_invert, FALSE, FALSE, 0); + g_signal_connect(select_all, "clicked", G_CALLBACK(_select_all_callback), gui); + g_signal_connect(select_none, "clicked", G_CALLBACK(_select_none_callback), gui); + g_signal_connect(select_invert, "clicked", G_CALLBACK(_select_invert_callback), gui); box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(content_box), box, FALSE, FALSE, 0); - GtkWidget *reload_button = gtk_button_new_with_label(_("update database from selected xmp files")); - GtkWidget *overwrite_button = gtk_button_new_with_label(_("overwrite selected xmp files")); + GtkWidget *reload_button = gtk_button_new_with_label(_("keep the xmp edit")); + GtkWidget *overwrite_button = gtk_button_new_with_label(_("keep the database edit")); + GtkWidget *newest_button = gtk_button_new_with_label(_("keep the newest edit")); + GtkWidget *oldest_button = gtk_button_new_with_label(_("keep the oldest edit")); gtk_box_pack_start(GTK_BOX(box), reload_button, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(box), overwrite_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), newest_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), oldest_button, FALSE, FALSE, 0); g_signal_connect(reload_button, "clicked", G_CALLBACK(_reload_button_clicked), gui); g_signal_connect(overwrite_button, "clicked", G_CALLBACK(_overwrite_button_clicked), gui); + g_signal_connect(newest_button, "clicked", G_CALLBACK(_newest_button_clicked), gui); + g_signal_connect(oldest_button, "clicked", G_CALLBACK(_oldest_button_clicked), gui); gtk_widget_show_all(dialog); From 6c9cbca1eb34e248a84b3d3cb53c961a3d8c3cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Fri, 8 Oct 2021 21:42:19 +0200 Subject: [PATCH 04/12] crawler: add 2 columns most recent edit and time difference --- src/control/crawler.c | 55 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 0f3f4394ca68..325a5f12847e 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -45,6 +45,8 @@ typedef enum dt_control_crawler_cols_t DT_CONTROL_CRAWLER_COL_TS_DB, DT_CONTROL_CRAWLER_COL_TS_XMP_INT, // new timestamp to db DT_CONTROL_CRAWLER_COL_TS_DB_INT, + DT_CONTROL_CRAWLER_COL_REPORT, + DT_CONTROL_CRAWLER_COL_TIME_DELTA, DT_CONTROL_CRAWLER_NUM_COLS } dt_control_crawler_cols_t; @@ -450,6 +452,23 @@ static void _oldest_button_clicked(GtkButton *button, gpointer user_data) _cleanup_GList(rows_to_remove); } +static gchar* str_time_delta(const int time_delta) +{ + // display the time difference as a legible string + int seconds = time_delta; + + int minutes = seconds / 60; + seconds -= 60 * minutes; + + int hours = minutes / 60; + minutes -= 60 * hours; + + int days = hours / 24; + hours -= 24 * days; + + return g_strdup_printf(_("%id %02dh %02dm %02ds"), days, hours, minutes, seconds); +} + // show a popup window with a list of updated images/xmp files and allow the user to tell dt what to do about them void dt_control_crawler_show_image_list(GList *images) { @@ -468,7 +487,9 @@ void dt_control_crawler_show_image_list(GList *images) G_TYPE_STRING, // timestamp from xmp G_TYPE_STRING, // timestamp from db G_TYPE_INT, // timestamp to db - G_TYPE_INT); + G_TYPE_INT, + G_TYPE_STRING, // report: newer version + G_TYPE_STRING);// time delta gui->model = GTK_TREE_MODEL(store); @@ -480,14 +501,26 @@ void dt_control_crawler_show_image_list(GList *images) struct tm tm_stamp; strftime(timestamp_db, sizeof(timestamp_db), "%c", localtime_r(&item->timestamp_db, &tm_stamp)); strftime(timestamp_xmp, sizeof(timestamp_xmp), "%c", localtime_r(&item->timestamp_xmp, &tm_stamp)); + + const time_t time_delta = abs(item->timestamp_db - item->timestamp_xmp); + gchar *timestamp_delta = str_time_delta(time_delta); + gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, DT_CONTROL_CRAWLER_COL_ID, item->id, DT_CONTROL_CRAWLER_COL_IMAGE_PATH, - item->image_path, DT_CONTROL_CRAWLER_COL_XMP_PATH, item->xmp_path, - DT_CONTROL_CRAWLER_COL_TS_XMP, timestamp_xmp, DT_CONTROL_CRAWLER_COL_TS_DB, timestamp_db, - DT_CONTROL_CRAWLER_COL_TS_XMP_INT, item->timestamp_xmp, DT_CONTROL_CRAWLER_COL_TS_DB_INT, - item->timestamp_db, -1); + gtk_list_store_set(store, &iter, + DT_CONTROL_CRAWLER_COL_ID, item->id, + DT_CONTROL_CRAWLER_COL_IMAGE_PATH, item->image_path, + DT_CONTROL_CRAWLER_COL_XMP_PATH, item->xmp_path, + DT_CONTROL_CRAWLER_COL_TS_XMP, timestamp_xmp, + DT_CONTROL_CRAWLER_COL_TS_DB, timestamp_db, + DT_CONTROL_CRAWLER_COL_TS_XMP_INT, item->timestamp_xmp, + DT_CONTROL_CRAWLER_COL_TS_DB_INT, item->timestamp_db, + DT_CONTROL_CRAWLER_COL_REPORT, (item->timestamp_xmp > item->timestamp_db) ? _("xmp") + : _("database"), + DT_CONTROL_CRAWLER_COL_TIME_DELTA, timestamp_delta, + -1); g_free(item->image_path); g_free(item->xmp_path); + g_free(timestamp_delta); } g_list_free_full(images, g_free); @@ -514,6 +547,16 @@ void dt_control_crawler_show_image_list(GList *images) DT_CONTROL_CRAWLER_COL_TS_DB, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); + column = gtk_tree_view_column_new_with_attributes(_("newest"), gtk_cell_renderer_text_new(), "text", + DT_CONTROL_CRAWLER_COL_REPORT, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); + + GtkCellRenderer *renderer_date = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("time difference"), renderer_date, "text", + DT_CONTROL_CRAWLER_COL_TIME_DELTA, NULL); + g_object_set(renderer_date, "xalign", 1., NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column); + gtk_container_add(GTK_CONTAINER(scroll), tree); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); From 1fd58ac0d7d448625264fafaa21f4eb7f6e29182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Fri, 8 Oct 2021 22:48:39 +0200 Subject: [PATCH 05/12] crawler: use long abs to please Clang --- src/control/crawler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 325a5f12847e..272741a16d8b 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -502,7 +502,7 @@ void dt_control_crawler_show_image_list(GList *images) strftime(timestamp_db, sizeof(timestamp_db), "%c", localtime_r(&item->timestamp_db, &tm_stamp)); strftime(timestamp_xmp, sizeof(timestamp_xmp), "%c", localtime_r(&item->timestamp_xmp, &tm_stamp)); - const time_t time_delta = abs(item->timestamp_db - item->timestamp_xmp); + const time_t time_delta = labs(item->timestamp_db - item->timestamp_xmp); gchar *timestamp_delta = str_time_delta(time_delta); gtk_list_store_append(store, &iter); From 7acf153b702e769a516a84e5404a4407d5d88bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Sat, 9 Oct 2021 13:26:14 +0200 Subject: [PATCH 06/12] =?UTF-8?q?crawler:=C2=A0make=20requested=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/control/crawler.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 272741a16d8b..d93f51730f31 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -220,8 +220,7 @@ static void dt_control_crawler_response_callback(GtkWidget *dialog, gint respons static void _delete_selected_rows(GList *rr_list, GtkTreeModel *model) { // Remove TreeView rows from rr_list. It needs to be populated before - GList *node; - for(node = rr_list; node != NULL; node = g_list_next(node)) + for(GList *node = rr_list; node != NULL; node = g_list_next(node)) { GtkTreePath *path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data); @@ -302,7 +301,7 @@ static void _append_row_to_remove(GtkTreeModel *model, GtkTreePath *path, gpoint } -static void _cleanup_GList(GList *list) +static void _cleanup_tree_rows_list(GList *list) { g_list_foreach(list, (GFunc) gtk_tree_row_reference_free, NULL); g_list_free(list); @@ -416,7 +415,7 @@ static void _reload_button_clicked(GtkButton *button, gpointer user_data) GList *rows_to_remove = NULL; gtk_tree_selection_selected_foreach(selection, sync_xmp_to_db, &rows_to_remove); _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_GList(rows_to_remove); + _cleanup_tree_rows_list(rows_to_remove); } // overwrite xmp with database @@ -427,7 +426,7 @@ void _overwrite_button_clicked(GtkButton *button, gpointer user_data) GList *rows_to_remove = NULL; gtk_tree_selection_selected_foreach(selection, sync_db_to_xmp, &rows_to_remove); _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_GList(rows_to_remove); + _cleanup_tree_rows_list(rows_to_remove); } // overwrite the oldest with the newest @@ -438,7 +437,7 @@ static void _newest_button_clicked(GtkButton *button, gpointer user_data) GList *rows_to_remove = NULL; gtk_tree_selection_selected_foreach(selection, sync_newest_to_oldest, &rows_to_remove); _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_GList(rows_to_remove); + _cleanup_tree_rows_list(rows_to_remove); } // overwrite the newest with the oldest @@ -449,7 +448,7 @@ static void _oldest_button_clicked(GtkButton *button, gpointer user_data) GList *rows_to_remove = NULL; gtk_tree_selection_selected_foreach(selection, sync_oldest_to_newest, &rows_to_remove); _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_GList(rows_to_remove); + _cleanup_tree_rows_list(rows_to_remove); } static gchar* str_time_delta(const int time_delta) @@ -463,7 +462,7 @@ static gchar* str_time_delta(const int time_delta) int hours = minutes / 60; minutes -= 60 * hours; - int days = hours / 24; + const int days = hours / 24; hours -= 24 * days; return g_strdup_printf(_("%id %02dh %02dm %02ds"), days, hours, minutes, seconds); From cd4472150ea14e800bb16ff31afc5451b7a4f95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Sat, 9 Oct 2021 13:38:56 +0200 Subject: [PATCH 07/12] =?UTF-8?q?xmp=20write=20:=C2=A0return=20error=20cod?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/exif.cc | 12 ++++++------ src/common/image.c | 11 +++++++---- src/common/image.h | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/common/exif.cc b/src/common/exif.cc index 1587f4a67021..4a6bc691de88 100644 --- a/src/common/exif.cc +++ b/src/common/exif.cc @@ -1177,7 +1177,7 @@ static bool _exif_decode_exif_data(dt_image_t *img, Exiv2::ExifData &exifData) Exiv2::ExifData::const_iterator cm1_pos = exifData.findKey(Exiv2::ExifKey("Exif.Image.ColorMatrix1")); if((illu[0] != DT_LS_Unknown) && (cm1_pos != exifData.end()) && (cm1_pos->count() == 9)) { - for(int i = 0; i < 9; i++) colmatrix[0][i] = cm1_pos->toFloat(i); + for(int i = 0; i < 9; i++) colmatrix[0][i] = cm1_pos->toFloat(i); } else illu[0] = DT_LS_Unknown; @@ -1186,7 +1186,7 @@ static bool _exif_decode_exif_data(dt_image_t *img, Exiv2::ExifData &exifData) Exiv2::ExifData::const_iterator cm2_pos = exifData.findKey(Exiv2::ExifKey("Exif.Image.ColorMatrix2")); if((illu[1] != DT_LS_Unknown) && (cm2_pos != exifData.end()) && (cm2_pos->count() == 9)) { - for(int i = 0; i < 9; i++) colmatrix[1][i] = cm2_pos->toFloat(i); + for(int i = 0; i < 9; i++) colmatrix[1][i] = cm2_pos->toFloat(i); } else illu[1] = DT_LS_Unknown; @@ -1196,7 +1196,7 @@ static bool _exif_decode_exif_data(dt_image_t *img, Exiv2::ExifData &exifData) Exiv2::ExifData::const_iterator cm3_pos = exifData.findKey(Exiv2::ExifKey("Exif.Image.ColorMatrix3")); if((illu[2] != DT_LS_Unknown) && (cm3_pos != exifData.end()) && (cm3_pos->count() == 9)) { - for(int i = 0; i < 9; i++) colmatrix[2][i] = cm3_pos->toFloat(i); + for(int i = 0; i < 9; i++) colmatrix[2][i] = cm3_pos->toFloat(i); } else illu[2] = DT_LS_Unknown; @@ -1209,7 +1209,7 @@ static bool _exif_decode_exif_data(dt_image_t *img, Exiv2::ExifData &exifData) // otherwise we take the one closest >= D65 for(int i = 0; i < 3; i++) { - if((illu[i] != DT_LS_Unknown) && (_illu_to_temp(illu[i]) > sel_temp) && (sel_temp < D65temp)) + if((illu[i] != DT_LS_Unknown) && (_illu_to_temp(illu[i]) > sel_temp) && (sel_temp < D65temp)) { sel_illu = i; sel_temp = _illu_to_temp(illu[i]); @@ -1259,7 +1259,7 @@ static bool _exif_decode_exif_data(dt_image_t *img, Exiv2::ExifData &exifData) case DT_LS_D65: for(int i = 0; i < 9; i++) img->d65_color_matrix[i] = colmatrix[sel_illu][i]; break; - + default: fprintf(stderr,"[exif] did not find a proper dng correction matrix for illuminant %i\n", illu[sel_illu]); break; @@ -4175,8 +4175,8 @@ int dt_exif_xmp_write(const int imgid, const char *filename) { fprintf(stderr, "cannot write xmp file '%s': '%s'\n", filename, strerror(errno)); dt_control_log(_("cannot write xmp file '%s': '%s'"), filename, strerror(errno)); + return -1; } - } return 0; diff --git a/src/common/image.c b/src/common/image.c index e0c0ba9fe622..818c0254fbe8 100644 --- a/src/common/image.c +++ b/src/common/image.c @@ -321,7 +321,7 @@ dt_imageio_write_xmp_t dt_image_get_xmp_mode() { res = DT_WRITE_XMP_ALWAYS; dt_conf_set_string("write_sidecar_files", "on import"); - } + } return res; } @@ -1412,7 +1412,7 @@ static int _image_read_duplicates(const uint32_t id, const char *filename, const static uint32_t _image_import_internal(const int32_t film_id, const char *filename, gboolean override_ignore_jpegs, gboolean lua_locking, gboolean raise_signals) { - const dt_imageio_write_xmp_t xmp_mode = dt_image_get_xmp_mode(); + const dt_imageio_write_xmp_t xmp_mode = dt_image_get_xmp_mode(); char *normalized_filename = dt_util_normalize_path(filename); if(!normalized_filename || !dt_util_test_image_file(normalized_filename)) { @@ -2420,7 +2420,7 @@ int dt_image_local_copy_reset(const int32_t imgid) // xmp stuff // ******************************************************* -void dt_image_write_sidecar_file(const int32_t imgid) +int dt_image_write_sidecar_file(const int32_t imgid) { // TODO: compute hash and don't write if not needed! // write .xmp file @@ -2439,7 +2439,7 @@ void dt_image_write_sidecar_file(const int32_t imgid) dt_image_full_path(imgid, filename, sizeof(filename), &from_cache); // nothing to do, the original is not accessible and there is no local copy - if(!from_cache) return; + if(!from_cache) return 1; } dt_image_path_append_version(imgid, filename, sizeof(filename)); @@ -2457,8 +2457,11 @@ void dt_image_write_sidecar_file(const int32_t imgid) DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid); sqlite3_step(stmt); sqlite3_finalize(stmt); + return 0; } } + + return 1; // error : nothing written } void dt_image_synch_xmps(const GList *img) diff --git a/src/common/image.h b/src/common/image.h index 480f42120179..760efd430fe8 100644 --- a/src/common/image.h +++ b/src/common/image.h @@ -405,7 +405,7 @@ gboolean dt_image_safe_remove(const int32_t imgid); /* try to sync .xmp for all local copies */ void dt_image_local_copy_synch(void); // xmp functions: -void dt_image_write_sidecar_file(const int32_t imgid); +int dt_image_write_sidecar_file(const int32_t imgid); void dt_image_synch_xmp(const int selected); void dt_image_synch_xmps(const GList *img); void dt_image_synch_all_xmp(const gchar *pathname); From 99984cea8b9f0d3d50caad2737c8add42cbd665e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Sat, 9 Oct 2021 16:06:13 +0200 Subject: [PATCH 08/12] crawler: log synchronizations in window In place of stdout writing. Perform write checks on all operations, log and report to user. This is useful to notify write permissions issues or offline media. Add a GtkSpinner supposed to spin while sync is on-going. But it doesn't work. --- src/control/crawler.c | 214 ++++++++++++++++++++++++++++++++---------- 1 file changed, 167 insertions(+), 47 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index d93f51730f31..87faa440ae78 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -205,6 +205,9 @@ typedef struct dt_control_crawler_gui_t { GtkTreeView *tree; GtkTreeModel *model; + GtkWidget *log; + GtkWidget *spinner; + GList *rows_to_remove; } dt_control_crawler_gui_t; // close the window and clean up @@ -217,8 +220,11 @@ static void dt_control_crawler_response_callback(GtkWidget *dialog, gint respons } -static void _delete_selected_rows(GList *rr_list, GtkTreeModel *model) +static void _delete_selected_rows(dt_control_crawler_gui_t *gui) { + GList *rr_list = gui->rows_to_remove; + GtkTreeModel *model = gui->model; + // Remove TreeView rows from rr_list. It needs to be populated before for(GList *node = rr_list; node != NULL; node = g_list_next(node)) { @@ -231,6 +237,10 @@ static void _delete_selected_rows(GList *rr_list, GtkTreeModel *model) gtk_list_store_remove(GTK_LIST_STORE(model), &iter); } } + + // Cleanup the list of rows + g_list_foreach(rr_list, (GFunc) gtk_tree_row_reference_free, NULL); + g_list_free(rr_list); } @@ -292,35 +302,50 @@ static void _get_crawler_entry_from_model(GtkTreeModel *model, GtkTreeIter *iter } -static void _append_row_to_remove(GtkTreeModel *model, GtkTreePath *path, gpointer user_data) +static void _append_row_to_remove(GtkTreeModel *model, GtkTreePath *path, GList **rowref_list) { // append TreeModel rows to the list to remove - GList **rowref_list = (GList **)user_data; GtkTreeRowReference *rowref = gtk_tree_row_reference_new(model, path); *rowref_list = g_list_append(*rowref_list, rowref); } - -static void _cleanup_tree_rows_list(GList *list) +static void _log_synchronization(dt_control_crawler_gui_t *gui, gchar *message) { - g_list_foreach(list, (GFunc) gtk_tree_row_reference_free, NULL); - g_list_free(list); + // add a new line in the log TreeView + GtkTreeIter iter_log; + GtkTreeModel *model_log = gtk_tree_view_get_model(GTK_TREE_VIEW(gui->log)); + gtk_list_store_append(GTK_LIST_STORE(model_log), &iter_log); + gtk_list_store_set(GTK_LIST_STORE(model_log), &iter_log, + 0, message, + -1); } static void sync_xmp_to_db(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); _db_update_timestamp(entry.id, entry.timestamp_xmp); const int success = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); // success = 0, fail = 1 - if(!success) _append_row_to_remove(model, path, user_data); - fprintf(stdout, "%s synced XMP -> DB\n", entry.image_path); - - int *return_code = (int *)user_data; - *return_code |= success; + gchar *message; + if(!success) + { + _append_row_to_remove(model, path, &gui->rows_to_remove); + message = g_strdup_printf(_("SUCCESS: %s synced XMP -> DB"), entry.image_path); + _log_synchronization(gui, message); + } + else + { + message = g_strdup_printf(_("ERROR: %s NOT synced XMP -> DB"), entry.image_path); + _log_synchronization(gui, message); + g_free(message); + message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be offline or read-only.")); + _log_synchronization(gui, message); + } + g_free(message); g_free(entry.xmp_path); g_free(entry.image_path); } @@ -328,48 +353,91 @@ static void sync_xmp_to_db(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter * static void sync_db_to_xmp(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); - dt_image_write_sidecar_file(entry.id); + const int success = dt_image_write_sidecar_file(entry.id); // success = 0, fail = 1 - fprintf(stdout, "%s synced DB -> XMP\n", entry.image_path); - _append_row_to_remove(model, path, user_data); + gchar *message; + if(!success) + { + _append_row_to_remove(model, path, &gui->rows_to_remove); + message = g_strdup_printf(_("SUCCESS: %s synced DB -> XMP"), entry.image_path); + _log_synchronization(gui, message); + } + else + { + message = g_strdup_printf(_("ERROR: %s NOT synced DB -> XMP"), entry.image_path); + _log_synchronization(gui, message); + g_free(message); + message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be offline or read-only."), entry.xmp_path); + _log_synchronization(gui, message); + } + + g_free(message); g_free(entry.xmp_path); g_free(entry.image_path); - - // no return code, dt_image_write_sidecar_file always succeeds… ¯\_(ツ)_/¯ - // FIXME ? } static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); + int success; + gchar *message; if(entry.timestamp_xmp > entry.timestamp_db) { // WRITE XMP in DB _db_update_timestamp(entry.id, entry.timestamp_xmp); - dt_history_load_and_apply(entry.id, entry.xmp_path, 0); - fprintf(stdout, "%s synced XMP (new) -> DB (old)\n", entry.image_path); - // don't track the return code since the other path doesn't anyway + success = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); + if(!success) + { + message = g_strdup_printf(_("SUCCESS: %s synced new (XMP) -> old (DB)"), entry.image_path); + _log_synchronization(gui, message); + } + else + { + message = g_strdup_printf(_("ERROR: %s NOT synced new (XMP) -> old (DB)"), entry.image_path); + _log_synchronization(gui, message); + g_free(message); + message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be offline or read-only.")); + _log_synchronization(gui, message); + } } else if(entry.timestamp_xmp < entry.timestamp_db) { // WRITE DB in XMP - dt_image_write_sidecar_file(entry.id); + success = dt_image_write_sidecar_file(entry.id); fprintf(stdout, "%s synced DB (new) -> XMP (old)\n", entry.image_path); + if(!success) + { + message = g_strdup_printf(_("SUCCESS: %s synced new (DB) -> old (XMP)"), entry.image_path); + _log_synchronization(gui, message); + } + else + { + message = g_strdup_printf(_("ERROR: %s NOT synced new (DB) -> old (XMP)"), entry.image_path); + _log_synchronization(gui, message); + g_free(message); + message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be offline or read-only."), entry.xmp_path); + _log_synchronization(gui, message); + } } else { // we should never reach that part of the code // if both timestamps are equal, they should not be in this list in the first place - fprintf(stderr, "editing timestamps may be corrupted\n"); + success = 1; + message = g_strdup_printf(_("EXCEPTION: %s has inconsistent timestamps"), entry.image_path); + _log_synchronization(gui, message); } - _append_row_to_remove(model, path, user_data); + if(!success) _append_row_to_remove(model, path, &gui->rows_to_remove); + g_free(message); g_free(entry.xmp_path); g_free(entry.image_path); } @@ -377,32 +445,61 @@ static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTre static void sync_oldest_to_newest(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) { + dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); + int success; + gchar *message; if(entry.timestamp_xmp < entry.timestamp_db) { // WRITE XMP in DB _db_update_timestamp(entry.id, entry.timestamp_xmp); - dt_history_load_and_apply(entry.id, entry.xmp_path, 0); - fprintf(stdout, "%s synced XMP (old) -> DB (new)\n", entry.image_path); - // don't track the return code since the other path doesn't anyway + success = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); + if(!success) + { + message = g_strdup_printf(_("SUCCESS: %s synced old (XMP) -> new (DB)"), entry.image_path); + _log_synchronization(gui, message); + } + else + { + message = g_strdup_printf(_("ERROR: %s NOT synced old (XMP) -> new (DB)"), entry.image_path); + _log_synchronization(gui, message); + g_free(message); + message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be offline or read-only.")); + _log_synchronization(gui, message); + } } else if(entry.timestamp_xmp > entry.timestamp_db) { // WRITE DB in XMP - dt_image_write_sidecar_file(entry.id); - fprintf(stdout, "%s synced DB (old) -> XMP (new)\n", entry.image_path); + success = dt_image_write_sidecar_file(entry.id); + if(!success) + { + message = g_strdup_printf(_("SUCCESS: %s synced old (DB) -> new (XMP)"), entry.image_path); + _log_synchronization(gui, message); + } + else + { + message = g_strdup_printf(_("ERROR: %s NOT synced old (DB) -> new (XMP)"), entry.image_path); + _log_synchronization(gui, message); + g_free(message); + message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be offline or read-only."), entry.xmp_path); + _log_synchronization(gui, message); + } } else { // we should never reach that part of the code // if both timestamps are equal, they should not be in this list in the first place - fprintf(stderr, "editing timestamps may be corrupted\n"); + success = 1; + message = g_strdup_printf(_("EXCEPTION: %s has inconsistent timestamps"), entry.image_path); + _log_synchronization(gui, message); } - _append_row_to_remove(model, path, user_data); + if(!success) _append_row_to_remove(model, path, &gui->rows_to_remove); + g_free(message); g_free(entry.xmp_path); g_free(entry.image_path); } @@ -412,10 +509,11 @@ static void _reload_button_clicked(GtkButton *button, gpointer user_data) { dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); - GList *rows_to_remove = NULL; - gtk_tree_selection_selected_foreach(selection, sync_xmp_to_db, &rows_to_remove); - _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_tree_rows_list(rows_to_remove); + gui->rows_to_remove = NULL; + gtk_spinner_start(GTK_SPINNER(gui->spinner)); + gtk_tree_selection_selected_foreach(selection, sync_xmp_to_db, gui); + _delete_selected_rows(gui); + gtk_spinner_stop(GTK_SPINNER(gui->spinner)); } // overwrite xmp with database @@ -423,10 +521,11 @@ void _overwrite_button_clicked(GtkButton *button, gpointer user_data) { dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); - GList *rows_to_remove = NULL; - gtk_tree_selection_selected_foreach(selection, sync_db_to_xmp, &rows_to_remove); - _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_tree_rows_list(rows_to_remove); + gui->rows_to_remove = NULL; + gtk_spinner_start(GTK_SPINNER(gui->spinner)); + gtk_tree_selection_selected_foreach(selection, sync_db_to_xmp, gui); + _delete_selected_rows(gui); + gtk_spinner_stop(GTK_SPINNER(gui->spinner)); } // overwrite the oldest with the newest @@ -434,10 +533,11 @@ static void _newest_button_clicked(GtkButton *button, gpointer user_data) { dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); - GList *rows_to_remove = NULL; - gtk_tree_selection_selected_foreach(selection, sync_newest_to_oldest, &rows_to_remove); - _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_tree_rows_list(rows_to_remove); + gui->rows_to_remove = NULL; + gtk_spinner_start(GTK_SPINNER(gui->spinner)); + gtk_tree_selection_selected_foreach(selection, sync_newest_to_oldest, gui); + _delete_selected_rows(gui); + gtk_spinner_stop(GTK_SPINNER(gui->spinner)); } // overwrite the newest with the oldest @@ -445,10 +545,11 @@ static void _oldest_button_clicked(GtkButton *button, gpointer user_data) { dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; GtkTreeSelection *selection = gtk_tree_view_get_selection(gui->tree); - GList *rows_to_remove = NULL; - gtk_tree_selection_selected_foreach(selection, sync_oldest_to_newest, &rows_to_remove); - _delete_selected_rows(rows_to_remove, gui->model); - _cleanup_tree_rows_list(rows_to_remove); + gui->rows_to_remove = NULL; + gtk_spinner_start(GTK_SPINNER(gui->spinner)); + gtk_tree_selection_selected_foreach(selection, sync_oldest_to_newest, gui); + _delete_selected_rows(gui); + gtk_spinner_stop(GTK_SPINNER(gui->spinner)); } static gchar* str_time_delta(const int time_delta) @@ -590,6 +691,7 @@ void dt_control_crawler_show_image_list(GList *images) box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(content_box), box, FALSE, FALSE, 0); + GtkWidget *reload_button = gtk_button_new_with_label(_("keep the xmp edit")); GtkWidget *overwrite_button = gtk_button_new_with_label(_("keep the database edit")); GtkWidget *newest_button = gtk_button_new_with_label(_("keep the newest edit")); @@ -603,6 +705,24 @@ void dt_control_crawler_show_image_list(GList *images) g_signal_connect(newest_button, "clicked", G_CALLBACK(_newest_button_clicked), gui); g_signal_connect(oldest_button, "clicked", G_CALLBACK(_oldest_button_clicked), gui); + /* Feedback spinner in case synch happens over network and stales */ + gui->spinner = gtk_spinner_new(); + gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(gui->spinner), FALSE, FALSE, 0); + + /* Log report */ + scroll = gtk_scrolled_window_new(NULL, NULL); + gui->log = gtk_tree_view_new(); + gtk_box_pack_start(GTK_BOX(content_box), scroll, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(scroll), gui->log); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW(gui->log), -1, _("synchronization log"), renderer_text, + "text", 0, NULL); + GtkListStore *store_log = gtk_list_store_new (1, G_TYPE_STRING); + GtkTreeModel *model_log = GTK_TREE_MODEL(store_log); + gtk_tree_view_set_model(GTK_TREE_VIEW(gui->log), model_log); + g_object_unref(model_log); + gtk_widget_show_all(dialog); g_signal_connect(dialog, "response", G_CALLBACK(dt_control_crawler_response_callback), gui); From 0638c13af923bd4896b9fd70fa1f951571701104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Sat, 9 Oct 2021 17:37:10 +0200 Subject: [PATCH 09/12] crawler: improve write error messages --- src/control/crawler.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 87faa440ae78..2f96bd48978a 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -403,7 +403,7 @@ static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTre message = g_strdup_printf(_("ERROR: %s NOT synced new (XMP) -> old (DB)"), entry.image_path); _log_synchronization(gui, message); g_free(message); - message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be offline or read-only.")); + message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be full, offline or read-only.")); _log_synchronization(gui, message); } } @@ -422,7 +422,7 @@ static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTre message = g_strdup_printf(_("ERROR: %s NOT synced new (DB) -> old (XMP)"), entry.image_path); _log_synchronization(gui, message); g_free(message); - message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be offline or read-only."), entry.xmp_path); + message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be full, offline or read-only."), entry.xmp_path); _log_synchronization(gui, message); } } @@ -466,7 +466,7 @@ static void sync_oldest_to_newest(GtkTreeModel *model, GtkTreePath *path, GtkTre message = g_strdup_printf(_("ERROR: %s NOT synced old (XMP) -> new (DB)"), entry.image_path); _log_synchronization(gui, message); g_free(message); - message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be offline or read-only.")); + message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be full, offline or read-only.")); _log_synchronization(gui, message); } } @@ -484,7 +484,7 @@ static void sync_oldest_to_newest(GtkTreeModel *model, GtkTreePath *path, GtkTre message = g_strdup_printf(_("ERROR: %s NOT synced old (DB) -> new (XMP)"), entry.image_path); _log_synchronization(gui, message); g_free(message); - message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be offline or read-only."), entry.xmp_path); + message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be full, offline or read-only."), entry.xmp_path); _log_synchronization(gui, message); } } From 3006069ead4bf0e22ada432059037968d67d53a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Sat, 9 Oct 2021 17:38:00 +0200 Subject: [PATCH 10/12] crawler: improve error messages 2 --- src/control/crawler.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 2f96bd48978a..d9042aa3ad27 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -341,7 +341,7 @@ static void sync_xmp_to_db(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter * message = g_strdup_printf(_("ERROR: %s NOT synced XMP -> DB"), entry.image_path); _log_synchronization(gui, message); g_free(message); - message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be offline or read-only.")); + message = g_strdup_printf(_("ERROR: cannot write the database. The destination may be full, offline or read-only.")); _log_synchronization(gui, message); } @@ -371,7 +371,7 @@ static void sync_db_to_xmp(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter * message = g_strdup_printf(_("ERROR: %s NOT synced DB -> XMP"), entry.image_path); _log_synchronization(gui, message); g_free(message); - message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be offline or read-only."), entry.xmp_path); + message = g_strdup_printf(_("ERROR: cannot write %s \nThe destination may be full, offline or read-only."), entry.xmp_path); _log_synchronization(gui, message); } From b549a3c2740dcb0b4161c315f27f07cb28074769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Sat, 9 Oct 2021 17:44:53 +0200 Subject: [PATCH 11/12] =?UTF-8?q?crawler:=C2=A0move=20selection=20options?= =?UTF-8?q?=20above,=20add=20a=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/control/crawler.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index d9042aa3ad27..2dc73e831487 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -675,8 +675,6 @@ void dt_control_crawler_show_image_list(GList *images) GtkWidget *content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_add(GTK_CONTAINER(content_area), content_box); - gtk_box_pack_start(GTK_BOX(content_box), scroll, TRUE, TRUE, 0); - GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_box_pack_start(GTK_BOX(content_box), box, FALSE, FALSE, 0); GtkWidget *select_all = gtk_button_new_with_label(_("select all")); @@ -689,13 +687,16 @@ void dt_control_crawler_show_image_list(GList *images) g_signal_connect(select_none, "clicked", G_CALLBACK(_select_none_callback), gui); g_signal_connect(select_invert, "clicked", G_CALLBACK(_select_invert_callback), gui); - box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_start(GTK_BOX(content_box), box, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(content_box), scroll, TRUE, TRUE, 0); + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start(GTK_BOX(content_box), box, FALSE, FALSE, 1); + GtkWidget *label = gtk_label_new_with_mnemonic(_("on the selection:")); GtkWidget *reload_button = gtk_button_new_with_label(_("keep the xmp edit")); GtkWidget *overwrite_button = gtk_button_new_with_label(_("keep the database edit")); GtkWidget *newest_button = gtk_button_new_with_label(_("keep the newest edit")); GtkWidget *oldest_button = gtk_button_new_with_label(_("keep the oldest edit")); + gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(box), reload_button, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(box), overwrite_button, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(box), newest_button, FALSE, FALSE, 0); From 01a9a2a6ca4a584dc333dc69be26a67728400d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20PIERRE?= Date: Mon, 11 Oct 2021 11:12:12 +0200 Subject: [PATCH 12/12] crawler.c : rename a variable for clarity --- src/control/crawler.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/control/crawler.c b/src/control/crawler.c index 2dc73e831487..d8165aa0c0ba 100644 --- a/src/control/crawler.c +++ b/src/control/crawler.c @@ -327,10 +327,10 @@ static void sync_xmp_to_db(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter * dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); _db_update_timestamp(entry.id, entry.timestamp_xmp); - const int success = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); // success = 0, fail = 1 + const int error = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); // success = 0, fail = 1 gchar *message; - if(!success) + if(!error) { _append_row_to_remove(model, path, &gui->rows_to_remove); message = g_strdup_printf(_("SUCCESS: %s synced XMP -> DB"), entry.image_path); @@ -356,11 +356,11 @@ static void sync_db_to_xmp(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter * dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); - const int success = dt_image_write_sidecar_file(entry.id); // success = 0, fail = 1 + const int error = dt_image_write_sidecar_file(entry.id); // success = 0, fail = 1 gchar *message; - if(!success) + if(!error) { _append_row_to_remove(model, path, &gui->rows_to_remove); message = g_strdup_printf(_("SUCCESS: %s synced DB -> XMP"), entry.image_path); @@ -385,15 +385,15 @@ static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTre dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); - int success; + int error; gchar *message; if(entry.timestamp_xmp > entry.timestamp_db) { // WRITE XMP in DB _db_update_timestamp(entry.id, entry.timestamp_xmp); - success = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); - if(!success) + error = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); + if(!error) { message = g_strdup_printf(_("SUCCESS: %s synced new (XMP) -> old (DB)"), entry.image_path); _log_synchronization(gui, message); @@ -410,9 +410,9 @@ static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTre else if(entry.timestamp_xmp < entry.timestamp_db) { // WRITE DB in XMP - success = dt_image_write_sidecar_file(entry.id); + error = dt_image_write_sidecar_file(entry.id); fprintf(stdout, "%s synced DB (new) -> XMP (old)\n", entry.image_path); - if(!success) + if(!error) { message = g_strdup_printf(_("SUCCESS: %s synced new (DB) -> old (XMP)"), entry.image_path); _log_synchronization(gui, message); @@ -430,12 +430,12 @@ static void sync_newest_to_oldest(GtkTreeModel *model, GtkTreePath *path, GtkTre { // we should never reach that part of the code // if both timestamps are equal, they should not be in this list in the first place - success = 1; + error = 1; message = g_strdup_printf(_("EXCEPTION: %s has inconsistent timestamps"), entry.image_path); _log_synchronization(gui, message); } - if(!success) _append_row_to_remove(model, path, &gui->rows_to_remove); + if(!error) _append_row_to_remove(model, path, &gui->rows_to_remove); g_free(message); g_free(entry.xmp_path); @@ -448,15 +448,15 @@ static void sync_oldest_to_newest(GtkTreeModel *model, GtkTreePath *path, GtkTre dt_control_crawler_gui_t *gui = (dt_control_crawler_gui_t *)user_data; dt_control_crawler_result_t entry = { 0 }; _get_crawler_entry_from_model(model, iter, &entry); - int success; + int error; gchar *message; if(entry.timestamp_xmp < entry.timestamp_db) { // WRITE XMP in DB _db_update_timestamp(entry.id, entry.timestamp_xmp); - success = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); - if(!success) + error = dt_history_load_and_apply(entry.id, entry.xmp_path, 0); + if(!error) { message = g_strdup_printf(_("SUCCESS: %s synced old (XMP) -> new (DB)"), entry.image_path); _log_synchronization(gui, message); @@ -473,8 +473,8 @@ static void sync_oldest_to_newest(GtkTreeModel *model, GtkTreePath *path, GtkTre else if(entry.timestamp_xmp > entry.timestamp_db) { // WRITE DB in XMP - success = dt_image_write_sidecar_file(entry.id); - if(!success) + error = dt_image_write_sidecar_file(entry.id); + if(!error) { message = g_strdup_printf(_("SUCCESS: %s synced old (DB) -> new (XMP)"), entry.image_path); _log_synchronization(gui, message); @@ -492,12 +492,12 @@ static void sync_oldest_to_newest(GtkTreeModel *model, GtkTreePath *path, GtkTre { // we should never reach that part of the code // if both timestamps are equal, they should not be in this list in the first place - success = 1; + error = 1; message = g_strdup_printf(_("EXCEPTION: %s has inconsistent timestamps"), entry.image_path); _log_synchronization(gui, message); } - if(!success) _append_row_to_remove(model, path, &gui->rows_to_remove); + if(!error) _append_row_to_remove(model, path, &gui->rows_to_remove); g_free(message); g_free(entry.xmp_path);