2019-02-27 16:19:29 +00:00
|
|
|
/* 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 <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
#include <file/file_path.h>
|
2019-03-01 00:22:35 +00:00
|
|
|
#include <retro_miscellaneous.h>
|
2019-02-27 16:19:29 +00:00
|
|
|
#include <streams/file_stream.h>
|
2020-10-11 18:02:20 +00:00
|
|
|
#include <formats/rjson.h>
|
2019-02-27 16:19:29 +00:00
|
|
|
#include <string/stdstring.h>
|
2019-09-10 15:49:07 +00:00
|
|
|
#include <encodings/utf.h>
|
2020-06-04 11:19:24 +00:00
|
|
|
#include <time/rtime.h>
|
2019-02-27 16:19:29 +00:00
|
|
|
|
2019-03-01 00:22:35 +00:00
|
|
|
#include "file_path_special.h"
|
2019-06-26 05:23:32 +00:00
|
|
|
#include "paths.h"
|
2019-03-01 00:22:35 +00:00
|
|
|
#include "core_info.h"
|
|
|
|
#include "verbosity.h"
|
2019-09-10 15:13:25 +00:00
|
|
|
#include "msg_hash.h"
|
2019-03-01 00:22:35 +00:00
|
|
|
|
2020-03-05 14:33:18 +00:00
|
|
|
#if defined(HAVE_MENU)
|
|
|
|
#include "menu/menu_driver.h"
|
|
|
|
#endif
|
|
|
|
|
2019-03-01 00:22:35 +00:00
|
|
|
#include "runtime_file.h"
|
2019-02-27 16:19:29 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
#define LOG_FILE_RUNTIME_FORMAT_STR "%u:%02u:%02u"
|
|
|
|
#define LOG_FILE_LAST_PLAYED_FORMAT_STR "%04u-%02u-%02u %02u:%02u:%02u"
|
|
|
|
|
|
|
|
/* JSON Stuff... */
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
char **current_entry_val;
|
|
|
|
char *runtime_string;
|
|
|
|
char *last_played_string;
|
2019-02-28 16:00:32 +00:00
|
|
|
} RtlJSONContext;
|
2019-02-28 15:35:24 +00:00
|
|
|
|
2020-10-11 18:02:20 +00:00
|
|
|
static bool RtlJSONObjectMemberHandler(void *ctx, const char *s, size_t len)
|
2019-02-28 15:35:24 +00:00
|
|
|
{
|
2020-10-11 18:02:20 +00:00
|
|
|
RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2023-05-01 17:03:11 +00:00
|
|
|
/* Something went wrong */
|
2020-08-23 19:51:39 +00:00
|
|
|
if (p_ctx->current_entry_val)
|
2020-10-11 18:02:20 +00:00
|
|
|
return false;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-08-23 19:51:39 +00:00
|
|
|
if (len)
|
2019-02-28 15:35:24 +00:00
|
|
|
{
|
2020-08-23 19:51:39 +00:00
|
|
|
if (string_is_equal(s, "runtime"))
|
|
|
|
p_ctx->current_entry_val = &p_ctx->runtime_string;
|
|
|
|
else if (string_is_equal(s, "last_played"))
|
|
|
|
p_ctx->current_entry_val = &p_ctx->last_played_string;
|
2023-05-01 17:03:11 +00:00
|
|
|
/* Ignore unknown members */
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-10-11 18:02:20 +00:00
|
|
|
return true;
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
|
|
|
|
2020-10-11 18:02:20 +00:00
|
|
|
static bool RtlJSONStringHandler(void *ctx, const char *s, size_t len)
|
2019-02-28 15:35:24 +00:00
|
|
|
{
|
2020-10-11 18:02:20 +00:00
|
|
|
RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-08-23 19:51:39 +00:00
|
|
|
if (p_ctx->current_entry_val && len && !string_is_empty(s))
|
2019-02-28 15:35:24 +00:00
|
|
|
{
|
2020-08-23 19:51:39 +00:00
|
|
|
if (*p_ctx->current_entry_val)
|
|
|
|
free(*p_ctx->current_entry_val);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-08-23 19:51:39 +00:00
|
|
|
*p_ctx->current_entry_val = strdup(s);
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2023-05-01 17:03:11 +00:00
|
|
|
/* Ignore unknown members */
|
2020-08-23 19:51:39 +00:00
|
|
|
p_ctx->current_entry_val = NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-10-11 18:02:20 +00:00
|
|
|
return true;
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
2019-02-27 16:19:29 +00:00
|
|
|
|
|
|
|
/* 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)
|
|
|
|
{
|
2023-05-01 17:03:11 +00:00
|
|
|
rjson_t* parser;
|
2019-04-13 16:47:11 +00:00
|
|
|
unsigned runtime_hours = 0;
|
|
|
|
unsigned runtime_minutes = 0;
|
|
|
|
unsigned runtime_seconds = 0;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
unsigned last_played_year = 0;
|
|
|
|
unsigned last_played_month = 0;
|
|
|
|
unsigned last_played_day = 0;
|
|
|
|
unsigned last_played_hour = 0;
|
2019-02-27 16:19:29 +00:00
|
|
|
unsigned last_played_minute = 0;
|
|
|
|
unsigned last_played_second = 0;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
RtlJSONContext context = {0};
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Attempt to open log file */
|
2019-04-13 16:47:11 +00:00
|
|
|
RFILE *file = filestream_open(runtime_log->path,
|
|
|
|
RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (!file)
|
|
|
|
{
|
|
|
|
RARCH_ERR("Failed to open runtime log file: %s\n", runtime_log->path);
|
|
|
|
return;
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Initialise JSON parser */
|
2023-05-01 17:03:11 +00:00
|
|
|
if (!(parser = rjson_open_rfile(file)))
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
2019-02-28 15:35:24 +00:00
|
|
|
RARCH_ERR("Failed to create JSON parser.\n");
|
|
|
|
goto end;
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Configure parser */
|
2020-10-11 18:02:20 +00:00
|
|
|
rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Read file */
|
2020-10-11 18:02:20 +00:00
|
|
|
if (rjson_parse(parser, &context,
|
|
|
|
RtlJSONObjectMemberHandler,
|
|
|
|
RtlJSONStringHandler,
|
|
|
|
NULL, /* unused number handler */
|
|
|
|
NULL, NULL, NULL, NULL, /* unused object/array handlers */
|
|
|
|
NULL, NULL) /* unused boolean/null handlers */
|
|
|
|
!= RJSON_DONE)
|
2019-02-28 15:35:24 +00:00
|
|
|
{
|
2020-10-11 18:02:20 +00:00
|
|
|
if (rjson_get_source_context_len(parser))
|
2019-02-28 15:35:24 +00:00
|
|
|
{
|
2020-10-11 18:02:20 +00:00
|
|
|
RARCH_ERR("Error parsing chunk of runtime log file: %s\n---snip---\n%.*s\n---snip---\n",
|
|
|
|
runtime_log->path,
|
|
|
|
rjson_get_source_context_len(parser),
|
|
|
|
rjson_get_source_context_buf(parser));
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
|
|
|
RARCH_WARN("Error parsing runtime log file: %s\n", runtime_log->path);
|
2020-10-11 18:02:20 +00:00
|
|
|
RARCH_ERR("Error: Invalid JSON at line %d, column %d - %s.\n",
|
|
|
|
(int)rjson_get_source_line(parser),
|
|
|
|
(int)rjson_get_source_column(parser),
|
|
|
|
(*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Free parser */
|
2020-10-11 18:02:20 +00:00
|
|
|
rjson_free(parser);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Process string values read from JSON file */
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Runtime */
|
|
|
|
if (!string_is_empty(context.runtime_string))
|
|
|
|
{
|
2020-08-23 19:51:39 +00:00
|
|
|
if (sscanf(context.runtime_string,
|
|
|
|
LOG_FILE_RUNTIME_FORMAT_STR,
|
|
|
|
&runtime_hours,
|
|
|
|
&runtime_minutes,
|
|
|
|
&runtime_seconds) != 3)
|
2019-04-13 16:47:11 +00:00
|
|
|
{
|
|
|
|
RARCH_ERR("Runtime log file - invalid 'runtime' entry detected: %s\n", runtime_log->path);
|
|
|
|
goto end;
|
|
|
|
}
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Last played */
|
|
|
|
if (!string_is_empty(context.last_played_string))
|
|
|
|
{
|
2020-08-23 19:51:39 +00:00
|
|
|
if (sscanf(context.last_played_string,
|
|
|
|
LOG_FILE_LAST_PLAYED_FORMAT_STR,
|
|
|
|
&last_played_year,
|
|
|
|
&last_played_month,
|
|
|
|
&last_played_day,
|
|
|
|
&last_played_hour,
|
|
|
|
&last_played_minute,
|
|
|
|
&last_played_second) != 6)
|
2019-04-13 16:47:11 +00:00
|
|
|
{
|
|
|
|
RARCH_ERR("Runtime log file - invalid 'last played' entry detected: %s\n", runtime_log->path);
|
|
|
|
goto end;
|
|
|
|
}
|
2019-02-28 15:35:24 +00:00
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* If we reach this point then all is well
|
|
|
|
* > Assign values to runtime_log object */
|
2019-04-13 16:47:11 +00:00
|
|
|
runtime_log->runtime.hours = runtime_hours;
|
|
|
|
runtime_log->runtime.minutes = runtime_minutes;
|
|
|
|
runtime_log->runtime.seconds = runtime_seconds;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
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;
|
2019-02-28 15:35:24 +00:00
|
|
|
runtime_log->last_played.minute = last_played_minute;
|
|
|
|
runtime_log->last_played.second = last_played_second;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
end:
|
|
|
|
/* Clean up leftover strings */
|
|
|
|
if (context.runtime_string)
|
|
|
|
free(context.runtime_string);
|
|
|
|
if (context.last_played_string)
|
|
|
|
free(context.last_played_string);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Close log file */
|
|
|
|
filestream_close(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialise runtime log, loading current parameters
|
|
|
|
* if log file exists. Returned object must be free()'d.
|
2022-02-25 17:23:55 +00:00
|
|
|
* Returns NULL if core_path is invalid, or content_path
|
|
|
|
* is invalid and core does not support contentless
|
|
|
|
* operation */
|
2020-01-02 16:41:52 +00:00
|
|
|
runtime_log_t *runtime_log_init(
|
|
|
|
const char *content_path,
|
|
|
|
const char *core_path,
|
|
|
|
const char *dir_runtime_log,
|
|
|
|
const char *dir_playlist,
|
|
|
|
bool log_per_core)
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
2024-09-09 03:47:32 +00:00
|
|
|
char log_file_dir[DIR_MAX_LENGTH];
|
2024-09-09 14:01:29 +00:00
|
|
|
char content_name[NAME_MAX_LENGTH];
|
2024-09-09 14:02:15 +00:00
|
|
|
char core_name[NAME_MAX_LENGTH];
|
2019-02-27 16:19:29 +00:00
|
|
|
char log_file_path[PATH_MAX_LENGTH];
|
2019-02-28 11:09:37 +00:00
|
|
|
char tmp_buf[PATH_MAX_LENGTH];
|
2022-02-25 17:23:55 +00:00
|
|
|
bool supports_no_game = false;
|
2021-04-26 13:24:24 +00:00
|
|
|
core_info_t *core_info = NULL;
|
2020-06-06 12:49:34 +00:00
|
|
|
runtime_log_t *runtime_log = NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-06-06 12:49:34 +00:00
|
|
|
content_name[0] = '\0';
|
|
|
|
core_name[0] = '\0';
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2023-05-01 17:03:11 +00:00
|
|
|
if ( string_is_empty(dir_runtime_log)
|
|
|
|
&& string_is_empty(dir_playlist))
|
2019-02-28 11:09:37 +00:00
|
|
|
{
|
2019-04-27 02:21:10 +00:00
|
|
|
RARCH_ERR("Runtime log directory is undefined - cannot save"
|
|
|
|
" runtime log files.\n");
|
2019-02-28 11:09:37 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2019-04-27 02:21:10 +00:00
|
|
|
|
2023-05-01 17:03:11 +00:00
|
|
|
if ( string_is_empty(core_path)
|
|
|
|
|| string_is_equal(core_path, "builtin")
|
|
|
|
|| string_is_equal(core_path, "DETECT"))
|
2019-02-27 16:19:29 +00:00
|
|
|
return NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2022-02-25 17:23:55 +00:00
|
|
|
/* Get core info:
|
|
|
|
* - Need to know if core supports contentless operation
|
|
|
|
* - Need core name in order to generate file path when
|
|
|
|
* per-core logging is enabled
|
|
|
|
* Note: An annoyance - core name is required even when
|
|
|
|
* we are performing aggregate logging, since content
|
|
|
|
* name is sometimes dependent upon core
|
2019-03-07 12:28:21 +00:00
|
|
|
* (e.g. see TyrQuake below) */
|
2022-02-25 17:23:55 +00:00
|
|
|
if (core_info_find(core_path, &core_info))
|
|
|
|
{
|
|
|
|
supports_no_game = core_info->supports_no_game;
|
|
|
|
if (!string_is_empty(core_info->core_name))
|
|
|
|
strlcpy(core_name, core_info->core_name, sizeof(core_name));
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (string_is_empty(core_name))
|
|
|
|
return NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2022-07-26 05:01:10 +00:00
|
|
|
/* Get runtime log directory
|
|
|
|
* If 'custom' runtime log path is undefined,
|
|
|
|
* use default 'playlists/logs' directory... */
|
2020-01-02 16:41:52 +00:00
|
|
|
if (string_is_empty(dir_runtime_log))
|
2022-08-04 12:18:11 +00:00
|
|
|
fill_pathname_join_special(
|
2019-03-11 12:50:46 +00:00
|
|
|
tmp_buf,
|
2020-01-02 16:41:52 +00:00
|
|
|
dir_playlist,
|
2019-03-11 12:50:46 +00:00
|
|
|
"logs",
|
|
|
|
sizeof(tmp_buf));
|
|
|
|
else
|
2020-01-02 16:41:52 +00:00
|
|
|
strlcpy(tmp_buf, dir_runtime_log, sizeof(tmp_buf));
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-03-15 14:12:25 +00:00
|
|
|
if (string_is_empty(tmp_buf))
|
|
|
|
return NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-03-07 12:28:21 +00:00
|
|
|
if (log_per_core)
|
2022-08-04 12:18:11 +00:00
|
|
|
fill_pathname_join_special(
|
2019-03-07 12:28:21 +00:00
|
|
|
log_file_dir,
|
|
|
|
tmp_buf,
|
|
|
|
core_name,
|
|
|
|
sizeof(log_file_dir));
|
|
|
|
else
|
|
|
|
strlcpy(log_file_dir, tmp_buf, sizeof(log_file_dir));
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (string_is_empty(log_file_dir))
|
|
|
|
return NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Create directory, if required */
|
|
|
|
if (!path_is_directory(log_file_dir))
|
|
|
|
{
|
2019-04-22 00:33:12 +00:00
|
|
|
if (!path_mkdir(log_file_dir))
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
2019-04-27 02:21:10 +00:00
|
|
|
RARCH_ERR("[runtime] failed to create directory for"
|
|
|
|
" runtime log: %s.\n", log_file_dir);
|
2019-02-27 16:19:29 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2022-02-25 17:23:55 +00:00
|
|
|
/* Get content name */
|
|
|
|
if (string_is_empty(content_path))
|
|
|
|
{
|
|
|
|
/* If core supports contentless operation and
|
|
|
|
* no content is provided, 'content' is simply
|
|
|
|
* the name of the core itself */
|
|
|
|
if (supports_no_game)
|
2022-08-12 23:40:45 +00:00
|
|
|
{
|
2023-06-18 10:06:12 +00:00
|
|
|
size_t _len = strlcpy(content_name, core_name, sizeof(content_name));
|
|
|
|
strlcpy(content_name + _len, ".lrtl", sizeof(content_name) - _len);
|
2022-08-12 23:40:45 +00:00
|
|
|
}
|
2022-02-25 17:23:55 +00:00
|
|
|
}
|
|
|
|
/* NOTE: TyrQuake requires a specific hack, since all
|
2019-02-27 16:19:29 +00:00
|
|
|
* content has the same name... */
|
2022-02-25 17:23:55 +00:00
|
|
|
else if (string_is_equal(core_name, "TyrQuake"))
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
2024-09-10 14:02:06 +00:00
|
|
|
const char *slash = strrchr(content_path, '/');
|
|
|
|
const char *backslash = strrchr(content_path, '\\');
|
|
|
|
const char *last_slash = (!slash || (backslash > slash)) ? (char*)backslash : (char*)slash;
|
2019-02-27 16:19:29 +00:00
|
|
|
if (last_slash)
|
|
|
|
{
|
|
|
|
size_t path_length = last_slash + 1 - content_path;
|
|
|
|
if (path_length < PATH_MAX_LENGTH)
|
|
|
|
{
|
2023-06-18 10:06:12 +00:00
|
|
|
size_t _len;
|
2019-02-28 11:09:37 +00:00
|
|
|
memset(tmp_buf, 0, sizeof(tmp_buf));
|
2020-08-23 19:51:39 +00:00
|
|
|
strlcpy(tmp_buf,
|
|
|
|
content_path, path_length * sizeof(char));
|
2023-06-18 10:06:12 +00:00
|
|
|
_len = strlcpy(content_name,
|
2020-08-23 19:51:39 +00:00
|
|
|
path_basename(tmp_buf), sizeof(content_name));
|
2023-06-18 10:06:12 +00:00
|
|
|
strlcpy(content_name + _len, ".lrtl", sizeof(content_name) - _len);
|
2019-02-27 16:19:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-06-18 10:06:12 +00:00
|
|
|
size_t _len;
|
2019-02-27 16:19:29 +00:00
|
|
|
/* path_remove_extension() requires a char * (not const)
|
|
|
|
* so have to use a temporary buffer... */
|
2019-03-15 14:12:25 +00:00
|
|
|
char *tmp_buf_no_ext = NULL;
|
2019-04-13 16:47:11 +00:00
|
|
|
tmp_buf[0] = '\0';
|
2019-02-28 11:09:37 +00:00
|
|
|
strlcpy(tmp_buf, path_basename(content_path), sizeof(tmp_buf));
|
2019-04-13 16:47:11 +00:00
|
|
|
tmp_buf_no_ext = path_remove_extension(tmp_buf);
|
|
|
|
|
|
|
|
if (string_is_empty(tmp_buf_no_ext))
|
2019-03-15 14:12:25 +00:00
|
|
|
return NULL;
|
2019-04-13 16:47:11 +00:00
|
|
|
|
2023-06-18 10:06:12 +00:00
|
|
|
_len = strlcpy(content_name, tmp_buf_no_ext, sizeof(content_name));
|
|
|
|
strlcpy(content_name + _len, ".lrtl", sizeof(content_name) - _len);
|
2019-02-27 16:19:29 +00:00
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (string_is_empty(content_name))
|
|
|
|
return NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Build final log file path */
|
2022-08-12 23:40:45 +00:00
|
|
|
fill_pathname_join_special(log_file_path, log_file_dir,
|
2020-08-23 19:51:39 +00:00
|
|
|
content_name, sizeof(log_file_path));
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (string_is_empty(log_file_path))
|
|
|
|
return NULL;
|
2019-03-05 22:34:05 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Phew... If we get this far then all is well.
|
|
|
|
* > Create 'runtime_log' object */
|
2023-05-01 17:03:11 +00:00
|
|
|
if (!(runtime_log = (runtime_log_t*)malloc(sizeof(*runtime_log))))
|
2019-02-27 16:19:29 +00:00
|
|
|
return NULL;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* > Populate default values */
|
2019-04-13 16:47:11 +00:00
|
|
|
runtime_log->runtime.hours = 0;
|
|
|
|
runtime_log->runtime.minutes = 0;
|
|
|
|
runtime_log->runtime.seconds = 0;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
runtime_log->last_played.year = 0;
|
|
|
|
runtime_log->last_played.month = 0;
|
|
|
|
runtime_log->last_played.day = 0;
|
|
|
|
runtime_log->last_played.hour = 0;
|
2019-02-27 16:19:29 +00:00
|
|
|
runtime_log->last_played.minute = 0;
|
|
|
|
runtime_log->last_played.second = 0;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-06-27 14:36:08 +00:00
|
|
|
runtime_log->path[0] = '\0';
|
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
strlcpy(runtime_log->path, log_file_path, sizeof(runtime_log->path));
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Load existing log file, if it exists */
|
2019-05-30 11:19:52 +00:00
|
|
|
if (path_is_valid(runtime_log->path))
|
2019-04-13 16:47:11 +00:00
|
|
|
runtime_log_read_file(runtime_log);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
return runtime_log;
|
|
|
|
}
|
|
|
|
|
2023-04-28 18:50:43 +00:00
|
|
|
/* Convert from hours, minutes, seconds to microseconds */
|
|
|
|
static retro_time_t runtime_log_convert_hms2usec(unsigned hours,
|
|
|
|
unsigned minutes, unsigned seconds)
|
|
|
|
{
|
|
|
|
return ( (retro_time_t)hours * 60 * 60 * 1000000) +
|
|
|
|
((retro_time_t)minutes * 60 * 1000000) +
|
|
|
|
((retro_time_t)seconds * 1000000);
|
|
|
|
}
|
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Setters */
|
|
|
|
|
|
|
|
/* Adds specified microseconds value to current runtime */
|
2020-08-23 19:51:39 +00:00
|
|
|
void runtime_log_add_runtime_usec(
|
|
|
|
runtime_log_t *runtime_log, retro_time_t usec)
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
|
|
|
retro_time_t usec_old;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (!runtime_log)
|
|
|
|
return;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2023-04-28 18:50:43 +00:00
|
|
|
usec_old = runtime_log_convert_hms2usec(
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->runtime.hours,
|
|
|
|
runtime_log->runtime.minutes,
|
2023-04-28 18:50:43 +00:00
|
|
|
runtime_log->runtime.seconds);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
runtime_log_convert_usec2hms(usec_old + usec,
|
2020-08-23 19:51:39 +00:00
|
|
|
&runtime_log->runtime.hours,
|
|
|
|
&runtime_log->runtime.minutes,
|
|
|
|
&runtime_log->runtime.seconds);
|
2019-02-27 16:19:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* This function should never be needed, so just
|
|
|
|
* perform dumb value assignment (i.e. no validation
|
|
|
|
* using mktime()) */
|
2019-04-13 16:47:11 +00:00
|
|
|
runtime_log->last_played.year = year;
|
|
|
|
runtime_log->last_played.month = month;
|
|
|
|
runtime_log->last_played.day = day;
|
|
|
|
runtime_log->last_played.hour = hour;
|
2019-02-27 16:19:29 +00:00
|
|
|
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;
|
2020-06-04 11:19:24 +00:00
|
|
|
struct tm time_info;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (!runtime_log)
|
|
|
|
return;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Get current time */
|
|
|
|
time(¤t_time);
|
2020-06-04 11:19:24 +00:00
|
|
|
rtime_localtime(¤t_time, &time_info);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Extract values */
|
2020-06-04 11:19:24 +00:00
|
|
|
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;
|
2019-02-27 16:19:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Resets log to default (zero) values */
|
|
|
|
void runtime_log_reset(runtime_log_t *runtime_log)
|
|
|
|
{
|
|
|
|
if (!runtime_log)
|
|
|
|
return;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
runtime_log->runtime.hours = 0;
|
|
|
|
runtime_log->runtime.minutes = 0;
|
|
|
|
runtime_log->runtime.seconds = 0;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
runtime_log->last_played.year = 0;
|
|
|
|
runtime_log->last_played.month = 0;
|
|
|
|
runtime_log->last_played.day = 0;
|
|
|
|
runtime_log->last_played.hour = 0;
|
2019-02-27 16:19:29 +00:00
|
|
|
runtime_log->last_played.minute = 0;
|
|
|
|
runtime_log->last_played.second = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Getters */
|
|
|
|
|
|
|
|
/* Gets runtime in hours, minutes, seconds */
|
2023-06-15 23:23:24 +00:00
|
|
|
static void runtime_log_get_runtime_hms(runtime_log_t *runtime_log,
|
2019-04-27 02:21:10 +00:00
|
|
|
unsigned *hours, unsigned *minutes, unsigned *seconds)
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
|
|
|
if (!runtime_log)
|
|
|
|
return;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
*hours = runtime_log->runtime.hours;
|
2019-02-27 16:19:29 +00:00
|
|
|
*minutes = runtime_log->runtime.minutes;
|
|
|
|
*seconds = runtime_log->runtime.seconds;
|
|
|
|
}
|
|
|
|
|
2019-09-10 15:13:25 +00:00
|
|
|
/* Gets runtime as a pre-formatted string */
|
2020-08-23 19:51:39 +00:00
|
|
|
void runtime_log_get_runtime_str(runtime_log_t *runtime_log,
|
|
|
|
char *s, size_t len)
|
2019-09-10 15:13:25 +00:00
|
|
|
{
|
2022-08-25 11:03:08 +00:00
|
|
|
size_t _len = strlcpy(s,
|
|
|
|
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME),
|
|
|
|
len);
|
|
|
|
s[_len ] = ' ';
|
2019-09-10 15:13:25 +00:00
|
|
|
if (runtime_log)
|
2022-08-25 11:03:08 +00:00
|
|
|
{
|
2023-08-12 12:47:00 +00:00
|
|
|
snprintf(s + _len + 1, len - _len - 1, "%02u:%02u:%02u",
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->runtime.hours, runtime_log->runtime.minutes,
|
|
|
|
runtime_log->runtime.seconds);
|
2022-08-25 11:03:08 +00:00
|
|
|
}
|
2019-09-10 15:13:25 +00:00
|
|
|
else
|
2022-08-25 11:03:08 +00:00
|
|
|
{
|
|
|
|
s[_len+1] = '0';
|
|
|
|
s[_len+2] = '0';
|
|
|
|
s[_len+3] = ':';
|
|
|
|
s[_len+4] = '0';
|
|
|
|
s[_len+5] = '0';
|
|
|
|
s[_len+6] = ':';
|
|
|
|
s[_len+7] = '0';
|
|
|
|
s[_len+8] = '0';
|
|
|
|
s[_len+9] = '\0';
|
|
|
|
}
|
2019-09-10 15:13:25 +00:00
|
|
|
}
|
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* 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;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-04-13 16:47:11 +00:00
|
|
|
*year = runtime_log->last_played.year;
|
|
|
|
*month = runtime_log->last_played.month;
|
|
|
|
*day = runtime_log->last_played.day;
|
|
|
|
*hour = runtime_log->last_played.hour;
|
2019-02-27 16:19:29 +00:00
|
|
|
*minute = runtime_log->last_played.minute;
|
|
|
|
*second = runtime_log->last_played.second;
|
|
|
|
}
|
|
|
|
|
2019-09-10 15:13:25 +00:00
|
|
|
/* Gets last played entry values as a struct tm 'object'
|
2019-02-27 16:19:29 +00:00
|
|
|
* (e.g. for printing with strftime()) */
|
2023-06-15 23:23:24 +00:00
|
|
|
static void runtime_log_get_last_played_time(runtime_log_t *runtime_log,
|
2020-08-23 19:51:39 +00:00
|
|
|
struct tm *time_info)
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
|
|
|
/* Set tm values */
|
2019-09-10 15:13:25 +00:00
|
|
|
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;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-09-10 15:13:25 +00:00
|
|
|
/* Perform any required range adjustment + populate
|
|
|
|
* missing entries */
|
|
|
|
mktime(time_info);
|
|
|
|
}
|
|
|
|
|
2022-08-08 19:46:37 +00:00
|
|
|
static bool runtime_last_played_human(runtime_log_t *runtime_log,
|
2022-06-22 20:55:32 +00:00
|
|
|
char *str, size_t len)
|
|
|
|
{
|
2023-07-14 16:05:54 +00:00
|
|
|
size_t _len;
|
2022-06-22 20:55:32 +00:00
|
|
|
struct tm time_info;
|
|
|
|
time_t last_played;
|
|
|
|
time_t current;
|
|
|
|
time_t delta;
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
unsigned units[7][2] =
|
|
|
|
{
|
|
|
|
{MENU_ENUM_LABEL_VALUE_TIME_UNIT_SECONDS_SINGLE, MENU_ENUM_LABEL_VALUE_TIME_UNIT_SECONDS_PLURAL},
|
|
|
|
{MENU_ENUM_LABEL_VALUE_TIME_UNIT_MINUTES_SINGLE, MENU_ENUM_LABEL_VALUE_TIME_UNIT_MINUTES_PLURAL},
|
|
|
|
{MENU_ENUM_LABEL_VALUE_TIME_UNIT_HOURS_SINGLE, MENU_ENUM_LABEL_VALUE_TIME_UNIT_HOURS_PLURAL},
|
|
|
|
{MENU_ENUM_LABEL_VALUE_TIME_UNIT_DAYS_SINGLE, MENU_ENUM_LABEL_VALUE_TIME_UNIT_DAYS_PLURAL},
|
|
|
|
{MENU_ENUM_LABEL_VALUE_TIME_UNIT_WEEKS_SINGLE, MENU_ENUM_LABEL_VALUE_TIME_UNIT_WEEKS_PLURAL},
|
|
|
|
{MENU_ENUM_LABEL_VALUE_TIME_UNIT_MONTHS_SINGLE, MENU_ENUM_LABEL_VALUE_TIME_UNIT_MONTHS_PLURAL},
|
|
|
|
{MENU_ENUM_LABEL_VALUE_TIME_UNIT_YEARS_SINGLE, MENU_ENUM_LABEL_VALUE_TIME_UNIT_YEARS_PLURAL},
|
|
|
|
};
|
|
|
|
|
|
|
|
float periods[6] = {60.0f, 60.0f, 24.0f, 7.0f, 4.35f, 12.0f};
|
|
|
|
|
|
|
|
if (!runtime_log)
|
2022-08-08 19:46:37 +00:00
|
|
|
return false;
|
2022-06-22 20:55:32 +00:00
|
|
|
|
2022-07-02 12:08:41 +00:00
|
|
|
/* Get time */
|
2022-06-22 20:55:32 +00:00
|
|
|
runtime_log_get_last_played_time(runtime_log, &time_info);
|
|
|
|
|
|
|
|
last_played = mktime(&time_info);
|
2022-07-02 12:08:41 +00:00
|
|
|
current = time(NULL);
|
2022-06-22 20:55:32 +00:00
|
|
|
|
2022-08-08 19:46:37 +00:00
|
|
|
if ((delta = current - last_played) <= 0)
|
|
|
|
return false;
|
2022-06-22 20:55:32 +00:00
|
|
|
|
|
|
|
for (i = 0; delta >= periods[i] && i < sizeof(periods) - 1; i++)
|
|
|
|
delta /= periods[i];
|
|
|
|
|
|
|
|
/* Generate string */
|
2023-08-20 17:59:15 +00:00
|
|
|
_len = snprintf(str, len, "%u ", (int)delta);
|
|
|
|
_len += strlcpy(str + _len,
|
2023-06-20 17:52:38 +00:00
|
|
|
msg_hash_to_str((enum msg_hash_enums)units[i][(delta == 1) ? 0 : 1]),
|
2023-08-20 17:59:15 +00:00
|
|
|
len - _len);
|
2023-06-20 17:52:38 +00:00
|
|
|
|
2023-07-14 16:05:54 +00:00
|
|
|
str[ _len] = ' ';
|
|
|
|
str[++_len] = '\0';
|
|
|
|
strlcpy(str + _len,
|
2023-06-20 17:52:38 +00:00
|
|
|
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_TIME_UNIT_AGO),
|
2023-07-14 16:05:54 +00:00
|
|
|
len - _len);
|
2022-08-08 19:46:37 +00:00
|
|
|
|
|
|
|
return true;
|
2022-06-22 20:55:32 +00:00
|
|
|
}
|
|
|
|
|
2019-09-10 15:13:25 +00:00
|
|
|
/* Gets last played entry value as a pre-formatted string */
|
|
|
|
void runtime_log_get_last_played_str(runtime_log_t *runtime_log,
|
2020-05-25 14:46:53 +00:00
|
|
|
char *str, size_t len,
|
|
|
|
enum playlist_sublabel_last_played_style_type timedate_style,
|
|
|
|
enum playlist_sublabel_last_played_date_separator_type date_separator)
|
2019-09-10 15:13:25 +00:00
|
|
|
{
|
2020-05-25 14:46:53 +00:00
|
|
|
const char *format_str = "";
|
2023-04-30 17:30:07 +00:00
|
|
|
size_t _len = strlcpy(str, msg_hash_to_str(
|
|
|
|
MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED), len);
|
2019-09-10 15:13:25 +00:00
|
|
|
|
|
|
|
if (runtime_log)
|
|
|
|
{
|
2023-04-30 17:35:49 +00:00
|
|
|
bool has_am_pm = false;
|
2019-09-10 15:13:25 +00:00
|
|
|
/* Handle 12-hour clock options
|
|
|
|
* > These require extra work, due to AM/PM localisation */
|
|
|
|
switch (timedate_style)
|
|
|
|
{
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_YMD_HMS_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
/* Using switch statements to set the format
|
|
|
|
* string is verbose, but has far less performance
|
|
|
|
* impact than setting the date separator dynamically
|
|
|
|
* (i.e. no snprintf() or character replacement...) */
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %Y/%m/%d %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %Y.%m.%d %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %Y-%m-%d %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_YMD_HM_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %Y/%m/%d %I:%M %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %Y.%m.%d %I:%M %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %Y-%m-%d %I:%M %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HMS_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %m/%d/%Y %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %m.%d.%Y %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %m-%d-%Y %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HM_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %m/%d/%Y %I:%M %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %m.%d.%Y %I:%M %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %m-%d-%Y %I:%M %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MD_HM_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %m/%d %I:%M %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %m.%d %I:%M %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %m-%d %I:%M %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HMS_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %d/%m/%Y %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %d.%m.%Y %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %d-%m-%Y %I:%M:%S %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HM_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %d/%m/%Y %I:%M %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %d.%m.%Y %I:%M %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %d-%m-%Y %I:%M %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-21 23:26:06 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMM_HM_AMPM:
|
2020-05-25 14:46:53 +00:00
|
|
|
has_am_pm = true;
|
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
|
|
|
format_str = " %d/%m %I:%M %p";
|
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
|
|
|
format_str = " %d.%m %I:%M %p";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
format_str = " %d-%m %I:%M %p";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2019-09-10 15:13:25 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-05-25 14:46:53 +00:00
|
|
|
if (has_am_pm)
|
|
|
|
{
|
2023-04-30 17:30:07 +00:00
|
|
|
/* Get time */
|
|
|
|
struct tm time_info;
|
|
|
|
runtime_log_get_last_played_time(runtime_log, &time_info);
|
2023-08-20 17:59:15 +00:00
|
|
|
strftime_am_pm(str + _len, len - _len, format_str, &time_info);
|
2020-05-25 14:46:53 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-10 15:13:25 +00:00
|
|
|
/* Handle non-12-hour clock options */
|
|
|
|
switch (timedate_style)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_YMD_HM:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u/%02u/%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u.%02u.%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u-%02u-%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
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);
|
2019-09-10 15:13:25 +00:00
|
|
|
return;
|
2020-03-19 17:47:42 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_YMD:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u/%02u/%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u.%02u.%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u-%02u-%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.year,
|
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.day);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_YM:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u/%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u.%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u-%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.year,
|
|
|
|
runtime_log->last_played.month);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HMS:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u/%04u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u.%04u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u-%04u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.year,
|
|
|
|
runtime_log->last_played.hour,
|
|
|
|
runtime_log->last_played.minute,
|
|
|
|
runtime_log->last_played.second);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HM:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u/%04u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u.%04u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u-%04u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.year,
|
|
|
|
runtime_log->last_played.hour,
|
|
|
|
runtime_log->last_played.minute);
|
2019-09-10 15:13:25 +00:00
|
|
|
return;
|
2020-03-19 17:47:42 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MD_HM:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.hour,
|
|
|
|
runtime_log->last_played.minute);
|
2019-09-10 15:13:25 +00:00
|
|
|
return;
|
2020-03-19 17:47:42 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u/%04u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u.%04u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u-%04u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.year);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_MD:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.day);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HMS:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u/%04u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u.%04u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u-%04u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.year,
|
|
|
|
runtime_log->last_played.hour,
|
|
|
|
runtime_log->last_played.minute,
|
|
|
|
runtime_log->last_played.second);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HM:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u/%04u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u.%04u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u-%04u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.year,
|
|
|
|
runtime_log->last_played.hour,
|
|
|
|
runtime_log->last_played.minute);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMM_HM:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u %02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.hour,
|
|
|
|
runtime_log->last_played.minute);
|
2019-09-10 15:13:25 +00:00
|
|
|
return;
|
2020-03-19 17:47:42 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u/%04u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u.%04u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u-%04u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log->last_played.day,
|
|
|
|
runtime_log->last_played.month,
|
|
|
|
runtime_log->last_played.year);
|
2020-03-19 17:47:42 +00:00
|
|
|
return;
|
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_DDMM:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u/%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u.%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %02u-%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-03-19 17:47:42 +00:00
|
|
|
runtime_log->last_played.day, runtime_log->last_played.month);
|
|
|
|
return;
|
2022-06-22 20:55:32 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_AGO:
|
2023-08-20 17:59:15 +00:00
|
|
|
str[ _len] = ' ';
|
2024-09-09 03:47:32 +00:00
|
|
|
str[++_len] = '\0';
|
2023-08-20 17:59:15 +00:00
|
|
|
if (!(runtime_last_played_human(runtime_log, str + _len, len - _len - 2)))
|
|
|
|
strlcat(str + _len,
|
2022-08-08 19:46:37 +00:00
|
|
|
msg_hash_to_str(
|
|
|
|
MENU_ENUM_LABEL_VALUE_PLAYLIST_INLINE_CORE_DISPLAY_NEVER),
|
2023-08-20 17:59:15 +00:00
|
|
|
len - _len - 2);
|
2022-06-22 20:55:32 +00:00
|
|
|
return;
|
2019-09-10 15:13:25 +00:00
|
|
|
case PLAYLIST_LAST_PLAYED_STYLE_YMD_HMS:
|
|
|
|
default:
|
2020-05-25 14:46:53 +00:00
|
|
|
switch (date_separator)
|
|
|
|
{
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u/%02u/%02u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u.%02u.%02u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-04-30 17:30:07 +00:00
|
|
|
format_str = " %04u-%02u-%02u %02u:%02u:%02u";
|
2020-05-25 14:46:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len, format_str,
|
2020-08-23 19:51:39 +00:00
|
|
|
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);
|
2019-09-10 15:13:25 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2023-04-30 17:30:07 +00:00
|
|
|
snprintf(str + _len, len - _len,
|
|
|
|
" %s", msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_INLINE_CORE_DISPLAY_NEVER));
|
2019-02-27 16:19:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Status */
|
|
|
|
|
|
|
|
/* Returns true if log has a non-zero runtime entry */
|
|
|
|
bool runtime_log_has_runtime(runtime_log_t *runtime_log)
|
|
|
|
{
|
2020-08-23 19:51:39 +00:00
|
|
|
if (runtime_log)
|
|
|
|
return !(
|
2023-06-15 23:23:24 +00:00
|
|
|
(runtime_log->runtime.hours == 0)
|
|
|
|
&& (runtime_log->runtime.minutes == 0)
|
|
|
|
&& (runtime_log->runtime.seconds == 0));
|
2020-08-23 19:51:39 +00:00
|
|
|
return false;
|
2019-02-27 16:19:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Saving */
|
|
|
|
|
|
|
|
/* Saves specified runtime log to disk */
|
|
|
|
void runtime_log_save(runtime_log_t *runtime_log)
|
|
|
|
{
|
2020-05-21 23:26:06 +00:00
|
|
|
char value_string[64]; /* 64 characters should be
|
2019-04-27 02:21:10 +00:00
|
|
|
enough for a very long runtime... :) */
|
2019-04-13 16:47:11 +00:00
|
|
|
RFILE *file = NULL;
|
2020-10-11 18:02:20 +00:00
|
|
|
rjsonwriter_t* writer;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
if (!runtime_log)
|
|
|
|
return;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2021-12-04 13:21:12 +00:00
|
|
|
RARCH_LOG("[Runtime]: Saving runtime log file: \"%s\".\n", runtime_log->path);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Attempt to open log file */
|
2022-08-01 15:41:34 +00:00
|
|
|
if (!(file = filestream_open(runtime_log->path,
|
|
|
|
RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE)))
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
2021-12-04 13:21:12 +00:00
|
|
|
RARCH_ERR("[Runtime]: Failed to open runtime log file: \"%s\".\n", runtime_log->path);
|
2019-02-27 16:19:29 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Initialise JSON writer */
|
2022-08-01 15:41:34 +00:00
|
|
|
if (!(writer = rjsonwriter_open_rfile(file)))
|
2019-02-28 15:35:24 +00:00
|
|
|
{
|
2021-12-04 13:21:12 +00:00
|
|
|
RARCH_ERR("[Runtime]: Failed to create JSON writer.\n");
|
2019-02-28 15:35:24 +00:00
|
|
|
goto end;
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Write output file */
|
2022-08-26 12:44:29 +00:00
|
|
|
rjsonwriter_raw(writer, "{", 1);
|
|
|
|
rjsonwriter_raw(writer, "\n", 1);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* > Version entry */
|
2020-10-11 18:02:20 +00:00
|
|
|
rjsonwriter_add_spaces(writer, 2);
|
|
|
|
rjsonwriter_add_string(writer, "version");
|
2022-08-26 12:44:29 +00:00
|
|
|
rjsonwriter_raw(writer, ":", 1);
|
|
|
|
rjsonwriter_raw(writer, " ", 1);
|
2020-10-11 18:02:20 +00:00
|
|
|
rjsonwriter_add_string(writer, "1.0");
|
2022-08-26 12:44:29 +00:00
|
|
|
rjsonwriter_raw(writer, ",", 1);
|
|
|
|
rjsonwriter_raw(writer, "\n", 1);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* > Runtime entry */
|
2021-05-18 21:09:45 +00:00
|
|
|
snprintf(value_string,
|
|
|
|
sizeof(value_string),
|
|
|
|
LOG_FILE_RUNTIME_FORMAT_STR,
|
2019-04-27 02:21:10 +00:00
|
|
|
runtime_log->runtime.hours, runtime_log->runtime.minutes,
|
|
|
|
runtime_log->runtime.seconds);
|
2024-09-09 03:47:32 +00:00
|
|
|
|
2020-10-11 18:02:20 +00:00
|
|
|
rjsonwriter_add_spaces(writer, 2);
|
|
|
|
rjsonwriter_add_string(writer, "runtime");
|
2022-08-26 12:44:29 +00:00
|
|
|
rjsonwriter_raw(writer, ":", 1);
|
|
|
|
rjsonwriter_raw(writer, " ", 1);
|
2020-10-11 18:02:20 +00:00
|
|
|
rjsonwriter_add_string(writer, value_string);
|
2022-08-26 12:44:29 +00:00
|
|
|
rjsonwriter_raw(writer, ",", 1);
|
|
|
|
rjsonwriter_raw(writer, "\n", 1);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* > Last played entry */
|
|
|
|
value_string[0] = '\0';
|
2021-05-18 21:09:45 +00:00
|
|
|
snprintf(value_string, sizeof(value_string),
|
2019-04-27 02:21:10 +00:00
|
|
|
LOG_FILE_LAST_PLAYED_FORMAT_STR,
|
|
|
|
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);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-10-11 18:02:20 +00:00
|
|
|
rjsonwriter_add_spaces(writer, 2);
|
|
|
|
rjsonwriter_add_string(writer, "last_played");
|
2022-08-26 12:44:29 +00:00
|
|
|
rjsonwriter_raw(writer, ":", 1);
|
|
|
|
rjsonwriter_raw(writer, " ", 1);
|
2020-10-11 18:02:20 +00:00
|
|
|
rjsonwriter_add_string(writer, value_string);
|
2022-08-01 15:41:34 +00:00
|
|
|
rjsonwriter_raw(writer, "\n", 1);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* > Finalise */
|
2022-08-26 12:44:29 +00:00
|
|
|
rjsonwriter_raw(writer, "}", 1);
|
|
|
|
rjsonwriter_raw(writer, "\n", 1);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
/* Free JSON writer */
|
2020-10-11 18:02:20 +00:00
|
|
|
if (!rjsonwriter_free(writer))
|
|
|
|
{
|
|
|
|
RARCH_ERR("Error writing runtime log file: %s\n", runtime_log->path);
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-28 15:35:24 +00:00
|
|
|
end:
|
2019-02-27 16:19:29 +00:00
|
|
|
/* Close log file */
|
|
|
|
filestream_close(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Utility functions */
|
|
|
|
|
|
|
|
/* Convert from microseconds to hours, minutes, seconds */
|
2019-04-27 02:21:10 +00:00
|
|
|
void runtime_log_convert_usec2hms(retro_time_t usec,
|
|
|
|
unsigned *hours, unsigned *minutes, unsigned *seconds)
|
2019-02-27 16:19:29 +00:00
|
|
|
{
|
2019-04-08 21:13:39 +00:00
|
|
|
*seconds = (unsigned)(usec / 1000000);
|
2019-04-08 17:36:44 +00:00
|
|
|
*minutes = *seconds / 60;
|
|
|
|
*hours = *minutes / 60;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-02-27 16:19:29 +00:00
|
|
|
*seconds -= *minutes * 60;
|
|
|
|
*minutes -= *hours * 60;
|
|
|
|
}
|
2019-05-09 13:24:06 +00:00
|
|
|
|
|
|
|
/* Playlist manipulation */
|
|
|
|
|
|
|
|
/* Updates specified playlist entry runtime values with
|
|
|
|
* contents of associated log file */
|
2020-03-27 11:12:32 +00:00
|
|
|
void runtime_update_playlist(
|
|
|
|
playlist_t *playlist, size_t idx,
|
|
|
|
const char *dir_runtime_log,
|
|
|
|
const char *dir_playlist,
|
|
|
|
bool log_per_core,
|
2020-05-25 14:46:53 +00:00
|
|
|
enum playlist_sublabel_last_played_style_type timedate_style,
|
|
|
|
enum playlist_sublabel_last_played_date_separator_type date_separator)
|
2019-05-09 13:24:06 +00:00
|
|
|
{
|
2020-01-02 16:41:52 +00:00
|
|
|
char runtime_str[64];
|
|
|
|
char last_played_str[64];
|
2020-03-05 14:33:18 +00:00
|
|
|
runtime_log_t *runtime_log = NULL;
|
|
|
|
const struct playlist_entry *entry = NULL;
|
|
|
|
struct playlist_entry update_entry = {0};
|
2020-05-09 13:30:49 +00:00
|
|
|
#if defined(HAVE_MENU) && (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI))
|
2020-03-05 14:33:18 +00:00
|
|
|
const char *menu_ident = menu_driver_ident();
|
|
|
|
#endif
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Sanity check */
|
2020-03-27 11:12:32 +00:00
|
|
|
if (!playlist)
|
2019-05-09 13:24:06 +00:00
|
|
|
return;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
if (idx >= playlist_get_size(playlist))
|
|
|
|
return;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Set fallback playlist 'runtime_status'
|
|
|
|
* (saves 'if' checks later...) */
|
|
|
|
update_entry.runtime_status = PLAYLIST_RUNTIME_MISSING;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-09-10 15:13:25 +00:00
|
|
|
/* 'Attach' runtime/last played strings */
|
|
|
|
runtime_str[0] = '\0';
|
|
|
|
last_played_str[0] = '\0';
|
|
|
|
update_entry.runtime_str = runtime_str;
|
|
|
|
update_entry.last_played_str = last_played_str;
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Read current playlist entry */
|
|
|
|
playlist_get_index(playlist, idx, &entry);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Attempt to open log file */
|
2022-07-26 05:01:10 +00:00
|
|
|
if ((runtime_log = runtime_log_init(
|
2020-01-02 16:41:52 +00:00
|
|
|
entry->path,
|
|
|
|
entry->core_path,
|
2020-03-27 11:12:32 +00:00
|
|
|
dir_runtime_log,
|
|
|
|
dir_playlist,
|
2022-07-26 05:01:10 +00:00
|
|
|
log_per_core)))
|
2019-05-09 13:24:06 +00:00
|
|
|
{
|
|
|
|
/* Check whether a non-zero runtime has been recorded */
|
|
|
|
if (runtime_log_has_runtime(runtime_log))
|
|
|
|
{
|
|
|
|
/* Read current runtime */
|
|
|
|
runtime_log_get_runtime_hms(runtime_log,
|
2020-08-23 19:51:39 +00:00
|
|
|
&update_entry.runtime_hours,
|
|
|
|
&update_entry.runtime_minutes,
|
|
|
|
&update_entry.runtime_seconds);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log_get_runtime_str(runtime_log,
|
|
|
|
runtime_str, sizeof(runtime_str));
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Read last played timestamp */
|
|
|
|
runtime_log_get_last_played(runtime_log,
|
2020-08-23 19:51:39 +00:00
|
|
|
&update_entry.last_played_year,
|
|
|
|
&update_entry.last_played_month,
|
|
|
|
&update_entry.last_played_day,
|
|
|
|
&update_entry.last_played_hour,
|
|
|
|
&update_entry.last_played_minute,
|
|
|
|
&update_entry.last_played_second);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-05-25 14:46:53 +00:00
|
|
|
runtime_log_get_last_played_str(runtime_log,
|
2020-08-23 19:51:39 +00:00
|
|
|
last_played_str, sizeof(last_played_str),
|
|
|
|
timedate_style, date_separator);
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Playlist entry now contains valid runtime data */
|
|
|
|
update_entry.runtime_status = PLAYLIST_RUNTIME_VALID;
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Clean up */
|
|
|
|
free(runtime_log);
|
|
|
|
}
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2020-05-09 13:30:49 +00:00
|
|
|
#if defined(HAVE_MENU) && (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI))
|
|
|
|
/* Ozone and GLUI require runtime/last played strings
|
|
|
|
* to be populated even when no runtime is recorded */
|
|
|
|
if (update_entry.runtime_status != PLAYLIST_RUNTIME_VALID)
|
2019-09-10 15:13:25 +00:00
|
|
|
{
|
2020-05-09 13:30:49 +00:00
|
|
|
if (string_is_equal(menu_ident, "ozone") ||
|
|
|
|
string_is_equal(menu_ident, "glui"))
|
2019-09-10 15:13:25 +00:00
|
|
|
{
|
2020-08-23 19:51:39 +00:00
|
|
|
runtime_log_get_runtime_str(NULL,
|
|
|
|
runtime_str, sizeof(runtime_str));
|
|
|
|
runtime_log_get_last_played_str(NULL,
|
|
|
|
last_played_str, sizeof(last_played_str),
|
2020-05-25 14:46:53 +00:00
|
|
|
timedate_style, date_separator);
|
2020-05-09 13:30:49 +00:00
|
|
|
|
|
|
|
/* While runtime data does not exist, the playlist
|
|
|
|
* entry does now contain valid information... */
|
|
|
|
update_entry.runtime_status = PLAYLIST_RUNTIME_VALID;
|
2019-09-10 15:13:25 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-02 16:41:52 +00:00
|
|
|
#endif
|
2020-05-21 23:26:06 +00:00
|
|
|
|
2019-05-09 13:24:06 +00:00
|
|
|
/* Update playlist */
|
|
|
|
playlist_update_runtime(playlist, idx, &update_entry, false);
|
|
|
|
}
|
2022-02-25 17:23:55 +00:00
|
|
|
|
|
|
|
#if defined(HAVE_MENU)
|
|
|
|
/* Contentless cores manipulation */
|
|
|
|
|
|
|
|
/* Updates specified contentless core runtime values with
|
|
|
|
* contents of associated log file */
|
|
|
|
void runtime_update_contentless_core(
|
|
|
|
const char *core_path,
|
|
|
|
const char *dir_runtime_log,
|
|
|
|
const char *dir_playlist,
|
|
|
|
bool log_per_core,
|
|
|
|
enum playlist_sublabel_last_played_style_type timedate_style,
|
|
|
|
enum playlist_sublabel_last_played_date_separator_type date_separator)
|
|
|
|
{
|
|
|
|
char runtime_str[64];
|
|
|
|
char last_played_str[64];
|
|
|
|
core_info_t *core_info = NULL;
|
|
|
|
runtime_log_t *runtime_log = NULL;
|
|
|
|
contentless_core_runtime_info_t runtime_info = {0};
|
|
|
|
#if (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI))
|
|
|
|
const char *menu_ident = menu_driver_ident();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Sanity check */
|
2023-06-15 23:23:24 +00:00
|
|
|
if ( string_is_empty(core_path)
|
|
|
|
|| !core_info_find(core_path, &core_info)
|
|
|
|
|| !core_info->supports_no_game)
|
2022-02-25 17:23:55 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
/* Set fallback runtime status
|
|
|
|
* (saves 'if' checks later...) */
|
|
|
|
runtime_info.status = CONTENTLESS_CORE_RUNTIME_MISSING;
|
|
|
|
|
|
|
|
/* 'Attach' runtime/last played strings */
|
|
|
|
runtime_str[0] = '\0';
|
|
|
|
last_played_str[0] = '\0';
|
|
|
|
runtime_info.runtime_str = runtime_str;
|
|
|
|
runtime_info.last_played_str = last_played_str;
|
|
|
|
|
|
|
|
/* Attempt to open log file */
|
|
|
|
runtime_log = runtime_log_init(
|
|
|
|
NULL,
|
|
|
|
core_path,
|
|
|
|
dir_runtime_log,
|
|
|
|
dir_playlist,
|
|
|
|
log_per_core);
|
|
|
|
|
|
|
|
if (runtime_log)
|
|
|
|
{
|
|
|
|
/* Check whether a non-zero runtime has been recorded */
|
|
|
|
if (runtime_log_has_runtime(runtime_log))
|
|
|
|
{
|
|
|
|
/* Read current runtime */
|
|
|
|
runtime_log_get_runtime_str(runtime_log,
|
|
|
|
runtime_str, sizeof(runtime_str));
|
|
|
|
|
|
|
|
/* Read last played timestamp */
|
|
|
|
runtime_log_get_last_played_str(runtime_log,
|
|
|
|
last_played_str, sizeof(last_played_str),
|
|
|
|
timedate_style, date_separator);
|
|
|
|
|
|
|
|
/* Contentless core entry now contains valid runtime data */
|
|
|
|
runtime_info.status = CONTENTLESS_CORE_RUNTIME_VALID;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clean up */
|
|
|
|
free(runtime_log);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI))
|
|
|
|
/* Ozone and GLUI require runtime/last played strings
|
|
|
|
* to be populated even when no runtime is recorded */
|
|
|
|
if (runtime_info.status != CONTENTLESS_CORE_RUNTIME_VALID)
|
|
|
|
{
|
2023-06-15 23:23:24 +00:00
|
|
|
if ( string_is_equal(menu_ident, "ozone")
|
|
|
|
|| string_is_equal(menu_ident, "glui"))
|
2022-02-25 17:23:55 +00:00
|
|
|
{
|
|
|
|
runtime_log_get_runtime_str(NULL,
|
|
|
|
runtime_str, sizeof(runtime_str));
|
|
|
|
runtime_log_get_last_played_str(NULL,
|
|
|
|
last_played_str, sizeof(last_played_str),
|
|
|
|
timedate_style, date_separator);
|
|
|
|
|
|
|
|
/* While runtime data does not exist, the contentless
|
|
|
|
* core entry does now contain valid information... */
|
|
|
|
runtime_info.status = CONTENTLESS_CORE_RUNTIME_VALID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Update contentless core */
|
|
|
|
menu_contentless_cores_set_runtime(core_info->core_file_id.str,
|
|
|
|
&runtime_info);
|
|
|
|
}
|
|
|
|
#endif
|