Cloud Sync (#15548)

* cloud sync - algorithm and abstract cloud storage API

* WebDAV cloud sync driver, and associated net_http improvements

* Cloud sync settings menu
This commit is contained in:
Eric Warmenhoven 2023-08-04 03:20:50 -04:00 committed by GitHub
parent 45af11efe9
commit 893b0d142e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 2667 additions and 26 deletions

View file

@ -1490,6 +1490,7 @@ static struct config_array_setting *populate_settings_array(settings_t *settings
SETTING_ARRAY("bluetooth_driver", settings->arrays.bluetooth_driver, false, NULL, true);
SETTING_ARRAY("wifi_driver", settings->arrays.wifi_driver, false, NULL, true);
SETTING_ARRAY("location_driver", settings->arrays.location_driver,false, NULL, true);
SETTING_ARRAY("cloud_sync_driver", settings->arrays.cloud_sync_driver, false, NULL, true);
#ifdef HAVE_MENU
SETTING_ARRAY("menu_driver", settings->arrays.menu_driver, false, NULL, true);
#endif
@ -1516,6 +1517,9 @@ static struct config_array_setting *populate_settings_array(settings_t *settings
SETTING_ARRAY("midi_driver", settings->arrays.midi_driver, false, NULL, true);
SETTING_ARRAY("midi_input", settings->arrays.midi_input, true, DEFAULT_MIDI_INPUT, true);
SETTING_ARRAY("midi_output", settings->arrays.midi_output, true, DEFAULT_MIDI_OUTPUT, true);
SETTING_ARRAY("webdav_url", settings->arrays.webdav_url, false, NULL, true);
SETTING_ARRAY("webdav_username", settings->arrays.webdav_username, false, NULL, true);
SETTING_ARRAY("webdav_password", settings->arrays.webdav_password, false, NULL, true);
SETTING_ARRAY("youtube_stream_key", settings->arrays.youtube_stream_key, true, NULL, true);
SETTING_ARRAY("twitch_stream_key", settings->arrays.twitch_stream_key, true, NULL, true);
SETTING_ARRAY("facebook_stream_key", settings->arrays.facebook_stream_key, true, NULL, true);
@ -1848,6 +1852,8 @@ static struct config_bool_setting *populate_settings_bool(
SETTING_BOOL("core_updater_show_experimental_cores", &settings->bools.network_buildbot_show_experimental_cores, true, DEFAULT_NETWORK_BUILDBOT_SHOW_EXPERIMENTAL_CORES, false);
SETTING_BOOL("core_updater_auto_backup", &settings->bools.core_updater_auto_backup, true, DEFAULT_CORE_UPDATER_AUTO_BACKUP, false);
SETTING_BOOL("camera_allow", &settings->bools.camera_allow, true, false, false);
SETTING_BOOL("cloud_sync_enable", &settings->bools.cloud_sync_enable, true, false, false);
SETTING_BOOL("cloud_sync_destructive", &settings->bools.cloud_sync_destructive, true, false, false);
SETTING_BOOL("discord_allow", &settings->bools.discord_enable, true, false, false);
#if defined(VITA)
SETTING_BOOL("input_backtouch_enable", &settings->bools.input_backtouch_enable, false, DEFAULT_INPUT_BACKTOUCH_ENABLE, false);

View file

@ -427,6 +427,7 @@ typedef struct settings
char wifi_driver[32];
char led_driver[32];
char location_driver[32];
char cloud_sync_driver[32];
char menu_driver[32];
char cheevos_username[32];
char cheevos_password[256];
@ -460,6 +461,10 @@ typedef struct settings
char translation_service_url[2048];
char webdav_url[255];
char webdav_username[255];
char webdav_password[255];
char youtube_stream_key[PATH_MAX_LENGTH];
char twitch_stream_key[PATH_MAX_LENGTH];
char facebook_stream_key[PATH_MAX_LENGTH];
@ -899,6 +904,10 @@ typedef struct settings
bool steam_rich_presence_enable;
#endif
/* Cloud Sync */
bool cloud_sync_enable;
bool cloud_sync_destructive;
/* Misc. */
bool discord_enable;
bool threaded_data_runloop_enable;

View file

@ -1621,3 +1621,12 @@ STEAM INTEGRATION USING MIST
#ifdef HAVE_PRESENCE
#include "../network/presence.c"
#endif
/*============================================================
CLOUD SYNC
============================================================ */
#ifdef HAVE_CLOUDSYNC
#include "../tasks/task_cloudsync.c"
#include "../network/cloud_sync_driver.c"
#include "../network/cloud_sync/webdav.c"
#endif

View file

@ -1409,6 +1409,10 @@ MSG_HASH(
MENU_ENUM_LABEL_DEFERRED_SAVING_SETTINGS_LIST,
"deferred_saving_settings_list"
)
MSG_HASH(
MENU_ENUM_LABEL_DEFERRED_CLOUD_SYNC_SETTINGS_LIST,
"deferred_cloud_sync_settings_list"
)
MSG_HASH(
MENU_ENUM_LABEL_DEFERRED_THUMBNAILS_UPDATER_LIST,
"deferred_thumbnails_updater_list"
@ -3483,6 +3487,34 @@ MSG_HASH(
MENU_ENUM_LABEL_SAVING_SETTINGS,
"saving_settings"
)
MSG_HASH(
MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS,
"cloud_sync_settings"
)
MSG_HASH(
MENU_ENUM_LABEL_CLOUD_SYNC_ENABLE,
"cloud_sync_enable"
)
MSG_HASH(
MENU_ENUM_LABEL_CLOUD_SYNC_DESTRUCTIVE,
"cloud_sync_destructive"
)
MSG_HASH(
MENU_ENUM_LABEL_CLOUD_SYNC_DRIVER,
"cloud_sync_driver"
)
MSG_HASH(
MENU_ENUM_LABEL_CLOUD_SYNC_URL,
"cloud_sync_url"
)
MSG_HASH(
MENU_ENUM_LABEL_CLOUD_SYNC_USERNAME,
"cloud_sync_username"
)
MSG_HASH(
MENU_ENUM_LABEL_CLOUD_SYNC_PASSWORD,
"cloud_sync_password"
)
MSG_HASH(
MENU_ENUM_LABEL_SCAN_DIRECTORY,
"scan_directory"

View file

@ -1204,6 +1204,62 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_SAVING_SETTINGS,
"Change saving settings."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SETTINGS,
"Cloud Sync"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_CLOUD_SYNC_SETTINGS,
"Change cloud sync settings."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_ENABLE,
"Enable Cloud Sync"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_CLOUD_SYNC_ENABLE,
"Attempt to sync configs, sram, and states to a cloud storage provider."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE,
"Destructive Cloud Sync"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE,
"When disabled, files are moved to a backup folder before being overwritten or deleted."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DRIVER,
"Cloud Sync Backend"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_CLOUD_SYNC_DRIVER,
"Which cloud storage network protocol to use."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_URL,
"Cloud Storage URL"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_CLOUD_SYNC_URL,
"The URL for the API entry point to the cloud storage service."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_USERNAME,
"Username"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_CLOUD_SYNC_USERNAME,
"Your username for your cloud storage account."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_PASSWORD,
"Password"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_CLOUD_SYNC_PASSWORD,
"Your password for your cloud storage account."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_LOGGING_SETTINGS,
"Logging"

View file

@ -51,6 +51,9 @@ void net_http_connection_set_user_agent(struct http_connection_t *conn, const ch
void net_http_connection_set_headers(struct http_connection_t *conn, const char *headers);
void net_http_connection_set_content(struct http_connection_t *conn, const char *content_type,
size_t content_length, const void *content);
const char *net_http_connection_url(struct http_connection_t *conn);
const char* net_http_connection_method(struct http_connection_t* conn);
@ -93,6 +96,17 @@ int net_http_status(struct http_t *state);
**/
bool net_http_error(struct http_t *state);
/**
* net_http_headers:
*
* Leaf function.
*
* @return the response headers. The returned buffer is owned by the
* caller of net_http_new; it is not freed by net_http_delete.
* If the status is not 20x and accept_error is false, it returns NULL.
**/
struct string_list *net_http_headers(struct http_t *state);
/**
* net_http_data:
*

View file

@ -33,6 +33,7 @@
#include <compat/strl.h>
#include <string/stdstring.h>
#include <string.h>
#include <lists/string_list.h>
#include <retro_common_api.h>
#include <retro_miscellaneous.h>
@ -63,6 +64,7 @@ struct http_socket_state_t
struct http_t
{
char *data;
struct string_list *headers;
struct http_socket_state_t sock_state; /* ptr alignment */
size_t pos;
size_t len;
@ -81,10 +83,11 @@ struct http_connection_t
char *scan;
char *methodcopy;
char *contenttypecopy;
char *postdatacopy;
void *postdatacopy;
char *useragentcopy;
char *headerscopy;
struct http_socket_state_t sock_state; /* ptr alignment */
size_t contentlength;
int port;
};
@ -530,7 +533,10 @@ struct http_connection_t *net_http_connection_new(const char *url,
conn->methodcopy = strdup(method);
if (data)
{
conn->postdatacopy = strdup(data);
conn->contentlength = strlen(data);
}
if (!(conn->urlcopy = strdup(url)))
goto error;
@ -703,6 +709,25 @@ void net_http_connection_set_headers(
conn->headerscopy = headers ? strdup(headers) : NULL;
}
void net_http_connection_set_content(
struct http_connection_t *conn, const char *content_type,
size_t content_length, const void *content)
{
if (conn->contenttypecopy)
free(conn->contenttypecopy);
if (conn->postdatacopy)
free(conn->postdatacopy);
conn->contenttypecopy = content_type ? strdup(content_type) : NULL;
conn->contentlength = content_length;
if (content_length)
{
conn->postdatacopy = malloc(content_length);
memcpy(conn->postdatacopy, content, content_length);
}
}
const char *net_http_connection_url(struct http_connection_t *conn)
{
return conn->urlcopy;
@ -767,8 +792,7 @@ struct http_t *net_http_new(struct http_connection_t *conn)
if (conn->headerscopy)
net_http_send_str(&conn->sock_state, &error, conn->headerscopy,
strlen(conn->headerscopy));
/* This is not being set anywhere yet */
else if (conn->contenttypecopy)
if (conn->contenttypecopy)
{
net_http_send_str(&conn->sock_state, &error, "Content-Type: ",
STRLEN_CONST("Content-Type: "));
@ -778,12 +802,12 @@ struct http_t *net_http_new(struct http_connection_t *conn)
STRLEN_CONST("\r\n"));
}
if (conn->methodcopy && (string_is_equal(conn->methodcopy, "POST")))
if (conn->methodcopy && (string_is_equal(conn->methodcopy, "POST") || string_is_equal(conn->methodcopy, "PUT")))
{
size_t post_len, len;
char *len_str = NULL;
if (!conn->postdatacopy)
if (!conn->postdatacopy && !string_is_equal(conn->methodcopy, "PUT"))
goto err;
if (!conn->headerscopy)
@ -799,7 +823,7 @@ struct http_t *net_http_new(struct http_connection_t *conn)
net_http_send_str(&conn->sock_state, &error, "Content-Length: ",
STRLEN_CONST("Content-Length: "));
post_len = strlen(conn->postdatacopy);
post_len = conn->contentlength;
#ifdef _WIN32
len = snprintf(NULL, 0, "%" PRIuPTR, post_len);
len_str = (char*)malloc(len + 1);
@ -836,9 +860,9 @@ struct http_t *net_http_new(struct http_connection_t *conn)
net_http_send_str(&conn->sock_state, &error, "\r\n",
STRLEN_CONST("\r\n"));
if (conn->methodcopy && (string_is_equal(conn->methodcopy, "POST")))
if (conn->postdatacopy && conn->contentlength)
net_http_send_str(&conn->sock_state, &error, conn->postdatacopy,
strlen(conn->postdatacopy));
conn->contentlength);
if (!error)
{
@ -854,7 +878,12 @@ struct http_t *net_http_new(struct http_connection_t *conn)
state->buflen = 512;
if ((state->data = (char*)malloc(state->buflen)))
return state;
{
if ((state->headers = string_list_new()) &&
string_list_initialize(state->headers))
return state;
string_list_free(state->headers);
}
free(state);
}
@ -865,6 +894,8 @@ err:
free(conn->methodcopy);
if (conn->contenttypecopy)
free(conn->contenttypecopy);
if (conn->postdatacopy)
free(conn->postdatacopy);
conn->methodcopy = NULL;
conn->contenttypecopy = NULL;
conn->postdatacopy = NULL;
@ -981,12 +1012,24 @@ bool net_http_update(struct http_t *state, size_t* progress, size_t* total)
if (string_is_equal_case_insensitive(state->data, "Transfer-Encoding: chunked"))
state->bodytype = T_CHUNK;
/* TODO: save headers somewhere */
if (state->data[0]=='\0')
{
state->part = P_BODY;
if (state->bodytype == T_CHUNK)
state->part = P_BODY_CHUNKLEN;
if (state->status == 100)
{
state->part = P_HEADER_TOP;
}
else
{
state->part = P_BODY;
if (state->bodytype == T_CHUNK)
state->part = P_BODY_CHUNKLEN;
}
}
else
{
union string_list_elem_attr attr;
attr.i = 0;
string_list_append(state->headers, state->data, attr);
}
}
@ -1007,7 +1050,7 @@ bool net_http_update(struct http_t *state, size_t* progress, size_t* total)
{
if (state->error)
newlen = -1;
else
else if (state->len)
{
#ifdef HAVE_SSL
if (state->sock_state.ssl && state->sock_state.ssl_ctx)
@ -1158,6 +1201,26 @@ int net_http_status(struct http_t *state)
return state->status;
}
/**
* net_http_headers:
*
* Leaf function.
*
* @return the response headers. The returned buffer is owned by the
* caller of net_http_new; it is not freed by net_http_delete().
* If the status is not 20x and accept_error is false, it returns NULL.
**/
struct string_list *net_http_headers(struct http_t *state)
{
if (!state)
return NULL;
if (state->error)
return NULL;
return state->headers;
}
/**
* net_http_data:
*

View file

@ -234,11 +234,24 @@ int ssl_socket_send_all_blocking(void *state_data,
mbedtls_net_set_block(&state->net_ctx);
while ((ret = mbedtls_ssl_write(&state->ctx, data, size)) <= 0)
while (size)
{
if ( ret != MBEDTLS_ERR_SSL_WANT_READ &&
ret != MBEDTLS_ERR_SSL_WANT_WRITE)
return false;
ret = mbedtls_ssl_write(&state->ctx, data, size);
if (!ret)
continue;
if (ret < 0)
{
if ( ret != MBEDTLS_ERR_SSL_WANT_READ &&
ret != MBEDTLS_ERR_SSL_WANT_WRITE)
return false;
}
else
{
data += ret;
size -= ret;
}
}
return true;

View file

@ -57,6 +57,7 @@ enum string_list_type
STRING_LIST_INPUT_JOYPAD_DRIVERS,
STRING_LIST_INPUT_HID_DRIVERS,
STRING_LIST_RECORD_DRIVERS,
STRING_LIST_CLOUD_SYNC_DRIVERS,
#ifdef HAVE_LAKKA
STRING_LIST_TIMEZONES,
#endif

View file

@ -176,6 +176,7 @@ GENERIC_DEFERRED_PUSH(deferred_push_video_hdr_settings_list, DISPLAYLIST_
GENERIC_DEFERRED_PUSH(deferred_push_crt_switchres_settings_list, DISPLAYLIST_CRT_SWITCHRES_SETTINGS_LIST)
GENERIC_DEFERRED_PUSH(deferred_push_configuration_settings_list, DISPLAYLIST_CONFIGURATION_SETTINGS_LIST)
GENERIC_DEFERRED_PUSH(deferred_push_saving_settings_list, DISPLAYLIST_SAVING_SETTINGS_LIST)
GENERIC_DEFERRED_PUSH(deferred_push_cloud_sync_settings_list, DISPLAYLIST_CLOUD_SYNC_SETTINGS_LIST)
GENERIC_DEFERRED_PUSH(deferred_push_mixer_stream_settings_list, DISPLAYLIST_MIXER_STREAM_SETTINGS_LIST)
GENERIC_DEFERRED_PUSH(deferred_push_logging_settings_list, DISPLAYLIST_LOGGING_SETTINGS_LIST)
GENERIC_DEFERRED_PUSH(deferred_push_frame_throttle_settings_list, DISPLAYLIST_FRAME_THROTTLE_SETTINGS_LIST)
@ -702,6 +703,7 @@ static int menu_cbs_init_bind_deferred_push_compare_label(
{MENU_ENUM_LABEL_DEFERRED_CORE_INFORMATION_LIST, deferred_push_core_information_list},
{MENU_ENUM_LABEL_DEFERRED_CONFIGURATION_SETTINGS_LIST, deferred_push_configuration_settings_list},
{MENU_ENUM_LABEL_DEFERRED_SAVING_SETTINGS_LIST, deferred_push_saving_settings_list},
{MENU_ENUM_LABEL_DEFERRED_CLOUD_SYNC_SETTINGS_LIST, deferred_push_cloud_sync_settings_list},
{MENU_ENUM_LABEL_DEFERRED_MIXER_STREAM_SETTINGS_LIST, deferred_push_mixer_stream_settings_list},
{MENU_ENUM_LABEL_DEFERRED_LOGGING_SETTINGS_LIST, deferred_push_logging_settings_list},
{MENU_ENUM_LABEL_DEFERRED_FRAME_THROTTLE_SETTINGS_LIST, deferred_push_frame_throttle_settings_list},
@ -1262,8 +1264,11 @@ static int menu_cbs_init_bind_deferred_push_compare_label(
case MENU_ENUM_LABEL_DEFERRED_SAVING_SETTINGS_LIST:
BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_saving_settings_list);
break;
case MENU_ENUM_LABEL_DEFERRED_CLOUD_SYNC_SETTINGS_LIST:
BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_cloud_sync_settings_list);
break;
case MENU_ENUM_LABEL_DEFERRED_LOGGING_SETTINGS_LIST:
BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_saving_settings_list);
BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_logging_settings_list);
break;
case MENU_ENUM_LABEL_DEFERRED_FRAME_THROTTLE_SETTINGS_LIST:
BIND_ACTION_DEFERRED_PUSH(cbs, deferred_push_frame_throttle_settings_list);

View file

@ -366,6 +366,8 @@ static enum msg_hash_enums action_ok_dl_to_enum(unsigned lbl)
return MENU_ENUM_LABEL_DEFERRED_CONFIGURATION_SETTINGS_LIST;
case ACTION_OK_DL_SAVING_SETTINGS_LIST:
return MENU_ENUM_LABEL_DEFERRED_SAVING_SETTINGS_LIST;
case ACTION_OK_DL_CLOUD_SYNC_SETTINGS_LIST:
return MENU_ENUM_LABEL_DEFERRED_CLOUD_SYNC_SETTINGS_LIST;
case ACTION_OK_DL_LOGGING_SETTINGS_LIST:
return MENU_ENUM_LABEL_DEFERRED_LOGGING_SETTINGS_LIST;
case ACTION_OK_DL_FRAME_THROTTLE_SETTINGS_LIST:
@ -1672,6 +1674,7 @@ int generic_action_ok_displaylist_push(
case ACTION_OK_DL_CRT_SWITCHRES_SETTINGS_LIST:
case ACTION_OK_DL_CONFIGURATION_SETTINGS_LIST:
case ACTION_OK_DL_SAVING_SETTINGS_LIST:
case ACTION_OK_DL_CLOUD_SYNC_SETTINGS_LIST:
case ACTION_OK_DL_LOGGING_SETTINGS_LIST:
case ACTION_OK_DL_FRAME_THROTTLE_SETTINGS_LIST:
case ACTION_OK_DL_FRAME_TIME_COUNTER_SETTINGS_LIST:
@ -5924,6 +5927,7 @@ STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_parent_directory_push, ACTION_OK_DL_PARE
STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_directory_push, ACTION_OK_DL_DIRECTORY_PUSH)
STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_configurations_list, ACTION_OK_DL_CONFIGURATIONS_LIST)
STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_saving_list, ACTION_OK_DL_SAVING_SETTINGS_LIST)
STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_cloud_sync_list, ACTION_OK_DL_CLOUD_SYNC_SETTINGS_LIST)
STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_network_list, ACTION_OK_DL_NETWORK_SETTINGS_LIST)
STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_network_hosting_list, ACTION_OK_DL_NETWORK_HOSTING_SETTINGS_LIST)
STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_netplay_kick_list, ACTION_OK_DL_NETPLAY_KICK_LIST)
@ -8568,6 +8572,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_SUBSYSTEM_LOAD, action_ok_subsystem_add_load},
{MENU_ENUM_LABEL_CONFIGURATIONS, action_ok_configurations_list},
{MENU_ENUM_LABEL_SAVING_SETTINGS, action_ok_saving_list},
{MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS, action_ok_cloud_sync_list},
{MENU_ENUM_LABEL_LOGGING_SETTINGS, action_ok_logging_list},
{MENU_ENUM_LABEL_FRAME_THROTTLE_SETTINGS, action_ok_frame_throttle_list},
{MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS, action_ok_frame_time_counter_list},

View file

@ -252,6 +252,13 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_crt_switchres_hires_menu, MENU
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_driver_settings_list, MENU_ENUM_SUBLABEL_DRIVER_SETTINGS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_retro_achievements_settings_list, MENU_ENUM_SUBLABEL_RETRO_ACHIEVEMENTS_SETTINGS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_saving_settings_list, MENU_ENUM_SUBLABEL_SAVING_SETTINGS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cloud_sync_settings_list, MENU_ENUM_SUBLABEL_CLOUD_SYNC_SETTINGS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cloud_sync_enable, MENU_ENUM_SUBLABEL_CLOUD_SYNC_ENABLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cloud_sync_destructive, MENU_ENUM_SUBLABEL_CLOUD_SYNC_DESTRUCTIVE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cloud_sync_driver, MENU_ENUM_SUBLABEL_CLOUD_SYNC_DRIVER)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cloud_sync_url, MENU_ENUM_SUBLABEL_CLOUD_SYNC_URL)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cloud_sync_username, MENU_ENUM_SUBLABEL_CLOUD_SYNC_USERNAME)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cloud_sync_password, MENU_ENUM_SUBLABEL_CLOUD_SYNC_PASSWORD)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_logging_settings_list, MENU_ENUM_SUBLABEL_LOGGING_SETTINGS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_user_interface_settings_list, MENU_ENUM_SUBLABEL_USER_INTERFACE_SETTINGS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_ai_service_settings_list, MENU_ENUM_SUBLABEL_AI_SERVICE_SETTINGS)
@ -4784,6 +4791,27 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_SAVING_SETTINGS:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_saving_settings_list);
break;
case MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cloud_sync_settings_list);
break;
case MENU_ENUM_LABEL_CLOUD_SYNC_ENABLE:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cloud_sync_enable);
break;
case MENU_ENUM_LABEL_CLOUD_SYNC_DESTRUCTIVE:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cloud_sync_destructive);
break;
case MENU_ENUM_LABEL_CLOUD_SYNC_DRIVER:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cloud_sync_driver);
break;
case MENU_ENUM_LABEL_CLOUD_SYNC_URL:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cloud_sync_url);
break;
case MENU_ENUM_LABEL_CLOUD_SYNC_USERNAME:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cloud_sync_username);
break;
case MENU_ENUM_LABEL_CLOUD_SYNC_PASSWORD:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cloud_sync_password);
break;
case MENU_ENUM_LABEL_LOGGING_SETTINGS:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_logging_settings_list);
break;

View file

@ -629,6 +629,7 @@ DEFAULT_TITLE_MACRO(action_get_dump_disc_list, MENU_ENUM_LABEL_
DEFAULT_TITLE_MACRO(action_get_eject_disc, MENU_ENUM_LABEL_VALUE_EJECT_DISC)
#endif
DEFAULT_TITLE_MACRO(action_get_saving_settings_list, MENU_ENUM_LABEL_VALUE_SAVING_SETTINGS)
DEFAULT_TITLE_MACRO(action_get_cloud_sync_settings_list, MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SETTINGS)
DEFAULT_TITLE_MACRO(action_get_logging_settings_list, MENU_ENUM_LABEL_VALUE_LOGGING_SETTINGS)
DEFAULT_TITLE_MACRO(action_get_frame_throttle_settings_list, MENU_ENUM_LABEL_VALUE_FRAME_THROTTLE_SETTINGS)
DEFAULT_TITLE_MACRO(action_get_frame_time_counter_settings_list, MENU_ENUM_LABEL_VALUE_FRAME_TIME_COUNTER_SETTINGS)
@ -989,6 +990,7 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_DEFERRED_LOAD_DISC_LIST, action_get_load_disc_list},
{MENU_ENUM_LABEL_DEFERRED_CONFIGURATION_SETTINGS_LIST, action_get_configuration_settings_list },
{MENU_ENUM_LABEL_DEFERRED_SAVING_SETTINGS_LIST, action_get_saving_settings_list},
{MENU_ENUM_LABEL_DEFERRED_CLOUD_SYNC_SETTINGS_LIST, action_get_cloud_sync_settings_list},
{MENU_ENUM_LABEL_DEFERRED_LOGGING_SETTINGS_LIST, action_get_logging_settings_list},
{MENU_ENUM_LABEL_DEFERRED_FRAME_TIME_COUNTER_SETTINGS_LIST, action_get_frame_time_counter_settings_list },
{MENU_ENUM_LABEL_DEFERRED_FRAME_THROTTLE_SETTINGS_LIST, action_get_frame_throttle_settings_list},
@ -1597,6 +1599,9 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_DEFERRED_SAVING_SETTINGS_LIST:
BIND_ACTION_GET_TITLE(cbs, action_get_saving_settings_list);
break;
case MENU_ENUM_LABEL_DEFERRED_CLOUD_SYNC_SETTINGS_LIST:
BIND_ACTION_GET_TITLE(cbs, action_get_cloud_sync_settings_list);
break;
case MENU_ENUM_LABEL_DEFERRED_FRAME_THROTTLE_SETTINGS_LIST:
BIND_ACTION_GET_TITLE(cbs, action_get_frame_throttle_settings_list);
break;

View file

@ -10860,6 +10860,7 @@ static void materialui_list_insert(
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SIDELOAD_CORE_LIST))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CRT_SWITCHRES_SETTINGS))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SAVING_SETTINGS))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_LOGGING_SETTINGS))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_FRAME_THROTTLE_SETTINGS))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_RECORDING_SETTINGS))

View file

@ -2125,6 +2125,7 @@ static uintptr_t ozone_entries_icon_get_texture(
case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
case MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS:
return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD];
case MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE:
/* Only show icon in Throttle settings */

View file

@ -2956,6 +2956,7 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
case MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS:
return xmb->textures.list[XMB_TEXTURE_RELOAD];
case MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE:
/* Only show icon in Throttle settings */

View file

@ -120,6 +120,7 @@ enum
ACTION_OK_DL_LATENCY_SETTINGS_LIST,
ACTION_OK_DL_CONFIGURATION_SETTINGS_LIST,
ACTION_OK_DL_SAVING_SETTINGS_LIST,
ACTION_OK_DL_CLOUD_SYNC_SETTINGS_LIST,
ACTION_OK_DL_LOGGING_SETTINGS_LIST,
ACTION_OK_DL_FRAME_THROTTLE_SETTINGS_LIST,
ACTION_OK_DL_FRAME_TIME_COUNTER_SETTINGS_LIST,

View file

@ -10142,6 +10142,9 @@ unsigned menu_displaylist_build_list(
{MENU_ENUM_LABEL_SCREENSHOTS_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG, PARSE_ONLY_BOOL, true},
{MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG_AGGREGATE, PARSE_ONLY_BOOL, true},
#if HAVE_CLOUDSYNC
{MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS, PARSE_ACTION, true},
#endif
};
for (i = 0; i < ARRAY_SIZE(build_list); i++)
@ -10169,6 +10172,26 @@ unsigned menu_displaylist_build_list(
}
}
break;
case DISPLAYLIST_CLOUD_SYNC_SETTINGS_LIST:
{
menu_displaylist_build_info_t build_list[] = {
{MENU_ENUM_LABEL_CLOUD_SYNC_ENABLE, PARSE_ONLY_BOOL },
{MENU_ENUM_LABEL_CLOUD_SYNC_DESTRUCTIVE, PARSE_ONLY_BOOL },
{MENU_ENUM_LABEL_CLOUD_SYNC_DRIVER, PARSE_ONLY_STRING_OPTIONS },
{MENU_ENUM_LABEL_CLOUD_SYNC_URL, PARSE_ONLY_STRING },
{MENU_ENUM_LABEL_CLOUD_SYNC_USERNAME, PARSE_ONLY_STRING },
{MENU_ENUM_LABEL_CLOUD_SYNC_PASSWORD, PARSE_ONLY_STRING },
};
for (i = 0; i < ARRAY_SIZE(build_list); i++)
{
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
build_list[i].enum_idx, build_list[i].parse_type,
false) == 0)
count++;
}
}
break;
#ifdef HAVE_MIST
case DISPLAYLIST_STEAM_SETTINGS_LIST:
{
@ -13637,6 +13660,7 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type,
break;
#endif
case DISPLAYLIST_SAVING_SETTINGS_LIST:
case DISPLAYLIST_CLOUD_SYNC_SETTINGS_LIST:
case DISPLAYLIST_DRIVER_SETTINGS_LIST:
case DISPLAYLIST_LOGGING_SETTINGS_LIST:
case DISPLAYLIST_FRAME_THROTTLE_SETTINGS_LIST:

View file

@ -181,6 +181,7 @@ enum menu_displaylist_ctl_state
DISPLAYLIST_VIDEO_SETTINGS_LIST,
DISPLAYLIST_CONFIGURATION_SETTINGS_LIST,
DISPLAYLIST_SAVING_SETTINGS_LIST,
DISPLAYLIST_CLOUD_SYNC_SETTINGS_LIST,
DISPLAYLIST_LOGGING_SETTINGS_LIST,
DISPLAYLIST_FRAME_THROTTLE_SETTINGS_LIST,
DISPLAYLIST_FRAME_TIME_COUNTER_SETTINGS_LIST,

View file

@ -93,6 +93,7 @@
#endif
#include "../midi_driver.h"
#include "../location_driver.h"
#include "../network/cloud_sync_driver.h"
#include "../record/record_driver.h"
#include "../tasks/tasks_internal.h"
#include "../config.def.h"
@ -275,6 +276,7 @@ enum settings_list_type
SETTINGS_LIST_CONFIGURATION,
SETTINGS_LIST_LOGGING,
SETTINGS_LIST_SAVING,
SETTINGS_LIST_CLOUD_SYNC,
SETTINGS_LIST_REWIND,
SETTINGS_LIST_CHEAT_DETAILS,
SETTINGS_LIST_CHEAT_SEARCH,
@ -2936,8 +2938,8 @@ static void setting_get_string_representation_max_users(rarch_setting_t *setting
snprintf(s, len, "%d", *setting->value.target.unsigned_integer);
}
#ifdef HAVE_CHEEVOS
static void setting_get_string_representation_cheevos_password(
#if defined(HAVE_CHEEVOS) || defined(HAVE_CLOUDSYNC)
static void setting_get_string_representation_password(
rarch_setting_t *setting,
char *s, size_t len)
{
@ -11099,10 +11101,128 @@ static bool setting_append_list(
general_read_handler,
SD_FLAG_NONE);
CONFIG_ACTION(
list, list_info,
MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS,
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SETTINGS,
&group_info,
&subgroup_info,
parent_group);
END_SUB_GROUP(list, list_info, parent_group);
END_GROUP(list, list_info, parent_group);
}
break;
case SETTINGS_LIST_CLOUD_SYNC:
#ifdef HAVE_CLOUDSYNC
START_GROUP(list, list_info, &group_info,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_SETTINGS),
parent_group);
parent_group = msg_hash_to_str(MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS);
START_SUB_GROUP(list, list_info, "State", &group_info, &subgroup_info, parent_group);
CONFIG_BOOL(
list, list_info,
&settings->bools.cloud_sync_enable,
MENU_ENUM_LABEL_CLOUD_SYNC_ENABLE,
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_ENABLE,
false,
MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
CONFIG_BOOL(
list, list_info,
&settings->bools.cloud_sync_destructive,
MENU_ENUM_LABEL_CLOUD_SYNC_DESTRUCTIVE,
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DESTRUCTIVE,
false,
MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON,
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler,
SD_FLAG_NONE);
CONFIG_STRING_OPTIONS(
list, list_info,
settings->arrays.cloud_sync_driver,
sizeof(settings->arrays.cloud_sync_driver),
MENU_ENUM_LABEL_CLOUD_SYNC_DRIVER,
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_DRIVER,
"null",
config_get_cloud_sync_driver_options(),
&group_info,
&subgroup_info,
parent_group,
general_read_handler,
general_write_handler);
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_IS_DRIVER);
(*list)[list_info->index - 1].action_ok = setting_action_ok_uint;
(*list)[list_info->index - 1].action_left = setting_string_action_left_driver;
(*list)[list_info->index - 1].action_right = setting_string_action_right_driver;
CONFIG_STRING(
list, list_info,
settings->arrays.webdav_url,
sizeof(settings->arrays.webdav_url),
MENU_ENUM_LABEL_CLOUD_SYNC_URL,
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_URL,
"",
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_STRING_LINE_EDIT;
(*list)[list_info->index - 1].action_start = setting_generic_action_start_default;
CONFIG_STRING(
list, list_info,
settings->arrays.webdav_username,
sizeof(settings->arrays.webdav_username),
MENU_ENUM_LABEL_CLOUD_SYNC_USERNAME,
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_USERNAME,
"",
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_STRING_LINE_EDIT;
(*list)[list_info->index - 1].action_start = setting_generic_action_start_default;
CONFIG_STRING(
list, list_info,
settings->arrays.webdav_password,
sizeof(settings->arrays.webdav_password),
MENU_ENUM_LABEL_CLOUD_SYNC_PASSWORD,
MENU_ENUM_LABEL_VALUE_CLOUD_SYNC_PASSWORD,
"",
&group_info,
&subgroup_info,
parent_group,
general_write_handler,
general_read_handler);
(*list)[list_info->index - 1].get_string_representation =
&setting_get_string_representation_password;
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_PASSWORD_LINE_EDIT;
(*list)[list_info->index - 1].action_start = setting_generic_action_start_default;
END_SUB_GROUP(list, list_info, parent_group);
END_GROUP(list, list_info, parent_group);
#endif
break;
case SETTINGS_LIST_FRAME_TIME_COUNTER:
START_GROUP(list, list_info, &group_info, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FRAME_TIME_COUNTER_SETTINGS), parent_group);
@ -21994,7 +22114,7 @@ static bool setting_append_list(
general_write_handler,
general_read_handler);
(*list)[list_info->index - 1].get_string_representation =
&setting_get_string_representation_cheevos_password;
&setting_get_string_representation_password;
SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_ALLOW_INPUT);
(*list)[list_info->index - 1].ui_type = ST_UI_TYPE_PASSWORD_LINE_EDIT;
(*list)[list_info->index - 1].action_start = setting_generic_action_start_default;
@ -22917,6 +23037,7 @@ static rarch_setting_t *menu_setting_new_internal(rarch_setting_info_t *list_inf
SETTINGS_LIST_CONFIGURATION,
SETTINGS_LIST_LOGGING,
SETTINGS_LIST_SAVING,
SETTINGS_LIST_CLOUD_SYNC,
SETTINGS_LIST_REWIND,
SETTINGS_LIST_CHEAT_DETAILS,
SETTINGS_LIST_CHEAT_SEARCH,

View file

@ -1920,6 +1920,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_DEFERRED_CRT_SWITCHRES_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_CONFIGURATION_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_SAVING_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_CLOUD_SYNC_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_FRAME_THROTTLE_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_REWIND_SETTINGS_LIST,
MENU_ENUM_LABEL_DEFERRED_FRAME_TIME_COUNTER_SETTINGS_LIST,
@ -3062,6 +3063,13 @@ enum msg_hash_enums
MENU_LABEL(CONFIGURATION_SETTINGS),
MENU_LABEL(LOGGING_SETTINGS),
MENU_LABEL(SAVING_SETTINGS),
MENU_LABEL(CLOUD_SYNC_SETTINGS),
MENU_LABEL(CLOUD_SYNC_ENABLE),
MENU_LABEL(CLOUD_SYNC_DESTRUCTIVE),
MENU_LABEL(CLOUD_SYNC_DRIVER),
MENU_LABEL(CLOUD_SYNC_URL),
MENU_LABEL(CLOUD_SYNC_USERNAME),
MENU_LABEL(CLOUD_SYNC_PASSWORD),
MENU_LABEL(RECORDING_SETTINGS),
MENU_LABEL(OVERLAY_SETTINGS),
MENU_LABEL(REWIND_SETTINGS),

803
network/cloud_sync/webdav.c Normal file
View file

@ -0,0 +1,803 @@
/* RetroArch - A frontend for libretro.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <encodings/base64.h>
#include <lrc_hash.h>
#include <net/net_http.h>
#include <string/stdstring.h>
#include <time/rtime.h>
#include "../cloud_sync_driver.h"
#include "../../retroarch.h"
#include "../../tasks/tasks_internal.h"
#include "../../verbosity.h"
typedef struct
{
char path[PATH_MAX_LENGTH];
char file[PATH_MAX_LENGTH];
cloud_sync_complete_handler_t cb;
void *user_data;
RFILE *rfile;
} webdav_cb_state_t;
typedef void (*webdav_mkdir_cb_t)(bool success, webdav_cb_state_t *state);
typedef struct
{
char url[PATH_MAX_LENGTH];
char *last_slash;
webdav_mkdir_cb_t cb;
webdav_cb_state_t *cb_st;
} webdav_mkdir_state_t;
// TODO: all of this HTTP auth stuff should probably live in libretro-common/net?
typedef struct
{
char url[PATH_MAX_LENGTH];
bool basic;
char *basic_auth_header;
char *username;
char *ha1hash;
char *realm;
char *nonce;
char *algo;
char *opaque;
char *cnonce;
bool qop_auth;
unsigned nc;
char *digest_auth_header;
} webdav_state_t;
static webdav_state_t webdav_driver_st = {0};
webdav_state_t *webdav_state_get_ptr(void)
{
return &webdav_driver_st;
}
static char *webdav_create_basic_auth(void)
{
settings_t *settings = config_get_ptr();
size_t len = 0;
char userpass[512];
char *base64auth;
int flen;
if (!string_is_empty(settings->arrays.webdav_username))
len += strlcpy(userpass + len, settings->arrays.webdav_username, sizeof(userpass) - len);
userpass[len++] = ':';
if (!string_is_empty(settings->arrays.webdav_password))
len += strlcpy(userpass + len, settings->arrays.webdav_password, sizeof(userpass) - len);
userpass[len] = '\0';
base64auth = base64(userpass, (int)len, &flen);
len = strlcpy(userpass, "Authorization: Basic ", sizeof(userpass));
len += strlcpy(userpass + len, base64auth, sizeof(userpass) - len);
free(base64auth);
userpass[len++] = '\r';
userpass[len++] = '\n';
userpass[len ] = '\0';
return strdup(userpass);
}
static void webdav_cleanup_digest(void)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
if (webdav_st->ha1hash)
free(webdav_st->ha1hash);
webdav_st->ha1hash = NULL;
if (webdav_st->realm)
free(webdav_st->realm);
webdav_st->realm = NULL;
if (webdav_st->nonce)
free(webdav_st->nonce);
webdav_st->nonce = NULL;
if (webdav_st->algo)
free(webdav_st->algo);
webdav_st->algo = NULL;
if (webdav_st->opaque)
free(webdav_st->opaque);
webdav_st->opaque = NULL;
webdav_st->qop_auth = false;
webdav_st->nc = 1;
if (webdav_st->digest_auth_header)
free(webdav_st->digest_auth_header);
webdav_st->digest_auth_header = NULL;
}
static char *webdav_create_ha1_hash(char *user, char *realm, char *pass)
{
char *hash = malloc(33);
MD5_CTX md5;
unsigned char digest[16];
MD5_Init(&md5);
MD5_Update(&md5, user, strlen(user));
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, realm, strlen(realm));
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, pass, strlen(pass));
MD5_Final(digest, &md5);
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
);
return hash;
}
static bool webdav_create_digest_auth(char *digest)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
settings_t *settings = config_get_ptr();
char *ptr = digest + strlen("WWW-Authenticate: Digest ");
char *end = ptr + strlen(ptr);
size_t sz;
if (string_is_empty(settings->arrays.webdav_username) &&
string_is_empty(settings->arrays.webdav_password))
return false;
webdav_cleanup_digest();
webdav_st->username = settings->arrays.webdav_username;
while (ptr < end)
{
while (ISSPACE(*ptr))
++ptr;
if (!*ptr)
break;
if (string_starts_with(ptr, "realm=\""))
{
ptr += strlen("realm=\"");
sz = strchr(ptr, '"') + 1 - ptr;
webdav_st->realm = malloc(sz);
strlcpy(webdav_st->realm, ptr, sz);
ptr += sz;
webdav_st->ha1hash = webdav_create_ha1_hash(webdav_st->username, webdav_st->realm, settings->arrays.webdav_password);
}
else if (string_starts_with(ptr, "qop=\""))
{
char *tail;
ptr += strlen("qop=\"");
tail = strchr(ptr, '"');
while (ptr < tail)
{
if (string_starts_with(ptr, "auth") &&
(ptr[4] == ',' || ptr[4] == '"'))
{
webdav_st->qop_auth = true;
break;
}
while (*ptr != ',' && *ptr != '"' && *ptr != '\0')
ptr++;
ptr++;
}
/* not even going to try for auth-int, sorry */
if (!webdav_st->qop_auth)
return false;
while (*ptr != ',' && *ptr != '"' && *ptr != '\0')
ptr++;
ptr++;
}
else if (string_starts_with(ptr, "nonce=\""))
{
ptr += strlen("nonce=\"");
sz = strchr(ptr, '"') + 1 - ptr;
webdav_st->nonce = malloc(sz);
strlcpy(webdav_st->nonce, ptr, sz);
ptr += sz;
}
else if (string_starts_with(ptr, "algorithm="))
{
ptr += strlen("algorithm=");
sz = strchr(ptr, ',') + 1 - ptr;
webdav_st->algo = malloc(sz);
strlcpy(webdav_st->algo, ptr, sz);
ptr += sz;
}
else if (string_starts_with(ptr, "opaque=\""))
{
ptr += strlen("opaque=\"");
sz = strchr(ptr, '"') + 1 - ptr;
webdav_st->opaque = malloc(sz);
strlcpy(webdav_st->opaque, ptr, sz);
ptr += sz;
}
else
{
while (*ptr != '=' && *ptr != '\0')
ptr++;
ptr++;
if (*ptr == '"')
{
ptr++;
while (*ptr != '"' && *ptr != '\0')
ptr++;
ptr++;
}
else
{
while (*ptr != ',' && *ptr != ',')
ptr++;
}
}
while (ISSPACE(*ptr))
++ptr;
if (*ptr == ',')
ptr++;
}
if (!webdav_st->ha1hash || !webdav_st->nonce)
return false;
webdav_st->cnonce = "1a2b3c4f";
return true;
}
static char *webdav_create_ha1(void)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
char *hash;
MD5_CTX md5;
unsigned char digest[16];
if (!string_is_equal(webdav_st->algo, "MD5-sess"))
return strdup(webdav_st->ha1hash);
hash = malloc(33);
MD5_Init(&md5);
MD5_Update(&md5, webdav_st->ha1hash, 32);
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, webdav_st->nonce, strlen(webdav_st->nonce));
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, webdav_st->cnonce, strlen(webdav_st->cnonce));
MD5_Final(digest, &md5);
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
);
return hash;
}
static char *webdav_create_ha2(const char *method, const char *path)
{
/* no attempt at supporting auth-int, everything else uses this */
char *hash = malloc(33);
MD5_CTX md5;
unsigned char digest[16];
MD5_Init(&md5);
MD5_Update(&md5, method, strlen(method));
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, path, strlen(path));
MD5_Final(digest, &md5);
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
);
return hash;
}
static char *webdav_create_digest_response(const char *method, const char *path)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
char *ha1 = webdav_create_ha1();
char *ha2 = webdav_create_ha2(method, path);
char *hash = malloc(33);
MD5_CTX md5;
unsigned char digest[16];
MD5_Init(&md5);
MD5_Update(&md5, ha1, 32);
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, webdav_st->nonce, strlen(webdav_st->nonce));
if (webdav_st->qop_auth)
{
char nonceCount[10];
snprintf(nonceCount, sizeof(nonceCount), "%08x", webdav_st->nc);
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, nonceCount, strlen(nonceCount));
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, webdav_st->cnonce, strlen(webdav_st->cnonce));
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, "auth", strlen("auth"));
}
MD5_Update(&md5, ":", 1);
MD5_Update(&md5, ha2, 32);
MD5_Final(digest, &md5);
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
);
free(ha1);
free(ha2);
return hash;
}
static char *webdav_create_digest_auth_header(const char *method, const char *url)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
char *header;
char *response;
char nonceCount[10];
const char *path = url;
int count = 0;
size_t len = 0;
size_t total = 0;
do
{
path++;
path = strchr(path, '/');
count++;
} while (count < 3 && *path != '\0');
response = webdav_create_digest_response(method, path);
snprintf(nonceCount, sizeof(nonceCount), "%08x", webdav_st->nc);
len = strlen("Authorization: Digest ");
len += strlen("username=\"") + strlen(webdav_st->username) + strlen("\", ");
len += strlen("realm=\"") + strlen(webdav_st->realm) + strlen("\", ");
len += strlen("nonce=\"") + strlen(webdav_st->nonce) + strlen("\", ");
len += strlen("uri=\"") + strlen(path) + strlen("\", ");
len += strlen("nc=\"") + strlen(nonceCount) + strlen("\", ");
len += strlen("cnonce=\"") + strlen(webdav_st->cnonce) + strlen("\", ");
if (webdav_st->qop_auth)
len += strlen("qop=\"auth\", ");
if (webdav_st->opaque)
len += strlen("opaque=\"") + strlen(webdav_st->opaque) + strlen("\", ");
len += strlen("response=\"") + strlen(response) + strlen("\"\r\n");
len += 1;
total = len;
len = 0;
header = malloc(total);
len = strlcpy(header, "Authorization: Digest username=\"", total - len);
len += strlcpy(header + len, webdav_st->username, total - len);
len += strlcpy(header + len, "\", realm=\"", total - len);
len += strlcpy(header + len, webdav_st->realm, total - len);
len += strlcpy(header + len, "\", nonce=\"", total - len);
len += strlcpy(header + len, webdav_st->nonce, total - len);
len += strlcpy(header + len, "\", uri=\"", total - len);
len += strlcpy(header + len, path, total - len);
len += strlcpy(header + len, "\", nc=\"", total - len);
len += strlcpy(header + len, nonceCount, total - len);
len += strlcpy(header + len, "\", cnonce=\"", total - len);
len += strlcpy(header + len, webdav_st->cnonce, total - len);
if (webdav_st->qop_auth)
len += strlcpy(header + len, "\", qop=\"auth", total - len);
if (webdav_st->opaque)
{
len += strlcpy(header + len, "\", opaque=\"", total - len);
len += strlcpy(header + len, webdav_st->opaque, total - len);
}
len += strlcpy(header + len, "\", response=\"", total - len);
len += strlcpy(header + len, response, total - len);
len += strlcpy(header + len, "\"\r\n", total - len);
free(response);
return header;
}
static char *webdav_get_auth_header(const char *method, const char *url)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
settings_t *settings = config_get_ptr();
if (string_is_empty(settings->arrays.webdav_username) &&
string_is_empty(settings->arrays.webdav_password))
return NULL;
if (webdav_st->basic)
{
if (!webdav_st->basic_auth_header)
webdav_st->basic_auth_header = webdav_create_basic_auth();
return webdav_st->basic_auth_header;
}
if (webdav_st->digest_auth_header)
free(webdav_st->digest_auth_header);
webdav_st->digest_auth_header = webdav_create_digest_auth_header(method, url);
return webdav_st->digest_auth_header;
}
static void webdav_stat_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
bool success = (data && data->status >= 200 && data->status < 300);
if (!webdav_cb_st)
return;
if (data && data->status == 401 && data->headers && webdav_st->basic == true)
{
int i;
webdav_st->basic = false;
for (i = 0; i < data->headers->size; i++)
{
if (!string_starts_with(data->headers->elems[i].data, "WWW-Authenticate: Digest "))
continue;
if (webdav_create_digest_auth(data->headers->elems[i].data))
{
task_push_webdav_stat(webdav_st->url, true,
webdav_get_auth_header("OPTIONS", webdav_st->url),
webdav_stat_cb, webdav_cb_st);
return;
}
}
}
webdav_cb_st->cb(webdav_cb_st->user_data, NULL, success, NULL);
free(webdav_cb_st);
}
static bool webdav_sync_begin(cloud_sync_complete_handler_t cb, void *user_data)
{
settings_t *settings = config_get_ptr();
const char *url = settings->arrays.webdav_url;
webdav_state_t *webdav_st = webdav_state_get_ptr();
const char *auth_header;
if (string_is_empty(url))
return false;
// TODO: LOCK?
strlcpy(webdav_st->url, url, sizeof(webdav_st->url));
fill_pathname_slash(webdav_st->url, sizeof(webdav_st->url));
/* url/username/password may have changed, redo auth check */
webdav_st->basic = true;
auth_header = webdav_get_auth_header(NULL, NULL);
if (auth_header)
{
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
webdav_cb_st->cb = cb;
webdav_cb_st->user_data = user_data;
task_push_webdav_stat(webdav_st->url, true, auth_header, webdav_stat_cb, webdav_cb_st);
}
else
cb(user_data, NULL, true, NULL);
return true;
}
static bool webdav_sync_end(cloud_sync_complete_handler_t cb, void *user_data)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
// TODO: UNLOCK?
if (webdav_st->basic_auth_header)
free(webdav_st->basic_auth_header);
webdav_st->basic_auth_header = NULL;
webdav_cleanup_digest();
cb(user_data, NULL, true, NULL);
return true;
}
static void webdav_read_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
{
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
RFILE *file = NULL;
bool success;
success = (data &&
((data->status >= 200 && data->status < 300) || data->status == 404));
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
if (data && data->data && webdav_cb_st)
{
// TODO: it would be better if writing to the file happened during the network reads
file = filestream_open(webdav_cb_st->file,
RETRO_VFS_FILE_ACCESS_READ_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
if (file)
{
filestream_write(file, data->data, data->len);
filestream_seek(file, 0, SEEK_SET);
}
}
if (webdav_cb_st)
{
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, file);
free(webdav_cb_st);
}
}
static bool webdav_read(const char *path, const char *file, cloud_sync_complete_handler_t cb, void *user_data)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
char url[PATH_MAX_LENGTH];
char url_encoded[PATH_MAX_LENGTH];
fill_pathname_join_special(url, webdav_st->url, path, sizeof(url));
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
webdav_cb_st->cb = cb;
webdav_cb_st->user_data = user_data;
strlcpy(webdav_cb_st->path, path, sizeof(webdav_cb_st->path));
strlcpy(webdav_cb_st->file, file, sizeof(webdav_cb_st->file));
task_push_http_transfer_with_headers(url_encoded, true, NULL,
webdav_get_auth_header("GET", url_encoded),
webdav_read_cb, webdav_cb_st);
return true;
}
static void webdav_mkdir_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
{
webdav_mkdir_state_t *webdav_mkdir_st = (webdav_mkdir_state_t *)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
if (!webdav_mkdir_st)
return;
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
if (!data || data->status < 200 || data->status >= 400)
{
webdav_mkdir_st->cb(false, webdav_mkdir_st->cb_st);
free(webdav_mkdir_st);
return;
}
*webdav_mkdir_st->last_slash++ = '/';
webdav_mkdir_st->last_slash = strchr(webdav_mkdir_st->last_slash, '/');
if (webdav_mkdir_st->last_slash)
{
*webdav_mkdir_st->last_slash = '\0';
task_push_webdav_mkdir(webdav_mkdir_st->url, true,
webdav_get_auth_header("MVCOL", webdav_mkdir_st->url),
webdav_mkdir_cb, webdav_mkdir_st);
}
else
{
webdav_mkdir_st->cb(true, webdav_mkdir_st->cb_st);
free(webdav_mkdir_st);
}
}
static void webdav_ensure_dir(const char *dir, webdav_mkdir_cb_t cb, webdav_cb_state_t *webdav_cb_st)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
webdav_mkdir_state_t *webdav_mkdir_st = (webdav_mkdir_state_t *)malloc(sizeof(webdav_mkdir_state_t));
http_transfer_data_t data;
char url[PATH_MAX_LENGTH];
fill_pathname_join_special(url, webdav_st->url, dir, sizeof(url));
net_http_urlencode_full(webdav_mkdir_st->url, url, sizeof(webdav_mkdir_st->url));
webdav_mkdir_st->last_slash = strchr(webdav_mkdir_st->url + strlen(webdav_st->url) - 1, '/');
webdav_mkdir_st->cb = cb;
webdav_mkdir_st->cb_st = webdav_cb_st;
/* this is a recursive callback, set it up so it looks like it's still proceeding */
data.status = 200;
webdav_mkdir_cb(NULL, &data, webdav_mkdir_st, NULL);
}
static void webdav_update_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
{
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
bool success = (data && data->status >= 200 && data->status < 300);
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
if (webdav_cb_st)
{
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, webdav_cb_st->rfile);
free(webdav_cb_st);
}
}
static void webdav_do_update(bool success, webdav_cb_state_t *webdav_cb_st)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
char url_encoded[PATH_MAX_LENGTH];
char url[PATH_MAX_LENGTH];
void *buf;
int64_t len;
if (!webdav_cb_st)
return;
if (!success)
{
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, false, webdav_cb_st->rfile);
free(webdav_cb_st);
return;
}
// TODO: would be better to read file as it's being written to wire, this is very inefficient
len = filestream_get_size(webdav_cb_st->rfile);
buf = malloc((size_t)(len + 1));
filestream_read(webdav_cb_st->rfile, buf, len);
fill_pathname_join_special(url, webdav_st->url, webdav_cb_st->path, sizeof(url));
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
task_push_webdav_put(url_encoded, buf, len, true,
webdav_get_auth_header("PUT", url_encoded),
webdav_update_cb, webdav_cb_st);
free(buf);
}
static bool webdav_update(const char *path, RFILE *rfile, cloud_sync_complete_handler_t cb, void *user_data)
{
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
char dir[PATH_MAX_LENGTH];
// TODO: if !settings->bools.cloud_sync_destructive, should move to deleted/ first
webdav_cb_st->cb = cb;
webdav_cb_st->user_data = user_data;
strlcpy(webdav_cb_st->path, path, sizeof(webdav_cb_st->path));
webdav_cb_st->rfile = rfile;
if (strchr(path, '/'))
{
fill_pathname_basedir(dir, path, sizeof(dir));
webdav_ensure_dir(dir, webdav_do_update, webdav_cb_st);
}
else
webdav_do_update(true, webdav_cb_st);
return true;
}
static void webdav_delete_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
{
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
bool success = (data != NULL && data->status >= 200 && data->status < 300);
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
if (webdav_cb_st)
{
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, NULL);
free(webdav_cb_st);
}
}
static void webdav_backup_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
{
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
bool success = (data != NULL && data->status >= 200 && data->status < 300);
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
if (webdav_cb_st)
{
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, NULL);
free(webdav_cb_st);
}
}
static void webdav_do_backup(bool success, webdav_cb_state_t *webdav_cb_st)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
char dest_encoded[PATH_MAX_LENGTH];
char dest[PATH_MAX_LENGTH];
char url_encoded[PATH_MAX_LENGTH];
char url[PATH_MAX_LENGTH];
size_t len;
struct tm tm_;
time_t cur_time = time(NULL);
if (!webdav_cb_st)
return;
if (!success)
{
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, false, NULL);
free(webdav_cb_st);
return;
}
fill_pathname_join_special(url, webdav_st->url, webdav_cb_st->path, sizeof(url));
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
fill_pathname_join_special(url, webdav_st->url, "deleted/", sizeof(url));
len = fill_pathname_join_special(dest, url, webdav_cb_st->path, sizeof(dest));
rtime_localtime(&cur_time, &tm_);
strftime(dest + len, sizeof(dest) - len, "-%y%m%d-%H%M%S", &tm_);
net_http_urlencode_full(dest_encoded, dest, sizeof(dest_encoded));
task_push_webdav_move(url_encoded, dest_encoded, true,
webdav_get_auth_header("MOVE", url_encoded),
webdav_backup_cb, webdav_cb_st);
}
static bool webdav_delete(const char *path, cloud_sync_complete_handler_t cb, void *user_data)
{
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
settings_t *settings = config_get_ptr();
webdav_cb_st->cb = cb;
webdav_cb_st->user_data = user_data;
strlcpy(webdav_cb_st->path, path, sizeof(webdav_cb_st->path));
/*
* Should all cloud_sync_destructive handling be done in task_cloudsync? I
* think not because it gives each driver a chance to do a move rather than a
* delete/update. Or we could add a cloud_sync_move() API to the driver.
*/
if (settings->bools.cloud_sync_destructive)
{
webdav_state_t *webdav_st = webdav_state_get_ptr();
char url_encoded[PATH_MAX_LENGTH];
char url[PATH_MAX_LENGTH];
fill_pathname_join_special(url, webdav_st->url, path, sizeof(url));
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
task_push_webdav_delete(url_encoded, true,
webdav_get_auth_header("DELETE", url_encoded),
webdav_delete_cb, webdav_cb_st);
}
else
{
char dir[PATH_MAX_LENGTH] = {0};
size_t _len;
_len = strlcat(dir, "deleted/", sizeof(dir));
fill_pathname_basedir(dir + _len, path, sizeof(dir) - _len);
webdav_ensure_dir(dir, webdav_do_backup, webdav_cb_st);
}
return true;
}
cloud_sync_driver_t cloud_sync_webdav = {
webdav_sync_begin,
webdav_sync_end,
webdav_read,
webdav_update,
webdav_delete,
"webdav" /* ident */
};

