runtime_file.c 49 KB

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