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(¤t_time);
507 rtime_localtime(¤t_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