126
network/cloud_sync_driver.c Normal file
View file

@ -0,0 +1,126 @@
/* RetroArch - A frontend for libretro.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "cloud_sync_driver.h"
#include "../list_special.h"
#include "../retroarch.h"
#include "../verbosity.h"
static cloud_sync_driver_t cloud_sync_null = {
NULL, /* sync_begin */
NULL, /* sync_end */
NULL, /* read */
NULL, /* update */
NULL, /* delete */
"null" /* ident */
};
const cloud_sync_driver_t *cloud_sync_drivers[] = {
&cloud_sync_webdav,
&cloud_sync_null,
NULL
};
static cloud_sync_driver_state_t cloud_sync_driver_st = {0};
cloud_sync_driver_state_t *cloud_sync_state_get_ptr(void)
{
return &cloud_sync_driver_st;
}
/**
* config_get_cloud_sync_driver_options:
*
* Get an enumerated list of all cloud sync driver names, separated by '|'.
*
* @return string listing of all cloud sync driver names, separated by '|'.
**/
const char* config_get_cloud_sync_driver_options(void)
{
return char_list_new_special(STRING_LIST_CLOUD_SYNC_DRIVERS, NULL);
}
void cloud_sync_find_driver(
settings_t *settings,
const char *prefix,
bool verbosity_enabled)
{
cloud_sync_driver_state_t
*cloud_sync_st = &cloud_sync_driver_st;
int i = (int)driver_find_index(
"cloud_sync_driver",
settings->arrays.cloud_sync_driver);
if (i >= 0)
cloud_sync_st->driver = (const cloud_sync_driver_t*)
cloud_sync_drivers[i];
else
{
if (verbosity_enabled)
{
unsigned d;
RARCH_ERR("Couldn't find any %s named \"%s\"\n", prefix,
settings->arrays.cloud_sync_driver);
RARCH_LOG_OUTPUT("Available %ss are:\n", prefix);
for (d = 0; cloud_sync_drivers[d]; d++)
RARCH_LOG_OUTPUT("\t%s\n", cloud_sync_drivers[d]->ident);
RARCH_WARN("Going to default to first %s...\n", prefix);
}
cloud_sync_st->driver = (const cloud_sync_driver_t*)cloud_sync_drivers[0];
}
}
bool cloud_sync_begin(cloud_sync_complete_handler_t cb, void *user_data)
{
const cloud_sync_driver_t *driver = cloud_sync_state_get_ptr()->driver;
if (driver && driver->sync_begin)
return driver->sync_begin(cb, user_data);
return false;
}
bool cloud_sync_end(cloud_sync_complete_handler_t cb, void *user_data)
{
const cloud_sync_driver_t *driver = cloud_sync_state_get_ptr()->driver;
if (driver && driver->sync_end)
return driver->sync_end(cb, user_data);
return false;
}
bool cloud_sync_read(const char *path, const char *file, cloud_sync_complete_handler_t cb, void *user_data)
{
const cloud_sync_driver_t *driver = cloud_sync_state_get_ptr()->driver;
if (driver && driver->read)
return driver->read(path, file, cb, user_data);
return false;
}
bool cloud_sync_update(const char *path, RFILE *file,
cloud_sync_complete_handler_t cb, void *user_data)
{
const cloud_sync_driver_t *driver = cloud_sync_state_get_ptr()->driver;
if (driver && driver->update)
return driver->update(path, file, cb, user_data);
return false;
}
bool cloud_sync_delete(const char *path, cloud_sync_complete_handler_t cb, void *user_data)
{
const cloud_sync_driver_t *driver = cloud_sync_state_get_ptr()->driver;
if (driver && driver->delete)
return driver->delete(path, cb, user_data);
return false;
}

