1 /* Copyright  (C) 2010-2019 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (runtime_file.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <ctype.h>
27 #include <locale.h>
28 
29 #include <file/file_path.h>
30 #include <retro_miscellaneous.h>
31 #include <streams/file_stream.h>
32 #include <formats/rjson.h>
33 #include <string/stdstring.h>
34 #include <encodings/utf.h>
35 #include <time/rtime.h>
36 
37 #include "file_path_special.h"
38 #include "paths.h"
39 #include "core_info.h"
40 #include "verbosity.h"
41 #include "msg_hash.h"
42 
43 #if defined(HAVE_MENU)
44 #include "menu/menu_driver.h"
45 #endif
46 
47 #include "runtime_file.h"
48 
49 #define LOG_FILE_RUNTIME_FORMAT_STR "%u:%02u:%02u"
50 #define LOG_FILE_LAST_PLAYED_FORMAT_STR "%04u-%02u-%02u %02u:%02u:%02u"
51 
52 /* JSON Stuff... */
53 
54 typedef struct
55 {
56    char **current_entry_val;
57    char *runtime_string;
58    char *last_played_string;
59 } RtlJSONContext;
60 
RtlJSONObjectMemberHandler(void * ctx,const char * s,size_t len)61 static bool RtlJSONObjectMemberHandler(void *ctx, const char *s, size_t len)
62 {
63    RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
64 
65    if (p_ctx->current_entry_val)
66    {
67       /* something went wrong */
68       return false;
69    }
70 
71    if (len)
72    {
73       if (string_is_equal(s, "runtime"))
74          p_ctx->current_entry_val = &p_ctx->runtime_string;
75       else if (string_is_equal(s, "last_played"))
76          p_ctx->current_entry_val = &p_ctx->last_played_string;
77       /* ignore unknown members */
78    }
79 
80    return true;
81 }
82 
RtlJSONStringHandler(void * ctx,const char * s,size_t len)83 static bool RtlJSONStringHandler(void *ctx, const char *s, size_t len)
84 {
85    RtlJSONContext *p_ctx = (RtlJSONContext*)ctx;
86 
87    if (p_ctx->current_entry_val && len && !string_is_empty(s))
88    {
89       if (*p_ctx->current_entry_val)
90          free(*p_ctx->current_entry_val);
91 
92       *p_ctx->current_entry_val = strdup(s);
93    }
94    /* ignore unknown members */
95 
96    p_ctx->current_entry_val = NULL;
97 
98    return true;
99 }
100 
101 /* Initialisation */
102 
103 /* Parses log file referenced by runtime_log->path.
104  * Does nothing if log file does not exist. */
runtime_log_read_file(runtime_log_t * runtime_log)105 static void runtime_log_read_file(runtime_log_t *runtime_log)
106 {
107    unsigned runtime_hours      = 0;
108    unsigned runtime_minutes    = 0;
109    unsigned runtime_seconds    = 0;
110 
111    unsigned last_played_year   = 0;
112    unsigned last_played_month  = 0;
113    unsigned last_played_day    = 0;
114    unsigned last_played_hour   = 0;
115    unsigned last_played_minute = 0;
116    unsigned last_played_second = 0;
117 
118    RtlJSONContext context      = {0};
119    rjson_t* parser;
120 
121    /* Attempt to open log file */
122    RFILE *file                 = filestream_open(runtime_log->path,
123          RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
124 
125    if (!file)
126    {
127       RARCH_ERR("Failed to open runtime log file: %s\n", runtime_log->path);
128       return;
129    }
130 
131    /* Initialise JSON parser */
132    parser = rjson_open_rfile(file);
133    if (!parser)
134    {
135       RARCH_ERR("Failed to create JSON parser.\n");
136       goto end;
137    }
138 
139    /* Configure parser */
140    rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM);
141 
142    /* Read file */
143    if (rjson_parse(parser, &context,
144          RtlJSONObjectMemberHandler,
145          RtlJSONStringHandler,
146          NULL,                   /* unused number handler */
147          NULL, NULL, NULL, NULL, /* unused object/array handlers */
148          NULL, NULL)             /* unused boolean/null handlers */
149          != RJSON_DONE)
150    {
151       if (rjson_get_source_context_len(parser))
152       {
153          RARCH_ERR("Error parsing chunk of runtime log file: %s\n---snip---\n%.*s\n---snip---\n",
154                runtime_log->path,
155                rjson_get_source_context_len(parser),
156                rjson_get_source_context_buf(parser));
157       }
158       RARCH_WARN("Error parsing runtime log file: %s\n", runtime_log->path);
159       RARCH_ERR("Error: Invalid JSON at line %d, column %d - %s.\n",
160             (int)rjson_get_source_line(parser),
161             (int)rjson_get_source_column(parser),
162             (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
163    }
164 
165    /* Free parser */
166    rjson_free(parser);
167 
168    /* Process string values read from JSON file */
169 
170    /* Runtime */
171    if (!string_is_empty(context.runtime_string))
172    {
173       if (sscanf(context.runtime_string,
174                LOG_FILE_RUNTIME_FORMAT_STR,
175                &runtime_hours,
176                &runtime_minutes,
177                &runtime_seconds) != 3)
178       {
179          RARCH_ERR("Runtime log file - invalid 'runtime' entry detected: %s\n", runtime_log->path);
180          goto end;
181       }
182    }
183 
184    /* Last played */
185    if (!string_is_empty(context.last_played_string))
186    {
187       if (sscanf(context.last_played_string,
188                LOG_FILE_LAST_PLAYED_FORMAT_STR,
189                &last_played_year,
190                &last_played_month,
191                &last_played_day,
192                &last_played_hour,
193                &last_played_minute,
194                &last_played_second) != 6)
195       {
196          RARCH_ERR("Runtime log file - invalid 'last played' entry detected: %s\n", runtime_log->path);
197          goto end;
198       }
199    }
200 
201    /* If we reach this point then all is well
202     * > Assign values to runtime_log object */
203    runtime_log->runtime.hours      = runtime_hours;
204    runtime_log->runtime.minutes    = runtime_minutes;
205    runtime_log->runtime.seconds    = runtime_seconds;
206 
207    runtime_log->last_played.year   = last_played_year;
208    runtime_log->last_played.month  = last_played_month;
209    runtime_log->last_played.day    = last_played_day;
210    runtime_log->last_played.hour   = last_played_hour;
211    runtime_log->last_played.minute = last_played_minute;
212    runtime_log->last_played.second = last_played_second;
213 
214 end:
215 
216    /* Clean up leftover strings */
217    if (context.runtime_string)
218       free(context.runtime_string);
219    if (context.last_played_string)
220       free(context.last_played_string);
221 
222    /* Close log file */
223    filestream_close(file);
224 }
225 
226 /* Initialise runtime log, loading current parameters
227  * if log file exists. Returned object must be free()'d.
228  * Returns NULL if content_path and/or core_path are invalid */
runtime_log_init(const char * content_path,const char * core_path,const char * dir_runtime_log,const char * dir_playlist,bool log_per_core)229 runtime_log_t *runtime_log_init(
230       const char *content_path,
231       const char *core_path,
232       const char *dir_runtime_log,
233       const char *dir_playlist,
234       bool log_per_core)
235 {
236    char content_name[PATH_MAX_LENGTH];
237    char core_name[PATH_MAX_LENGTH];
238    char log_file_dir[PATH_MAX_LENGTH];
239    char log_file_path[PATH_MAX_LENGTH];
240    char tmp_buf[PATH_MAX_LENGTH];
241    core_info_t *core_info     = NULL;
242    runtime_log_t *runtime_log = NULL;
243 
244    content_name[0]            = '\0';
245    core_name[0]               = '\0';
246    log_file_dir[0]            = '\0';
247    log_file_path[0]           = '\0';
248    tmp_buf[0]                 = '\0';
249 
250    if (  string_is_empty(dir_runtime_log) &&
251          string_is_empty(dir_playlist))
252    {
253       RARCH_ERR("Runtime log directory is undefined - cannot save"
254             " runtime log files.\n");
255       return NULL;
256    }
257 
258    if (  string_is_empty(core_path) ||
259          string_is_equal(core_path, "builtin") ||
260          string_is_equal(core_path, "DETECT") ||
261          string_is_empty(content_path))
262       return NULL;
263 
264    /* Get core name
265     * Note: An annoyance - this is required even when
266     * we are performing aggregate (not per core) logging,
267     * since content name is sometimes dependent upon core
268     * (e.g. see TyrQuake below) */
269    if (core_info_find(core_path, &core_info) &&
270        core_info->core_name)
271       strlcpy(core_name, core_info->core_name, sizeof(core_name));
272 
273    if (string_is_empty(core_name))
274       return NULL;
275 
276    /* Get runtime log directory */
277    if (string_is_empty(dir_runtime_log))
278    {
279       /* If 'custom' runtime log path is undefined,
280        * use default 'playlists/logs' directory... */
281       fill_pathname_join(
282             tmp_buf,
283             dir_playlist,
284             "logs",
285             sizeof(tmp_buf));
286    }
287    else
288       strlcpy(tmp_buf, dir_runtime_log, sizeof(tmp_buf));
289 
290    if (string_is_empty(tmp_buf))
291       return NULL;
292 
293    if (log_per_core)
294       fill_pathname_join(
295             log_file_dir,
296             tmp_buf,
297             core_name,
298             sizeof(log_file_dir));
299    else
300       strlcpy(log_file_dir, tmp_buf, sizeof(log_file_dir));
301 
302    if (string_is_empty(log_file_dir))
303       return NULL;
304 
305    /* Create directory, if required */
306    if (!path_is_directory(log_file_dir))
307    {
308       if (!path_mkdir(log_file_dir))
309       {
310          RARCH_ERR("[runtime] failed to create directory for"
311                " runtime log: %s.\n", log_file_dir);
312          return NULL;
313       }
314    }
315 
316    /* Get content name
317     * NOTE: TyrQuake requires a specific hack, since all
318     * content has the same name... */
319    if (string_is_equal(core_name, "TyrQuake"))
320    {
321       const char *last_slash = find_last_slash(content_path);
322       if (last_slash)
323       {
324          size_t path_length = last_slash + 1 - content_path;
325          if (path_length < PATH_MAX_LENGTH)
326          {
327             memset(tmp_buf, 0, sizeof(tmp_buf));
328             strlcpy(tmp_buf,
329                   content_path, path_length * sizeof(char));
330             strlcpy(content_name,
331                   path_basename(tmp_buf), sizeof(content_name));
332          }
333       }
334    }
335    else
336    {
337       /* path_remove_extension() requires a char * (not const)
338        * so have to use a temporary buffer... */
339       char *tmp_buf_no_ext = NULL;
340       tmp_buf[0]           = '\0';
341       strlcpy(tmp_buf, path_basename(content_path), sizeof(tmp_buf));
342       tmp_buf_no_ext       = path_remove_extension(tmp_buf);
343 
344       if (string_is_empty(tmp_buf_no_ext))
345          return NULL;
346 
347       strlcpy(content_name, tmp_buf_no_ext, sizeof(content_name));
348    }
349 
350    if (string_is_empty(content_name))
351       return NULL;
352 
353    /* Build final log file path */
354    fill_pathname_join(log_file_path, log_file_dir,
355          content_name, sizeof(log_file_path));
356    strlcat(log_file_path, FILE_PATH_RUNTIME_EXTENSION,
357          sizeof(log_file_path));
358 
359    if (string_is_empty(log_file_path))
360       return NULL;
361 
362    /* Phew... If we get this far then all is well.
363     * > Create 'runtime_log' object */
364    runtime_log                     = (runtime_log_t*)
365       malloc(sizeof(*runtime_log));
366    if (!runtime_log)
367       return NULL;
368 
369    /* > Populate default values */
370    runtime_log->runtime.hours      = 0;
371    runtime_log->runtime.minutes    = 0;
372    runtime_log->runtime.seconds    = 0;
373 
374    runtime_log->last_played.year   = 0;
375    runtime_log->last_played.month  = 0;
376    runtime_log->last_played.day    = 0;
377    runtime_log->last_played.hour   = 0;
378    runtime_log->last_played.minute = 0;
379    runtime_log->last_played.second = 0;
380 
381    runtime_log->path[0]            = '\0';
382 
383    strlcpy(runtime_log->path, log_file_path, sizeof(runtime_log->path));
384 
385    /* Load existing log file, if it exists */
386    if (path_is_valid(runtime_log->path))
387       runtime_log_read_file(runtime_log);
388 
389    return runtime_log;
390 }
391 
392 /* Setters */
393 
394 /* Set runtime to specified hours, minutes, seconds value */
runtime_log_set_runtime_hms(runtime_log_t * runtime_log,unsigned hours,unsigned minutes,unsigned seconds)395 void runtime_log_set_runtime_hms(runtime_log_t *runtime_log,
396       unsigned hours, unsigned minutes, unsigned seconds)
397 {
398    retro_time_t usec;
399 
400    if (!runtime_log)
401       return;
402 
403    /* Converting to usec and back again may be considered a
404     * waste of CPU cycles, but this allows us to handle any
405     * kind of broken input without issue - i.e. user can enter
406     * minutes and seconds values > 59, and everything still
407     * works correctly */
408    runtime_log_convert_hms2usec(hours, minutes, seconds, &usec);
409 
410    runtime_log_convert_usec2hms(usec,
411          &runtime_log->runtime.hours,
412          &runtime_log->runtime.minutes,
413          &runtime_log->runtime.seconds);
414 }
415 
416 /* Set runtime to specified microseconds value */
runtime_log_set_runtime_usec(runtime_log_t * runtime_log,retro_time_t usec)417 void runtime_log_set_runtime_usec(
418       runtime_log_t *runtime_log, retro_time_t usec)
419 {
420    if (!runtime_log)
421       return;
422 
423    runtime_log_convert_usec2hms(usec,
424          &runtime_log->runtime.hours,
425          &runtime_log->runtime.minutes,
426          &runtime_log->runtime.seconds);
427 }
428 
429 /* Adds specified hours, minutes, seconds value to current runtime */
runtime_log_add_runtime_hms(runtime_log_t * runtime_log,unsigned hours,unsigned minutes,unsigned seconds)430 void runtime_log_add_runtime_hms(
431       runtime_log_t *runtime_log,
432       unsigned hours,
433       unsigned minutes,
434       unsigned seconds)
435 {
436    retro_time_t usec_old;
437    retro_time_t usec_new;
438 
439    if (!runtime_log)
440       return;
441 
442    runtime_log_convert_hms2usec(
443          runtime_log->runtime.hours,
444          runtime_log->runtime.minutes,
445          runtime_log->runtime.seconds,
446          &usec_old);
447 
448    runtime_log_convert_hms2usec(hours, minutes, seconds, &usec_new);
449 
450    runtime_log_convert_usec2hms(usec_old + usec_new,
451          &runtime_log->runtime.hours,
452          &runtime_log->runtime.minutes,
453          &runtime_log->runtime.seconds);
454 }
455 
456 /* Adds specified microseconds value to current runtime */
runtime_log_add_runtime_usec(runtime_log_t * runtime_log,retro_time_t usec)457 void runtime_log_add_runtime_usec(
458       runtime_log_t *runtime_log, retro_time_t usec)
459 {
460    retro_time_t usec_old;
461 
462    if (!runtime_log)
463       return;
464 
465    runtime_log_convert_hms2usec(
466          runtime_log->runtime.hours,
467          runtime_log->runtime.minutes,
468          runtime_log->runtime.seconds,
469          &usec_old);
470 
471    runtime_log_convert_usec2hms(usec_old + usec,
472          &runtime_log->runtime.hours,
473          &runtime_log->runtime.minutes,
474          &runtime_log->runtime.seconds);
475 }
476 
477 /* Sets last played entry to specified value */
runtime_log_set_last_played(runtime_log_t * runtime_log,unsigned year,unsigned month,unsigned day,unsigned hour,unsigned minute,unsigned second)478 void runtime_log_set_last_played(runtime_log_t *runtime_log,
479       unsigned year, unsigned month, unsigned day,
480       unsigned hour, unsigned minute, unsigned second)
481 {
482    if (!runtime_log)
483       return;
484 
485    /* This function should never be needed, so just
486     * perform dumb value assignment (i.e. no validation
487     * using mktime()) */
488    runtime_log->last_played.year   = year;
489    runtime_log->last_played.month  = month;
490    runtime_log->last_played.day    = day;
491    runtime_log->last_played.hour   = hour;
492    runtime_log->last_played.minute = minute;
493    runtime_log->last_played.second = second;
494 }
495 
496 /* Sets last played entry to current date/time */
runtime_log_set_last_played_now(runtime_log_t * runtime_log)497 void runtime_log_set_last_played_now(runtime_log_t *runtime_log)
498 {
499    time_t current_time;
500    struct tm time_info;
501 
502    if (!runtime_log)
503       return;
504 
505    /* Get current time */
506    time(&current_time);
507    rtime_localtime(&current_time, &time_info);
508 
509    /* Extract values */
510    runtime_log->last_played.year   = (unsigned)time_info.tm_year + 1900;
511    runtime_log->last_played.month  = (unsigned)time_info.tm_mon + 1;
512    runtime_log->last_played.day    = (unsigned)time_info.tm_mday;
513    runtime_log->last_played.hour   = (unsigned)time_info.tm_hour;
514    runtime_log->last_played.minute = (unsigned)time_info.tm_min;
515    runtime_log->last_played.second = (unsigned)time_info.tm_sec;
516 }
517 
518 /* Resets log to default (zero) values */
runtime_log_reset(runtime_log_t * runtime_log)519 void runtime_log_reset(runtime_log_t *runtime_log)
520 {
521    if (!runtime_log)
522       return;
523 
524    runtime_log->runtime.hours      = 0;
525    runtime_log->runtime.minutes    = 0;
526    runtime_log->runtime.seconds    = 0;
527 
528    runtime_log->last_played.year   = 0;
529    runtime_log->last_played.month  = 0;
530    runtime_log->last_played.day    = 0;
531    runtime_log->last_played.hour   = 0;
532    runtime_log->last_played.minute = 0;
533    runtime_log->last_played.second = 0;
534 }
535 
536 /* Getters */
537 
538 /* Gets runtime in hours, minutes, seconds */
runtime_log_get_runtime_hms(runtime_log_t * runtime_log,unsigned * hours,unsigned * minutes,unsigned * seconds)539 void runtime_log_get_runtime_hms(runtime_log_t *runtime_log,
540       unsigned *hours, unsigned *minutes, unsigned *seconds)
541 {
542    if (!runtime_log)
543       return;
544 
545    *hours   = runtime_log->runtime.hours;
546    *minutes = runtime_log->runtime.minutes;
547    *seconds = runtime_log->runtime.seconds;
548 }
549 
550 /* Gets runtime in microseconds */
runtime_log_get_runtime_usec(runtime_log_t * runtime_log,retro_time_t * usec)551 void runtime_log_get_runtime_usec(
552       runtime_log_t *runtime_log, retro_time_t *usec)
553 {
554    if (runtime_log)
555       runtime_log_convert_hms2usec( runtime_log->runtime.hours,
556             runtime_log->runtime.minutes, runtime_log->runtime.seconds,
557             usec);
558 }
559 
560 /* Gets runtime as a pre-formatted string */
runtime_log_get_runtime_str(runtime_log_t * runtime_log,char * s,size_t len)561 void runtime_log_get_runtime_str(runtime_log_t *runtime_log,
562       char *s, size_t len)
563 {
564    if (runtime_log)
565       snprintf(s, len, "%s %02u:%02u:%02u",
566             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME),
567             runtime_log->runtime.hours, runtime_log->runtime.minutes,
568             runtime_log->runtime.seconds);
569    else
570       snprintf(s, len, "%s 00:00:00",
571             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME));
572 }
573 
574 /* Gets last played entry values */
runtime_log_get_last_played(runtime_log_t * runtime_log,unsigned * year,unsigned * month,unsigned * day,unsigned * hour,unsigned * minute,unsigned * second)575 void runtime_log_get_last_played(runtime_log_t *runtime_log,
576       unsigned *year, unsigned *month, unsigned *day,
577       unsigned *hour, unsigned *minute, unsigned *second)
578 {
579    if (!runtime_log)
580       return;
581 
582    *year   = runtime_log->last_played.year;
583    *month  = runtime_log->last_played.month;
584    *day    = runtime_log->last_played.day;
585    *hour   = runtime_log->last_played.hour;
586    *minute = runtime_log->last_played.minute;
587    *second = runtime_log->last_played.second;
588 }
589 
590 /* Gets last played entry values as a struct tm 'object'
591  * (e.g. for printing with strftime()) */
runtime_log_get_last_played_time(runtime_log_t * runtime_log,struct tm * time_info)592 void runtime_log_get_last_played_time(runtime_log_t *runtime_log,
593       struct tm *time_info)
594 {
595    if (!runtime_log || !time_info)
596       return;
597 
598    /* Set tm values */
599    time_info->tm_year  = (int)runtime_log->last_played.year  - 1900;
600    time_info->tm_mon   = (int)runtime_log->last_played.month - 1;
601    time_info->tm_mday  = (int)runtime_log->last_played.day;
602    time_info->tm_hour  = (int)runtime_log->last_played.hour;
603    time_info->tm_min   = (int)runtime_log->last_played.minute;
604    time_info->tm_sec   = (int)runtime_log->last_played.second;
605    time_info->tm_isdst = -1;
606 
607    /* Perform any required range adjustment + populate
608     * missing entries */
609    mktime(time_info);
610 }
611 
last_played_strftime(runtime_log_t * runtime_log,char * str,size_t len,const char * format)612 static void last_played_strftime(runtime_log_t *runtime_log,
613       char *str, size_t len, const char *format)
614 {
615    struct tm time_info;
616    char *local = NULL;
617 
618    if (!runtime_log)
619       return;
620 
621    /* Get time */
622    runtime_log_get_last_played_time(runtime_log, &time_info);
623 
624    /* Ensure correct locale is set */
625    setlocale(LC_TIME, "");
626 
627    /* Generate string */
628 #if defined(__linux__) && !defined(ANDROID)
629    strftime(str, len, format, &time_info);
630 #else
631    strftime(str, len, format, &time_info);
632    local = local_to_utf8_string_alloc(str);
633 
634    if (!string_is_empty(local))
635       strlcpy(str, local, len);
636 
637    if (local)
638    {
639       free(local);
640       local = NULL;
641    }
642 #endif
643 }
644 
645 /* Gets last played entry value as a pre-formatted string */
runtime_log_get_last_played_str(runtime_log_t * runtime_log,char * str,size_t len,enum playlist_sublabel_last_played_style_type timedate_style,enum playlist_sublabel_last_played_date_separator_type date_separator)646 void runtime_log_get_last_played_str(runtime_log_t *runtime_log,
647       char *str, size_t len,
648       enum playlist_sublabel_last_played_style_type timedate_style,
649       enum playlist_sublabel_last_played_date_separator_type date_separator)
650 {
651    char tmp[64];
652    bool has_am_pm         = false;
653    const char *format_str = "";
654 
655    tmp[0] = '\0';
656 
657    if (runtime_log)
658    {
659       /* Handle 12-hour clock options
660        * > These require extra work, due to AM/PM localisation */
661       switch (timedate_style)
662       {
663          case PLAYLIST_LAST_PLAYED_STYLE_YMD_HMS_AMPM:
664             has_am_pm = true;
665             /* Using switch statements to set the format
666              * string is verbose, but has far less performance
667              * impact than setting the date separator dynamically
668              * (i.e. no snprintf() or character replacement...) */
669             switch (date_separator)
670             {
671                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
672                   format_str = " %Y/%m/%d %I:%M:%S %p";
673                   break;
674                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
675                   format_str = " %Y.%m.%d %I:%M:%S %p";
676                   break;
677                default:
678                   format_str = " %Y-%m-%d %I:%M:%S %p";
679                   break;
680             }
681             break;
682          case PLAYLIST_LAST_PLAYED_STYLE_YMD_HM_AMPM:
683             has_am_pm = true;
684             switch (date_separator)
685             {
686                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
687                   format_str = " %Y/%m/%d %I:%M %p";
688                   break;
689                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
690                   format_str = " %Y.%m.%d %I:%M %p";
691                   break;
692                default:
693                   format_str = " %Y-%m-%d %I:%M %p";
694                   break;
695             }
696             break;
697          case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HMS_AMPM:
698             has_am_pm = true;
699             switch (date_separator)
700             {
701                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
702                   format_str = " %m/%d/%Y %I:%M:%S %p";
703                   break;
704                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
705                   format_str = " %m.%d.%Y %I:%M:%S %p";
706                   break;
707                default:
708                   format_str = " %m-%d-%Y %I:%M:%S %p";
709                   break;
710             }
711             break;
712          case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HM_AMPM:
713             has_am_pm = true;
714             switch (date_separator)
715             {
716                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
717                   format_str = " %m/%d/%Y %I:%M %p";
718                   break;
719                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
720                   format_str = " %m.%d.%Y %I:%M %p";
721                   break;
722                default:
723                   format_str = " %m-%d-%Y %I:%M %p";
724                   break;
725             }
726             break;
727          case PLAYLIST_LAST_PLAYED_STYLE_MD_HM_AMPM:
728             has_am_pm = true;
729             switch (date_separator)
730             {
731                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
732                   format_str = " %m/%d %I:%M %p";
733                   break;
734                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
735                   format_str = " %m.%d %I:%M %p";
736                   break;
737                default:
738                   format_str = " %m-%d %I:%M %p";
739                   break;
740             }
741             break;
742          case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HMS_AMPM:
743             has_am_pm = true;
744             switch (date_separator)
745             {
746                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
747                   format_str = " %d/%m/%Y %I:%M:%S %p";
748                   break;
749                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
750                   format_str = " %d.%m.%Y %I:%M:%S %p";
751                   break;
752                default:
753                   format_str = " %d-%m-%Y %I:%M:%S %p";
754                   break;
755             }
756             break;
757          case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HM_AMPM:
758             has_am_pm = true;
759             switch (date_separator)
760             {
761                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
762                   format_str = " %d/%m/%Y %I:%M %p";
763                   break;
764                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
765                   format_str = " %d.%m.%Y %I:%M %p";
766                   break;
767                default:
768                   format_str = " %d-%m-%Y %I:%M %p";
769                   break;
770             }
771             break;
772          case PLAYLIST_LAST_PLAYED_STYLE_DDMM_HM_AMPM:
773             has_am_pm = true;
774             switch (date_separator)
775             {
776                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
777                   format_str = " %d/%m %I:%M %p";
778                   break;
779                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
780                   format_str = " %d.%m %I:%M %p";
781                   break;
782                default:
783                   format_str = " %d-%m %I:%M %p";
784                   break;
785             }
786             break;
787          default:
788             has_am_pm = false;
789             break;
790       }
791 
792       if (has_am_pm)
793       {
794          last_played_strftime(runtime_log, tmp, sizeof(tmp), format_str);
795          snprintf(str, len, "%s%s",
796                msg_hash_to_str(
797                   MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
798                tmp);
799          return;
800       }
801 
802       /* Handle non-12-hour clock options */
803       switch (timedate_style)
804       {
805          case PLAYLIST_LAST_PLAYED_STYLE_YMD_HM:
806             switch (date_separator)
807             {
808                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
809                   format_str = "%s %04u/%02u/%02u %02u:%02u";
810                   break;
811                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
812                   format_str = "%s %04u.%02u.%02u %02u:%02u";
813                   break;
814                default:
815                   format_str = "%s %04u-%02u-%02u %02u:%02u";
816                   break;
817             }
818             snprintf(str, len, format_str,
819                   msg_hash_to_str(
820                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
821                   runtime_log->last_played.year,
822                   runtime_log->last_played.month,
823                   runtime_log->last_played.day,
824                   runtime_log->last_played.hour,
825                   runtime_log->last_played.minute);
826             return;
827          case PLAYLIST_LAST_PLAYED_STYLE_YMD:
828             switch (date_separator)
829             {
830                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
831                   format_str = "%s %04u/%02u/%02u";
832                   break;
833                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
834                   format_str = "%s %04u.%02u.%02u";
835                   break;
836                default:
837                   format_str = "%s %04u-%02u-%02u";
838                   break;
839             }
840             snprintf(str, len, format_str,
841                   msg_hash_to_str(
842                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
843                   runtime_log->last_played.year,
844                   runtime_log->last_played.month,
845                   runtime_log->last_played.day);
846             return;
847          case PLAYLIST_LAST_PLAYED_STYLE_YM:
848             switch (date_separator)
849             {
850                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
851                   format_str = "%s %04u/%02u";
852                   break;
853                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
854                   format_str = "%s %04u.%02u";
855                   break;
856                default:
857                   format_str = "%s %04u-%02u";
858                   break;
859             }
860             snprintf(str, len, format_str,
861                   msg_hash_to_str(
862                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
863                   runtime_log->last_played.year,
864                   runtime_log->last_played.month);
865             return;
866          case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HMS:
867             switch (date_separator)
868             {
869                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
870                   format_str = "%s %02u/%02u/%04u %02u:%02u:%02u";
871                   break;
872                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
873                   format_str = "%s %02u.%02u.%04u %02u:%02u:%02u";
874                   break;
875                default:
876                   format_str = "%s %02u-%02u-%04u %02u:%02u:%02u";
877                   break;
878             }
879             snprintf(str, len, format_str,
880                   msg_hash_to_str(
881                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
882                   runtime_log->last_played.month,
883                   runtime_log->last_played.day,
884                   runtime_log->last_played.year,
885                   runtime_log->last_played.hour,
886                   runtime_log->last_played.minute,
887                   runtime_log->last_played.second);
888             return;
889          case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY_HM:
890             switch (date_separator)
891             {
892                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
893                   format_str = "%s %02u/%02u/%04u %02u:%02u";
894                   break;
895                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
896                   format_str = "%s %02u.%02u.%04u %02u:%02u";
897                   break;
898                default:
899                   format_str = "%s %02u-%02u-%04u %02u:%02u";
900                   break;
901             }
902             snprintf(str, len, format_str,
903                   msg_hash_to_str(
904                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
905                   runtime_log->last_played.month,
906                   runtime_log->last_played.day,
907                   runtime_log->last_played.year,
908                   runtime_log->last_played.hour,
909                   runtime_log->last_played.minute);
910             return;
911          case PLAYLIST_LAST_PLAYED_STYLE_MD_HM:
912             switch (date_separator)
913             {
914                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
915                   format_str = "%s %02u/%02u %02u:%02u";
916                   break;
917                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
918                   format_str = "%s %02u.%02u %02u:%02u";
919                   break;
920                default:
921                   format_str = "%s %02u-%02u %02u:%02u";
922                   break;
923             }
924             snprintf(str, len, format_str,
925                   msg_hash_to_str(
926                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
927                   runtime_log->last_played.month,
928                   runtime_log->last_played.day,
929                   runtime_log->last_played.hour,
930                   runtime_log->last_played.minute);
931             return;
932          case PLAYLIST_LAST_PLAYED_STYLE_MDYYYY:
933             switch (date_separator)
934             {
935                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
936                   format_str = "%s %02u/%02u/%04u";
937                   break;
938                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
939                   format_str = "%s %02u.%02u.%04u";
940                   break;
941                default:
942                   format_str = "%s %02u-%02u-%04u";
943                   break;
944             }
945             snprintf(str, len, format_str,
946                   msg_hash_to_str(
947                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
948                   runtime_log->last_played.month,
949                   runtime_log->last_played.day,
950                   runtime_log->last_played.year);
951             return;
952          case PLAYLIST_LAST_PLAYED_STYLE_MD:
953             switch (date_separator)
954             {
955                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
956                   format_str = "%s %02u/%02u";
957                   break;
958                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
959                   format_str = "%s %02u.%02u";
960                   break;
961                default:
962                   format_str = "%s %02u-%02u";
963                   break;
964             }
965             snprintf(str, len, format_str,
966                   msg_hash_to_str(
967                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
968                   runtime_log->last_played.month,
969                   runtime_log->last_played.day);
970             return;
971          case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HMS:
972             switch (date_separator)
973             {
974                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
975                   format_str = "%s %02u/%02u/%04u %02u:%02u:%02u";
976                   break;
977                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
978                   format_str = "%s %02u.%02u.%04u %02u:%02u:%02u";
979                   break;
980                default:
981                   format_str = "%s %02u-%02u-%04u %02u:%02u:%02u";
982                   break;
983             }
984             snprintf(str, len, format_str,
985                   msg_hash_to_str(
986                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
987                   runtime_log->last_played.day,
988                   runtime_log->last_played.month,
989                   runtime_log->last_played.year,
990                   runtime_log->last_played.hour,
991                   runtime_log->last_played.minute,
992                   runtime_log->last_played.second);
993             return;
994          case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY_HM:
995             switch (date_separator)
996             {
997                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
998                   format_str = "%s %02u/%02u/%04u %02u:%02u";
999                   break;
1000                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
1001                   format_str = "%s %02u.%02u.%04u %02u:%02u";
1002                   break;
1003                default:
1004                   format_str = "%s %02u-%02u-%04u %02u:%02u";
1005                   break;
1006             }
1007             snprintf(str, len, format_str,
1008                   msg_hash_to_str(
1009                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
1010                   runtime_log->last_played.day,
1011                   runtime_log->last_played.month,
1012                   runtime_log->last_played.year,
1013                   runtime_log->last_played.hour,
1014                   runtime_log->last_played.minute);
1015             return;
1016          case PLAYLIST_LAST_PLAYED_STYLE_DDMM_HM:
1017             switch (date_separator)
1018             {
1019                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
1020                   format_str = "%s %02u/%02u %02u:%02u";
1021                   break;
1022                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
1023                   format_str = "%s %02u.%02u %02u:%02u";
1024                   break;
1025                default:
1026                   format_str = "%s %02u-%02u %02u:%02u";
1027                   break;
1028             }
1029             snprintf(str, len, format_str,
1030                   msg_hash_to_str(
1031                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
1032                   runtime_log->last_played.day,
1033                   runtime_log->last_played.month,
1034                   runtime_log->last_played.hour,
1035                   runtime_log->last_played.minute);
1036             return;
1037          case PLAYLIST_LAST_PLAYED_STYLE_DDMMYYYY:
1038             switch (date_separator)
1039             {
1040                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
1041                   format_str = "%s %02u/%02u/%04u";
1042                   break;
1043                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
1044                   format_str = "%s %02u.%02u.%04u";
1045                   break;
1046                default:
1047                   format_str = "%s %02u-%02u-%04u";
1048                   break;
1049             }
1050             snprintf(str, len, format_str,
1051                   msg_hash_to_str(
1052                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
1053                   runtime_log->last_played.day,
1054                   runtime_log->last_played.month,
1055                   runtime_log->last_played.year);
1056             return;
1057          case PLAYLIST_LAST_PLAYED_STYLE_DDMM:
1058             switch (date_separator)
1059             {
1060                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
1061                   format_str = "%s %02u/%02u";
1062                   break;
1063                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
1064                   format_str = "%s %02u.%02u";
1065                   break;
1066                default:
1067                   format_str = "%s %02u-%02u";
1068                   break;
1069             }
1070             snprintf(str, len, format_str,
1071                   msg_hash_to_str(
1072                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
1073                   runtime_log->last_played.day, runtime_log->last_played.month);
1074             return;
1075          case PLAYLIST_LAST_PLAYED_STYLE_YMD_HMS:
1076          default:
1077             switch (date_separator)
1078             {
1079                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_SLASH:
1080                   format_str = "%s %04u/%02u/%02u %02u:%02u:%02u";
1081                   break;
1082                case PLAYLIST_LAST_PLAYED_DATE_SEPARATOR_PERIOD:
1083                   format_str = "%s %04u.%02u.%02u %02u:%02u:%02u";
1084                   break;
1085                default:
1086                   format_str = "%s %04u-%02u-%02u %02u:%02u:%02u";
1087                   break;
1088             }
1089             snprintf(str, len, format_str,
1090                   msg_hash_to_str(
1091                      MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
1092                   runtime_log->last_played.year,
1093                   runtime_log->last_played.month,
1094                   runtime_log->last_played.day,
1095                   runtime_log->last_played.hour,
1096                   runtime_log->last_played.minute,
1097                   runtime_log->last_played.second);
1098             return;
1099       }
1100    }
1101    else
1102       snprintf(str, len,
1103             "%s %s",
1104             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
1105             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_INLINE_CORE_DISPLAY_NEVER)
1106             );
1107 }
1108 
1109 /* Status */
1110 
1111 /* Returns true if log has a non-zero runtime entry */
runtime_log_has_runtime(runtime_log_t * runtime_log)1112 bool runtime_log_has_runtime(runtime_log_t *runtime_log)
1113 {
1114    if (runtime_log)
1115       return !(
1116             (runtime_log->runtime.hours   == 0) &&
1117             (runtime_log->runtime.minutes == 0) &&
1118             (runtime_log->runtime.seconds == 0));
1119    return false;
1120 }
1121 
1122 /* Returns true if log has a non-zero last played entry */
runtime_log_has_last_played(runtime_log_t * runtime_log)1123 bool runtime_log_has_last_played(runtime_log_t *runtime_log)
1124 {
1125    if (runtime_log)
1126       return !(
1127             (runtime_log->last_played.year   == 0) &&
1128             (runtime_log->last_played.month  == 0) &&
1129             (runtime_log->last_played.day    == 0) &&
1130             (runtime_log->last_played.hour   == 0) &&
1131             (runtime_log->last_played.minute == 0) &&
1132             (runtime_log->last_played.second == 0));
1133    return false;
1134 }
1135 
1136 /* Saving */
1137 
1138 /* Saves specified runtime log to disk */
runtime_log_save(runtime_log_t * runtime_log)1139 void runtime_log_save(runtime_log_t *runtime_log)
1140 {
1141    char value_string[64]; /* 64 characters should be
1142                              enough for a very long runtime... :) */
1143    RFILE *file            = NULL;
1144    rjsonwriter_t* writer;
1145 
1146    if (!runtime_log)
1147       return;
1148 
1149    RARCH_LOG("Saving runtime log file: %s\n", runtime_log->path);
1150 
1151    /* Attempt to open log file */
1152    file = filestream_open(runtime_log->path,
1153          RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE);
1154 
1155    if (!file)
1156    {
1157       RARCH_ERR("Failed to open runtime log file: %s\n", runtime_log->path);
1158       return;
1159    }
1160 
1161    /* Initialise JSON writer */
1162    writer = rjsonwriter_open_rfile(file);
1163    if (!writer)
1164    {
1165       RARCH_ERR("Failed to create JSON writer.\n");
1166       goto end;
1167    }
1168 
1169    /* Write output file */
1170    rjsonwriter_add_start_object(writer);
1171    rjsonwriter_add_newline(writer);
1172 
1173    /* > Version entry */
1174    rjsonwriter_add_spaces(writer, 2);
1175    rjsonwriter_add_string(writer, "version");
1176    rjsonwriter_add_colon(writer);
1177    rjsonwriter_add_space(writer);
1178    rjsonwriter_add_string(writer, "1.0");
1179    rjsonwriter_add_comma(writer);
1180    rjsonwriter_add_newline(writer);
1181 
1182    /* > Runtime entry */
1183    snprintf(value_string,
1184          sizeof(value_string),
1185          LOG_FILE_RUNTIME_FORMAT_STR,
1186          runtime_log->runtime.hours, runtime_log->runtime.minutes,
1187          runtime_log->runtime.seconds);
1188 
1189    rjsonwriter_add_spaces(writer, 2);
1190    rjsonwriter_add_string(writer, "runtime");
1191    rjsonwriter_add_colon(writer);
1192    rjsonwriter_add_space(writer);
1193    rjsonwriter_add_string(writer, value_string);
1194    rjsonwriter_add_comma(writer);
1195    rjsonwriter_add_newline(writer);
1196 
1197    /* > Last played entry */
1198    value_string[0] = '\0';
1199    snprintf(value_string, sizeof(value_string),
1200          LOG_FILE_LAST_PLAYED_FORMAT_STR,
1201          runtime_log->last_played.year, runtime_log->last_played.month,
1202          runtime_log->last_played.day,
1203          runtime_log->last_played.hour, runtime_log->last_played.minute,
1204          runtime_log->last_played.second);
1205 
1206    rjsonwriter_add_spaces(writer, 2);
1207    rjsonwriter_add_string(writer, "last_played");
1208    rjsonwriter_add_colon(writer);
1209    rjsonwriter_add_space(writer);
1210    rjsonwriter_add_string(writer, value_string);
1211    rjsonwriter_add_newline(writer);
1212 
1213    /* > Finalise */
1214    rjsonwriter_add_end_object(writer);
1215    rjsonwriter_add_newline(writer);
1216 
1217    /* Free JSON writer */
1218    if (!rjsonwriter_free(writer))
1219    {
1220       RARCH_ERR("Error writing runtime log file: %s\n", runtime_log->path);
1221    }
1222 
1223 end:
1224    /* Close log file */
1225    filestream_close(file);
1226 }
1227 
1228 /* Utility functions */
1229 
1230 /* Convert from hours, minutes, seconds to microseconds */
runtime_log_convert_hms2usec(unsigned hours,unsigned minutes,unsigned seconds,retro_time_t * usec)1231 void runtime_log_convert_hms2usec(unsigned hours,
1232       unsigned minutes, unsigned seconds, retro_time_t *usec)
1233 {
1234    *usec = ((retro_time_t)hours   * 60 * 60 * 1000000) +
1235            ((retro_time_t)minutes * 60      * 1000000) +
1236            ((retro_time_t)seconds           * 1000000);
1237 }
1238 
1239 /* Convert from microseconds to hours, minutes, seconds */
runtime_log_convert_usec2hms(retro_time_t usec,unsigned * hours,unsigned * minutes,unsigned * seconds)1240 void runtime_log_convert_usec2hms(retro_time_t usec,
1241       unsigned *hours, unsigned *minutes, unsigned *seconds)
1242 {
1243    *seconds  = (unsigned)(usec / 1000000);
1244    *minutes  = *seconds / 60;
1245    *hours    = *minutes / 60;
1246 
1247    *seconds -= *minutes * 60;
1248    *minutes -= *hours * 60;
1249 }
1250 
1251 /* Playlist manipulation */
1252 
1253 /* Updates specified playlist entry runtime values with
1254  * contents of associated log file */
runtime_update_playlist(playlist_t * playlist,size_t idx,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)1255 void runtime_update_playlist(
1256       playlist_t *playlist, size_t idx,
1257       const char *dir_runtime_log,
1258       const char *dir_playlist,
1259       bool log_per_core,
1260       enum playlist_sublabel_last_played_style_type timedate_style,
1261       enum playlist_sublabel_last_played_date_separator_type date_separator)
1262 {
1263    char runtime_str[64];
1264    char last_played_str[64];
1265    runtime_log_t *runtime_log             = NULL;
1266    const struct playlist_entry *entry     = NULL;
1267    struct playlist_entry update_entry     = {0};
1268 #if defined(HAVE_MENU) && (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI))
1269    const char *menu_ident                 = menu_driver_ident();
1270 #endif
1271 
1272    /* Sanity check */
1273    if (!playlist)
1274       return;
1275 
1276    if (idx >= playlist_get_size(playlist))
1277       return;
1278 
1279    /* Set fallback playlist 'runtime_status'
1280     * (saves 'if' checks later...) */
1281    update_entry.runtime_status = PLAYLIST_RUNTIME_MISSING;
1282 
1283    /* 'Attach' runtime/last played strings */
1284    runtime_str[0]               = '\0';
1285    last_played_str[0]           = '\0';
1286    update_entry.runtime_str     = runtime_str;
1287    update_entry.last_played_str = last_played_str;
1288 
1289    /* Read current playlist entry */
1290    playlist_get_index(playlist, idx, &entry);
1291 
1292    /* Attempt to open log file */
1293    runtime_log = runtime_log_init(
1294          entry->path,
1295          entry->core_path,
1296          dir_runtime_log,
1297          dir_playlist,
1298          log_per_core);
1299 
1300    if (runtime_log)
1301    {
1302       /* Check whether a non-zero runtime has been recorded */
1303       if (runtime_log_has_runtime(runtime_log))
1304       {
1305          /* Read current runtime */
1306          runtime_log_get_runtime_hms(runtime_log,
1307                &update_entry.runtime_hours,
1308                &update_entry.runtime_minutes,
1309                &update_entry.runtime_seconds);
1310 
1311          runtime_log_get_runtime_str(runtime_log,
1312                runtime_str, sizeof(runtime_str));
1313 
1314          /* Read last played timestamp */
1315          runtime_log_get_last_played(runtime_log,
1316                &update_entry.last_played_year,
1317                &update_entry.last_played_month,
1318                &update_entry.last_played_day,
1319                &update_entry.last_played_hour,
1320                &update_entry.last_played_minute,
1321                &update_entry.last_played_second);
1322 
1323          runtime_log_get_last_played_str(runtime_log,
1324                last_played_str, sizeof(last_played_str),
1325                timedate_style, date_separator);
1326 
1327          /* Playlist entry now contains valid runtime data */
1328          update_entry.runtime_status = PLAYLIST_RUNTIME_VALID;
1329       }
1330 
1331       /* Clean up */
1332       free(runtime_log);
1333    }
1334 
1335 #if defined(HAVE_MENU) && (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI))
1336    /* Ozone and GLUI require runtime/last played strings
1337     * to be populated even when no runtime is recorded */
1338    if (update_entry.runtime_status != PLAYLIST_RUNTIME_VALID)
1339    {
1340       if (string_is_equal(menu_ident, "ozone") ||
1341           string_is_equal(menu_ident, "glui"))
1342       {
1343          runtime_log_get_runtime_str(NULL,
1344                runtime_str, sizeof(runtime_str));
1345          runtime_log_get_last_played_str(NULL,
1346                last_played_str, sizeof(last_played_str),
1347                timedate_style, date_separator);
1348 
1349          /* While runtime data does not exist, the playlist
1350           * entry does now contain valid information... */
1351          update_entry.runtime_status = PLAYLIST_RUNTIME_VALID;
1352       }
1353    }
1354 #endif
1355 
1356    /* Update playlist */
1357    playlist_update_runtime(playlist, idx, &update_entry, false);
1358 }
1359