From 33c27606d367c4b1110b1b1c582d331a1a47fc0c Mon Sep 17 00:00:00 2001
From: jdgleaver <james@leaver.myzen.co.uk>
Date: Fri, 1 Mar 2019 14:06:40 +0000
Subject: [PATCH] Finalise integration of per-content runtime logs (runtime
 sublabels on all playlists, 'last played' timestamp added to playlist
 sublablels, content_runtime.lpl retired)

---
 command.c                    |  14 ---
 configuration.c              |  22 ----
 configuration.h              |   1 -
 defaults.h                   |   1 -
 file_path_special.h          |   1 -
 file_path_str.c              |   3 -
 intl/msg_hash_us.h           |   4 +
 menu/cbs/menu_cbs_sublabel.c |  55 ++++++----
 menu/menu_displaylist.c      |  65 +++++++----
 msg_hash.h                   |   1 +
 playlist.c                   | 203 +++++++++++++++++++++++++++++++----
 playlist.h                   |  21 ++--
 retroarch.c                  |  96 +++--------------
 13 files changed, 300 insertions(+), 187 deletions(-)

diff --git a/command.c b/command.c
index fc7a0ff97a..72e818eaa8 100644
--- a/command.c
+++ b/command.c
@@ -2222,13 +2222,6 @@ TODO: Add a setting for these tweaks */
          }
          g_defaults.music_history = NULL;
 
-         if (g_defaults.content_runtime)
-         {
-            playlist_write_runtime_file(g_defaults.content_runtime);
-            playlist_free(g_defaults.content_runtime);
-         }
-         g_defaults.content_runtime = NULL;
-
 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
          if (g_defaults.video_history)
          {
@@ -2279,13 +2272,6 @@ TODO: Add a setting for these tweaks */
                   settings->paths.path_content_music_history,
                   content_history_size);
 
-            RARCH_LOG("%s: [%s].\n",
-                  msg_hash_to_str(MSG_LOADING_HISTORY_FILE),
-                  settings->paths.path_content_runtime);
-            g_defaults.content_runtime = playlist_init(
-                  settings->paths.path_content_runtime,
-                  content_history_size);
-
 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
             RARCH_LOG("%s: [%s].\n",
                   msg_hash_to_str(MSG_LOADING_HISTORY_FILE),
diff --git a/configuration.c b/configuration.c
index 338a59e05d..55d67b7a75 100644
--- a/configuration.c
+++ b/configuration.c
@@ -1234,8 +1234,6 @@ static struct config_path_setting *populate_settings_path(settings_t *settings,
          settings->paths.path_content_video_history, false, NULL, true);
    SETTING_PATH("content_image_history_path",
          settings->paths.path_content_image_history, false, NULL, true);
-   SETTING_PATH("content_runtime_path",
-         settings->paths.path_content_runtime, false, NULL, true);
 #ifdef HAVE_OVERLAY
    SETTING_PATH("input_overlay",
          settings->paths.path_overlay, false, NULL, true);
@@ -2085,7 +2083,6 @@ void config_set_defaults(void)
    *settings->paths.path_content_music_history   = '\0';
    *settings->paths.path_content_image_history   = '\0';
    *settings->paths.path_content_video_history   = '\0';
-   *settings->paths.path_content_runtime         = '\0';
    *settings->paths.path_cheat_settings    = '\0';
    *settings->paths.path_shader            = '\0';
 #ifndef IOS
@@ -3069,25 +3066,6 @@ static bool config_load_file(const char *path, bool set_defaults,
       }
    }
 