View file

@ -0,0 +1,77 @@
/* RetroArch - A frontend for libretro.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CLOUD_SYNC_DRIVER__H
#define __CLOUD_SYNC_DRIVER__H
#include <boolean.h>
#include <stddef.h>
#include <streams/file_stream.h>
#include "../configuration.h"
RETRO_BEGIN_DECLS
/*
* For a read, `success' indicates whether we successfully communicated with the
* server. We may ask to read a file that doesn't exist; in that case, `success'
* is true and `file' is NULL. `file' is expected to be close()'d by the handler
* if non-NULL.
*/
typedef void (*cloud_sync_complete_handler_t)(void *user_data, const char *path, bool success, RFILE *file);
typedef struct cloud_sync_driver
{
bool (*sync_begin)(cloud_sync_complete_handler_t cb, void *user_data);
bool (*sync_end)(cloud_sync_complete_handler_t cb, void *user_data);
bool (*read)(const char *path, const char *file, cloud_sync_complete_handler_t cb, void *user_data);
bool (*update)(const char *path, RFILE *file, cloud_sync_complete_handler_t cb, void *user_data);
bool (*delete)(const char *path, cloud_sync_complete_handler_t cb, void *user_data);
const char *ident;
} cloud_sync_driver_t;
typedef struct
{
const cloud_sync_driver_t *driver;
} cloud_sync_driver_state_t;
cloud_sync_driver_state_t *cloud_sync_state_get_ptr(void);
extern cloud_sync_driver_t cloud_sync_webdav;
extern const cloud_sync_driver_t *cloud_sync_drivers[];
/**
* config_get_cloud_sync_driver_options:
*
* Get an enumerated list of all cloud_sync driver names, separated by '|'.
*
* Returns: string listing of all cloud_sync driver names, separated by '|'.
**/
const char* config_get_cloud_sync_driver_options(void);
void cloud_sync_find_driver(settings_t *settings, const char *prefix, bool verbosity_enabled);
bool cloud_sync_begin(cloud_sync_complete_handler_t cb, void *user_data);
bool cloud_sync_end(cloud_sync_complete_handler_t cb, void *user_data);
bool cloud_sync_read(const char *path, const char *file, cloud_sync_complete_handler_t cb, void *user_data);
bool cloud_sync_update(const char *path, RFILE *file, cloud_sync_complete_handler_t cb, void *user_data);
bool cloud_sync_delete(const char *path, cloud_sync_complete_handler_t cb, void *user_data);
RETRO_END_DECLS
#endif

