/* * Copyright (C) 2009 Intel Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "syncevo-server.h" #include "syncevo-session.h" /* for return value definitions */ /* TODO: would be nice to have a non-synthesis-dependent API but for now it's like this... */ #include #include "config.h" #include "sync-ui-config.h" #include "sync-ui.h" #include "sync-config-widget.h" /* local copy of GtkInfoBar, used when GTK+ < 2.18 */ #include "gtkinfobar.h" #ifdef USE_MOBLIN_UX #include "mux-frame.h" #ifdef MX_GTK_0_99_1 #include #else #include #endif #endif static gboolean support_canceling = FALSE; #define REPORTS_PER_CALL 10 #define SYNC_UI_ICON_SIZE 48 #define STRING_VARIANT_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) enum { PAGE_MAIN, PAGE_SETTINGS, PAGE_EMERGENCY, }; typedef enum bluetooth_type { SYNC_BLUETOOTH_NONE, SYNC_BLUETOOTH_GNOME, SYNC_BLUETOOTH_MOBLIN } bluetooth_type; typedef enum app_state { SYNC_UI_STATE_CURRENT_STATE, SYNC_UI_STATE_GETTING_SERVER, SYNC_UI_STATE_NO_SERVER, SYNC_UI_STATE_SERVER_OK, SYNC_UI_STATE_SERVER_FAILURE, SYNC_UI_STATE_SYNCING, } app_state; typedef enum ui_operation { OP_SYNC, /* use sync mode from config */ OP_SYNC_SLOW, OP_SYNC_REFRESH_FROM_CLIENT, OP_SYNC_REFRESH_FROM_SERVER, OP_SAVE, OP_RESTORE, } ui_operation; typedef struct operation_data { app_data *data; ui_operation operation; gboolean started; const char *dir; /* for OP_RESTORE */ } operation_data; struct _app_data { GtkWidget *sync_win; GtkWidget *services_win; /* will be NULL when USE_MOBLIN_UX is set*/ GtkWidget *emergency_win; /* will be NULL when USE_MOBLIN_UX is set*/ #ifdef USE_MOBLIN_UX GtkWidget *notebook; /* only in use with USE_MOBLIN_UX */ GtkWidget *back_btn; /* only in use with USE_MOBLIN_UX */ #endif GtkWidget *settings_btn; /* only in use with USE_MOBLIN_UX */ guint settings_id; GtkWidget *service_box; GtkWidget *info_bar; GtkWidget *no_connection_box; GtkWidget *main_frame; GtkWidget *log_frame; GtkWidget *server_icon_box; GtkWidget *offline_label; GtkWidget *progress; GtkWidget *sync_status_label; GtkWidget *spinner_image; GtkWidget *sync_btn; GtkWidget *change_service_btn; GtkWidget *emergency_btn; GtkWidget *server_label; GtkWidget *autosync_box; GtkWidget *autosync_toggle; GtkWidget *last_synced_label; GtkWidget *sources_box; GtkWidget *new_service_btn; GtkWidget *new_device_btn; GtkWidget *services_box; GtkWidget *devices_box; GtkWidget *scrolled_window; GtkWidget *expanded_config; GtkWidget *settings_close_btn; GtkWidget *emergency_label; GtkWidget *emergency_expander; GtkWidget *emergency_source_table; GtkWidget *refresh_from_server_btn_label; GtkWidget *refresh_from_client_btn_label; GtkWidget *emergency_backup_table; GtkWidget *emergency_close_btn; GtkWidget *password_dialog_entry; char *password_dialog_id; gboolean forced_emergency; GHashTable *emergency_sources; guint backup_count; gboolean online; gboolean syncing; gboolean synced_this_session; int last_sync; guint last_sync_src_id; ui_operation current_operation; server_config *current_service; app_state current_state; guint service_list_updates_left; gboolean open_current; /* should the service list open the current service when it populates next time*/ char *config_id_to_open; SyncevoServer *server; SyncevoSession *running_session; /* session that is currently active */ bluetooth_type bluetooth_wizard; }; static void set_sync_progress (app_data *data, float progress, char *status); static void set_app_state (app_data *data, app_state state); static void show_main_view (app_data *data); static void update_emergency_view (app_data *data); static void update_emergency_expander (app_data *data); static void show_emergency_view (app_data *data); static void show_services_list (app_data *data, const char *config_id_to_open); static void update_services_list (app_data *data); static void update_service_ui (app_data *data); static void setup_new_service_clicked (GtkButton *btn, app_data *data); static gboolean source_config_update_widget (source_config *source); static void get_presence_cb (SyncevoServer *server, char *status, char **transport, GError *error, app_data *data); static void get_reports_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data); static void start_session_cb (SyncevoServer *server, char *path, GError *error, operation_data *op_data); static void get_config_for_main_win_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, app_data *data); void toggle_set_active (GtkWidget *toggle, gboolean active) { #ifdef USE_MOBLIN_UX /* MxGtkLightSwitch does not have "active" property yet */ mx_gtk_light_switch_set_active (MX_GTK_LIGHT_SWITCH (toggle), active); #else g_object_set (toggle, "active", active, NULL); #endif } gboolean toggle_get_active (GtkWidget *toggle) { #ifdef USE_MOBLIN_UX /* MxGtkLightSwitch does not have "active" property yet */ return mx_gtk_light_switch_get_active (MX_GTK_LIGHT_SWITCH (toggle)); #else gboolean active; g_object_get (toggle, "active", &active, NULL); return active; #endif } void show_error_dialog (GtkWidget *widget, const char* message) { GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (widget)); GtkWidget *w; w = gtk_message_dialog_new (window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", message); gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); } static void remove_child (GtkWidget *widget, GtkContainer *container) { gtk_container_remove (container, widget); } static void change_service_clicked_cb (GtkButton *btn, app_data *data) { /* data->open_current = TRUE; */ show_services_list (data, NULL); } static void emergency_clicked_cb (GtkButton *btn, app_data *data) { show_emergency_view (data); } char* get_pretty_source_name (const char *source_name) { /* TRANSLATORS: There have been name changes to keep things in line with * the rest of the moblin UI. Please make sure the name you use matches * the ones in e.g. the panels. */ if (strcmp (source_name, "addressbook") == 0) { return g_strdup (_("Contacts")); } else if (strcmp (source_name, "calendar") == 0) { return g_strdup (_("Appointments")); } else if (strcmp (source_name, "todo") == 0) { return g_strdup (_("Tasks")); } else if (strcmp (source_name, "memo") == 0) { return g_strdup (_("Notes")); } else if (strcmp (source_name, "calendar+todo") == 0) { /* TRANSLATORS: This is a "combination source" for syncing with devices * that combine appointments and tasks. the name should match the ones * used for calendar and todo above */ return g_strdup (_("Appointments & Tasks")); } else { char *tmp; tmp = g_strdup (source_name); tmp[0] = g_ascii_toupper (tmp[0]); return tmp; } } char* get_pretty_source_name_markup (const char *source_name) { char *plain, *markup; plain = get_pretty_source_name (source_name); markup = g_markup_escape_text (plain, -1); g_free (plain); return markup; } static void reload_config (app_data *data, const char *server) { server_config_free (data->current_service); data->forced_emergency = FALSE; g_hash_table_remove_all (data->emergency_sources); if (!server || strlen (server) == 0) { data->current_service = NULL; update_service_ui (data); set_app_state (data, SYNC_UI_STATE_SERVER_OK); } else { data->synced_this_session = FALSE; data->current_service = g_slice_new0 (server_config); data->current_service->name = g_strdup (server); set_app_state (data, SYNC_UI_STATE_GETTING_SERVER); syncevo_server_get_config (data->server, data->current_service->name, FALSE, (SyncevoServerGetConfigCb)get_config_for_main_win_cb, data); } } static void abort_sync_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { /* TODO show in UI: failed to abort sync (while syncing) */ g_error_free (error); } /* status change handler takes care of updating UI */ } static void sync_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { /* TODO show in UI: sync failed (failed to even start) */ g_error_free (error); g_object_unref (session); return; } set_sync_progress (data, 0.0, _("Starting sync")); /* stop updates of "last synced" */ if (data->last_sync_src_id > 0) g_source_remove (data->last_sync_src_id); set_app_state (data, SYNC_UI_STATE_SYNCING); } gboolean show_confirmation (GtkWidget *widget, const char *message, const char *yes, const char *no) { GtkWidget *w; int ret; w = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (widget)), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message); gtk_dialog_add_buttons (GTK_DIALOG (w), no, GTK_RESPONSE_NO, yes, GTK_RESPONSE_YES, NULL); ret = gtk_dialog_run (GTK_DIALOG (w)); gtk_widget_destroy (w); return (ret == GTK_RESPONSE_YES); } static void slow_sync (app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: slow sync confirmation dialog message. Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to slow sync with %s?"), data->current_service->pretty_name); /* TRANSLATORS: slow sync confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, do slow sync"), _("No, cancel sync"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SYNC_SLOW; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void slow_sync_clicked_cb (GtkButton *btn, app_data *data) { slow_sync (data); } static void refresh_from_server_clicked_cb (GtkButton *btn, app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to delete all local data and replace it with " "data from %s? This is not usually advised."), data->current_service->pretty_name); /* TRANSLATORS: "refresh from peer" confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, delete and replace"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = peer_is_client (data->current_service->config) ? OP_SYNC_REFRESH_FROM_CLIENT : OP_SYNC_REFRESH_FROM_SERVER; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void refresh_from_client_clicked_cb (GtkButton *btn, app_data *data) { operation_data *op_data; char *message; /* TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder * is service/device name */ message = g_strdup_printf (_("Do you want to delete all data in %s and replace it with " "your local data? This is not usually advised."), data->current_service->pretty_name); /* TRANSLATORS: "refresh from local side" confirmation dialog buttons */ if (!show_confirmation (data->sync_win, message, _("Yes, delete and replace"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = peer_is_client (data->current_service->config) ? OP_SYNC_REFRESH_FROM_SERVER : OP_SYNC_REFRESH_FROM_CLIENT; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void start_sync (app_data *data) { operation_data *op_data; if (data->syncing) { syncevo_session_abort (data->running_session, (SyncevoSessionGenericCb)abort_sync_cb, data); set_sync_progress (data, -1.0, _("Trying to cancel sync")); } else { op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SYNC; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); } } static void sync_clicked_cb (GtkButton *btn, app_data *data) { g_return_if_fail (data->current_service); start_sync (data); } #define DAY 60 * 60 * 24 #define HALF_DAY 60 * 60 * 12 #define HOUR 60 * 60 #define HALF_HOUR 60 * 30 #define MINUTE 60 #define HALF_MINUTE 30 static gboolean refresh_last_synced_label (app_data *data) { GTimeVal val; glong diff; char *msg; int delay; g_get_current_time (&val); diff = val.tv_sec - data->last_sync; if (!data->current_service) { msg = g_strdup (_("No service or device selected")); delay = -1; } else if (data->last_sync <= 0) { msg = g_strdup (data->current_service->pretty_name); /* we don't know */ delay = -1; } else if (diff < HALF_MINUTE) { /* TRANSLATORS: This is the title on main view. Placeholder is * the service name. Example: "Google - synced just now" */ msg = g_strdup_printf (_("%s - synced just now"), data->current_service->pretty_name); delay = 10; } else if (diff < MINUTE + HALF_MINUTE) { msg = g_strdup_printf (_("%s - synced a minute ago"), data->current_service->pretty_name); delay = MINUTE; } else if (diff < HOUR) { msg = g_strdup_printf (_("%s - synced %ld minutes ago"), data->current_service->pretty_name, (diff + HALF_MINUTE) / MINUTE); delay = MINUTE; } else if (diff < HOUR + HALF_HOUR) { msg = g_strdup_printf (_("%s - synced an hour ago"), data->current_service->pretty_name); delay = HOUR; } else if (diff < DAY) { msg = g_strdup_printf (_("%s - synced %ld hours ago"), data->current_service->pretty_name, (diff + HALF_HOUR) / (HOUR)); delay = HOUR; } else if (diff < DAY + HALF_DAY) { msg = g_strdup_printf (_("%s - synced a day ago"), data->current_service->pretty_name); delay = HOUR; } else { msg = g_strdup_printf (_("%s - synced %ld days ago"), data->current_service->pretty_name, (diff + HALF_DAY) / (DAY)); delay = HOUR; } gtk_label_set_text (GTK_LABEL (data->server_label), msg); g_free (msg); if (data->last_sync_src_id > 0) g_source_remove (data->last_sync_src_id); if (delay > 0) data->last_sync_src_id = g_timeout_add_seconds (delay, (GSourceFunc)refresh_last_synced_label, data); return FALSE; } static void set_sync_progress (app_data *data, float progress, char *status) { if (progress >= 0) gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->progress), progress); if (status) gtk_progress_bar_set_text (GTK_PROGRESS_BAR (data->progress), status); } static void set_info_bar (GtkWidget *widget, GtkMessageType type, SyncErrorResponse response_id, const char *message) { GtkWidget *container, *label; GtkInfoBar *bar = GTK_INFO_BAR (widget); if (!message) { gtk_widget_hide (widget); return; } container = gtk_info_bar_get_action_area (bar); gtk_container_foreach (GTK_CONTAINER (container), (GtkCallback)remove_child, container); switch (response_id) { case SYNC_ERROR_RESPONSE_SYNC: /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. * "You've just restored a backup. The changes have not been " * "synced with %s yet" */ gtk_info_bar_add_button (bar, _("Sync now"), response_id); break; case SYNC_ERROR_RESPONSE_EMERGENCY: /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. * "A normal sync is not possible at this time..." message. * "Other options" will open Emergency view */ gtk_info_bar_add_button (bar, _("Slow sync"), SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC); gtk_info_bar_add_button (bar, _("Other options..."), response_id); break; case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: /* TRANSLATORS: Action button in info bar in main view. Shown e.g. * when no service is selected. Will open configuration view */ gtk_info_bar_add_button (bar, _("Select sync service"), response_id); break; case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: /* TRANSLATORS: Action button in info bar in main view. Shown e.g. * login to service fails. Will open configuration view for this service */ gtk_info_bar_add_button (bar, _("Edit service settings"), response_id); break; case SYNC_ERROR_RESPONSE_NONE: break; default: g_warn_if_reached (); } gtk_info_bar_set_message_type (bar, type); container = gtk_info_bar_get_content_area (bar); gtk_container_foreach (GTK_CONTAINER (container), (GtkCallback)remove_child, container); label = gtk_label_new (message); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_size_request (label, 450, -1); gtk_box_pack_start (GTK_BOX (container), label, FALSE, FALSE, 8); gtk_widget_show (label); gtk_widget_show (widget); } static void set_app_state (app_data *data, app_state state) { if (state != SYNC_UI_STATE_CURRENT_STATE) data->current_state = state; switch (data->current_state) { case SYNC_UI_STATE_GETTING_SERVER: gtk_widget_hide (data->service_box); gtk_widget_hide (data->info_bar); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); refresh_last_synced_label (data); gtk_widget_set_sensitive (data->main_frame, TRUE); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); break; case SYNC_UI_STATE_SERVER_FAILURE: gtk_widget_hide (data->service_box); gtk_widget_hide (data->autosync_box); gtk_widget_hide (data->progress); refresh_last_synced_label (data); /* info bar content should be set earlier */ gtk_widget_show (data->info_bar); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); gtk_widget_set_sensitive (data->main_frame, FALSE); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); break; case SYNC_UI_STATE_SERVER_OK: if (data->online) { gtk_widget_hide (data->no_connection_box); } else { gtk_widget_show (data->no_connection_box); } /* TRANSLATORS: These are for the button in main view, right side. Keep line length below ~20 characters, use two lines if needed */ gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync now")); if (!data->current_service) { gtk_widget_hide (data->service_box); gtk_widget_hide (data->autosync_box); gtk_widget_hide (data->progress); set_info_bar (data->info_bar, GTK_MESSAGE_INFO, SYNC_ERROR_RESPONSE_SETTINGS_SELECT, _("You haven't selected a sync service or device yet. " "Sync services let you synchronize your data " "between your netbook and a web service. You can " "also sync directly with some devices.")); refresh_last_synced_label (data); gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); gtk_widget_set_sensitive (data->sync_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->change_service_btn); } else { gtk_widget_hide (data->info_bar); gtk_widget_show (data->service_box); gtk_widget_show (data->autosync_box); gtk_widget_set_sensitive (data->sync_btn, data->online); gtk_widget_set_sensitive (data->emergency_btn, TRUE); if (data->synced_this_session && data->current_operation != OP_RESTORE) { gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync again")); } else { gtk_widget_hide (data->progress); } gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->sync_btn); } gtk_widget_set_sensitive (data->main_frame, TRUE); gtk_widget_set_sensitive (data->change_service_btn, TRUE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, TRUE); data->syncing = FALSE; break; case SYNC_UI_STATE_SYNCING: /* we have a active session, and a session is running (the running session may or may not be ours) */ gtk_widget_show (data->progress); if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restoring")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Syncing")); } gtk_widget_set_sensitive (data->main_frame, FALSE); gtk_widget_set_sensitive (data->change_service_btn, FALSE); gtk_widget_set_sensitive (data->emergency_btn, FALSE); if (data->settings_btn) gtk_widget_set_sensitive (data->settings_btn, FALSE); gtk_widget_set_sensitive (data->sync_btn, support_canceling && data->current_operation != OP_RESTORE); if (support_canceling && support_canceling && data->current_operation != OP_RESTORE) { /* TRANSLATORS: This is for the button in main view, right side. Keep line length below ~20 characters, use two lines if needed */ gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Cancel sync")); } data->syncing = TRUE; break; default: g_assert_not_reached (); } } #ifdef USE_MOBLIN_UX static GtkWidget* switch_dummy_to_mux_frame (GtkWidget *dummy) { GtkWidget *frame, *parent; const char *title; g_assert (GTK_IS_BIN (dummy)); frame = mux_frame_new (); gtk_widget_set_name (frame, gtk_widget_get_name (dummy)); title = gtk_frame_get_label (GTK_FRAME(dummy)); if (title && strlen (title) > 0) gtk_frame_set_label (GTK_FRAME (frame), title); parent = gtk_widget_get_parent (dummy); g_assert (GTK_IS_BOX (parent)); gtk_widget_reparent (gtk_bin_get_child (GTK_BIN (dummy)), frame); gtk_container_remove (GTK_CONTAINER (parent), dummy); gtk_box_pack_start (GTK_BOX (parent), frame, TRUE, TRUE, 0); gtk_widget_show (frame); return frame; } static void set_page (app_data *data, int page) { int current = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (page != current) { gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), page); if (page != PAGE_MAIN) { gtk_widget_show (data->back_btn); } else { gtk_widget_hide (data->back_btn); } /* make sure the toggle is correct */ g_signal_handler_block (data->settings_btn, data->settings_id); if (page == PAGE_SETTINGS) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), TRUE); } else if (current == PAGE_SETTINGS) { gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), FALSE); } g_signal_handler_unblock (data->settings_btn, data->settings_id); } gtk_window_present (GTK_WINDOW (data->sync_win)); } static void settings_toggled (GtkToggleButton *button, app_data *data) { int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (page == PAGE_SETTINGS) { show_main_view (data); } else { show_services_list (data, NULL); } } static gboolean key_press_cb (GtkWidget *widget, GdkEventKey *event, app_data *data) { int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); if (event->keyval == GDK_KEY_Escape && page != PAGE_MAIN) { show_main_view (data); } return FALSE; } /* For some reason metacity sometimes won't maximize but will if asked * another time. For the record, I'm not proud of writing this */ static gboolean try_maximize (GtkWindow *win) { static int count = 0; count++; gtk_window_maximize (win); return (count < 10); } static void setup_windows (app_data *data, GtkWidget *main, GtkWidget *settings, GtkWidget *emergency) { GtkWidget *tmp, *toolbar, *close_btn; GtkToolItem *item; g_assert (GTK_IS_WINDOW (main)); g_assert (GTK_IS_WINDOW (settings)); g_assert (GTK_IS_WINDOW (emergency)); data->sync_win = main; data->services_win = NULL; data->emergency_win = NULL; /* populate the notebook with window contents */ data->notebook = gtk_notebook_new (); gtk_widget_show (data->notebook); gtk_notebook_set_show_tabs (GTK_NOTEBOOK (data->notebook), FALSE); gtk_notebook_set_show_border (GTK_NOTEBOOK (data->notebook), FALSE); gtk_window_maximize (GTK_WINDOW (data->sync_win)); g_timeout_add (10, (GSourceFunc)try_maximize, data->sync_win); gtk_window_set_decorated (GTK_WINDOW (data->sync_win), FALSE); gtk_widget_set_name (data->sync_win, "meego_win"); g_signal_connect (data->sync_win, "key-press-event", G_CALLBACK (key_press_cb), data); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (data->sync_win))); gtk_container_remove (GTK_CONTAINER (data->sync_win), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (settings))); gtk_container_remove (GTK_CONTAINER (settings), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (emergency))); gtk_container_remove (GTK_CONTAINER (emergency), tmp); gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); g_object_unref (tmp); tmp = gtk_vbox_new (FALSE, 0); gtk_widget_show (tmp); gtk_container_add (GTK_CONTAINER (data->sync_win), tmp); gtk_box_pack_end (GTK_BOX (tmp), data->notebook, TRUE, TRUE, 0); /* create the window toolbar */ toolbar = gtk_toolbar_new (); gtk_widget_set_name (toolbar, "moblin-toolbar"); gtk_box_pack_start (GTK_BOX (tmp), toolbar, FALSE, FALSE, 0); data->back_btn = gtk_button_new_with_label (_("Back to sync")); gtk_widget_set_name (data->back_btn, "moblin-toolbar-button"); gtk_widget_set_can_focus (data->back_btn, FALSE); gtk_widget_set_no_show_all (data->back_btn, TRUE); g_signal_connect_swapped (data->back_btn, "clicked", G_CALLBACK (show_main_view), data); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), data->back_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0); item = gtk_tool_item_new (); gtk_tool_item_set_expand (item, TRUE); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 1); data->settings_btn = gtk_toggle_button_new (); gtk_widget_set_can_focus (data->settings_btn, FALSE); gtk_widget_set_name (data->settings_btn, "moblin-settings-button"); data->settings_id = g_signal_connect (data->settings_btn, "toggled", G_CALLBACK (settings_toggled), data); gtk_container_add (GTK_CONTAINER (data->settings_btn), gtk_image_new_from_icon_name ("preferences-other", GTK_ICON_SIZE_LARGE_TOOLBAR)); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), data->settings_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); close_btn = gtk_button_new (); gtk_widget_set_can_focus (close_btn, FALSE); gtk_widget_set_name (close_btn, "moblin-close-button"); g_signal_connect (close_btn, "clicked", G_CALLBACK (gtk_main_quit), NULL); gtk_container_add (GTK_CONTAINER (close_btn), gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_LARGE_TOOLBAR)); item = gtk_tool_item_new (); gtk_container_add (GTK_CONTAINER (item), close_btn); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); gtk_widget_show_all (toolbar); /* no need for close buttons */ gtk_widget_hide (data->settings_close_btn); gtk_widget_hide (data->emergency_close_btn); } static void show_emergency_view (app_data *data) { update_emergency_view (data); set_page (data, PAGE_EMERGENCY); } static void show_services_list (app_data *data, const char *config_id_to_open) { g_free (data->config_id_to_open); data->config_id_to_open = g_strdup (config_id_to_open); set_page (data, PAGE_SETTINGS); update_services_list (data); } static void show_main_view (app_data *data) { set_page (data, PAGE_MAIN); } #else /* return the placeholders themselves when not using Moblin UX */ static GtkWidget* switch_dummy_to_mux_frame (GtkWidget *dummy) { return dummy; } static void setup_windows (app_data *data, GtkWidget *main, GtkWidget *settings, GtkWidget *emergency) { data->sync_win = main; data->services_win = settings; data->emergency_win = emergency; gtk_window_set_transient_for (GTK_WINDOW (data->services_win), GTK_WINDOW (data->sync_win)); gtk_window_set_transient_for (GTK_WINDOW (data->emergency_win), GTK_WINDOW (data->sync_win)); g_signal_connect (data->services_win, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); g_signal_connect (data->emergency_win, "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL); } static void show_emergency_view (app_data *data) { update_emergency_view (data); gtk_widget_hide (data->services_win); gtk_window_present (GTK_WINDOW (data->emergency_win)); } static void show_services_list (app_data *data, const char *config_id_to_open) { g_free (data->config_id_to_open); data->config_id_to_open = g_strdup (config_id_to_open); gtk_widget_hide (data->emergency_win); gtk_window_present (GTK_WINDOW (data->services_win)); update_services_list (data); } static void show_main_view (app_data *data) { gtk_widget_hide (data->services_win); gtk_widget_hide (data->emergency_win); gtk_window_present (GTK_WINDOW (data->sync_win)); } #endif /* This is a hacky way to achieve autoscrolling when the expanders open/close */ static void services_box_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, app_data *data) { if (GTK_IS_WIDGET (data->expanded_config)) { int y; GtkAdjustment *adj; GtkAllocation alloc; gtk_widget_translate_coordinates (data->expanded_config, data->services_box, 0, 0, NULL, &y); gtk_widget_get_allocation (data->expanded_config, &alloc); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data->scrolled_window)); gtk_adjustment_clamp_page (adj, y, y + alloc.height); data->expanded_config = NULL; } } static void info_bar_response_cb (GtkInfoBar *info_bar, SyncErrorResponse response_id, app_data *data) { switch (response_id) { case SYNC_ERROR_RESPONSE_SYNC: start_sync (data); break; case SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC: slow_sync (data); break; case SYNC_ERROR_RESPONSE_EMERGENCY: show_emergency_view (data); break; case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: data->open_current = TRUE; show_services_list (data, NULL); break; case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: show_services_list (data, NULL); break; default: g_warn_if_reached (); } } static void new_device_clicked_cb (GtkButton *btn, app_data *data) { DBusGProxy *proxy; DBusGConnection *bus; char *argv[2] = {"bluetooth-wizard", NULL}; GError *error = NULL; switch (data->bluetooth_wizard) { case SYNC_BLUETOOTH_MOBLIN: bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); if (bus) { proxy = dbus_g_proxy_new_for_name (bus, "org.moblin.UX.Shell.Toolbar", "/org/moblin/UX/Shell/Toolbar", "org.moblin.UX.Shell.Toolbar"); dbus_g_proxy_call_no_reply (proxy, "ShowPanel", G_TYPE_STRING, "bluetooth-panel", G_TYPE_INVALID, G_TYPE_INVALID); g_object_unref (proxy); } break; case SYNC_BLUETOOTH_GNOME: if (!gdk_spawn_on_screen (gtk_window_get_screen (GTK_WINDOW (data->sync_win)), NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) { g_warning ("Failed to spawn bluetooth-wizard: %s", error->message); g_error_free (error); return; } break; default: ; } } static void name_has_owner_cb (DBusGProxy *proxy, gboolean has_owner, GError *error, app_data *data) { if (has_owner) { gtk_widget_show (data->new_device_btn); data->bluetooth_wizard = SYNC_BLUETOOTH_MOBLIN; } g_object_unref (proxy); } static void init_bluetooth_ui (app_data *data) { char *bt_wizard; DBusGConnection *bus; DBusGProxy *proxy; data->bluetooth_wizard = SYNC_BLUETOOTH_NONE; /* look for gnome bluetooth wizard first */ bt_wizard = g_find_program_in_path ("bluetooth-wizard"); if (bt_wizard) { gtk_widget_show (data->new_device_btn); data->bluetooth_wizard = SYNC_BLUETOOTH_GNOME; g_free (bt_wizard); } else { /* try Moblin shell next (bluetooth panel integrates bt wizard) */ bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); proxy = dbus_g_proxy_new_for_name (bus, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); if (proxy) { org_freedesktop_DBus_name_has_owner_async (proxy, "org.moblin.UX.Shell.Toolbar", (org_freedesktop_DBus_name_has_owner_reply)name_has_owner_cb, data); } } } static void autosync_toggle_cb (GtkWidget *widget, gpointer x, app_data *data) { if (data->current_service && data->current_service->config) { gboolean new_active, old_active = FALSE; char *autosync = NULL; new_active = toggle_get_active (widget); syncevo_config_get_value (data->current_service->config, NULL, "autoSync", &autosync); old_active = (g_strcmp0 (autosync, "1") == 0); if (old_active != new_active) { char *new_val; operation_data *op_data; new_val = new_active ? "1": "0"; syncevo_config_set_value (data->current_service->config, NULL, "autoSync", new_val); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_SAVE; op_data->started = FALSE; syncevo_server_start_no_sync_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); } } } static void build_autosync_ui (app_data *data) { char *txt; /* TRANSLATORS: label for checkbutton/toggle in main view. * Please stick to similar length strings or break the line with * "\n" if absolutely needed */ txt = _("Automatic sync"); #ifdef USE_MOBLIN_UX GtkWidget *lbl; lbl = gtk_label_new (txt); gtk_widget_show (lbl); gtk_box_pack_end (GTK_BOX (data->autosync_box), lbl, FALSE, FALSE, 0); data->autosync_toggle = mx_gtk_light_switch_new (); gtk_widget_show (data->autosync_toggle); gtk_box_pack_end (GTK_BOX (data->autosync_box), data->autosync_toggle, FALSE, FALSE, 0); g_signal_connect (data->autosync_toggle, "switch-flipped", G_CALLBACK (autosync_toggle_cb), data); #else GtkWidget *align; align = gtk_alignment_new (0.5, 1.0, 1.0, 0.0); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (data->autosync_box), align, TRUE, TRUE, 0); data->autosync_toggle = gtk_check_button_new_with_label (txt); gtk_container_add (GTK_CONTAINER (align), data->autosync_toggle); g_signal_connect (data->autosync_toggle, "notify::active", G_CALLBACK (autosync_toggle_cb), data); #endif gtk_widget_show (data->autosync_toggle); } static void glade_name_workaround (GtkBuilder *builder, const char *name) { GtkWidget *w; w = GTK_WIDGET (gtk_builder_get_object (builder, name)); if (w) { gtk_widget_set_name (w, name); } } static gboolean init_ui (app_data *data) { GtkBuilder *builder; GError *error = NULL; GtkWidget /* *frame, */ * service_error_box, *btn; GtkAdjustment *adj; gtk_rc_parse (THEMEDIR "sync-ui.rc"); builder = gtk_builder_new (); gtk_builder_add_from_file (builder, GLADEDIR "ui.xml", &error); if (error) { g_printerr ("Failed to load user interface from %s: %s\n", GLADEDIR "ui.xml", error->message); g_error_free (error); g_object_unref (builder); return FALSE; } data->service_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_box")); service_error_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_error_box")); data->info_bar = gtk_info_bar_new (); gtk_widget_set_no_show_all (data->info_bar, TRUE); g_signal_connect (data->info_bar, "response", G_CALLBACK (info_bar_response_cb), data); gtk_box_pack_start (GTK_BOX (service_error_box), data->info_bar, TRUE, TRUE, 16); data->no_connection_box = GTK_WIDGET (gtk_builder_get_object (builder, "no_connection_box")); data->server_icon_box = GTK_WIDGET (gtk_builder_get_object (builder, "server_icon_box")); data->offline_label = GTK_WIDGET (gtk_builder_get_object (builder, "offline_label")); data->progress = GTK_WIDGET (gtk_builder_get_object (builder, "progressbar")); data->change_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "change_service_btn")); data->emergency_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_btn")); data->sync_btn = GTK_WIDGET (gtk_builder_get_object (builder, "sync_btn")); data->sync_status_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_status_label")); data->spinner_image = GTK_WIDGET (gtk_builder_get_object (builder, "spinner_image")); gtk_image_set_from_file (GTK_IMAGE (data->spinner_image), THEMEDIR "sync-spinner.gif"); gtk_widget_set_no_show_all (data->spinner_image, TRUE); gtk_widget_hide (data->spinner_image); data->autosync_box = GTK_WIDGET (gtk_builder_get_object (builder, "autosync_box")); build_autosync_ui (data); data->server_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_service_label")); data->last_synced_label = GTK_WIDGET (gtk_builder_get_object (builder, "last_synced_label")); data->sources_box = GTK_WIDGET (gtk_builder_get_object (builder, "sources_box")); data->new_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_service_btn")); gtk_widget_set_size_request (data->new_service_btn, SYNC_UI_LIST_BTN_WIDTH, SYNC_UI_LIST_ICON_SIZE); g_signal_connect (data->new_service_btn, "clicked", G_CALLBACK (setup_new_service_clicked), data); /* service list view */ data->scrolled_window = GTK_WIDGET (gtk_builder_get_object (builder, "scrolledwindow")); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data->scrolled_window)); data->services_box = GTK_WIDGET (gtk_builder_get_object (builder, "services_box")); gtk_container_set_focus_vadjustment (GTK_CONTAINER (data->services_box), adj); g_signal_connect(data->services_box, "size-allocate", G_CALLBACK (services_box_allocate_cb), data); data->devices_box = GTK_WIDGET (gtk_builder_get_object (builder, "devices_box")); data->settings_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "settings_close_btn")); /* emergency view */ btn = GTK_WIDGET (gtk_builder_get_object (builder, "slow_sync_btn")); g_signal_connect (btn, "clicked", G_CALLBACK (slow_sync_clicked_cb), data); data->refresh_from_server_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn_label")); g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn")), "clicked", G_CALLBACK (refresh_from_server_clicked_cb), data); data->refresh_from_client_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn_label")); g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn")), "clicked", G_CALLBACK (refresh_from_client_clicked_cb), data); data->emergency_label = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_label")); data->emergency_expander = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_expander")); data->emergency_source_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_source_table")); data->emergency_backup_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_backup_table")); data->emergency_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_close_btn")); /* No (documented) way to add own widgets to gtkbuilder it seems... swap the all dummy widgets with Muxwidgets */ setup_windows (data, GTK_WIDGET (gtk_builder_get_object (builder, "sync_win")), GTK_WIDGET (gtk_builder_get_object (builder, "services_win")), GTK_WIDGET (gtk_builder_get_object (builder, "emergency_win"))); data->main_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "main_frame"))); data->log_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "log_frame"))); /* frame = */ switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "services_list_frame"))); /* frame = */ switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "emergency_frame"))); g_signal_connect (data->sync_win, "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect (data->change_service_btn, "clicked", G_CALLBACK (change_service_clicked_cb), data); g_signal_connect (data->emergency_btn, "clicked", G_CALLBACK (emergency_clicked_cb), data); g_signal_connect (data->sync_btn, "clicked", G_CALLBACK (sync_clicked_cb), data); g_signal_connect_swapped (data->emergency_close_btn, "clicked", G_CALLBACK (show_main_view), data); g_signal_connect_swapped (data->settings_close_btn, "clicked", G_CALLBACK (show_main_view), data); g_signal_connect (data->emergency_btn, "clicked", G_CALLBACK (emergency_clicked_cb), data); data->new_device_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_device_btn")); g_signal_connect (data->new_device_btn, "clicked", G_CALLBACK (new_device_clicked_cb), data); /* workarounds for glade not working with gtkbuilder >= 2.20: * widgets do not get names. */ glade_name_workaround (builder, "meego_win"); glade_name_workaround (builder, "sync_data_and_type_box"); glade_name_workaround (builder, "log_frame"); glade_name_workaround (builder, "backup_frame"); glade_name_workaround (builder, "services_frame"); glade_name_workaround (builder, "sync_service_label"); glade_name_workaround (builder, "sync_status_label"); glade_name_workaround (builder, "no_server_label"); glade_name_workaround (builder, "sync_failure_label"); glade_name_workaround (builder, "sync_btn"); init_bluetooth_ui (data); g_object_unref (builder); return TRUE; } static void load_icon (const char *uri, GtkBox *icon_box, guint icon_size) { GError *error = NULL; GdkPixbuf *pixbuf; GtkWidget *image; const char *filename; if (uri && strlen (uri) > 0) { if (g_str_has_prefix (uri, "file://")) { filename = uri+7; } else { g_warning ("only file:// icon uri is supported: %s", uri); filename = THEMEDIR "sync-generic.png"; } } else { filename = THEMEDIR "sync-generic.png"; } pixbuf = gdk_pixbuf_new_from_file_at_scale (filename, icon_size, icon_size, TRUE, &error); if (!pixbuf) { g_warning ("Failed to load service icon: %s", error->message); g_error_free (error); return; } image = gtk_image_new_from_pixbuf (pixbuf); gtk_widget_set_size_request (image, icon_size, icon_size); g_object_unref (pixbuf); gtk_container_foreach (GTK_CONTAINER(icon_box), (GtkCallback)remove_child, icon_box); gtk_box_pack_start (icon_box, image, FALSE, FALSE, 0); gtk_widget_show (image); } static void emergency_toggle_notify_active_cb (GtkWidget *widget, gpointer p, app_data *data) { gboolean active; char *source; active = toggle_get_active (widget); source = g_object_get_data (G_OBJECT (widget), "source"); g_return_if_fail (source); if (active) { g_hash_table_insert (data->emergency_sources, g_strdup (source), ""); } else { g_hash_table_remove (data->emergency_sources, source); } update_emergency_expander (data); } static GtkWidget* add_emergency_toggle_widget (app_data *data, const char *title, gboolean active, guint row, guint col) { GtkWidget *toggle; #ifdef USE_MOBLIN_UX GtkWidget *label; col = col * 2; label = gtk_label_new (title); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_table_attach (GTK_TABLE (data->emergency_source_table), label, col, col + 1, row, row + 1, GTK_FILL, GTK_FILL, 16, 0); toggle = mx_gtk_light_switch_new (); toggle_set_active (toggle, active); g_signal_connect (toggle, "switch-flipped", G_CALLBACK (emergency_toggle_notify_active_cb), data); #else toggle = gtk_check_button_new_with_label (title); toggle_set_active (toggle, active); g_signal_connect (toggle, "notify::active", G_CALLBACK (emergency_toggle_notify_active_cb), data); #endif gtk_widget_show (toggle); gtk_table_attach (GTK_TABLE (data->emergency_source_table), toggle, col + 1, col + 2, row, row + 1, GTK_FILL, GTK_FILL, 0, 0); return toggle; } static void update_emergency_expander (app_data *data) { char *text, *sources = NULL; GHashTableIter iter; char *name; g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&name, NULL)) { char *pretty, *tmp; pretty = get_pretty_source_name (name); if (sources) { tmp = g_strdup_printf ("%s, %s", sources, pretty); g_free (sources); g_free (pretty); sources = tmp; } else { sources = pretty; } } if (sources) { /* This is the expander label in emergency view. It summarizes the * currently selected data sources. First placeholder is service/device * name, second a comma separeted list of sources. * E.g. "Affected data: Google Contacts, Appointments" */ text = g_strdup_printf (_("Affected data: %s %s"), data->current_service->pretty_name, sources); g_free (sources); } else { text = g_strdup_printf (_("Affected data: none")); } gtk_expander_set_label (GTK_EXPANDER (data->emergency_expander), text); g_free (text); } static void add_emergency_source (const char *name, GHashTable *source, app_data *data) { source_config *conf; GtkWidget *toggle; guint rows, cols; guint row; static guint col; gboolean active = TRUE; char *pretty_name; conf = g_hash_table_lookup (data->current_service->source_configs, name); g_object_get (data->emergency_source_table, "n-rows", &rows, "n-columns", &cols, NULL); if (cols != 1 && col == 0){ col = 1; row = rows - 1; } else { col = 0; row = rows; } active = (g_hash_table_lookup (data->emergency_sources, name) != NULL); pretty_name = get_pretty_source_name (name); toggle = add_emergency_toggle_widget (data, pretty_name, active, row, col); gtk_widget_set_sensitive (toggle, source_config_is_usable (conf)); g_object_set_data_full (G_OBJECT (toggle), "source", g_strdup (name), g_free); g_free (pretty_name); } static void update_backup_visibilities (app_data *data) { char *key; GHashTableIter iter; GList *l, *widgets; widgets = gtk_container_get_children ( GTK_CONTAINER (data->emergency_backup_table)); gtk_widget_show_all (data->emergency_backup_table); /* hide backup widgets that do not contain selected sources */ g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, NULL)) { for (l = widgets; l; l = l->next) { if (!g_object_get_data (G_OBJECT (l->data), key)) { gtk_widget_hide (GTK_WIDGET (l->data)); } } } g_list_free (widgets); } static void restore_clicked_cb (GtkButton *btn, app_data *data) { const char *dir, *time_str; operation_data *op_data; char *message; dir = g_object_get_data (G_OBJECT (btn), "dir"); time_str = g_object_get_data (G_OBJECT (btn), "time"); g_return_if_fail (dir && time_str); /* TRANSLATORS: confirmation for restoring a backup. placeholder is the * backup time string defined below */ message = g_strdup_printf (_("Do you want to restore the backup from %s? " "All changes you have made since then will be lost."), time_str); if (!show_confirmation (data->sync_win, message, _("Yes, restore"), _("No"))) { g_free (message); return; } g_free (message); op_data = g_slice_new (operation_data); op_data->data = data; op_data->operation = OP_RESTORE; op_data->dir = dir; op_data->started = FALSE; syncevo_server_start_session (data->server, data->current_service->name, (SyncevoServerStartSessionCb)start_session_cb, op_data); show_main_view (data); } static void add_backup (app_data *data, const char *peername, const char *dir, long endtime, GList *sources) { GtkWidget *timelabel, *label, *blabel, *button, *box;; guint rows; char *text; char time_str[60]; struct tm *tim; tim = localtime (&endtime); /* TRANSLATORS: date/time for strftime(), used in emergency view backup * label. Any time format that shows date and time is good. */ strftime (time_str, sizeof (time_str), _("%x %X"), tim); g_object_get (data->emergency_backup_table, "n-rows", &rows, NULL); box = gtk_vbox_new (TRUE, 6); gtk_table_attach (GTK_TABLE (data->emergency_backup_table), box, 0, 1, rows, rows + 1, GTK_EXPAND|GTK_FILL, GTK_FILL, 16, 0); timelabel = gtk_label_new (time_str); gtk_misc_set_alignment (GTK_MISC (timelabel), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (timelabel), TRUE); gtk_widget_set_size_request (timelabel, 600, -1); gtk_box_pack_start (GTK_BOX (box), timelabel, TRUE, TRUE, 0); /* TRANSLATORS: label for a backup in emergency view. Placeholder is * service or device name */ text = g_strdup_printf (_("Backed up before syncing with %s"), peername); label = gtk_label_new (text); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_size_request (label, 600, -1); gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0); g_free (text); button = gtk_button_new (); gtk_table_attach (GTK_TABLE (data->emergency_backup_table), button, 1, 2, rows, rows + 1, GTK_FILL, GTK_SHRINK, 32, 0); g_object_set_data_full (G_OBJECT (button), "dir", g_strdup(dir), g_free); g_object_set_data_full (G_OBJECT (button), "time", g_strdup(time_str), g_free); g_signal_connect (button, "clicked", G_CALLBACK (restore_clicked_cb), data); blabel = gtk_label_new (_("Restore")); gtk_misc_set_padding (GTK_MISC (blabel), 32, 0); gtk_container_add (GTK_CONTAINER (button), blabel); for (; sources; sources = sources->next) { g_object_set_data (G_OBJECT (box), (char *)sources->data, ""); g_object_set_data (G_OBJECT (button), (char *)sources->data, ""); } } static void get_reports_for_backups_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data) { guint len, i; if (error) { g_warning ("Error in Session.GetReports: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } len = syncevo_reports_get_length (reports); for (i = 0; i < len; i++) { GHashTable *report = syncevo_reports_index (reports, i); GHashTableIter iter; char *key, *val; /* long status = -1; */ long endtime = -1; char *peername = NULL; char *dir = NULL; GList *backup_sources = NULL; g_hash_table_iter_init (&iter, report); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { char **strs; strs = g_strsplit (key, "-", 6); if (!strs) { continue; } if (g_strcmp0 (strs[0], "source") == 0 && g_strcmp0 (strs[2], "backup") == 0 && g_strcmp0 (strs[3], "before") == 0) { backup_sources = g_list_prepend (backup_sources, g_strdup (strs[1])); } else if (g_strcmp0 (strs[0], "end") == 0) { endtime = strtol (val, NULL, 10); } else if (g_strcmp0 (strs[0], "status") == 0) { /* status = strtol (val, NULL, 10); */ } else if (g_strcmp0 (strs[0], "peer") == 0) { peername = val; } else if (g_strcmp0 (strs[0], "dir") == 0) { dir = val; } g_strfreev (strs); } if (peername && dir && endtime > 0) { add_backup (data, peername, dir, endtime, backup_sources); } g_list_foreach (backup_sources, (GFunc)g_free, NULL); g_list_free (backup_sources); } data->backup_count += len; if (len == REPORTS_PER_CALL) { syncevo_server_get_reports (data->server, "", data->backup_count, REPORTS_PER_CALL, (SyncevoServerGetReportsCb)get_reports_for_backups_cb, data); } update_backup_visibilities (data); } static const char* get_syncevo_context (const char *config_name) { char *context; context = g_strrstr (config_name, "@"); if (!context) { context = ""; } return context; } static void update_emergency_view (app_data *data) { char *text; if (!data->current_service) { g_warning ("no service defined in Emergency view"); return; } if (data->forced_emergency) { text = g_strdup_printf ( /* TRANSLATORS: this is an explanation in Emergency view. * Placeholder is a service/device name */ _("A normal sync with %s is not possible at this time. " "You can do a slow two-way sync or start from scratch. You " "can also restore a backup, but a slow sync or starting from " "scratch will still be required before normal sync is " "possible."), data->current_service->pretty_name); } else { /* TRANSLATORS: this is an explanation in Emergency view. * Placeholder is a service/device name */ text = g_strdup_printf ( _("If something has gone horribly wrong, you can try a " "slow sync, start from scratch or restore from backup.")); } gtk_label_set_text (GTK_LABEL (data->emergency_label), text); g_free (text); /* TRANSLATORS: These are a buttons in Emergency view. Placeholder is a * service/device name. Please don't use too long lines, but feel free to * use several lines. */ text = g_strdup_printf (_("Delete all your local\n" "data and replace with\n" "data from %s"), data->current_service->pretty_name); gtk_label_set_text (GTK_LABEL (data->refresh_from_server_btn_label), text); g_free (text); text = g_strdup_printf (_("Delete all data on\n" "%s and replace\n" "with your local data"), data->current_service->pretty_name); gtk_label_set_text (GTK_LABEL (data->refresh_from_client_btn_label), text); g_free (text); gtk_container_foreach (GTK_CONTAINER (data->emergency_source_table), (GtkCallback)remove_child, data->emergency_source_table); gtk_table_resize (GTK_TABLE (data->emergency_source_table), 1, 1); /* using this instead of current_service->source_configs * to get the same order as the configuration has... */ syncevo_config_foreach_source (data->current_service->config, (ConfigFunc)add_emergency_source, data); update_emergency_expander (data); data->backup_count = 0; gtk_container_foreach (GTK_CONTAINER (data->emergency_backup_table), (GtkCallback)remove_child, data->emergency_backup_table); gtk_table_resize (GTK_TABLE (data->emergency_backup_table), 1, 1); syncevo_server_get_reports (data->server, get_syncevo_context (data->current_service->name), 0, REPORTS_PER_CALL, (SyncevoServerGetReportsCb)get_reports_for_backups_cb, data); } static void update_service_source_ui (const char *name, source_config *conf, app_data *data) { GtkWidget *lbl, *box; char *pretty_name, *title; if (!source_config_is_usable (conf)) { return; } conf->box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (data->sources_box), conf->box, FALSE, FALSE, 8); pretty_name = get_pretty_source_name_markup (name); title = g_strdup_printf ("%s", pretty_name); lbl = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (lbl), title); g_free (pretty_name); g_free (title); gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (conf->box), lbl, TRUE, TRUE, 0); box = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (conf->box), box, FALSE, FALSE, 0); conf->info_bar = gtk_info_bar_new (); gtk_box_pack_start (GTK_BOX (box), conf->info_bar, TRUE, TRUE, 16); gtk_widget_set_no_show_all (conf->info_bar, TRUE); g_signal_connect (conf->info_bar, "response", G_CALLBACK (info_bar_response_cb), data); conf->label = gtk_label_new (NULL); gtk_misc_set_alignment (GTK_MISC (conf->label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (conf->box), conf->label, TRUE, TRUE, 0); source_config_update_widget (conf); gtk_widget_show_all (conf->box); } static void check_source_cb (SyncevoSession *session, GError *error, source_config *source) { if (error) { if(error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE)) { /* source is not supported locally */ if (source) { source->supported_locally = FALSE; if (source->box) { /* widget is already on screen, hide it */ gtk_widget_hide (source->box); } } } else { g_warning ("CheckSource failed: %s", error->message); /* non-fatal, unknown error */ } g_error_free (error); return; } } static void update_service_ui (app_data *data) { char *icon_uri = NULL; char *autosync = NULL; gtk_container_foreach (GTK_CONTAINER (data->sources_box), (GtkCallback)remove_child, data->sources_box); if (data->current_service && data->current_service->config) { syncevo_config_get_value (data->current_service->config, NULL, "IconURI", &icon_uri); syncevo_config_get_value (data->current_service->config, NULL, "autoSync", &autosync); g_hash_table_foreach (data->current_service->source_configs, (GHFunc)update_service_source_ui, data); } load_icon (icon_uri, GTK_BOX (data->server_icon_box), SYNC_UI_ICON_SIZE); toggle_set_active (data->autosync_toggle, g_strcmp0 (autosync, "1") == 0); refresh_last_synced_label (data); gtk_widget_show_all (data->sources_box); } static void unexpand_config_widget (GtkWidget *w, GtkWidget *exception) { if (SYNC_IS_CONFIG_WIDGET (w) && exception && exception != w) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (w), FALSE); } } static void config_widget_expanded_cb (GtkWidget *widget, GParamSpec *pspec, app_data *data) { if (sync_config_widget_get_expanded (SYNC_CONFIG_WIDGET (widget))) { data->expanded_config = widget; gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)unexpand_config_widget, widget); } } static void config_widget_changed_cb (GtkWidget *widget, app_data *data) { if (sync_config_widget_get_current (SYNC_CONFIG_WIDGET (widget))) { const char *name = NULL; name = sync_config_widget_get_name (SYNC_CONFIG_WIDGET (widget)); reload_config (data, name); show_main_view (data); } else { reload_config (data, NULL); update_services_list (data); } } static SyncConfigWidget* add_configuration_to_box (GtkBox *box, SyncevoConfig *config, const char *name, gboolean has_template, gboolean has_configuration, app_data *data) { GtkWidget *item = NULL; gboolean current = FALSE; const char *current_name = NULL; if (data->current_service) { current_name = data->current_service->pretty_name; if (data->current_service->name && name && g_ascii_strcasecmp (name, data->current_service->name) == 0) { current = TRUE; } } item = sync_config_widget_new (data->server, name, config, current, current_name, has_configuration, has_template); g_signal_connect (item, "changed", G_CALLBACK (config_widget_changed_cb), data); g_signal_connect (item, "notify::expanded", G_CALLBACK (config_widget_expanded_cb), data); gtk_widget_show (item); gtk_box_pack_start (box, item, FALSE, FALSE, 0); if (current) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), data->open_current); } if (g_strcmp0 (name, "default") == 0) { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), TRUE); } if (data->config_id_to_open) { sync_config_widget_expand_id (SYNC_CONFIG_WIDGET (item), data->config_id_to_open); } return SYNC_CONFIG_WIDGET (item); } static void find_new_service_config (SyncConfigWidget *w, GtkWidget **found) { if (SYNC_IS_CONFIG_WIDGET (w)) { if (!sync_config_widget_get_configured (w) && !sync_config_widget_get_has_template (w)) { *found = GTK_WIDGET (w); } } } typedef struct config_data { app_data *data; char *name; gboolean has_configuration; gboolean has_template; GHashTable *device_templates; } config_data; #define LEGAL_CONFIG_NAME_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ1234567890-_" static void get_config_for_config_widget_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, config_data *c_data) { char *ready, *is_peer, *url, *type; c_data->data->service_list_updates_left--; if (error) { /* show in UI? */ g_warning ("Server.GetConfig() failed: %s", error->message); g_error_free (error); return; } syncevo_config_get_value (config, NULL, "ConsumerReady", &ready); syncevo_config_get_value (config, NULL, "PeerIsClient", &is_peer); syncevo_config_get_value (config, NULL, "syncURL", &url); syncevo_config_get_value (config, NULL, "peerType", &type); if (g_strcmp0 ("1", ready) != 0 || (type && g_strcmp0 ("WebDAV", type) == 0) || (url && g_str_has_prefix (url, "local://@"))) { /* Ignore existing configs and templates unless they are explicitly marked as "ConsumerReady. Also ignore webdav (and the local syncs used for webdav) for now */ } else if (is_peer && g_strcmp0 ("1", is_peer) == 0) { if (url) { SyncConfigWidget *w; char *fp, *tmp, *template_name, *device_name = NULL; char **fpv = NULL; syncevo_config_get_value (config, NULL, "deviceName", &tmp); if (!tmp) { device_name = g_strdup (c_data->name); } else { device_name = g_strcanon (g_strdup (tmp), LEGAL_CONFIG_NAME_CHARS, '-'); } syncevo_config_get_value (config, NULL, "templateName", &template_name); if (!template_name) { syncevo_config_get_value (config, NULL, "fingerPrint", &fp); if (fp) { fpv = g_strsplit_set (fp, ",;", 2); if (g_strv_length (fpv) > 0) { template_name = fpv[0]; } } } /* keep a list of added devices */ w = g_hash_table_lookup (c_data->device_templates, url); if (!w) { w = add_configuration_to_box (GTK_BOX (c_data->data->devices_box), config, device_name, c_data->has_template, c_data->has_configuration, c_data->data); g_hash_table_insert (c_data->device_templates, url, w); sync_config_widget_add_alternative_config (w, template_name, config, c_data->has_configuration); } else { /* TODO: might want to add a new widget, if user has created more * configs for same device: this really requires us to look at * all configs / templates, then decide what to sho w*/ /* there is a widget for this device already, add this info there*/ sync_config_widget_add_alternative_config (w, template_name, config, c_data->has_configuration); } g_free (device_name); g_strfreev (fpv); } } else { add_configuration_to_box (GTK_BOX (c_data->data->services_box), config, c_data->name, c_data->has_template, c_data->has_configuration, c_data->data); } g_free (c_data->name); g_hash_table_unref (c_data->device_templates); g_slice_free (config_data, c_data); } static void get_config_for_config_widget (app_data *data, const char *config, gboolean has_template, gboolean has_configuration, GHashTable *device_templates) { config_data *c_data; data->service_list_updates_left++; c_data = g_slice_new0 (config_data); c_data->data = data; c_data->name = g_strdup (config); c_data->has_template = has_template; c_data->has_configuration = has_configuration; if (device_templates) { c_data->device_templates = g_hash_table_ref (device_templates); } syncevo_server_get_config (data->server, config, !has_configuration, (SyncevoServerGetConfigCb)get_config_for_config_widget_cb, c_data); } static void setup_new_service_clicked (GtkButton *btn, app_data *data) { GtkWidget *widget = NULL; gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)unexpand_config_widget, NULL); /* if a new service config has already been added, use that. * Otherwise add one. */ gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)find_new_service_config, &widget); if (!widget) { get_config_for_config_widget (data, "default", TRUE, FALSE, NULL); } else { sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (widget), TRUE); } } typedef struct templates_data { app_data *data; char **templates; } templates_data; static void get_configs_cb (SyncevoServer *server, char **configs, GError *error, templates_data *templ_data) { char **config_iter, **template_iter, **templates; app_data *data; GHashTable *device_templates; templ_data->data->service_list_updates_left = 0; templates = templ_data->templates; data = templ_data->data; g_slice_free (templates_data, templ_data); if (error) { show_main_view (data); g_warning ("Server.GetConfigs() failed: %s", error->message); g_strfreev (templates); g_error_free (error); return; } device_templates = g_hash_table_new (g_str_hash, g_str_equal); for (template_iter = templates; *template_iter; template_iter++){ gboolean found_config = FALSE; for (config_iter = configs; *config_iter; config_iter++) { if (*template_iter && *config_iter && g_ascii_strncasecmp (*template_iter, *config_iter, strlen (*config_iter)) == 0) { /* have template and config */ get_config_for_config_widget (data, *config_iter, TRUE, TRUE, device_templates); found_config = TRUE; break; } } if (!found_config) { /* have template, no config */ get_config_for_config_widget (data, *template_iter, TRUE, FALSE, device_templates); } } for (config_iter = configs; *config_iter; config_iter++) { gboolean found_template = FALSE; for (template_iter = templates; *template_iter; template_iter++) { if (*template_iter && *config_iter && g_ascii_strncasecmp (*template_iter, *config_iter, strlen (*config_iter)) == 0) { found_template = TRUE; break; } } if (!found_template) { /* have config, no template */ get_config_for_config_widget (data, *config_iter, FALSE, TRUE, device_templates); } } /* config initialization might ref/unref as well... */ g_hash_table_unref (device_templates); g_strfreev (configs); g_strfreev (templates); } static void get_template_configs_cb (SyncevoServer *server, char **templates, GError *error, app_data *data) { templates_data *templ_data; if (error) { data->service_list_updates_left = 0; show_main_view (data); show_error_dialog (data->sync_win, _("Failed to get list of supported services from SyncEvolution")); g_warning ("Server.GetConfigs() failed: %s", error->message); g_error_free (error); return; } templ_data = g_slice_new (templates_data); templ_data->data = data; templ_data->templates = templates; syncevo_server_get_configs (data->server, FALSE, (SyncevoServerGetConfigsCb)get_configs_cb, templ_data); } static void update_services_list (app_data *data) { if (data->service_list_updates_left > 0) { return; } gtk_container_foreach (GTK_CONTAINER (data->services_box), (GtkCallback)remove_child, data->services_box); gtk_container_foreach (GTK_CONTAINER (data->devices_box), (GtkCallback)remove_child, data->devices_box); /* set temp number before we know the real one */ data->service_list_updates_left = 1; syncevo_server_get_configs (data->server, TRUE, (SyncevoServerGetConfigsCb)get_template_configs_cb, data); } static void get_config_for_main_win_cb (SyncevoServer *server, SyncevoConfig *config, GError *error, app_data *data) { if (error) { if (error->code == DBUS_GERROR_REMOTE_EXCEPTION && dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG)) { /* another syncevolution client probably removed the config */ reload_config (data, NULL); } else { g_warning ("Error in Server.GetConfig: %s", error->message); /* TRANSLATORS: message in main view */ set_info_bar (data->info_bar, GTK_MESSAGE_ERROR, SYNC_ERROR_RESPONSE_NONE, _("There was a problem communicating with the " "sync process. Please try again later.")); set_app_state (data, SYNC_UI_STATE_SERVER_FAILURE); } g_error_free (error); return; } if (config) { GHashTableIter iter; char *name; source_config *source; server_config_init (data->current_service, config); set_app_state (data, SYNC_UI_STATE_SERVER_OK); /* get "locally supported" status for all sources */ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&source)) { syncevo_server_check_source (data->server, data->current_service->name, name, (SyncevoServerGenericCb)check_source_cb, source); } syncevo_server_get_presence (server, data->current_service->name, (SyncevoServerGetPresenceCb)get_presence_cb, data); syncevo_server_get_reports (server, data->current_service->name, 0, 1, (SyncevoServerGetReportsCb)get_reports_cb, data); update_service_ui (data); } } static void set_running_session_status (app_data *data, SyncevoSessionStatus status, int error_code) { if (status & SYNCEVO_STATUS_QUEUEING) { g_warning ("Running session is queued, this shouldn't happen..."); } else if (status & SYNCEVO_STATUS_IDLE) { set_app_state (data, SYNC_UI_STATE_SERVER_OK); } else if (status & SYNCEVO_STATUS_DONE) { char *err; err = get_error_string_for_code (error_code, NULL); if (err) { if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restore failed")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync failed")); } g_free (err); } else { if (data->current_operation == OP_RESTORE) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restore complete")); } else { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync complete")); } } set_app_state (data, SYNC_UI_STATE_SERVER_OK); set_sync_progress (data, 1.0, ""); } else if (status & SYNCEVO_STATUS_RUNNING || status & SYNCEVO_STATUS_SUSPENDING || status & SYNCEVO_STATUS_ABORTING) { set_app_state (data, SYNC_UI_STATE_SYNCING); } if (status & SYNCEVO_STATUS_WAITING) { gtk_widget_show (data->spinner_image); } else { gtk_widget_hide (data->spinner_image); } } static void running_session_status_changed_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, app_data *data) { set_running_session_status (data, status, error_code); } static void get_running_session_status_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } set_running_session_status (data, status, error_code); } typedef struct source_progress_data { app_data *data; SyncevoSourcePhase phase; const char *source; } source_progress_data; static void find_updated_source_progress (const char *name, SyncevoSourcePhase phase, source_progress_data *prog_data) { GHashTable *configs = prog_data->data->current_service->source_configs; source_config *config; config = g_hash_table_lookup (configs, name); if (config) { if (phase != config->phase) { config->phase = phase; prog_data->phase = config->phase; prog_data->source = name; } } } static void running_session_progress_changed_cb (SyncevoSession *session, int progress, SyncevoSourceProgresses *source_progresses, app_data *data) { source_progress_data *prog_data = g_slice_new0 (source_progress_data); prog_data->data = data; prog_data->phase = SYNCEVO_PHASE_NONE; prog_data->source = NULL; syncevo_source_progresses_foreach (source_progresses, (SourceProgressFunc)find_updated_source_progress, prog_data); if (!prog_data->source) { set_sync_progress (data, ((float)progress) / 100, NULL); } else { char *name; char *msg = NULL; name = get_pretty_source_name (prog_data->source); switch (prog_data->phase) { case SYNCEVO_PHASE_PREPARING: msg = g_strdup_printf (_("Preparing '%s'"), name); break; case SYNCEVO_PHASE_RECEIVING: msg = g_strdup_printf (_("Receiving '%s'"), name); break; case SYNCEVO_PHASE_SENDING: msg = g_strdup_printf (_("Sending '%s'"), name); break; default: ; } if (msg) { set_sync_progress (data, ((float)progress) / 100, msg); } g_free (msg); g_free (name); } g_slice_free (source_progress_data, prog_data); } typedef struct source_stats { long status; long mode; long local_changes; long remote_changes; long local_rejections; long remote_rejections; } source_stats; static void free_source_stats (source_stats *stats) { g_slice_free (source_stats, stats); } static gboolean handle_source_report_item (char **strs, const char *value, GHashTable *sources) { source_stats *stats; char **tmp; char *name; if (g_strv_length (strs) < 3) { return FALSE; } /* replace '__' with '_' and '_+' with '-' */ tmp = g_strsplit (strs[1], "__", 0); name = g_strjoinv ("_", tmp); g_strfreev (tmp); tmp = g_strsplit (name, "_+", 0); g_free (name); name = g_strjoinv ("-", tmp); g_strfreev (tmp); stats = g_hash_table_lookup (sources, name); if (!stats) { stats = g_slice_new0 (source_stats); g_hash_table_insert (sources, g_strdup (name), stats); } g_free (name); if (strcmp (strs[2], "stat") == 0) { if (g_strv_length (strs) != 6) { return FALSE; } if (strcmp (strs[3], "remote") == 0) { if (strcmp (strs[4], "added") == 0 || strcmp (strs[4], "updated") == 0 || strcmp (strs[4], "removed") == 0) { stats->remote_changes += strtol (value, NULL, 10); } else if (strcmp (strs[5], "reject") == 0) { stats->remote_rejections += strtol (value, NULL, 10); } } else if (strcmp (strs[3], "local") == 0) { if (strcmp (strs[4], "added") == 0 || strcmp (strs[4], "updated") == 0 || strcmp (strs[4], "removed") == 0) { stats->local_changes += strtol (value, NULL, 10); } else if (strcmp (strs[5], "reject") == 0) { stats->local_rejections += strtol (value, NULL, 10); } } else { return FALSE; } } else if (strcmp (strs[2], "mode") == 0) { stats->mode = strtol (value, NULL, 10); } else if (strcmp (strs[2], "status") == 0) { stats->status = strtol (value, NULL, 10); } else if (strcmp (strs[2], "resume") == 0) { } else if (strcmp (strs[2], "first") == 0) { } else if (strcmp (strs[2], "backup") == 0) { if (g_strv_length (strs) != 4) { return FALSE; } if (strcmp (strs[3], "before") == 0) { } else if (strcmp (strs[3], "after") == 0) { } else { return FALSE; } } else { return FALSE; } return TRUE; } static char* get_report_summary (source_config *source) { char *rejects = NULL; char *changes = NULL; char *msg = NULL; if (!source->stats_set) { return g_strdup (""); } if (source->local_rejections + source->remote_rejections == 0) { rejects = NULL; } else if (source->local_rejections == 0) { rejects = g_strdup_printf (ngettext ("There was one remote rejection.", "There were %ld remote rejections.", source->remote_rejections), source->remote_rejections); } else if (source->remote_rejections == 0) { rejects = g_strdup_printf (ngettext ("There was one local rejection.", "There were %ld local rejections.", source->local_rejections), source->local_rejections); } else { rejects = g_strdup_printf (_ ("There were %ld local rejections and %ld remote rejections."), source->local_rejections, source->remote_rejections); } if (source->local_changes + source->remote_changes == 0) { changes = g_strdup_printf (_("Last time: No changes.")); } else if (source->local_changes == 0) { changes = g_strdup_printf (ngettext ("Last time: Sent one change.", "Last time: Sent %ld changes.", source->remote_changes), source->remote_changes); } else if (source->remote_changes == 0) { // This is about changes made to the local data. Not all of these // changes were requested by the remote server, so "applied" // is a better word than "received" (bug #5185). changes = g_strdup_printf (ngettext ("Last time: Applied one change.", "Last time: Applied %ld changes.", source->local_changes), source->local_changes); } else { changes = g_strdup_printf (_("Last time: Applied %ld changes and sent %ld changes."), source->local_changes, source->remote_changes); } if (rejects) msg = g_strdup_printf ("%s\n%s", changes, rejects); else msg = g_strdup (changes); g_free (rejects); g_free (changes); return msg; } /* return TRUE if no errors are shown */ static gboolean source_config_update_widget (source_config *source) { char *msg; gboolean show_error; SyncErrorResponse response; if (!source->label) { return TRUE; } msg = get_error_string_for_code (source->status, &response); if (msg) { show_error = TRUE; set_info_bar (source->info_bar, GTK_MESSAGE_ERROR, response, msg); } else { show_error = FALSE; gtk_widget_hide (source->info_bar); msg = get_report_summary (source); gtk_label_set_text (GTK_LABEL (source->label), msg); } g_free (msg); return !show_error; } static void get_reports_cb (SyncevoServer *server, SyncevoReports *reports, GError *error, app_data *data) { long status = -1; long common_status = -1; source_stats *stats; GHashTable *sources; /* key is source name, value is a source_stats */ GHashTableIter iter; const char *key, *val; source_config *source_conf; char *error_msg; SyncErrorResponse response; gboolean have_source_errors; GHashTable *report = NULL; guint len; if (error) { g_warning ("Error in Session.GetReports: %s", error->message); g_error_free (error); /* non-fatal, unknown error */ return; } sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_source_stats); len = syncevo_reports_get_length (reports); if (len > 0) { report = syncevo_reports_index (reports, 0); val = g_hash_table_lookup (report, "dir"); if (!val || strlen (val) == 0) { /* dummy report for first time sync info*/ if (len > 1) { report = syncevo_reports_index (reports, 1); } else { report = NULL; } } } if (report) { g_hash_table_iter_init (&iter, report); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { char **strs; strs = g_strsplit (key, "-", 6); if (!strs) { continue; } if (strcmp (strs[0], "source") == 0) { if (!handle_source_report_item (strs, val, sources)) { g_warning ("Unidentified sync report item: %s=%s", key, val); } } else if (strcmp (strs[0], "start") == 0) { /* not used */ } else if (strcmp (strs[0], "end") == 0) { data->last_sync = strtol (val, NULL, 10); } else if (strcmp (strs[0], "status") == 0) { status = strtol (val, NULL, 10); } else if (strcmp (strs[0], "peer") == 0) { /* not used */ } else if (strcmp (strs[0], "error") == 0) { /* not used */ } else if (strcmp (strs[0], "dir") == 0) { /* not used */ } else { g_warning ("Unidentified sync report item: %s=%s", key, val); } g_strfreev (strs); } } else { common_status = 0; } /* sources now has all statistics we want */ /* ficure out if all sources have same status or if there's a slow sync */ data->forced_emergency = FALSE; g_hash_table_remove_all (data->emergency_sources); g_hash_table_iter_init (&iter, sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { if (stats->status == 22001) { /* ignore abort because of another source slow syncing */ } else if (stats->status == 22000) { common_status = stats->status; data->forced_emergency = TRUE; g_hash_table_insert (data->emergency_sources, g_strdup (key), ""); } else if (common_status == -1) { common_status = stats->status; } else if (common_status != stats->status) { common_status = 0; } } if (status != 200) { /* don't want to show a sync time for failed syncs */ data->last_sync = -1; } if (!data->forced_emergency) { /* if user initiates a emergency sync wihtout forced_emergency, enable all sources by default*/ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&source_conf)) { if (source_config_is_usable (source_conf)) { g_hash_table_insert (data->emergency_sources, g_strdup (key), ""); } } } /* get common error message */ error_msg = get_error_string_for_code (common_status, &response); have_source_errors = FALSE; g_hash_table_iter_init (&iter, sources); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { /* store the statistics in source config */ source_conf = g_hash_table_lookup (data->current_service->source_configs, key); if (source_conf) { source_conf->stats_set = TRUE; source_conf->local_changes = stats->local_changes; source_conf->remote_changes = stats->remote_changes; source_conf->local_rejections = stats->local_rejections; source_conf->remote_rejections = stats->remote_rejections; if (error_msg) { /* there is a service-wide error, no need to show here */ source_conf->status = 0; } else { source_conf->status = stats->status; } /* if ui has been constructed already, update it */ if (!source_config_update_widget (source_conf)) { have_source_errors = TRUE; } } } if (!error_msg && !have_source_errors) { /* no common source errors or individual source errors: it's still possible that there are sync errors */ error_msg = get_error_string_for_code (status, &response); } /* update service UI */ refresh_last_synced_label (data); if (error_msg) { GtkMessageType type = GTK_MESSAGE_ERROR; if (response == SYNC_ERROR_RESPONSE_EMERGENCY) { type = GTK_MESSAGE_QUESTION; } if (!data->synced_this_session) { /* TRANSLATORS: the placeholder is a error message (hopefully) * explaining the problem */ char *msg = g_strdup_printf (_("There was a problem with last sync:\n%s"), error_msg); g_free (error_msg); error_msg = msg; } set_info_bar (data->info_bar, type, response, error_msg); g_free (error_msg); } else if (data->current_operation == OP_RESTORE) { /* special case for just after restoring */ error_msg = g_strdup_printf (_("You've just restored a backup. The changes have not been " "synced with %s yet"), data->current_service->pretty_name); set_info_bar (data->info_bar, GTK_MESSAGE_INFO, SYNC_ERROR_RESPONSE_SYNC, error_msg); } g_hash_table_destroy (sources); } static void set_config_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); /* TODO show in UI: save failed */ } g_object_unref (session); } static void restore_cb (SyncevoSession *session, GError *error, app_data *data) { if (error) { g_warning ("Error in Session.Restore: %s", error->message); g_error_free (error); return; } } static void restore_backup (app_data *data, SyncevoSession *session, const char *dir) { char **sources; GHashTableIter iter; int i = 0; char *source; sources = g_malloc0 (sizeof (char*) * (g_hash_table_size (data->emergency_sources) + 1)); g_hash_table_iter_init (&iter, data->emergency_sources); while (g_hash_table_iter_next (&iter, (gpointer)&source, NULL)) { sources[i++] = g_strdup (source); } sources[i] = NULL; syncevo_session_restore (session, dir, TRUE, (const char**)sources, (SyncevoSessionGenericCb)restore_cb, data); g_strfreev (sources); } static void save_config (app_data *data, SyncevoSession *session) { syncevo_session_set_config (session, TRUE, FALSE, data->current_service->config, (SyncevoSessionGenericCb)set_config_cb, data); } static void do_sync (operation_data *op_data, SyncevoSession *session) { GHashTable *source_modes; GHashTableIter iter; source_config *source; SyncevoSyncMode mode = SYNCEVO_SYNC_NONE; app_data *data = op_data->data; source_modes = syncevo_source_modes_new (); if (op_data->operation != OP_SYNC) { /* in an emergency sync, set non-emergency sources to not sync*/ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { if (g_hash_table_lookup (data->emergency_sources, source->name) == NULL) { syncevo_source_modes_add (source_modes, source->name, SYNCEVO_SYNC_NONE); } } } /* override all non-supported with "none". */ g_hash_table_iter_init (&iter, data->current_service->source_configs); while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { if (!source->supported_locally) { syncevo_source_modes_add (source_modes, source->name, SYNCEVO_SYNC_NONE); } } /* use given mode or use default for normal syncs */ switch (op_data->operation) { case OP_SYNC: mode = SYNCEVO_SYNC_DEFAULT; break; case OP_SYNC_SLOW: mode = SYNCEVO_SYNC_SLOW; break; case OP_SYNC_REFRESH_FROM_CLIENT: mode = SYNCEVO_SYNC_REFRESH_FROM_CLIENT; break; case OP_SYNC_REFRESH_FROM_SERVER: mode = SYNCEVO_SYNC_REFRESH_FROM_SERVER; break; default: g_warn_if_reached(); } syncevo_session_sync (session, mode, source_modes, (SyncevoSessionGenericCb)sync_cb, data); syncevo_source_modes_free (source_modes); } static void set_config_for_sync_cb (SyncevoSession *session, GError *error, operation_data *op_data) { if (error) { g_warning ("Error in Session.SetConfig: %s", error->message); g_error_free (error); /* TODO show in UI: sync failed (failed to even start) */ return; } do_sync (op_data, session); } static void run_operation (operation_data *op_data, SyncevoSession *session) { SyncevoConfig *config; /* when we first get idle, start the operation */ if (op_data->started) { return; } op_data->started = TRUE; op_data->data->current_operation = op_data->operation; /* time for business */ switch (op_data->operation) { case OP_SYNC: case OP_SYNC_SLOW: case OP_SYNC_REFRESH_FROM_CLIENT: case OP_SYNC_REFRESH_FROM_SERVER: /* Make sure we don't get change diffs printed out, then sync */ config = g_hash_table_new (g_str_hash, g_str_equal); syncevo_config_set_value (config, NULL, "printChanges", "0"); syncevo_session_set_config (session, TRUE, TRUE, config, (SyncevoSessionGenericCb)set_config_for_sync_cb, op_data); syncevo_config_free (config); break; case OP_SAVE: save_config (op_data->data, session); break; case OP_RESTORE: restore_backup (op_data->data, session, op_data->dir); break; default: g_warn_if_reached (); } } /* Our sync session status */ static void status_changed_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, operation_data *op_data) { switch (status) { case SYNCEVO_STATUS_IDLE: run_operation (op_data, session); break; case SYNCEVO_STATUS_DONE: op_data->data->synced_this_session = TRUE; /* no need for sync session anymore */ g_object_unref (session); /* refresh stats -- the service may no longer be the one syncing, * and we might have only saved config but what the heck... */ syncevo_server_get_reports (op_data->data->server, op_data->data->current_service->name, 0, 1, (SyncevoServerGetReportsCb)get_reports_cb, op_data->data); g_slice_free (operation_data, op_data); default: ; } } /* Our sync (or config-save) session status */ static void get_status_cb (SyncevoSession *session, SyncevoSessionStatus status, guint error_code, SyncevoSourceStatuses *source_statuses, GError *error, operation_data *op_data) { if (error) { g_warning ("Error in Session.GetStatus: %s", error->message); g_error_free (error); g_object_unref (session); switch (op_data->operation) { case OP_SYNC: /* TODO show in UI: sync failed (failed to even start) */ break; case OP_SAVE: /* TODO show in UI: save failed */ break; default: g_warn_if_reached (); } g_slice_free (operation_data, op_data); return; } if (status == SYNCEVO_STATUS_IDLE) { run_operation (op_data, session); } } static void start_session_cb (SyncevoServer *server, char *path, GError *error, operation_data *op_data) { SyncevoSession *session; app_data *data = op_data->data; if (error) { g_warning ("Error in Server.StartSession: %s", error->message); g_error_free (error); g_free (path); switch (op_data->operation) { case OP_SYNC: /* TODO show in UI: sync failed (failed to even start) */ break; case OP_SAVE: /* TODO show in UI: save failed */ break; default: g_warn_if_reached (); } g_slice_free (operation_data, op_data); return; } session = syncevo_session_new (path); if (data->running_session && strcmp (path, syncevo_session_get_path (data->running_session)) != 0) { /* This is a really unfortunate event: Someone got a session and we did not have time to set UI insensitive... */ gtk_label_set_markup (GTK_LABEL (data->server_label), _("Waiting for current operation to finish...")); gtk_widget_show_all (data->sources_box); } /* we want to know about status changes to our session */ g_signal_connect (session, "status-changed", G_CALLBACK (status_changed_cb), op_data); syncevo_session_get_status (session, (SyncevoSessionGetStatusCb)get_status_cb, op_data); g_free (path); } /* TODO: this function should accept source/peer name as param */ char* get_error_string_for_code (int error_code, SyncErrorResponse *response) { if (response) { *response = SYNC_ERROR_RESPONSE_NONE; } switch (error_code) { case -1: /* no errorcode */ case 0: case 200: case LOCERR_USERABORT: case LOCERR_USERSUSPEND: return NULL; case 22000: if (response) { *response = SYNC_ERROR_RESPONSE_EMERGENCY; } /* TRANSLATORS: next strings are error messages. */ return g_strdup (_("A normal sync is not possible at this time. The server " "suggests a slow sync, but this might not always be " "what you want if both ends already have data.")); case 22002: return g_strdup (_("The sync process died unexpectedly.")); case 22003: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup (_("Password request was not answered. You can save the " "password in the settings to prevent the request.")); case 506: /* TODO use the service device name here, this is a remote problem */ return g_strdup (_("There was a problem processing sync request. " "Trying again may help.")); case DB_Unauthorized: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("Failed to login. Could there be a problem with " "your username or password?")); case DB_Forbidden: return g_strdup(_("Forbidden")); case DB_NotFound: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } /* TRANSLATORS: data source means e.g. calendar or addressbook */ return g_strdup(_("A data source could not be found. Could there be a " "problem with the settings?")); case DB_Fatal: case DB_Error: return g_strdup(_("Remote database error")); case LOCAL_STATUS_CODE + DB_Fatal: /* This can happen when EDS is borked, restart it may help... */ return g_strdup(_("There is a problem with the local database. " "Syncing again or rebooting may help.")); case DB_Full: return g_strdup(_("No space on disk")); case LOCERR_PROCESSMSG: return g_strdup(_("Failed to process SyncML")); case LOCERR_AUTHFAIL: return g_strdup(_("Server authorization failed")); case LOCERR_CFGPARSE: return g_strdup(_("Failed to parse configuration file")); case LOCERR_CFGREAD: return g_strdup(_("Failed to read configuration file")); case LOCERR_NOCFG: return g_strdup(_("No configuration found")); case LOCERR_NOCFGFILE: return g_strdup(_("No configuration file found")); case LOCERR_BADCONTENT: return g_strdup(_("Server sent bad content")); case LOCERR_CERT_EXPIRED: return g_strdup(_("Connection certificate has expired")); case LOCERR_CERT_INVALID: return g_strdup(_("Connection certificate is invalid")); case LOCERR_CONN: case LOCERR_NOCONN: case LOCERR_TRANSPFAIL: case LOCERR_TIMEOUT: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("We were unable to connect to the server. The problem " "could be temporary or there could be something wrong " "with the settings.")); case LOCERR_BADURL: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("The server URL is bad")); case LOCERR_SRVNOTFOUND: if (response) { *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; } return g_strdup(_("The server was not found")); default: return g_strdup_printf (_("Error %d"), error_code); } } static void server_shutdown_cb (SyncevoServer *server, app_data *data) { if (data->syncing) { gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Sync failed")); set_sync_progress (data, 1.0 , ""); set_app_state (data, SYNC_UI_STATE_SERVER_OK); } /* re-init server here */ } static void set_running_session (app_data *data, const char *path) { if (data->running_session) { g_object_unref (data->running_session); } if (!path) { data->running_session = NULL; return; } data->running_session = syncevo_session_new (path); g_signal_connect (data->running_session, "progress-changed", G_CALLBACK (running_session_progress_changed_cb), data); g_signal_connect (data->running_session, "status-changed", G_CALLBACK (running_session_status_changed_cb), data); syncevo_session_get_status (data->running_session, (SyncevoSessionGetStatusCb)get_running_session_status_cb, data); } static void set_online_status (app_data *data, gboolean online) { if (online != data->online) { data->online = online; if (data->current_state == SYNC_UI_STATE_SERVER_OK) { if (data->online) { gtk_widget_hide (data->no_connection_box); } else { gtk_widget_show (data->no_connection_box); } } gtk_widget_set_sensitive (data->sync_btn, data->online); } } static void get_presence_cb (SyncevoServer *server, char *status, char **transports, GError *error, app_data *data) { if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* non-fatal, ignore in UI */ return; } if (data->current_service && status) { set_online_status (data, strcmp (status, "") == 0); } g_free (status); g_strfreev (transports); } static void password_dialog_response_cb (GtkWidget *dialog, int response, app_data *data) { const char *password; GHashTable *return_dict; return_dict = g_hash_table_new (g_str_hash, g_str_equal); if (response == GTK_RESPONSE_OK) { password = gtk_entry_get_text (GTK_ENTRY (data->password_dialog_entry)); g_hash_table_insert (return_dict, "password", (gpointer)password); } syncevo_server_info_response (data->server, data->password_dialog_id, "response", return_dict, NULL, NULL); g_hash_table_destroy (return_dict); g_free (data->password_dialog_id); data->password_dialog_id = NULL; gtk_widget_destroy (dialog); } static void info_request_cb (SyncevoServer *syncevo, char *id, char *session_path, char *state, char *handler_path, char *type, GHashTable *parameters, app_data *data) { GHashTable *t; GtkWidget *dialog, *content, *label, *align; char *msg; if (g_strcmp0 (state, "request") != 0 || g_strcmp0 (type, "password") != 0) { /* not handling other stuff */ return; } if (!data->running_session || g_strcmp0 (session_path, syncevo_session_get_path (data->running_session)) != 0) { /* not our problem */ return; } t = g_hash_table_new (g_str_hash, g_str_equal); syncevo_server_info_response (syncevo, id, "working", t, NULL, NULL); g_hash_table_destroy (t); data->password_dialog_id = g_strdup (id); /* TRANSLATORS: password request dialog contents: title, cancel button * and ok button */ dialog = gtk_dialog_new_with_buttons (_("Password is required for sync"), GTK_WINDOW (data->sync_win), GTK_DIALOG_DESTROY_WITH_PARENT, _("Cancel sync"), GTK_RESPONSE_CANCEL, _("Sync with password"), GTK_RESPONSE_OK, NULL); content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); /* TRANSLATORS: password request dialog message, placeholder is service name */ msg = g_strdup_printf (_("Please enter password for syncing with %s:"), data->current_service->pretty_name); label = gtk_label_new (msg); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_widget_set_size_request (label, 500, -1); gtk_widget_show (label); gtk_container_add (GTK_CONTAINER (align), label); g_free (msg); align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); gtk_widget_show (align); gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); data->password_dialog_entry = gtk_entry_new (); gtk_entry_set_max_length (GTK_ENTRY (data->password_dialog_entry), 99); gtk_entry_set_width_chars (GTK_ENTRY (data->password_dialog_entry), 30); gtk_entry_set_visibility (GTK_ENTRY (data->password_dialog_entry), FALSE); gtk_widget_show (data->password_dialog_entry); gtk_container_add (GTK_CONTAINER (align), data->password_dialog_entry); g_signal_connect (dialog, "response", G_CALLBACK (password_dialog_response_cb), data); gtk_window_present (GTK_WINDOW (dialog)); gtk_widget_grab_focus (data->password_dialog_entry); } static void server_presence_changed_cb (SyncevoServer *server, char *config_name, char *status, char *transport, app_data *data) { if (data->current_service && config_name && status && g_ascii_strcasecmp (data->current_service->name, config_name) == 0) { set_online_status (data, strcmp (status, "") == 0); } } static void server_templates_changed_cb (SyncevoServer *server, app_data *data) { if (gtk_widget_get_visible (data->services_box)) { update_services_list (data); } } static void server_session_changed_cb (SyncevoServer *server, char *path, gboolean started, app_data *data) { if (started) { set_running_session (data, path); } else if (data->running_session && strcmp (syncevo_session_get_path (data->running_session), path) == 0 ) { set_running_session (data, NULL); } } static void get_sessions_cb (SyncevoServer *server, SyncevoSessions *sessions, GError *error, app_data *data) { const char *path; if (error) { g_warning ("Server.GetSessions failed: %s", error->message); g_error_free (error); /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ return; } /* assume first one is active */ path = syncevo_sessions_index (sessions, 0); set_running_session (data, path); syncevo_sessions_free (sessions); } static void get_config_for_default_peer_cb (SyncevoServer *syncevo, SyncevoConfig *config, GError *error, app_data *data) { char *name; if (error) { g_warning ("Server.GetConfig failed: %s", error->message); g_error_free (error); /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ return; } syncevo_config_get_value (config, NULL, "defaultPeer", &name); reload_config (data, name); syncevo_config_free (config); } app_data* sync_ui_create () { app_data *data; data = g_slice_new0 (app_data); data->online = TRUE; data->current_state = SYNC_UI_STATE_GETTING_SERVER; data->forced_emergency = FALSE; data->emergency_sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!init_ui (data)) { return NULL; } data->server = syncevo_server_get_default(); g_signal_connect (data->server, "shutdown", G_CALLBACK (server_shutdown_cb), data); g_signal_connect (data->server, "session-changed", G_CALLBACK (server_session_changed_cb), data); g_signal_connect (data->server, "presence-changed", G_CALLBACK (server_presence_changed_cb), data); g_signal_connect (data->server, "templates-changed", G_CALLBACK (server_templates_changed_cb), data); g_signal_connect (data->server, "info-request", G_CALLBACK (info_request_cb), data); syncevo_server_get_config (data->server, "", FALSE, (SyncevoServerGetConfigCb)get_config_for_default_peer_cb, data); syncevo_server_get_sessions (data->server, (SyncevoServerGetSessionsCb)get_sessions_cb, data); gtk_window_present (GTK_WINDOW (data->sync_win)); return data; } void sync_ui_show_settings (app_data *data, const char *id) { show_services_list (data, id); } GtkWindow* sync_ui_get_main_window (app_data *data) { return GTK_WINDOW(data->sync_win); }