-   if (string_is_empty(settings->paths.path_content_runtime))
-   {
-      if (string_is_empty(settings->paths.directory_content_history))
-      {
-         fill_pathname_resolve_relative(
-               settings->paths.path_content_runtime,
-               path_config,
-               file_path_str(FILE_PATH_CONTENT_RUNTIME),
-               sizeof(settings->paths.path_content_runtime));
-      }
-      else
-      {
-         fill_pathname_join(settings->paths.path_content_runtime,
-               settings->paths.directory_content_history,
-               file_path_str(FILE_PATH_CONTENT_RUNTIME),
-               sizeof(settings->paths.path_content_runtime));
-      }
-   }
-
    if (!string_is_empty(settings->paths.directory_screenshot))
    {
       if (string_is_equal(settings->paths.directory_screenshot, "default"))
diff --git a/configuration.h b/configuration.h
index 79f64ef833..517da9e807 100644
--- a/configuration.h
+++ b/configuration.h
@@ -552,7 +552,6 @@ typedef struct settings
       char path_content_music_history[PATH_MAX_LENGTH];
       char path_content_image_history[PATH_MAX_LENGTH];
       char path_content_video_history[PATH_MAX_LENGTH];
-      char path_content_runtime[PATH_MAX_LENGTH];
       char path_libretro_info[PATH_MAX_LENGTH];
       char path_cheat_settings[PATH_MAX_LENGTH];
       char path_shader[PATH_MAX_LENGTH];
diff --git a/defaults.h b/defaults.h
index 1a0c58f694..117e14a39a 100644
--- a/defaults.h
+++ b/defaults.h
@@ -103,7 +103,6 @@ struct defaults
 #ifndef IS_SALAMANDER
    playlist_t *content_history;
    playlist_t *content_favorites;
-   playlist_t *content_runtime;
 #ifdef HAVE_IMAGEVIEWER
    playlist_t *image_history;
 #endif
diff --git a/file_path_special.h b/file_path_special.h
index c8e5c2ba68..8b7cc3e3ef 100644
--- a/file_path_special.h
+++ b/file_path_special.h
@@ -38,7 +38,6 @@ enum file_path_enum
    FILE_PATH_LOG_ERROR,
    FILE_PATH_LOG_INFO,
    FILE_PATH_CONTENT_HISTORY,
-   FILE_PATH_CONTENT_RUNTIME,
    FILE_PATH_CONTENT_FAVORITES,
    FILE_PATH_CONTENT_MUSIC_HISTORY,
    FILE_PATH_CONTENT_VIDEO_HISTORY,
diff --git a/file_path_str.c b/file_path_str.c
index 30aa0ff79e..46aad7605f 100644
--- a/file_path_str.c
+++ b/file_path_str.c
@@ -200,9 +200,6 @@ const char *file_path_str(enum file_path_enum enum_idx)
       case FILE_PATH_CONTENT_HISTORY:
          str = "content_history.lpl";
          break;
-      case FILE_PATH_CONTENT_RUNTIME:
-         str = "content_runtime.lpl";
-         break;
       case FILE_PATH_CONTENT_FAVORITES:
          str = "content_favorites.lpl";
          break;
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index 0ae886806a..daf0544f1e 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -8334,3 +8334,7 @@ MSG_HASH(
     MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME,
     "Play Time:"
     )
+MSG_HASH(
+    MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED,
+    "Last Played:"
+    )
diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c
index 151c823fe9..7071270c82 100644
--- a/menu/cbs/menu_cbs_sublabel.c
+++ b/menu/cbs/menu_cbs_sublabel.c
@@ -826,6 +826,12 @@ static int action_bind_sublabel_playlist_entry(
    unsigned runtime_hours = 0;
    unsigned runtime_minutes = 0;
    unsigned runtime_seconds = 0;
+   unsigned last_played_year = 0;
+   unsigned last_played_month = 0;
+   unsigned last_played_day = 0;
+   unsigned last_played_hour = 0;
+   unsigned last_played_minute = 0;
+   unsigned last_played_second = 0;
    
    if (!settings->bools.playlist_show_sublabels)
       return 0;
@@ -836,7 +842,7 @@ static int action_bind_sublabel_playlist_entry(
       return 0;
    if (i >= playlist_get_size(playlist))
       return 0;
-
+   
    /* Read playlist entry */
    playlist_get_index(playlist, i, NULL, NULL, NULL, &core_name, NULL, NULL);
    
@@ -849,43 +855,56 @@ static int action_bind_sublabel_playlist_entry(
       msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_CORE),
       core_name);
    
-   /* Get runtime *if* 'content_runtime_log' is enabled
-    * NB: Runtime is currently stored in an independent
-    * 'content_runtime.lpl' file, similar to the content
-    * history. It therefore only really makes sense to
-    * check runtime when viewing the content history
-    * playlist. If runtime were added to all playlists
-    * (would be nice), we could do this trivially for all
-    * content. */
+   /* Get runtime info *if* runtime logging is enabled
+    * *and* this is a valid playlist type */
    if (!settings->bools.content_runtime_log)
       return 0;
    
-   if (!string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY))
-         && !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_HISTORY_TAB)))
+   /* Note: This looks heavy, but each string_is_equal() call will
+    * return almost immediately */
+   if (!string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY)) &&
+       !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_HISTORY_TAB)) &&
+       !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_FAVORITES_LIST)) &&
+       !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES_TAB)) &&
+       !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_PLAYLIST_LIST)) &&
+       !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_HORIZONTAL_MENU)))
       return 0;
    
    /* Any available runtime values are now copied to the content
     * history playlist when it is parsed by menu_displaylist, so
     * we can extract them directly via index */
    playlist_get_runtime_index(playlist, i, NULL, NULL,
-            &runtime_hours, &runtime_minutes, &runtime_seconds);
+         &runtime_hours, &runtime_minutes, &runtime_seconds,
+         &last_played_year, &last_played_month, &last_played_day,
+         &last_played_hour, &last_played_minute, &last_played_second);
    