View file

@ -18,6 +18,7 @@ OTHER_CFLAGS = $(inherited) -DHAVE_CC_RESAMPLER
OTHER_CFLAGS = $(inherited) -DHAVE_CHD
OTHER_CFLAGS = $(inherited) -DHAVE_CHEATS
OTHER_CFLAGS = $(inherited) -DHAVE_CHEEVOS
OTHER_CFLAGS = $(inherited) -DHAVE_CLOUDSYNC
OTHER_CFLAGS = $(inherited) -DHAVE_COCOA_METAL
OTHER_CFLAGS = $(inherited) -DHAVE_COMMAND
OTHER_CFLAGS = $(inherited) -DHAVE_CONFIGFILE

View file

@ -1596,6 +1596,7 @@
"-DHAVE_CC_RESAMPLER",
"-DHAVE_CHEATS",
"-DHAVE_CHEEVOS",
"-DHAVE_CLOUDSYNC",
"-DHAVE_COCOATOUCH",
"-DHAVE_COCOA_METAL",
"-DHAVE_CONFIGFILE",
@ -1743,6 +1744,7 @@
"-DHAVE_CC_RESAMPLER",
"-DHAVE_CHEATS",
"-DHAVE_CHEEVOS",
"-DHAVE_CLOUDSYNC",
"-DHAVE_COCOATOUCH",
"-DHAVE_COCOA_METAL",
"-DHAVE_CONFIGFILE",
@ -1918,6 +1920,7 @@
"-DHAVE_CC_RESAMPLER",
"-DHAVE_CHEATS",
"-DHAVE_CHEEVOS",
"-DHAVE_CLOUDSYNC",
"-DHAVE_COCOATOUCH",
"-DHAVE_COCOA_METAL",
"-DHAVE_CONFIGFILE",
@ -2082,6 +2085,7 @@
"-DHAVE_CC_RESAMPLER",
"-DHAVE_CHEATS",
"-DHAVE_CHEEVOS",
"-DHAVE_CLOUDSYNC",
"-DHAVE_COCOATOUCH",
"-DHAVE_COCOA_METAL",
"-DHAVE_CONFIGFILE",

