diff --git a/command.h b/command.h index 6fd256541b9f..5c036c4dd2d7 100644 --- a/command.h +++ b/command.h @@ -152,6 +152,10 @@ enum event_command #ifdef HAVE_CLOUDSYNC /* Trigger cloud sync */ CMD_EVENT_CLOUD_SYNC, + /* Resolve cloud sync conflicts by keeping local files */ + CMD_EVENT_CLOUD_SYNC_RESOLVE_KEEP_LOCAL, + /* Resolve cloud sync conflicts by keeping server files */ + CMD_EVENT_CLOUD_SYNC_RESOLVE_KEEP_SERVER, #endif /* Shutdown the OS */ CMD_EVENT_SHUTDOWN, diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index bc9096010e7d..c73e6221b9fa 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3022,6 +3022,14 @@ MSG_HASH( MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_NOW, "cloud_sync_sync_now" ) +MSG_HASH( + MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_LOCAL, + "cloud_sync_resolve_keep_local" + ) +MSG_HASH( + MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_SERVER, + "cloud_sync_resolve_keep_server" + ) MSG_HASH( MENU_ENUM_LABEL_RDB_ENTRY, "rdb_entry" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index e27b0b521e15..07416aa1e6e5 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -239,6 +239,22 @@ MSG_HASH( MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_NOW, "Manually trigger cloud synchronization." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_RESOLVE_KEEP_LOCAL, + "Resolve Conflicts: Keep Local" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_RESOLVE_KEEP_LOCAL, + "Resolve all conflicts by uploading local files to the server." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_RESOLVE_KEEP_SERVER, + "Resolve Conflicts: Keep Server" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CLOUD_SYNC_RESOLVE_KEEP_SERVER, + "Resolve all conflicts by downloading server files, replacing local copies." + ) MSG_HASH( MENU_ENUM_SUBLABEL_QUIT_RETROARCH_NOSAVE, "Quit RetroArch application. Configuration save on exit is disabled." diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index d48c4e4cd09c..59564c220970 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -636,6 +636,8 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_core_list_unload, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_download_core, MENU_ENUM_SUBLABEL_DOWNLOAD_CORE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_update_installed_cores, MENU_ENUM_SUBLABEL_UPDATE_INSTALLED_CORES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_sync_now, MENU_ENUM_SUBLABEL_CLOUD_SYNC_SYNC_NOW) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_resolve_keep_local, MENU_ENUM_SUBLABEL_CLOUD_SYNC_RESOLVE_KEEP_LOCAL) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_resolve_keep_server, MENU_ENUM_SUBLABEL_CLOUD_SYNC_RESOLVE_KEEP_SERVER) #if defined(ANDROID) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_switch_installed_cores_pfd, MENU_ENUM_SUBLABEL_SWITCH_INSTALLED_CORES_PFD) #endif @@ -4632,6 +4634,12 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_NOW: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_sync_now); break; + case MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_LOCAL: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_resolve_keep_local); + break; + case MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_SERVER: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_resolve_keep_server); + break; #if defined(ANDROID) case MENU_ENUM_LABEL_SWITCH_INSTALLED_CORES_PFD: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_switch_installed_cores_pfd); diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 6ec6ea2e3081..6920acd42f6e 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -11116,6 +11116,23 @@ unsigned menu_displaylist_build_list( false) == 0) count++; } + + /* Add action items when cloud sync is enabled */ + if (settings->bools.cloud_sync_enable) + { + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_NOW, + PARSE_ACTION, false) == 0) + count++; + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_LOCAL, + PARSE_ACTION, false) == 0) + count++; + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_SERVER, + PARSE_ACTION, false) == 0) + count++; + } } break; #ifdef HAVE_MIST diff --git a/menu/menu_setting.c b/menu/menu_setting.c index cc6d115642b9..832a4c11d9ed 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -10222,6 +10222,24 @@ static bool setting_append_list( &subgroup_info, parent_group); MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_CLOUD_SYNC); + + CONFIG_ACTION( + list, list_info, + MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_LOCAL, + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_RESOLVE_KEEP_LOCAL, + &group_info, + &subgroup_info, + parent_group); + MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_CLOUD_SYNC_RESOLVE_KEEP_LOCAL); + + CONFIG_ACTION( + list, list_info, + MENU_ENUM_LABEL_CLOUD_SYNC_RESOLVE_KEEP_SERVER, + MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_RESOLVE_KEEP_SERVER, + &group_info, + &subgroup_info, + parent_group); + MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_CLOUD_SYNC_RESOLVE_KEEP_SERVER); #endif CONFIG_ACTION( @@ -11795,6 +11813,9 @@ static bool setting_append_list( general_write_handler, general_read_handler, SD_FLAG_NONE); + (*list)[list_info->index - 1].action_ok = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_left = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_right = &setting_bool_action_right_with_refresh; CONFIG_BOOL( list, list_info, diff --git a/msg_hash.h b/msg_hash.h index 42012d692493..57ef945b24c2 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -3302,6 +3302,8 @@ enum msg_hash_enums MENU_LABEL(CLOUD_SYNC_USERNAME), MENU_LABEL(CLOUD_SYNC_PASSWORD), MENU_LABEL(CLOUD_SYNC_SYNC_NOW), + MENU_LABEL(CLOUD_SYNC_RESOLVE_KEEP_LOCAL), + MENU_LABEL(CLOUD_SYNC_RESOLVE_KEEP_SERVER), MENU_LABEL(RECORDING_SETTINGS), MENU_LABEL(OVERLAY_SETTINGS), MENU_LABEL(REWIND_SETTINGS), diff --git a/retroarch.c b/retroarch.c index d4678031c920..8ba4f0323aa6 100644 --- a/retroarch.c +++ b/retroarch.c @@ -4641,6 +4641,12 @@ bool command_event(enum event_command cmd, void *data) case CMD_EVENT_CLOUD_SYNC: task_push_cloud_sync(); break; + case CMD_EVENT_CLOUD_SYNC_RESOLVE_KEEP_LOCAL: + task_push_cloud_sync_resolve_keep_local(); + break; + case CMD_EVENT_CLOUD_SYNC_RESOLVE_KEEP_SERVER: + task_push_cloud_sync_resolve_keep_server(); + break; #endif case CMD_EVENT_MENU_RESET_TO_DEFAULT_CONFIG: config_set_defaults(global_get_ptr()); diff --git a/tasks/task_cloudsync.c b/tasks/task_cloudsync.c index 5a818f4b26d0..75fd4e70dddb 100644 --- a/tasks/task_cloudsync.c +++ b/tasks/task_cloudsync.c @@ -69,6 +69,8 @@ typedef struct bool need_manifest_uploaded; bool failures; bool conflicts; + /* Conflict resolution mode: 0=none, 1=keep_local, 2=keep_server */ + int conflict_resolution; uint32_t uploads; uint32_t downloads; retro_time_t start_time; @@ -76,6 +78,10 @@ typedef struct static slock_t *tcs_running_lock = NULL; +/* Forward declarations for conflict resolution */ +static void task_cloud_sync_upload_current_file(task_cloud_sync_state_t *sync_state); +static void task_cloud_sync_fetch_server_file(task_cloud_sync_state_t *sync_state); + static void task_cloud_sync_begin_handler(void *user_data, const char *path, bool success, RFILE *file) { retro_task_t *task = (retro_task_t *)user_data; @@ -680,19 +686,28 @@ static void task_cloud_sync_fetch_server_file(task_cloud_sync_state_t *sync_stat static void task_cloud_sync_resolve_conflict(task_cloud_sync_state_t *sync_state) { - /* - * rather than pop up some UI let's just resolve it ourselves! - * three options: - * 1. rename the server file and replace it - * 2. rename the local file and replace it - * 3. ignore it - * If we ignore it then we need to keep it out of the new local manifest - */ struct item_file *server_file = &sync_state->server_manifest->list[sync_state->server_idx]; - RARCH_WARN(CSPFX "Conflicting change of %s.\n", CS_FILE_KEY(server_file)); - task_cloud_sync_add_to_updated_manifest(sync_state, CS_FILE_KEY(server_file), CS_FILE_HASH(server_file), true); - /* no need to mark need_manifest_uploaded, nothing changed */ - sync_state->conflicts = true; + + if (sync_state->conflict_resolution == 1) + { + /* Keep local: upload local file to server */ + RARCH_LOG(CSPFX "Conflict on %s, keeping local.\n", CS_FILE_KEY(server_file)); + task_cloud_sync_upload_current_file(sync_state); + } + else if (sync_state->conflict_resolution == 2) + { + /* Keep server: download server file */ + RARCH_LOG(CSPFX "Conflict on %s, keeping server.\n", CS_FILE_KEY(server_file)); + task_cloud_sync_fetch_server_file(sync_state); + } + else + { + /* Default: ignore the conflict, keep file out of local manifest */ + RARCH_WARN(CSPFX "Conflicting change of %s.\n", CS_FILE_KEY(server_file)); + task_cloud_sync_add_to_updated_manifest(sync_state, CS_FILE_KEY(server_file), CS_FILE_HASH(server_file), true); + /* no need to mark need_manifest_uploaded, nothing changed */ + sync_state->conflicts = true; + } } static void task_cloud_sync_upload_cb(void *user_data, const char *path, bool success, RFILE *file) @@ -1333,7 +1348,7 @@ static bool task_cloud_sync_task_finder(retro_task_t *task, void *user_data) return task->handler == task_cloud_sync_task_handler; } -void task_push_cloud_sync(void) +static void task_push_cloud_sync_with_mode(int conflict_resolution) { char task_title[128]; task_finder_data_t find_data; @@ -1364,8 +1379,9 @@ void task_push_cloud_sync(void) return; } - sync_state->phase = CLOUD_SYNC_PHASE_BEGIN; - sync_state->start_time = cpu_features_get_time_usec(); + sync_state->phase = CLOUD_SYNC_PHASE_BEGIN; + sync_state->start_time = cpu_features_get_time_usec(); + sync_state->conflict_resolution = conflict_resolution; strlcpy(task_title, "Cloud Sync in progress", sizeof(task_title)); @@ -1377,6 +1393,11 @@ void task_push_cloud_sync(void) task_queue_push(task); } +void task_push_cloud_sync(void) +{ + task_push_cloud_sync_with_mode(0); +} + void task_push_cloud_sync_update_driver(void) { char manifest_path[PATH_MAX_LENGTH]; @@ -1392,3 +1413,15 @@ void task_push_cloud_sync_update_driver(void) task_cloud_sync_manifest_filename(manifest_path, sizeof(manifest_path), false); filestream_delete(manifest_path); } + +void task_push_cloud_sync_resolve_keep_local(void) +{ + RARCH_LOG(CSPFX "Starting sync with conflict resolution: keep local.\n"); + task_push_cloud_sync_with_mode(1); +} + +void task_push_cloud_sync_resolve_keep_server(void) +{ + RARCH_LOG(CSPFX "Starting sync with conflict resolution: keep server.\n"); + task_push_cloud_sync_with_mode(2); +} diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index 055281132ca4..54f853780613 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -275,6 +275,8 @@ extern const char* const input_builtin_autoconfs[]; /* cloud sync tasks */ void task_push_cloud_sync_update_driver(void); void task_push_cloud_sync(void); +void task_push_cloud_sync_resolve_keep_local(void); +void task_push_cloud_sync_resolve_keep_server(void); RETRO_END_DECLS