+   /* Check whether a non-zero runtime has been recorded */
    if ((runtime_hours > 0) || (runtime_minutes > 0) || (runtime_seconds > 0))
    {
       int n = 0;
       char tmp[64];
-      tmp[0] = '\0';
       
+      /* Runtime label */
+      tmp[0] = '\0';
       n = snprintf(tmp, sizeof(tmp), "\n%s %02u:%02u:%02u",
          msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME),
          runtime_hours, runtime_minutes, runtime_seconds);
       
-      /* Stupid nonsense... GCC will generate warnings if we
-       * don't do something here... */
       if ((n < 0) || (n >= 64))
-      {
-         n = 0;
-      }
+         n = 0; /* Silence GCC warnings... */
+      
+      if (!string_is_empty(tmp))
+         strlcat(s, tmp, len);
+      
+      /* Last played label */
+      tmp[0] = '\0';
+      n = snprintf(tmp, sizeof(tmp), "\n%s %04u/%02u/%02u - %02u:%02u:%02u",
+         msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED),
+         last_played_year, last_played_month, last_played_day,
+         last_played_hour, last_played_minute, last_played_second);
+      
+      if ((n < 0) || (n >= 64))
+         n = 0; /* Silence GCC warnings... */
       
       if (!string_is_empty(tmp))
          strlcat(s, tmp, len);
diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c
index dfa7a46402..b25fcc6346 100644
--- a/menu/menu_displaylist.c
+++ b/menu/menu_displaylist.c
@@ -93,6 +93,7 @@
 #include "../wifi/wifi_driver.h"
 #include "../tasks/tasks_internal.h"
 #include "../dynamic.h"
+#include "../runtime_file.h"
 
 static char new_path_entry[4096]        = {0};
 static char new_lbl_entry[4096]         = {0};