View file

@ -164,6 +164,9 @@
#ifdef HAVE_WIFI
#include "network/wifi_driver.h"
#endif
#ifdef HAVE_CLOUDSYNC
#include "network/cloud_sync_driver.h"
#endif
#endif
#ifdef HAVE_THREADS
@ -498,6 +501,18 @@ static const void *find_driver_nonempty(
return wifi_drivers[i];
}
}
#endif
#ifdef HAVE_CLOUDSYNC
else if (string_is_equal(label, "cloud_sync_driver"))
{
if (cloud_sync_drivers[i])
{
const char *ident = cloud_sync_drivers[i]->ident;
strlcpy(s, ident, len);
return cloud_sync_drivers[i];
}
}
#endif
return NULL;
}
@ -1606,6 +1621,17 @@ struct string_list *string_list_new_special(enum string_list_type type,
string_list_append(s, opt, attr);
}
break;
case STRING_LIST_CLOUD_SYNC_DRIVERS:
#ifdef HAVE_CLOUDSYNC
for (i = 0; cloud_sync_drivers[i]; i++)
{
const char *opt = cloud_sync_drivers[i]->ident;
*len += STRLEN_CONST(cloud_sync_drivers[i]->ident) + 1;
string_list_append(s, opt, attr);
}
#endif
break;
#ifdef HAVE_LAKKA
case STRING_LIST_TIMEZONES:
{
@ -2796,6 +2822,9 @@ bool command_event(enum event_command cmd, void *data)
}
}
#endif
#ifdef HAVE_CLOUDSYNC
task_push_cloud_sync();
#endif
#if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL)
runloop_st->runtime_shader_preset_path[0] = '\0';
#endif
@ -4883,6 +4912,9 @@ int rarch_main(int argc, char *argv[], void *data)
}
ui_companion_driver_init_first();
#if HAVE_CLOUDSYNC
task_push_cloud_sync();
#endif
#if !defined(HAVE_MAIN) || defined(HAVE_QT)
for (;;)
{
@ -6519,6 +6551,10 @@ bool retroarch_main_init(int argc, char *argv[])
#endif
#ifdef HAVE_WIFI
wifi_driver_ctl(RARCH_WIFI_CTL_FIND_DRIVER, NULL);
#endif
#ifdef HAVE_CLOUDSYNC
cloud_sync_find_driver(settings,
"cloud sync driver", verbosity_enabled);
#endif
location_driver_find_driver(settings,
"location driver", verbosity_enabled);

1064
tasks/task_cloudsync.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -177,6 +177,7 @@ task_finished:
{
size_t len = 0;
char *tmp = (char*)net_http_data(http->handle, &len, false);
struct string_list *headers = net_http_headers(http->handle);
if (tmp && http->cb)
http->cb(tmp, len);
@ -188,16 +189,18 @@ task_finished:
{
if (tmp)
free(tmp);
string_list_free(headers);
task_set_error(task,
strldup("Task cancelled.", sizeof("Task cancelled.")));
}
else
{
data = (http_transfer_data_t*)malloc(sizeof(*data));
data->data = tmp;
data->len = len;
data->status = net_http_status(http->handle);
data = (http_transfer_data_t*)malloc(sizeof(*data));
data->data = tmp;
data->headers = headers;
data->len = len;
data->status = net_http_status(http->handle);
task_set_data(task, data);
@ -219,6 +222,7 @@ static void task_http_transfer_cleanup(retro_task_t *task)
http_transfer_data_t* data = (http_transfer_data_t*)task_get_data(task);
if (data)
{
string_list_free(data->headers);
if (data->data)
free(data->data);
free(data);
@ -346,6 +350,112 @@ void* task_push_http_transfer(const char *url, bool mute,
return NULL;
}
void *task_push_webdav_stat(const char *url, bool mute, const char *headers,
retro_task_callback_t cb, void *user_data)
{
struct http_connection_t *conn;
if (string_is_empty(url))
return NULL;
if (!(conn = net_http_connection_new(url, "OPTIONS", NULL)))
return NULL;
if (headers)
net_http_connection_set_headers(conn, headers);
return task_push_http_transfer_generic(conn, url, mute, NULL, cb, user_data);
}
void* task_push_webdav_mkdir(const char *url, bool mute,
const char *headers,
retro_task_callback_t cb, void *user_data)
{
struct http_connection_t *conn;
if (string_is_empty(url))
return NULL;
if (!(conn = net_http_connection_new(url, "MKCOL", NULL)))
return NULL;
if (headers)
net_http_connection_set_headers(conn, headers);
return task_push_http_transfer_generic(conn, url, mute, NULL, cb, user_data);
}
void* task_push_webdav_put(const char *url,
const void *put_data, size_t len, bool mute,
const char *headers, retro_task_callback_t cb, void *user_data)
{
struct http_connection_t *conn;
char expect[1024];
size_t _len;
if (string_is_empty(url))
return NULL;
if (!(conn = net_http_connection_new(url, "PUT", NULL)))
return NULL;
_len = strlcpy(expect, "Expect: 100-continue\r\n", sizeof(expect));
if (headers)
{
strlcpy(expect + _len, headers, sizeof(expect) - _len);
net_http_connection_set_headers(conn, expect);
}
if (put_data)
net_http_connection_set_content(conn, NULL, len, put_data);
return task_push_http_transfer_generic(conn, url, mute, NULL, cb, user_data);
}
void* task_push_webdav_delete(const char *url, bool mute,
const char *headers,
retro_task_callback_t cb, void *user_data)
{
struct http_connection_t *conn;
if (string_is_empty(url))
return NULL;
if (!(conn = net_http_connection_new(url, "DELETE", NULL)))
return NULL;
if (headers)
net_http_connection_set_headers(conn, headers);
return task_push_http_transfer_generic(conn, url, mute, NULL, cb, user_data);
}
void *task_push_webdav_move(const char *url,
const char *dest, bool mute, const char *headers,
retro_task_callback_t cb, void *userdata)
{
struct http_connection_t *conn;
char dest_header[PATH_MAX_LENGTH + 512];
size_t len;
if (string_is_empty(url))
return NULL;
if (!(conn = net_http_connection_new(url, "MOVE", NULL)))
return NULL;
len = strlcpy(dest_header, "Destination: ", sizeof(dest_header));
len += strlcpy(dest_header + len, dest, sizeof(dest_header) - len);
len += strlcpy(dest_header + len, "\r\n", sizeof(dest_header) - len);
if (headers)
strlcpy(dest_header + len, headers, sizeof(dest_header) - len);
net_http_connection_set_headers(conn, dest_header);
return task_push_http_transfer_generic(conn, url, mute, NULL, cb, userdata);
}
void* task_push_http_transfer_file(const char* url, bool mute,
const char* type,
retro_task_callback_t cb, file_transfer_t* transfer_data)

View file

@ -21,6 +21,7 @@
#include <boolean.h>
#include <retro_common_api.h>
#include <retro_miscellaneous.h>
#include <lists/string_list.h>
#include <queues/task_queue.h>
#include <gfx/scaler/scaler.h>
@ -85,6 +86,7 @@ struct screenshot_task_state
typedef struct
{
char *data;
struct string_list *headers;
size_t len;
int status;
} http_transfer_data_t;
@ -109,6 +111,17 @@ void *task_push_http_post_transfer_with_headers(const char *url, const char *pos
task_retriever_info_t *http_task_get_transfer_list(void);
void *task_push_webdav_stat(const char *url, bool mute, const char *headers,
retro_task_callback_t cb, void *userdata);
void *task_push_webdav_mkdir(const char *url, bool mute, const char *headers,
retro_task_callback_t cb, void *userdata);
void *task_push_webdav_put(const char *url, const void *put_data, size_t len, bool mute, const char *headers,
retro_task_callback_t cb, void *userdata);
void *task_push_webdav_delete(const char *url, bool mute, const char *headers,
retro_task_callback_t cb, void *userdata);
void *task_push_webdav_move(const char *url, const char *dest, bool mute, const char *headers,
retro_task_callback_t cb, void *userdata);
bool task_push_bluetooth_scan(retro_task_callback_t cb);
bool task_push_wifi_scan(retro_task_callback_t cb);
@ -292,6 +305,9 @@ void menu_explore_wait_for_init_task(void);
extern const char* const input_builtin_autoconfs[];
/* cloud sync tasks */
void task_push_cloud_sync(void);
RETRO_END_DECLS
#endif