diff --git a/Makefile.common b/Makefile.common index 18e8a0e02b..4e90d7b47e 100644 --- a/Makefile.common +++ b/Makefile.common @@ -239,6 +239,7 @@ OBJ += frontend/frontend.o \ core_info.o \ $(LIBRETRO_COMM_DIR)/file/config_file.o \ $(LIBRETRO_COMM_DIR)/file/config_file_userdata.o \ + $(LIBRETRO_COMM_DIR)/file/runtime_file.o \ tasks/task_screenshot.o \ tasks/task_powerstate.o \ $(LIBRETRO_COMM_DIR)/gfx/scaler/scaler.o \ diff --git a/file_path_special.h b/file_path_special.h index 092b8e7d77..c8e5c2ba68 100644 --- a/file_path_special.h +++ b/file_path_special.h @@ -94,7 +94,8 @@ enum file_path_enum FILE_PATH_S3M_EXTENSION, FILE_PATH_XM_EXTENSION, FILE_PATH_CONFIG_EXTENSION, - FILE_PATH_CORE_INFO_EXTENSION + FILE_PATH_CORE_INFO_EXTENSION, + FILE_PATH_RUNTIME_EXTENSION }; enum application_special_type diff --git a/file_path_str.c b/file_path_str.c index 33dd18c85a..30aa0ff79e 100644 --- a/file_path_str.c +++ b/file_path_str.c @@ -227,6 +227,9 @@ const char *file_path_str(enum file_path_enum enum_idx) case FILE_PATH_TTF_FONT: str = "font.ttf"; break; + case FILE_PATH_RUNTIME_EXTENSION: + str = ".lrtl"; + break; case FILE_PATH_UNKNOWN: default: break; diff --git a/griffin/griffin.c b/griffin/griffin.c index 11ab4cf1c5..69a957bf16 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -140,6 +140,11 @@ CONFIG FILE #include "../managers/core_manager.c" #include "../managers/core_option_manager.c" +/*============================================================ +RUNTIME FILE +============================================================ */ +#include "../libretro-common/file/runtime_file.c" + /*============================================================ ACHIEVEMENTS ============================================================ */ diff --git a/libretro-common/file/runtime_file.c b/libretro-common/file/runtime_file.c new file mode 100644 index 0000000000..47489cd65c --- /dev/null +++ b/libretro-common/file/runtime_file.c @@ -0,0 +1,534 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (runtime_file.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define LOG_FILE_FORMAT_STR "%u:%02u:%02u\n%04u-%02u-%02u %02u:%02u:%02u\n" + +/* Initialisation */ + +/* Parses log file referenced by runtime_log->path. + * Does nothing if log file does not exist. */ +static void runtime_log_read_file(runtime_log_t *runtime_log) +{ + unsigned runtime_hours = 0; + unsigned runtime_minutes = 0; + unsigned runtime_seconds = 0; + + unsigned last_played_year = 0; + unsigned last_played_month = 0; + unsigned last_played_day = 0; + unsigned last_played_hour = 0; + unsigned last_played_minute = 0; + unsigned last_played_second = 0; + + int ret = 0; + RFILE *file = NULL; + + /* Check if log file exists */ + if (!filestream_exists(runtime_log->path)) + return; + + /* Attempt to open log file */ + file = filestream_open(runtime_log->path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + { + RARCH_ERR("Failed to open runtime log file: %s\n", runtime_log->path); + return; + } + + /* Parse log file */ + ret = filestream_scanf(file, LOG_FILE_FORMAT_STR, + &runtime_hours, &runtime_minutes, &runtime_seconds, + &last_played_year, &last_played_month, &last_played_day, + &last_played_hour, &last_played_minute, &last_played_second); + + if (ret == 9) + { + /* All is well - assign values to runtime_log object */ + runtime_log->runtime.hours = runtime_hours; + runtime_log->runtime.minutes = runtime_minutes; + runtime_log->runtime.seconds = runtime_seconds; + + runtime_log->last_played.year = last_played_year; + runtime_log->last_played.month = last_played_month; + runtime_log->last_played.day = last_played_day; + runtime_log->last_played.hour = last_played_hour; + runtime_log->last_played.minute = last_played_minute; + runtime_log->last_played.second = last_played_second; + } + else + RARCH_ERR("Invalid runtime log file: %s\n", runtime_log->path); + + /* Close log file */ + filestream_close(file); +} + +/* Initialise runtime log, loading current parameters + * if log file exists. Returned object must be free()'d. + * Returns NULL if content_path and/or core_path are invalid */ +runtime_log_t *runtime_log_init(const char *content_path, const char *core_path) +{ + settings_t *settings = config_get_ptr(); + core_info_list_t *core_info = NULL; + runtime_log_t *runtime_log = NULL; + + const char *savefile_dir = dir_get(RARCH_DIR_SAVEFILE); + char content_name[PATH_MAX_LENGTH]; + char core_name[PATH_MAX_LENGTH]; + char log_file_dir[PATH_MAX_LENGTH]; + char log_file_path[PATH_MAX_LENGTH]; + + unsigned i; + + content_name[0] = '\0'; + core_name[0] = '\0'; + log_file_dir[0] = '\0'; + log_file_path[0] = '\0'; + + /* Error checking */ + if (!settings) + return NULL; + + if (string_is_empty(content_path) || string_is_empty(core_path) || string_is_empty(savefile_dir)) + return NULL; + + if (string_is_equal(core_path, "builtin")) + return NULL; + + /* Get core name */ + core_info_get_list(&core_info); + + if (!core_info) + return NULL; + + for (i = 0; i < core_info->count; i++) + { + if (string_is_equal(core_info->list[i].path, core_path)) + { + strlcpy(core_name, core_info->list[i].core_name, sizeof(core_name)); + break; + } + } + + if (string_is_empty(core_name)) + return NULL; + + /* Get runtime log directory */ + if (settings->bools.sort_savefiles_enable) + { + fill_pathname_join( + log_file_dir, + savefile_dir, + core_name, + sizeof(log_file_dir)); + } + else + { + strlcpy(log_file_dir, savefile_dir, sizeof(log_file_dir)); + } + + if (string_is_empty(log_file_dir)) + return NULL; + + /* Create directory, if required */ + if (!path_is_directory(log_file_dir)) + { + path_mkdir(log_file_dir); + + if(!path_is_directory(log_file_dir)) + { + RARCH_ERR("Failed to create directory for runtime log: %s.\n", log_file_dir); + return NULL; + } + } + + /* Get content name + * Note: TyrQuake requires a specific hack, since all + * content has the same name... */ + if (string_is_equal(core_name, "TyrQuake")) + { + const char *last_slash = find_last_slash(content_path); + if (last_slash) + { + size_t path_length = last_slash + 1 - content_path; + if (path_length < PATH_MAX_LENGTH) + { + char tmp[PATH_MAX_LENGTH]; + memset(tmp, 0, sizeof(tmp)); + strlcpy(tmp, content_path, path_length * sizeof(char)); + strlcpy(content_name, path_basename(tmp), sizeof(content_name)); + } + } + } + else + { + /* path_remove_extension() requires a char * (not const) + * so have to use a temporary buffer... */ + char *tmp = strdup(path_basename(content_path)); + + strlcpy( + content_name, + path_remove_extension(tmp), + sizeof(content_name)); + + if (!string_is_empty(tmp)) + free(tmp); + } + + if (string_is_empty(content_name)) + return NULL; + + /* Build final log file path */ + fill_pathname_join(log_file_path, log_file_dir, content_name, sizeof(log_file_path)); + + if (!settings->bools.sort_savefiles_enable) + { + strlcat(log_file_path, " - ", sizeof(log_file_path)); + strlcat(log_file_path, core_name, sizeof(log_file_path)); + } + + strlcat(log_file_path, file_path_str(FILE_PATH_RUNTIME_EXTENSION), sizeof(log_file_path)); + + if (string_is_empty(log_file_path)) + return NULL; + + /* Phew... If we get this far then all is well. + * > Create 'runtime_log' object */ + runtime_log = (runtime_log_t*)calloc(1, sizeof(*runtime_log)); + if (!runtime_log) + return NULL; + + /* > Populate default values */ + runtime_log->runtime.hours = 0; + runtime_log->runtime.minutes = 0; + runtime_log->runtime.seconds = 0; + + runtime_log->last_played.year = 0; + runtime_log->last_played.month = 0; + runtime_log->last_played.day = 0; + runtime_log->last_played.hour = 0; + runtime_log->last_played.minute = 0; + runtime_log->last_played.second = 0; + + strlcpy(runtime_log->path, log_file_path, sizeof(runtime_log->path)); + + /* Load existing log file, if it exists */ + runtime_log_read_file(runtime_log); + + return runtime_log; +} + +/* Setters */ + +/* Set runtime to specified hours, minutes, seconds value */ +void runtime_log_set_runtime_hms(runtime_log_t *runtime_log, unsigned hours, unsigned minutes, unsigned seconds) +{ + retro_time_t usec; + + if (!runtime_log) + return; + + /* Converting to usec and back again may be considered a + * waste of CPU cycles, but this allows us to handle any + * kind of broken input without issue - i.e. user can enter + * minutes and seconds values > 59, and everything still + * works correctly */ + runtime_log_convert_hms2usec(hours, minutes, seconds, &usec); + + runtime_log_convert_usec2hms(usec, + &runtime_log->runtime.hours, &runtime_log->runtime.minutes, &runtime_log->runtime.seconds); +} + +/* Set runtime to specified microseconds value */ +void runtime_log_set_runtime_usec(runtime_log_t *runtime_log, retro_time_t usec) +{ + if (!runtime_log) + return; + + runtime_log_convert_usec2hms(usec, + &runtime_log->runtime.hours, &runtime_log->runtime.minutes, &runtime_log->runtime.seconds); +} + +/* Adds specified hours, minutes, seconds value to current runtime */ +void runtime_log_add_runtime_hms(runtime_log_t *runtime_log, unsigned hours, unsigned minutes, unsigned seconds) +{ + retro_time_t usec_old; + retro_time_t usec_new; + + if (!runtime_log) + return; + + runtime_log_convert_hms2usec( + runtime_log->runtime.hours, runtime_log->runtime.minutes, runtime_log->runtime.seconds, + &usec_old); + + runtime_log_convert_hms2usec(hours, minutes, seconds, &usec_new); + + runtime_log_convert_usec2hms(usec_old + usec_new, + &runtime_log->runtime.hours, &runtime_log->runtime.minutes, &runtime_log->runtime.seconds); +} + +/* Adds specified microseconds value to current runtime */ +void runtime_log_add_runtime_usec(runtime_log_t *runtime_log, retro_time_t usec) +{ + retro_time_t usec_old; + + if (!runtime_log) + return; + + runtime_log_convert_hms2usec( + runtime_log->runtime.hours, runtime_log->runtime.minutes, runtime_log->runtime.seconds, + &usec_old); + + runtime_log_convert_usec2hms(usec_old + usec, + &runtime_log->runtime.hours, &runtime_log->runtime.minutes, &runtime_log->runtime.seconds); +} + +/* Sets last played entry to specified value */ +void runtime_log_set_last_played(runtime_log_t *runtime_log, + unsigned year, unsigned month, unsigned day, + unsigned hour, unsigned minute, unsigned second) +{ + if (!runtime_log) + return; + + /* This function should never be needed, so just + * perform dumb value assignment (i.e. no validation + * using mktime()) */ + runtime_log->last_played.year = year; + runtime_log->last_played.month = month; + runtime_log->last_played.day = day; + runtime_log->last_played.hour = hour; + runtime_log->last_played.minute = minute; + runtime_log->last_played.second = second; +} + +/* Sets last played entry to current date/time */ +void runtime_log_set_last_played_now(runtime_log_t *runtime_log) +{ + time_t current_time; + struct tm * time_info; + + if (!runtime_log) + return; + + /* Get current time */ + time(¤t_time); + time_info = localtime(¤t_time); + + /* This can actually happen, but if does we probably + * have bigger problems to worry about... */ + if(!time_info) + { + RARCH_ERR("Failed to get current time.\n"); + return; + } + + /* Extract values */ + runtime_log->last_played.year = (unsigned)time_info->tm_year + 1900; + runtime_log->last_played.month = (unsigned)time_info->tm_mon + 1; + runtime_log->last_played.day = (unsigned)time_info->tm_mday; + runtime_log->last_played.hour = (unsigned)time_info->tm_hour; + runtime_log->last_played.minute = (unsigned)time_info->tm_min; + runtime_log->last_played.second = (unsigned)time_info->tm_sec; +} + +/* Resets log to default (zero) values */ +void runtime_log_reset(runtime_log_t *runtime_log) +{ + if (!runtime_log) + return; + + runtime_log->runtime.hours = 0; + runtime_log->runtime.minutes = 0; + runtime_log->runtime.seconds = 0; + + runtime_log->last_played.year = 0; + runtime_log->last_played.month = 0; + runtime_log->last_played.day = 0; + runtime_log->last_played.hour = 0; + runtime_log->last_played.minute = 0; + runtime_log->last_played.second = 0; +} + +/* Getters */ + +/* Gets runtime in hours, minutes, seconds */ +void runtime_log_get_runtime_hms(runtime_log_t *runtime_log, unsigned *hours, unsigned *minutes, unsigned *seconds) +{ + if (!runtime_log) + return; + + *hours = runtime_log->runtime.hours; + *minutes = runtime_log->runtime.minutes; + *seconds = runtime_log->runtime.seconds; +} + +/* Gets runtime in microseconds */ +void runtime_log_get_runtime_usec(runtime_log_t *runtime_log, retro_time_t *usec) +{ + if (!runtime_log) + return; + + runtime_log_convert_hms2usec( + runtime_log->runtime.hours, runtime_log->runtime.minutes, runtime_log->runtime.seconds, + usec); +} + +/* Gets last played entry values */ +void runtime_log_get_last_played(runtime_log_t *runtime_log, + unsigned *year, unsigned *month, unsigned *day, + unsigned *hour, unsigned *minute, unsigned *second) +{ + if (!runtime_log) + return; + + *year = runtime_log->last_played.year; + *month = runtime_log->last_played.month; + *day = runtime_log->last_played.day; + *hour = runtime_log->last_played.hour; + *minute = runtime_log->last_played.minute; + *second = runtime_log->last_played.second; +} + +/* Gets last played entry values as a time_t 'object' + * (e.g. for printing with strftime()) */ +void runtime_log_get_last_played_time(runtime_log_t *runtime_log, time_t *time) +{ + struct tm time_info; + + if (!runtime_log) + return; + + if (!time) + return; + + /* Set tm values */ + time_info.tm_year = (int)runtime_log->last_played.year - 1900; + time_info.tm_mon = (int)runtime_log->last_played.month - 1; + time_info.tm_mday = (int)runtime_log->last_played.day; + time_info.tm_hour = (int)runtime_log->last_played.hour; + time_info.tm_min = (int)runtime_log->last_played.minute; + time_info.tm_sec = (int)runtime_log->last_played.second; + time_info.tm_isdst = -1; + + /* Get time */ + *time = mktime(&time_info); +} + +/* Status */ + +/* Returns true if log has a non-zero runtime entry */ +bool runtime_log_has_runtime(runtime_log_t *runtime_log) +{ + if (!runtime_log) + return false; + + return !((runtime_log->runtime.hours == 0) && + (runtime_log->runtime.minutes == 0) && + (runtime_log->runtime.seconds == 0)); +} + +/* Returns true if log has a non-zero last played entry */ +bool runtime_log_has_last_played(runtime_log_t *runtime_log) +{ + if (!runtime_log) + return false; + + return !((runtime_log->last_played.year == 0) && + (runtime_log->last_played.month == 0) && + (runtime_log->last_played.day == 0) && + (runtime_log->last_played.hour == 0) && + (runtime_log->last_played.minute == 0) && + (runtime_log->last_played.second == 0)); +} + +/* Saving */ + +/* Saves specified runtime log to disk */ +void runtime_log_save(runtime_log_t *runtime_log) +{ + int ret = 0; + RFILE *file = NULL; + + if (!runtime_log) + return; + + /* Attempt to open log file */ + file = filestream_open(runtime_log->path, RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + { + RARCH_ERR("Failed to open runtime log file: %s\n", runtime_log->path); + return; + } + + /* Write log file contents */ + ret = filestream_printf(file, LOG_FILE_FORMAT_STR, + runtime_log->runtime.hours, runtime_log->runtime.minutes, runtime_log->runtime.seconds, + runtime_log->last_played.year, runtime_log->last_played.month, runtime_log->last_played.day, + runtime_log->last_played.hour, runtime_log->last_played.minute, runtime_log->last_played.second); + + if (ret <= 0) + RARCH_ERR("Failed to write runtime log file: %s\n", runtime_log->path); + + /* Close log file */ + filestream_close(file); +} + +/* Utility functions */ + +/* Convert from hours, minutes, seconds to microseconds */ +void runtime_log_convert_hms2usec(unsigned hours, unsigned minutes, unsigned seconds, retro_time_t *usec) +{ + *usec = ((retro_time_t)hours * 60 * 60 * 1000000) + + ((retro_time_t)minutes * 60 * 1000000) + + ((retro_time_t)seconds * 1000000); +} + +/* Convert from microseconds to hours, minutes, seconds */ +void runtime_log_convert_usec2hms(retro_time_t usec, unsigned *hours, unsigned *minutes, unsigned *seconds) +{ + *seconds = usec / 1000000; + *minutes = *seconds / 60; + *hours = *minutes / 60; + + *seconds -= *minutes * 60; + *minutes -= *hours * 60; +} diff --git a/libretro-common/include/file/runtime_file.h b/libretro-common/include/file/runtime_file.h new file mode 100644 index 0000000000..e7f0abac30 --- /dev/null +++ b/libretro-common/include/file/runtime_file.h @@ -0,0 +1,133 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (runtime_file.h). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __RUNTIME_FILE_H +#define __RUNTIME_FILE_H + +#include +#include + +#include +#include + +RETRO_BEGIN_DECLS + +typedef struct +{ + unsigned hours; + unsigned minutes; + unsigned seconds; +} rtl_runtime_t; + +typedef struct +{ + unsigned year; + unsigned month; + unsigned day; + unsigned hour; + unsigned minute; + unsigned second; +} rtl_last_played_t; + +typedef struct +{ + rtl_runtime_t runtime; + rtl_last_played_t last_played; + char path[PATH_MAX_LENGTH]; +} runtime_log_t; + +/* Initialisation */ + +/* Initialise runtime log, loading current parameters + * if log file exists. Returned object must be free()'d. + * Returns NULL if content_path and/or core_path are invalid */ +runtime_log_t *runtime_log_init(const char *content_path, const char *core_path); + +/* Setters */ + +/* Set runtime to specified hours, minutes, seconds value */ +void runtime_log_set_runtime_hms(runtime_log_t *runtime_log, unsigned hours, unsigned minutes, unsigned seconds); + +/* Set runtime to specified microseconds value */ +void runtime_log_set_runtime_usec(runtime_log_t *runtime_log, retro_time_t usec); + +/* Adds specified hours, minutes, seconds value to current runtime */ +void runtime_log_add_runtime_hms(runtime_log_t *runtime_log, unsigned hours, unsigned minutes, unsigned seconds); + +/* Adds specified microseconds value to current runtime */ +void runtime_log_add_runtime_usec(runtime_log_t *runtime_log, retro_time_t usec); + +/* Sets last played entry to specified value */ +void runtime_log_set_last_played(runtime_log_t *runtime_log, + unsigned year, unsigned month, unsigned day, + unsigned hour, unsigned minute, unsigned second); + +/* Sets last played entry to current date/time */ +void runtime_log_set_last_played_now(runtime_log_t *runtime_log); + +/* Resets log to default (zero) values */ +void runtime_log_reset(runtime_log_t *runtime_log); + +/* Getters */ +/* (Not strictly required, since we can get everything + * from runtime_log directly - but perhaps it is logically + * cleaner to have a symmetrical set/get interface) */ + +/* Gets runtime in hours, minutes, seconds */ +void runtime_log_get_runtime_hms(runtime_log_t *runtime_log, unsigned *hours, unsigned *minutes, unsigned *seconds); + +/* Gets runtime in microseconds */ +void runtime_log_get_runtime_usec(runtime_log_t *runtime_log, retro_time_t *usec); + +/* Gets last played entry values */ +void runtime_log_get_last_played(runtime_log_t *runtime_log, + unsigned *year, unsigned *month, unsigned *day, + unsigned *hour, unsigned *minute, unsigned *second); + +/* Gets last played entry values as a time_t 'object' + * (e.g. for printing with strftime()) */ +void runtime_log_get_last_played_time(runtime_log_t *runtime_log, time_t *time); + +/* Status */ + +/* Returns true if log has a non-zero runtime entry */ +bool runtime_log_has_runtime(runtime_log_t *runtime_log); + +/* Returns true if log has a non-zero last played entry */ +bool runtime_log_has_last_played(runtime_log_t *runtime_log); + +/* Saving */ + +/* Saves specified runtime log to disk */ +void runtime_log_save(runtime_log_t *runtime_log); + +/* Utility functions */ + +/* Convert from hours, minutes, seconds to microseconds */ +void runtime_log_convert_hms2usec(unsigned hours, unsigned minutes, unsigned seconds, retro_time_t *usec); + +/* Convert from microseconds to hours, minutes, seconds */ +void runtime_log_convert_usec2hms(retro_time_t usec, unsigned *hours, unsigned *minutes, unsigned *seconds); + +RETRO_END_DECLS + +#endif diff --git a/retroarch.c b/retroarch.c index 61b99006e7..8b996952de 100644 --- a/retroarch.c +++ b/retroarch.c @@ -53,6 +53,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H #include "config.h" @@ -2387,73 +2388,105 @@ bool rarch_ctl(enum rarch_ctl_state state, void *data) case RARCH_CTL_CONTENT_RUNTIME_LOG_DEINIT: { settings_t *settings = config_get_ptr(); - unsigned seconds = libretro_core_runtime_usec / 1000 / 1000; - unsigned minutes = seconds / 60; - unsigned hours = minutes / 60; + unsigned hours = 0; + unsigned minutes = 0; + unsigned seconds = 0; char log[PATH_MAX_LENGTH] = {0}; - size_t pos = 0; + int n = 0; - seconds -= minutes * 60; - minutes -= hours * 60; - - pos = strlcpy(log, "Content ran for a total of", sizeof(log)); - - if (hours > 0) - pos += snprintf(log + pos, sizeof(log) - pos, ", %u hours", hours); - - if (minutes > 0) - pos += snprintf(log + pos, sizeof(log) - pos, ", %u minutes", minutes); - - pos += snprintf(log + pos, sizeof(log) - pos, ", %u seconds", seconds); - - if (pos < sizeof(log) - 2) - { - log[pos++] = '.'; - log[pos++] = '\n'; - } + runtime_log_convert_usec2hms(libretro_core_runtime_usec, &hours, &minutes, &seconds); + n = snprintf(log, sizeof(log), + "Content ran for a total of: %02u hours, %02u minutes, %02u seconds.\n ", + hours, minutes, seconds); + if ((n < 0) || (n >= PATH_MAX_LENGTH)) + n = 0; /* Just silence any potential gcc warnings... */ RARCH_LOG(log); - if (settings->bools.content_runtime_log && g_defaults.content_runtime) + if (settings->bools.content_runtime_log) { - const char *path = path_get(RARCH_PATH_CONTENT); + const char *content_path = path_get(RARCH_PATH_CONTENT); const char *core_path = path_get(RARCH_PATH_CORE); - if (!string_is_empty(path) && !string_is_empty(core_path) && !string_is_equal(core_path, "builtin")) + if (!string_is_empty(content_path) && !string_is_empty(core_path) && !string_is_equal(core_path, "builtin")) { - playlist_push_runtime(g_defaults.content_runtime, path, core_path, 0, 0, 0); + unsigned playlist_hours = 0; + unsigned playlist_minutes = 0; + unsigned playlist_seconds = 0; + runtime_log_t *runtime_log = NULL; + bool playlist_file_is_valid = false; + bool runtime_log_file_is_valid = false; - /* if entry already existed, the runtime won't be updated, so manually update it again */ - if (playlist_get_size(g_defaults.content_runtime) > 0) + /* Intialise content_runtime playlist entry and get + * existing values */ + if (g_defaults.content_runtime) { - unsigned runtime_hours = 0; - unsigned runtime_minutes = 0; - unsigned runtime_seconds = 0; + /* Push current entry to the top (does not update runtime + * values), or create new entry if it does not already exist */ + playlist_push_runtime(g_defaults.content_runtime, content_path, core_path, 0, 0, 0); - playlist_get_runtime_index(g_defaults.content_runtime, 0, NULL, NULL, - &runtime_hours, &runtime_minutes, &runtime_seconds); - - runtime_seconds += seconds; - - if (runtime_seconds >= 60) + /* Get current runtime */ + if (playlist_get_size(g_defaults.content_runtime) > 0) { - unsigned new_minutes = runtime_seconds / 60; - runtime_minutes += new_minutes; - runtime_seconds -= new_minutes * 60; + playlist_get_runtime_index(g_defaults.content_runtime, 0, NULL, NULL, + &playlist_hours, &playlist_minutes, &playlist_seconds); + + playlist_file_is_valid = true; + } + } + + /* Initialise runtime log file */ + runtime_log = runtime_log_init(content_path, core_path); + if (runtime_log) + { + /* If runtime log file is empty, populate it with values + * from content_runtime playlist */ + if (!runtime_log_has_runtime(runtime_log)) + { + runtime_log_set_runtime_hms(runtime_log, + playlist_hours, playlist_minutes, playlist_seconds); } - runtime_minutes += minutes; + /* Add additional runtime */ + runtime_log_add_runtime_usec(runtime_log, libretro_core_runtime_usec); - if (runtime_minutes >= 60) + /* Read back current runtime, so we can copy it + * to content_runtime playlist */ + runtime_log_get_runtime_hms(runtime_log, + &playlist_hours, &playlist_minutes, &playlist_seconds); + + /* Update 'last played' entry */ + runtime_log_set_last_played_now(runtime_log); + + /* Save runtime log file */ + runtime_log_save(runtime_log); + + /* Clean up */ + free(runtime_log); + + runtime_log_file_is_valid = true; + } + + /* Update content_runtime playlist */ + if (playlist_file_is_valid) + { + /* If something went wrong with the runtime log + * file (can't happen...), then playlist_hours/minutes/seconds + * still contains original (old) values. Have to update them + * manually... */ + if (!runtime_log_file_is_valid) { - unsigned new_hours = runtime_minutes / 60; - runtime_hours += new_hours; - runtime_minutes -= new_hours * 60; + retro_time_t usec_old; + + runtime_log_convert_hms2usec( + playlist_hours, playlist_minutes, playlist_seconds, &usec_old); + + runtime_log_convert_usec2hms(usec_old + libretro_core_runtime_usec, + &playlist_hours, &playlist_minutes, &playlist_seconds); } - runtime_hours += hours; - - playlist_update_runtime(g_defaults.content_runtime, 0, path, core_path, runtime_hours, runtime_minutes, runtime_seconds); + playlist_update_runtime(g_defaults.content_runtime, 0, content_path, core_path, + playlist_hours, playlist_minutes, playlist_seconds); } } }