@@ -1325,7 +1326,7 @@ static int menu_displaylist_parse_playlist(menu_displaylist_info_t *info,
    size_t list_size = playlist_size(playlist);
    settings_t *settings = config_get_ptr();
    bool is_rgui = string_is_equal(settings->arrays.menu_driver, "rgui");
-   bool get_runtime = string_is_equal(path_playlist, "history") && g_defaults.content_runtime && settings->bools.content_runtime_log;
+   bool get_runtime = false;
    char label_spacer[PL_LABEL_SPACER_MAXLEN];
 
    label_spacer[0] = '\0';
@@ -1333,6 +1334,16 @@ static int menu_displaylist_parse_playlist(menu_displaylist_info_t *info,
    if (list_size == 0)
       goto error;
 
+   /* Check whether runtime logging info should be parsed */
+   if (settings->bools.content_runtime_log)
+   {
+      /* Runtime logging is valid for every type of playlist *apart from*
+       * images/music/video history */
+      get_runtime = !(string_is_equal(path_playlist, "images_history") ||
+                      string_is_equal(path_playlist, "music_history")  ||
+                      string_is_equal(path_playlist, "video_history"));
+   }
+
    /* Get spacer for menu entry labels (<content><spacer><core>) */
    if (is_rgui)
       strlcpy(label_spacer, PL_LABEL_SPACER_RGUI, sizeof(label_spacer));
@@ -1368,32 +1379,46 @@ static int menu_displaylist_parse_playlist(menu_displaylist_info_t *info,
       playlist_get_index(playlist, i,
             &path, &label, &core_path, &core_name, NULL, NULL);
 
-      /* If this is the content history playlist and runtime logging
-       * is enabled, extract any available runtime values */
+      /* Extract any available runtime values, if required */
       if (get_runtime)
       {
-         unsigned j;
+         runtime_log_t *runtime_log = NULL;
+         runtime_log = runtime_log_init(path, core_path);
 
-         /* Search 'content_runtime.lpl' until we find the current
-          * content+core combo */
-         for (j = 0; j < playlist_get_size(g_defaults.content_runtime); j++)
+         if (runtime_log)
          {
-            const char *runtime_path = NULL;
-            const char *runtime_core_path = NULL;
-            unsigned runtime_hours;
-            unsigned runtime_minutes;
-            unsigned runtime_seconds;
-
-            playlist_get_runtime_index(g_defaults.content_runtime, j, &runtime_path, &runtime_core_path,
-               &runtime_hours, &runtime_minutes, &runtime_seconds);
-
-            if (string_is_equal(path, runtime_path) && string_is_equal(core_path, runtime_core_path))
+            /* Check whether a non-zero runtime has been recorded */
+            if (runtime_log_has_runtime(runtime_log))
             {
-               playlist_update_runtime(playlist, i, NULL, NULL,
-                  runtime_hours, runtime_minutes, runtime_seconds);
+               unsigned runtime_hours;
+               unsigned runtime_minutes;
+               unsigned runtime_seconds;
+               unsigned last_played_year;
+               unsigned last_played_month;
+               unsigned last_played_day;
+               unsigned last_played_hour;
+               unsigned last_played_minute;
+               unsigned last_played_second;
 
-               break;
+               /* Read current runtime */
+               runtime_log_get_runtime_hms(runtime_log,
+                     &runtime_hours, &runtime_minutes, &runtime_seconds);
+
+               /* Read last played timestamp */
+               runtime_log_get_last_played(runtime_log,
+                     &last_played_year, &last_played_month, &last_played_day,
+                     &last_played_hour, &last_played_minute, &last_played_second);
+
+               /* Update playlist entry */
+               playlist_update_runtime(playlist, i, NULL, NULL,
+                     runtime_hours, runtime_minutes, runtime_seconds,
+                     last_played_year, last_played_month, last_played_day,
+                     last_played_hour, last_played_minute, last_played_second,
+                     false);
             }
+
+            /* Clean up */
+            free(runtime_log);
          }
       }
 
diff --git a/msg_hash.h b/msg_hash.h
index fb033dc476..91867422b8 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -2283,6 +2283,7 @@ enum msg_hash_enums
 
    MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_CORE,
    MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME,
+   MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED,
 
    MSG_LAST
 };
diff --git a/playlist.c b/playlist.c
index 5b50a9456f..a4ec675a66 100644
--- a/playlist.c
+++ b/playlist.c
@@ -48,6 +48,15 @@ struct playlist_entry
    unsigned runtime_hours;
    unsigned runtime_minutes;
    unsigned runtime_seconds;
+   /* Note: due to platform dependence, have to record
+    * timestamp as either a string or independent integer
+    * values. The latter is more verbose, but more efficient. */
+   unsigned last_played_year;
+   unsigned last_played_month;
+   unsigned last_played_day;
+   unsigned last_played_hour;
+   unsigned last_played_minute;
+   unsigned last_played_second;
 };
 
 struct content_playlist
@@ -133,8 +142,9 @@ void playlist_get_index(playlist_t *playlist,
 void playlist_get_runtime_index(playlist_t *playlist,
       size_t idx,
       const char **path, const char **core_path,
-      unsigned *runtime_hours, unsigned *runtime_minutes,
-      unsigned *runtime_seconds)
+      unsigned *runtime_hours, unsigned *runtime_minutes, unsigned *runtime_seconds,
+      unsigned *last_played_year, unsigned *last_played_month, unsigned *last_played_day,
+      unsigned *last_played_hour, unsigned *last_played_minute, unsigned *last_played_second)
 {
    if (!playlist)
       return;
@@ -149,6 +159,18 @@ void playlist_get_runtime_index(playlist_t *playlist,
       *runtime_minutes = playlist->entries[idx].runtime_minutes;
    if (runtime_seconds)
       *runtime_seconds = playlist->entries[idx].runtime_seconds;
+   if (last_played_year)
+      *last_played_year = playlist->entries[idx].last_played_year;
+   if (last_played_month)
+      *last_played_month = playlist->entries[idx].last_played_month;
+   if (last_played_day)
+      *last_played_day = playlist->entries[idx].last_played_day;
+   if (last_played_hour)
+      *last_played_hour = playlist->entries[idx].last_played_hour;
+   if (last_played_minute)
+      *last_played_minute = playlist->entries[idx].last_played_minute;
+   if (last_played_second)
+      *last_played_second = playlist->entries[idx].last_played_second;
 }
 
 /**
@@ -252,6 +274,12 @@ static void playlist_free_entry(struct playlist_entry *entry)
    entry->runtime_hours = 0;
    entry->runtime_minutes = 0;
    entry->runtime_seconds = 0;
+   entry->last_played_year = 0;
+   entry->last_played_month = 0;
+   entry->last_played_day = 0;
+   entry->last_played_hour = 0;
+   entry->last_played_minute = 0;
+   entry->last_played_second = 0;
 }
 
 void playlist_update(playlist_t *playlist, size_t idx,
@@ -319,8 +347,10 @@ void playlist_update(playlist_t *playlist, size_t idx,
 
 void playlist_update_runtime(playlist_t *playlist, size_t idx,
       const char *path, const char *core_path,
-      unsigned runtime_hours, unsigned runtime_minutes,
-      unsigned runtime_seconds)
+      unsigned runtime_hours, unsigned runtime_minutes, unsigned runtime_seconds,
+      unsigned last_played_year, unsigned last_played_month, unsigned last_played_day,
+      unsigned last_played_hour, unsigned last_played_minute, unsigned last_played_second,
+      bool register_update)
 {
    struct playlist_entry *entry = NULL;
 
@@ -334,7 +364,7 @@ void playlist_update_runtime(playlist_t *playlist, size_t idx,
       if (entry->path != NULL)
          free(entry->path);
       entry->path        = strdup(path);
-      playlist->modified = true;
+      playlist->modified = playlist->modified || register_update;
    }
 
    if (core_path && (core_path != entry->core_path))
@@ -343,32 +373,69 @@ void playlist_update_runtime(playlist_t *playlist, size_t idx,
          free(entry->core_path);
       entry->core_path   = NULL;
       entry->core_path   = strdup(core_path);
-      playlist->modified = true;
+      playlist->modified = playlist->modified || register_update;
    }
 
    if (runtime_hours != entry->runtime_hours)
    {
       entry->runtime_hours = runtime_hours;
-      playlist->modified = true;
+      playlist->modified = playlist->modified || register_update;
    }
 
    if (runtime_minutes != entry->runtime_minutes)
    {
       entry->runtime_minutes = runtime_minutes;
-      playlist->modified = true;
+      playlist->modified = playlist->modified || register_update;
    }
 
    if (runtime_seconds != entry->runtime_seconds)
    {
       entry->runtime_seconds = runtime_seconds;
-      playlist->modified = true;
+      playlist->modified = playlist->modified || register_update;
+   }
+
+   if (last_played_year != entry->last_played_year)
+   {
+      entry->last_played_year = last_played_year;
+      playlist->modified = playlist->modified || register_update;
+   }
+
+   if (last_played_month != entry->last_played_month)
+   {
+      entry->last_played_month = last_played_month;
+      playlist->modified = playlist->modified || register_update;
+   }
+
+   if (last_played_day != entry->last_played_day)
+   {
+      entry->last_played_day = last_played_day;
+      playlist->modified = playlist->modified || register_update;
+   }
+
+   if (last_played_hour != entry->last_played_hour)
+   {
+      entry->last_played_hour = last_played_hour;
+      playlist->modified = playlist->modified || register_update;
+   }
+
+   if (last_played_minute != entry->last_played_minute)
+   {
+      entry->last_played_minute = last_played_minute;
+      playlist->modified = playlist->modified || register_update;
+   }
+
+   if (last_played_second != entry->last_played_second)
+   {
+      entry->last_played_second = last_played_second;
+      playlist->modified = playlist->modified || register_update;
    }
 }
 
 bool playlist_push_runtime(playlist_t *playlist,
       const char *path, const char *core_path,
-      unsigned runtime_hours, unsigned runtime_minutes,
-      unsigned runtime_seconds)
+      unsigned runtime_hours, unsigned runtime_minutes, unsigned runtime_seconds,
+      unsigned last_played_year, unsigned last_played_month, unsigned last_played_day,
+      unsigned last_played_hour, unsigned last_played_minute, unsigned last_played_second)
 {
    size_t i;
    bool core_path_empty = string_is_empty(core_path);
@@ -447,6 +514,12 @@ bool playlist_push_runtime(playlist_t *playlist,
       playlist->entries[0].runtime_hours = runtime_hours;
       playlist->entries[0].runtime_minutes = runtime_minutes;
       playlist->entries[0].runtime_seconds = runtime_seconds;
+      playlist->entries[0].last_played_year = last_played_year;
+      playlist->entries[0].last_played_month = last_played_month;
+      playlist->entries[0].last_played_day = last_played_day;
+      playlist->entries[0].last_played_hour = last_played_hour;
+      playlist->entries[0].last_played_minute = last_played_minute;
+      playlist->entries[0].last_played_second = last_played_second;
    }
 
    playlist->size++;
@@ -549,15 +622,21 @@ bool playlist_push(playlist_t *playlist,
       memmove(playlist->entries + 1, playlist->entries,
             (playlist->cap - 1) * sizeof(struct playlist_entry));
 
-      playlist->entries[0].path            = NULL;
-      playlist->entries[0].label           = NULL;
-      playlist->entries[0].core_path       = NULL;
-      playlist->entries[0].core_name       = NULL;
-      playlist->entries[0].db_name         = NULL;
-      playlist->entries[0].crc32           = NULL;
-      playlist->entries[0].runtime_hours   = 0;
-      playlist->entries[0].runtime_minutes = 0;
-      playlist->entries[0].runtime_seconds = 0;
+      playlist->entries[0].path               = NULL;
+      playlist->entries[0].label              = NULL;
+      playlist->entries[0].core_path          = NULL;
+      playlist->entries[0].core_name          = NULL;
+      playlist->entries[0].db_name            = NULL;
+      playlist->entries[0].crc32              = NULL;
+      playlist->entries[0].runtime_hours      = 0;
+      playlist->entries[0].runtime_minutes    = 0;
+      playlist->entries[0].runtime_seconds    = 0;
+      playlist->entries[0].last_played_year   = 0;
+      playlist->entries[0].last_played_month  = 0;
+      playlist->entries[0].last_played_day    = 0;
+      playlist->entries[0].last_played_hour   = 0;
+      playlist->entries[0].last_played_minute = 0;
+      playlist->entries[0].last_played_second = 0;
       if (!string_is_empty(path))
          playlist->entries[0].path      = strdup(path);
       if (!string_is_empty(label))
@@ -711,6 +790,78 @@ void playlist_write_runtime_file(playlist_t *playlist)
          JSON_Writer_WriteColon(context.writer);
          JSON_Writer_WriteSpace(context.writer, 1);
          JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
+         JSON_Writer_WriteComma(context.writer);
+         JSON_Writer_WriteNewLine(context.writer);
+
+         memset(tmp, 0, sizeof(tmp));
+
+         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_year);
+
+         JSON_Writer_WriteSpace(context.writer, 6);
+         JSON_Writer_WriteString(context.writer, "last_played_year", strlen("last_played_year"), JSON_UTF8);
+         JSON_Writer_WriteColon(context.writer);
+         JSON_Writer_WriteSpace(context.writer, 1);
+         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
+         JSON_Writer_WriteComma(context.writer);
+         JSON_Writer_WriteNewLine(context.writer);
+
+         memset(tmp, 0, sizeof(tmp));
+
+         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_month);
+
+         JSON_Writer_WriteSpace(context.writer, 6);
+         JSON_Writer_WriteString(context.writer, "last_played_month", strlen("last_played_month"), JSON_UTF8);
+         JSON_Writer_WriteColon(context.writer);
+         JSON_Writer_WriteSpace(context.writer, 1);
+         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
+         JSON_Writer_WriteComma(context.writer);
+         JSON_Writer_WriteNewLine(context.writer);
+
+         memset(tmp, 0, sizeof(tmp));
+
+         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_day);
+
+         JSON_Writer_WriteSpace(context.writer, 6);
+         JSON_Writer_WriteString(context.writer, "last_played_day", strlen("last_played_day"), JSON_UTF8);
+         JSON_Writer_WriteColon(context.writer);
+         JSON_Writer_WriteSpace(context.writer, 1);
+         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
+         JSON_Writer_WriteComma(context.writer);
+         JSON_Writer_WriteNewLine(context.writer);
+
+         memset(tmp, 0, sizeof(tmp));
+
+         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_hour);
+
+         JSON_Writer_WriteSpace(context.writer, 6);
+         JSON_Writer_WriteString(context.writer, "last_played_hour", strlen("last_played_hour"), JSON_UTF8);
+         JSON_Writer_WriteColon(context.writer);
+         JSON_Writer_WriteSpace(context.writer, 1);
+         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
+         JSON_Writer_WriteComma(context.writer);
+         JSON_Writer_WriteNewLine(context.writer);
+
+         memset(tmp, 0, sizeof(tmp));
+
+         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_minute);
+
+         JSON_Writer_WriteSpace(context.writer, 6);
+         JSON_Writer_WriteString(context.writer, "last_played_minute", strlen("last_played_minute"), JSON_UTF8);
+         JSON_Writer_WriteColon(context.writer);
+         JSON_Writer_WriteSpace(context.writer, 1);
+         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
+         JSON_Writer_WriteComma(context.writer);
+         JSON_Writer_WriteNewLine(context.writer);
+
+         memset(tmp, 0, sizeof(tmp));
+
+         snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_second);
+
+         JSON_Writer_WriteSpace(context.writer, 6);
+         JSON_Writer_WriteString(context.writer, "last_played_second", strlen("last_played_second"), JSON_UTF8);
+         JSON_Writer_WriteColon(context.writer);
+         JSON_Writer_WriteSpace(context.writer, 1);
+         JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8);
          JSON_Writer_WriteNewLine(context.writer);
       }
 
@@ -1148,6 +1299,18 @@ static JSON_Parser_HandlerResult JSONObjectMemberHandler(JSON_Parser parser, cha
                pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_minutes;
             else if (string_is_equal(pValue, "runtime_seconds"))
                pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_seconds;
+            else if (string_is_equal(pValue, "last_played_year"))
+               pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year;
+            else if (string_is_equal(pValue, "last_played_month"))
+               pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month;
+            else if (string_is_equal(pValue, "last_played_day"))
+               pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_day;
+            else if (string_is_equal(pValue, "last_played_hour"))
+               pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_hour;
+            else if (string_is_equal(pValue, "last_played_minute"))
+               pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_minute;
+            else if (string_is_equal(pValue, "last_played_second"))
+               pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second;
             else
             {
                /* ignore unknown members */
diff --git a/playlist.h b/playlist.h
index 62e3c2c607..c22b07cd4e 100644
--- a/playlist.h
+++ b/playlist.h
@@ -82,8 +82,9 @@ void playlist_get_index(playlist_t *playlist,
 void playlist_get_runtime_index(playlist_t *playlist,
       size_t idx,
       const char **path, const char **core_path,
-      unsigned *runtime_hours, unsigned *runtime_minutes,
-      unsigned *runtime_seconds);
+      unsigned *runtime_hours, unsigned *runtime_minutes, unsigned *runtime_seconds,
+      unsigned *last_played_year, unsigned *last_played_month, unsigned *last_played_day,
+      unsigned *last_played_hour, unsigned *last_played_minute, unsigned *last_played_second);
 
 /**
  * playlist_delete_index:
@@ -112,8 +113,9 @@ bool playlist_push(playlist_t *playlist,
 
 bool playlist_push_runtime(playlist_t *playlist,
       const char *path, const char *core_path,
-      unsigned runtime_hours, unsigned runtime_minutes,
-      unsigned runtime_seconds);
+      unsigned runtime_hours, unsigned runtime_minutes, unsigned runtime_seconds,
+      unsigned last_played_year, unsigned last_played_month, unsigned last_played_day,
+      unsigned last_played_hour, unsigned last_played_minute, unsigned last_played_second);
 
 void playlist_update(playlist_t *playlist, size_t idx,
       const char *path, const char *label,
@@ -121,10 +123,17 @@ void playlist_update(playlist_t *playlist, size_t idx,
       const char *crc32,
       const char *db_name);
 
+/* Note: register_update determines whether the internal
+ * 'playlist->modified' flag is set when updating runtime
+ * values. Since these are normally set temporarily (for
+ * display purposes), we do not always want this function
+ * to trigger a re-write of the playlist file. */
 void playlist_update_runtime(playlist_t *playlist, size_t idx,
       const char *path, const char *core_path,
-      unsigned runtime_hours, unsigned runtime_minutes,
-      unsigned runtime_seconds);
+      unsigned runtime_hours, unsigned runtime_minutes, unsigned runtime_seconds,
+      unsigned last_played_year, unsigned last_played_month, unsigned last_played_day,
+      unsigned last_played_hour, unsigned last_played_minute, unsigned last_played_second,
+      bool register_update);
 
 void playlist_get_index_by_path(playlist_t *playlist,
       const char *search_path,
diff --git a/retroarch.c b/retroarch.c
index 8acf0156df..fc4ed11df7 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -108,8 +108,6 @@
 #include "file_path_special.h"
 #include "ui/ui_companion_driver.h"
 #include "verbosity.h"
-#include "defaults.h"
-#include "playlist.h"
 
 #include "frontend/frontend_driver.h"
 #include "audio/audio_driver.h"
@@ -2404,91 +2402,27 @@ bool rarch_ctl(enum rarch_ctl_state state, void *data)
             n = 0; /* Just silence any potential gcc warnings... */
          RARCH_LOG(log);
 
-         if (settings->bools.content_runtime_log)
+         /* Only write to file if logging is enabled *and* content has run
+          * for a non-zero length of time */
+         if (settings->bools.content_runtime_log && libretro_core_runtime_usec > 0)
          {
-            const char *content_path = path_get(RARCH_PATH_CONTENT);
-            const char *core_path = path_get(RARCH_PATH_CORE);
+            runtime_log_t *runtime_log = NULL;
 
-            if (!string_is_empty(content_path) && !string_is_empty(core_path) && !string_is_equal(core_path, "builtin"))
+            /* Initialise runtime log file */
+            runtime_log = runtime_log_init(path_get(RARCH_PATH_CONTENT), path_get(RARCH_PATH_CORE));
+            if (runtime_log)
             {
-               unsigned playlist_hours = 0;
-               unsigned playlist_minutes = 0;
-               unsigned playlist_seconds = 0;
-               runtime_log_t *runtime_log = NULL;
-               bool playlist_file_is_valid = false;
-               bool runtime_log_file_is_valid = false;
+               /* Add additional runtime */
+               runtime_log_add_runtime_usec(runtime_log, libretro_core_runtime_usec);
 
-               /* Intialise content_runtime playlist entry and get
-                * existing values */
-               if (g_defaults.content_runtime)
-               {
-                  /* Push current entry to the top (does not update runtime
-                   * values), or create new entry if it does not already exist */
-                  playlist_push_runtime(g_defaults.content_runtime, content_path, core_path, 0, 0, 0);
+               /* Update 'last played' entry */
+               runtime_log_set_last_played_now(runtime_log);
 
-                  /* Get current runtime */
-                  if (playlist_get_size(g_defaults.content_runtime) > 0)
-                  {
-                     playlist_get_runtime_index(g_defaults.content_runtime, 0, NULL, NULL,
-                        &playlist_hours, &playlist_minutes, &playlist_seconds);
+               /* Save runtime log file */
+               runtime_log_save(runtime_log);
 
-                     playlist_file_is_valid = true;
-                  }
-               }
-
-               /* Initialise runtime log file */
-               runtime_log = runtime_log_init(content_path, core_path);
-               if (runtime_log)
-               {
-                  /* If runtime log file is empty, populate it with values
-                   * from content_runtime playlist */
-                  if (!runtime_log_has_runtime(runtime_log))
-                  {
-                     runtime_log_set_runtime_hms(runtime_log,
-                           playlist_hours, playlist_minutes, playlist_seconds);
-                  }
-
-                  /* Add additional runtime */
-                  runtime_log_add_runtime_usec(runtime_log, libretro_core_runtime_usec);
-
-                  /* Read back current runtime, so we can copy it
-                   * to content_runtime playlist */
-                  runtime_log_get_runtime_hms(runtime_log,
-                        &playlist_hours, &playlist_minutes, &playlist_seconds);
-
-                  /* Update 'last played' entry */
-                  runtime_log_set_last_played_now(runtime_log);
-
-                  /* Save runtime log file */
-                  runtime_log_save(runtime_log);
-
-                  /* Clean up */
-                  free(runtime_log);
-
-                  runtime_log_file_is_valid = true;
-               }
-
-               /* Update content_runtime playlist */
-               if (playlist_file_is_valid)
-               {
-                  /* If something went wrong with the runtime log
-                   * file (can't happen...), then playlist_hours/minutes/seconds
-                   * still contains original (old) values. Have to update them
-                   * manually... */
-                  if (!runtime_log_file_is_valid)
-                  {
-                     retro_time_t usec_old;
-
-                     runtime_log_convert_hms2usec(
-                           playlist_hours, playlist_minutes, playlist_seconds, &usec_old);
-
-                     runtime_log_convert_usec2hms(usec_old + libretro_core_runtime_usec,
-                           &playlist_hours, &playlist_minutes, &playlist_seconds);
-                  }
-
-                  playlist_update_runtime(g_defaults.content_runtime, 0, content_path, core_path,
-                           playlist_hours, playlist_minutes, playlist_seconds);
-               }
+               /* Clean up */
+               free(runtime_log);
             }
          }