Ver código fonte

remove the playlist

ZengGengSen 1 ano atrás
pai
commit
22a70d11d3

+ 1 - 1
app/build.gradle

@@ -15,7 +15,7 @@ android {
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 
         ndk{
-            abiFilters  "armeabi-v7a","x86_64","arm64-v8a"
+            abiFilters  "armeabi-v7a"
         }
     }
 

+ 4 - 0
app/src/main/cpp/defaults.h

@@ -23,8 +23,10 @@
 #include <retro_miscellaneous.h>
 
 #ifndef IS_SALAMANDER
+#ifdef HAVE_PLAYLIST
 #include "playlist.h"
 #endif
+#endif
 
 enum default_dirs
 {
@@ -70,6 +72,7 @@ enum default_dirs
 
 struct defaults
 {
+#ifdef HAVE_PLAYLIST
 #ifndef IS_SALAMANDER
    playlist_t *content_history;
    playlist_t *content_favorites;
@@ -80,6 +83,7 @@ struct defaults
 #if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
    playlist_t *video_history;
 #endif
+#endif
 #endif
    int settings_out_latency;
 #ifdef HAVE_MENU

+ 0 - 1053
app/src/main/cpp/gfx/gfx_thumbnail.c

@@ -1,1053 +0,0 @@
-/* Copyright  (C) 2010-2019 The RetroArch team
- *
- * ---------------------------------------------------------------------------------------
- * The following license statement only applies to this file (gfx_thumbnail.c).
- * ---------------------------------------------------------------------------------------
- *
- * Permission is hereby granted, free of charge,
- * to any person obtaining a copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include <features/features_cpu.h>
-#include <file/file_path.h>
-#include <string/stdstring.h>
-
-#include "gfx_display.h"
-#include "gfx_animation.h"
-
-#include "gfx_thumbnail.h"
-
-#include "../tasks/tasks_internal.h"
-
-#define DEFAULT_GFX_THUMBNAIL_STREAM_DELAY  83.333333f
-#define DEFAULT_GFX_THUMBNAIL_FADE_DURATION 166.66667f
-
-/* Utility structure, sent as userdata when pushing
- * an image load */
-typedef struct
-{
-   uint64_t list_id;
-   gfx_thumbnail_t *thumbnail;
-} gfx_thumbnail_tag_t;
-
-static gfx_thumbnail_state_t gfx_thumb_st = {0}; /* uint64_t alignment */
-
-gfx_thumbnail_state_t *gfx_thumb_get_ptr(void)
-{
-   return &gfx_thumb_st;
-}
-
-/* Setters */
-
-/* When streaming thumbnails, sets time in ms that an
- * entry must be on screen before an image load is
- * requested
- * > if 'delay' is negative, default value is set */
-void gfx_thumbnail_set_stream_delay(float delay)
-{
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-
-   p_gfx_thumb->stream_delay = (delay >= 0.0f) ?
-         delay : DEFAULT_GFX_THUMBNAIL_STREAM_DELAY;
-}
-
-/* Sets duration in ms of the thumbnail 'fade in'
- * animation
- * > If 'duration' is negative, default value is set */
-void gfx_thumbnail_set_fade_duration(float duration)
-{
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-
-   p_gfx_thumb->fade_duration = (duration >= 0.0f) ?
-         duration : DEFAULT_GFX_THUMBNAIL_FADE_DURATION;
-}
-
-/* Specifies whether 'fade in' animation should be
- * triggered for missing thumbnails
- * > When 'true', allows menu driver to animate
- *   any 'thumbnail unavailable' notifications */
-void gfx_thumbnail_set_fade_missing(bool fade_missing)
-{
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-
-   p_gfx_thumb->fade_missing = fade_missing;
-}
-
-/* Callbacks */
-
-/* Fade animation callback - simply resets thumbnail
- * 'fade_active' status */
-static void gfx_thumbnail_fade_cb(void *userdata)
-{
-   gfx_thumbnail_t *thumbnail = (gfx_thumbnail_t*)userdata;
-   if (thumbnail)
-      thumbnail->flags |= GFX_THUMB_FLAG_FADE_ACTIVE;
-}
-
-/* Initialises thumbnail 'fade in' animation */
-static void gfx_thumbnail_init_fade(
-      gfx_thumbnail_state_t *p_gfx_thumb,
-      gfx_thumbnail_t *thumbnail)
-{
-   /* Sanity check */
-   if (!thumbnail)
-      return;
-
-   /* A 'fade in' animation is triggered if:
-    * - Thumbnail is available
-    * - Thumbnail is missing and 'fade_missing' is enabled */
-   if ((thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE) ||
-       (p_gfx_thumb->fade_missing &&
-            (thumbnail->status == GFX_THUMBNAIL_STATUS_MISSING)))
-   {
-      if (p_gfx_thumb->fade_duration > 0.0f)
-      {
-         gfx_animation_ctx_entry_t animation_entry;
-
-         thumbnail->alpha                 = 0.0f;
-         thumbnail->flags                |= GFX_THUMB_FLAG_FADE_ACTIVE;
-
-         animation_entry.easing_enum      = EASING_OUT_QUAD;
-         animation_entry.tag              = (uintptr_t)&thumbnail->alpha;
-         animation_entry.duration         = p_gfx_thumb->fade_duration;
-         animation_entry.target_value     = 1.0f;
-         animation_entry.subject          = &thumbnail->alpha;
-         animation_entry.cb               = gfx_thumbnail_fade_cb;
-         animation_entry.userdata         = thumbnail;
-
-         gfx_animation_push(&animation_entry);
-      }
-      else
-         thumbnail->alpha = 1.0f;
-   }
-}
-
-/* Used to process thumbnail data following completion
- * of image load task */
-static void gfx_thumbnail_handle_upload(
-      retro_task_t *task, void *task_data, void *user_data, const char *err)
-{
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-   struct texture_image *img          = (struct texture_image*)task_data;
-   gfx_thumbnail_tag_t *thumbnail_tag = (gfx_thumbnail_tag_t*)user_data;
-   bool fade_enabled                  = false;
-
-   /* Sanity check */
-   if (!thumbnail_tag)
-      goto end;
-
-   /* Ensure that we are operating on the correct
-    * thumbnail... */
-   if (thumbnail_tag->list_id != p_gfx_thumb->list_id)
-      goto end;
-
-   /* Only process image if we are waiting for it */
-   if (thumbnail_tag->thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
-      goto end;
-
-   /* Sanity check: if thumbnail already has a texture,
-    * we're in some kind of weird error state - in this
-    * case, the best course of action is to just reset
-    * the thumbnail... */
-   if (thumbnail_tag->thumbnail->texture)
-      gfx_thumbnail_reset(thumbnail_tag->thumbnail);
-
-   /* Set thumbnail 'missing' status by default
-    * (saves a number of checks later) */
-   thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
-
-   /* If we reach this stage, thumbnail 'fade in'
-    * animations should be applied (based on current
-    * thumbnail status and global configuration) */
-   fade_enabled = true;
-
-   /* Check we have a valid image */
-   if (!img || (img->width < 1) || (img->height < 1))
-      goto end;
-
-   /* Upload texture to GPU */
-   if (!video_driver_texture_load(
-            img, TEXTURE_FILTER_MIPMAP_LINEAR,
-            &thumbnail_tag->thumbnail->texture))
-      goto end;
-
-   /* Cache dimensions */
-   thumbnail_tag->thumbnail->width  = img->width;
-   thumbnail_tag->thumbnail->height = img->height;
-
-   /* Update thumbnail status */
-   thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_AVAILABLE;
-
-end:
-   /* Clean up */
-   if (img)
-   {
-      image_texture_free(img);
-      free(img);
-   }
-
-   if (thumbnail_tag)
-   {
-      /* Trigger 'fade in' animation, if required */
-      if (fade_enabled)
-         gfx_thumbnail_init_fade(p_gfx_thumb,
-               thumbnail_tag->thumbnail);
-
-      free(thumbnail_tag);
-   }
-}
-
-/* Core interface */
-
-/* When called, prevents the handling of any pending
- * thumbnail load requests
- * >> **MUST** be called before deleting any gfx_thumbnail_t
- *    objects passed to gfx_thumbnail_request() or
- *    gfx_thumbnail_process_stream(), otherwise
- *    heap-use-after-free errors *will* occur */
-void gfx_thumbnail_cancel_pending_requests(void)
-{
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-
-   p_gfx_thumb->list_id++;
-}
-
-/* Requests loading of the specified thumbnail
- * - If operation fails, 'thumbnail->status' will be set to
- *   GFX_THUMBNAIL_STATUS_MISSING
- * - If operation is successful, 'thumbnail->status' will be
- *   set to GFX_THUMBNAIL_STATUS_PENDING
- * 'thumbnail' will be populated with texture info/metadata
- * once the image load is complete
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- *         and gfx_thumbnail_set_content*()
- * NOTE 2: 'playlist' and 'idx' are only required here for
- *         on-demand thumbnail download support
- *         (an annoyance...) */ 
-void gfx_thumbnail_request(
-      gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id,
-      playlist_t *playlist, size_t idx, gfx_thumbnail_t *thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails)
-{
-   const char *thumbnail_path         = NULL;
-   bool has_thumbnail                 = false;
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-   
-   if (!path_data || !thumbnail)
-      return;
-
-   /* Reset thumbnail, then set 'missing' status by default
-    * (saves a number of checks later) */
-   gfx_thumbnail_reset(thumbnail);
-   thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
-
-   /* Update/extract thumbnail path */
-   if (gfx_thumbnail_is_enabled(path_data, thumbnail_id))
-      if (gfx_thumbnail_update_path(path_data, thumbnail_id))
-         has_thumbnail = gfx_thumbnail_get_path(path_data, thumbnail_id, &thumbnail_path);
-
-   /* Load thumbnail, if required */
-   if (has_thumbnail)
-   {
-      if (path_is_valid(thumbnail_path))
-      {
-         gfx_thumbnail_tag_t *thumbnail_tag =
-               (gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t));
-
-         if (!thumbnail_tag)
-            goto end;
-
-         /* Configure user data */
-         thumbnail_tag->thumbnail = thumbnail;
-         thumbnail_tag->list_id   = p_gfx_thumb->list_id;
-
-         /* Would like to cancel any existing image load tasks
-          * here, but can't see how to do it... */
-         if (task_push_image_load(
-               thumbnail_path, video_driver_supports_rgba(),
-               gfx_thumbnail_upscale_threshold,
-               gfx_thumbnail_handle_upload, thumbnail_tag))
-            thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
-      }
-#ifdef HAVE_NETWORKING
-      /* Handle on demand thumbnail downloads */
-      else if (network_on_demand_thumbnails)
-      {
-         const char *system                         = NULL;
-         const char *img_name                       = NULL;
-         static char last_img_name[PATH_MAX_LENGTH] = {0};
-
-         if (!playlist)
-            goto end;
-
-         /* Get current image name */
-         if (!gfx_thumbnail_get_img_name(path_data, &img_name))
-            goto end;
-
-         /* Only trigger a thumbnail download if image
-          * name has changed since the last call of
-          * gfx_thumbnail_request()
-          * > Allows gfx_thumbnail_request() to be used
-          *   for successive right/left thumbnail requests
-          *   with minimal duplication of effort
-          *   (i.e. task_push_pl_entry_thumbnail_download()
-          *   will automatically cancel if a download for the
-          *   existing playlist entry is pending, but the
-          *   checks required for this involve significant
-          *   overheads. We can avoid this entirely with
-          *   a simple string comparison) */
-         if (string_is_equal(img_name, last_img_name))
-            goto end;
-
-         strlcpy(last_img_name, img_name, sizeof(last_img_name));
-
-         /* Get system name */
-         if (!gfx_thumbnail_get_system(path_data, &system))
-            goto end;
-
-         /* Trigger thumbnail download */
-         task_push_pl_entry_thumbnail_download(
-               system, playlist, (unsigned)idx,
-               false, true);
-      }
-#endif
-   }
-
-end:
-   /* Trigger 'fade in' animation, if required */
-   if (thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING)
-      gfx_thumbnail_init_fade(p_gfx_thumb,
-            thumbnail);
-}
-
-/* Requests loading of a specific thumbnail image file
- * (may be used, for example, to load savestate images)
- * - If operation fails, 'thumbnail->status' will be set to
- *   MUI_THUMBNAIL_STATUS_MISSING
- * - If operation is successful, 'thumbnail->status' will be
- *   set to MUI_THUMBNAIL_STATUS_PENDING
- * 'thumbnail' will be populated with texture info/metadata
- * once the image load is complete */
-void gfx_thumbnail_request_file(
-      const char *file_path, gfx_thumbnail_t *thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold)
-{
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-   gfx_thumbnail_tag_t *thumbnail_tag = NULL;
-
-   if (!thumbnail)
-      return;
-
-   /* Reset thumbnail, then set 'missing' status by default
-    * (saves a number of checks later) */
-   gfx_thumbnail_reset(thumbnail);
-   thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
-
-   /* Check if file path is valid */
-   if (   string_is_empty(file_path)
-       || !path_is_valid(file_path))
-      return;
-
-   /* Load thumbnail */
-   if (!(thumbnail_tag = (gfx_thumbnail_tag_t*)malloc(sizeof(gfx_thumbnail_tag_t))))
-      return;
-
-   /* Configure user data */
-   thumbnail_tag->thumbnail = thumbnail;
-   thumbnail_tag->list_id   = p_gfx_thumb->list_id;
-
-   /* Would like to cancel any existing image load tasks
-    * here, but can't see how to do it... */
-   if (task_push_image_load(
-         file_path, video_driver_supports_rgba(),
-         gfx_thumbnail_upscale_threshold,
-         gfx_thumbnail_handle_upload, thumbnail_tag))
-      thumbnail->status = GFX_THUMBNAIL_STATUS_PENDING;
-}
-
-/* Resets (and free()s the current texture of) the
- * specified thumbnail */
-void gfx_thumbnail_reset(gfx_thumbnail_t *thumbnail)
-{
-   if (!thumbnail)
-      return;
-
-   /* Unload texture */
-   if (thumbnail->texture)
-      video_driver_texture_unload(&thumbnail->texture);
-
-   /* Ensure any 'fade in' animation is killed */
-   if (thumbnail->flags & GFX_THUMB_FLAG_FADE_ACTIVE)
-   {
-      uintptr_t tag = (uintptr_t)&thumbnail->alpha;
-      gfx_animation_kill_by_tag(&tag);
-   }
-
-   /* Reset all parameters */
-   thumbnail->status      = GFX_THUMBNAIL_STATUS_UNKNOWN;
-   thumbnail->texture     = 0;
-   thumbnail->width       = 0;
-   thumbnail->height      = 0;
-   thumbnail->alpha       = 0.0f;
-   thumbnail->delay_timer = 0.0f;
-   thumbnail->flags      &= ~(GFX_THUMB_FLAG_FADE_ACTIVE
-                            | GFX_THUMB_FLAG_CORE_ASPECT);
-}
-
-/* Stream processing */
-
-/* Requests loading of the specified thumbnail via
- * the stream interface
- * - Must be called on each frame for the duration
- *   that specified thumbnail is on-screen
- * - Actual load request is deferred by currently
- *   set stream delay
- * - Function becomes a no-op once load request is
- *   made
- * - Thumbnails loaded via this function must be
- *   deleted manually via gfx_thumbnail_reset()
- *   when they move off-screen
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- *         and gfx_thumbnail_set_content*()
- * NOTE 2: 'playlist' and 'idx' are only required here for
- *         on-demand thumbnail download support
- *         (an annoyance...)
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has a *single* thumbnail.
- *         If each entry has two thumbnails, use
- *         gfx_thumbnail_request_streams() for improved
- *         performance */
-void gfx_thumbnail_request_stream(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      enum gfx_thumbnail_id thumbnail_id,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails)
-{
-   gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-
-   /* Only process request if current status
-    * is GFX_THUMBNAIL_STATUS_UNKNOWN */
-   if (!thumbnail ||
-       (thumbnail->status != GFX_THUMBNAIL_STATUS_UNKNOWN))
-      return;
-
-   /* Check if stream delay timer has elapsed */
-   thumbnail->delay_timer += p_anim->delta_time;
-
-   if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
-   {
-      /* Sanity check */
-      if (!path_data)
-      {
-         /* No path information
-          * > Reset thumbnail and set missing status
-          *   to prevent repeated load attempts */
-         gfx_thumbnail_reset(thumbnail);
-         thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
-         thumbnail->alpha  = 1.0f;
-         return;
-      }
-
-      /* Request image load */
-      gfx_thumbnail_request(
-            path_data, thumbnail_id, playlist, idx, thumbnail,
-            gfx_thumbnail_upscale_threshold,
-            network_on_demand_thumbnails);
-   }
-}
-
-/* Requests loading of the specified thumbnails via
- * the stream interface
- * - Must be called on each frame for the duration
- *   that specified thumbnails are on-screen
- * - Actual load request is deferred by currently
- *   set stream delay
- * - Function becomes a no-op once load request is
- *   made
- * - Thumbnails loaded via this function must be
- *   deleted manually via gfx_thumbnail_reset()
- *   when they move off-screen
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- *         and gfx_thumbnail_set_content*()
- * NOTE 2: 'playlist' and 'idx' are only required here for
- *         on-demand thumbnail download support
- *         (an annoyance...)
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has *two* thumbnails.
- *         If each entry only has a single thumbnail, use
- *         gfx_thumbnail_request_stream() for improved
- *         performance */
-void gfx_thumbnail_request_streams(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *right_thumbnail,
-      gfx_thumbnail_t *left_thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails)
-{
-   bool process_right = false;
-   bool process_left  = false;
-
-   if (!right_thumbnail || !left_thumbnail)
-      return;
-
-   /* Only process request if current status
-    * is GFX_THUMBNAIL_STATUS_UNKNOWN */
-   process_right = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
-   process_left  = (left_thumbnail->status  == GFX_THUMBNAIL_STATUS_UNKNOWN);
-
-   if (process_right || process_left)
-   {
-      /* Check if stream delay timer has elapsed */
-      gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-      float delta_time                   = p_anim->delta_time;
-      bool request_right                 = false;
-      bool request_left                  = false;
-
-      if (process_right)
-      {
-         right_thumbnail->delay_timer += delta_time;
-         request_right                 =
-               (right_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
-      }
-
-      if (process_left)
-      {
-         left_thumbnail->delay_timer  += delta_time;
-         request_left                  =
-               (left_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
-      }
-
-      /* Check if one or more thumbnails should be requested */
-      if (request_right || request_left)
-      {
-         /* Sanity check */
-         if (!path_data)
-         {
-            /* No path information
-             * > Reset thumbnail and set missing status
-             *   to prevent repeated load attempts */
-            if (request_right)
-            {
-               gfx_thumbnail_reset(right_thumbnail);
-               right_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
-               right_thumbnail->alpha  = 1.0f;
-            }
-
-            if (request_left)
-            {
-               gfx_thumbnail_reset(left_thumbnail);
-               left_thumbnail->status  = GFX_THUMBNAIL_STATUS_MISSING;
-               left_thumbnail->alpha   = 1.0f;
-            }
-
-            return;
-         }
-
-         /* Request image load */
-         if (request_right)
-            gfx_thumbnail_request(
-                  path_data, GFX_THUMBNAIL_RIGHT, playlist, idx, right_thumbnail,
-                  gfx_thumbnail_upscale_threshold,
-                  network_on_demand_thumbnails);
-
-         if (request_left)
-            gfx_thumbnail_request(
-                  path_data, GFX_THUMBNAIL_LEFT, playlist, idx, left_thumbnail,
-                  gfx_thumbnail_upscale_threshold,
-                  network_on_demand_thumbnails);
-      }
-   }
-}
-
-/* Handles streaming of the specified thumbnail as it moves
- * on/off screen
- * - Must be called each frame for every on-screen entry
- * - Must be called once for each entry as it moves off-screen
- *   (or can be called each frame - overheads are small)
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- * NOTE 2: This function calls gfx_thumbnail_set_content*()
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has a *single* thumbnail.
- *         If each entry has two thumbnails, use
- *         gfx_thumbnail_process_streams() for improved
- *         performance */
-void gfx_thumbnail_process_stream(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      enum gfx_thumbnail_id thumbnail_id,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *thumbnail,
-      bool on_screen,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails)
-{
-   if (!thumbnail)
-      return;
-
-   if (on_screen)
-   {
-      /* Entry is on-screen
-       * > Only process if current status is
-       *   GFX_THUMBNAIL_STATUS_UNKNOWN */
-      if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
-      {
-         gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-
-         /* Check if stream delay timer has elapsed */
-         thumbnail->delay_timer += p_anim->delta_time;
-
-         if (thumbnail->delay_timer > p_gfx_thumb->stream_delay)
-         {
-            /* Update thumbnail content */
-            if (!path_data ||
-                !playlist ||
-                !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
-            {
-               /* Content is invalid
-                * > Reset thumbnail and set missing status */
-               gfx_thumbnail_reset(thumbnail);
-               thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
-               thumbnail->alpha  = 1.0f;
-               return;
-            }
-
-            /* Request image load */
-            gfx_thumbnail_request(
-                  path_data, thumbnail_id, playlist, idx, thumbnail,
-                  gfx_thumbnail_upscale_threshold,
-                  network_on_demand_thumbnails);
-         }
-      }
-   }
-   else
-   {
-      /* Entry is off-screen
-       * > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
-       *   thumbnail is already in a blank state - but we
-       *   must ensure that delay timer is set to zero */
-      if (thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
-         thumbnail->delay_timer = 0.0f;
-      /* In all other cases, reset thumbnail */
-      else
-         gfx_thumbnail_reset(thumbnail);
-   }
-}
-
-/* Handles streaming of the specified thumbnails as they move
- * on/off screen
- * - Must be called each frame for every on-screen entry
- * - Must be called once for each entry as it moves off-screen
- *   (or can be called each frame - overheads are small)
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- * NOTE 2: This function calls gfx_thumbnail_set_content*()
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has *two* thumbnails.
- *         If each entry only has a single thumbnail, use
- *         gfx_thumbnail_process_stream() for improved
- *         performance */
-void gfx_thumbnail_process_streams(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *right_thumbnail,
-      gfx_thumbnail_t *left_thumbnail,
-      bool on_screen,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails)
-{
-   if (!right_thumbnail || !left_thumbnail)
-      return;
-
-   if (on_screen)
-   {
-      /* Entry is on-screen
-       * > Only process if current status is
-       *   GFX_THUMBNAIL_STATUS_UNKNOWN */
-      bool process_right = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN);
-      bool process_left  = (left_thumbnail->status  == GFX_THUMBNAIL_STATUS_UNKNOWN);
-
-      if (process_right || process_left)
-      {
-         /* Check if stream delay timer has elapsed */
-         gfx_thumbnail_state_t *p_gfx_thumb = &gfx_thumb_st;
-         float delta_time                   = p_anim->delta_time;
-         bool request_right                 = false;
-         bool request_left                  = false;
-
-         if (process_right)
-         {
-            right_thumbnail->delay_timer += delta_time;
-            request_right                 =
-                  (right_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
-         }
-
-         if (process_left)
-         {
-            left_thumbnail->delay_timer  += delta_time;
-            request_left                  =
-                  (left_thumbnail->delay_timer > p_gfx_thumb->stream_delay);
-         }
-
-         /* Check if one or more thumbnails should be requested */
-         if (request_right || request_left)
-         {
-            /* Update thumbnail content */
-            if (!path_data ||
-                !playlist ||
-                !gfx_thumbnail_set_content_playlist(path_data, playlist, idx))
-            {
-               /* Content is invalid
-                * > Reset thumbnail and set missing status */
-               if (request_right)
-               {
-                  gfx_thumbnail_reset(right_thumbnail);
-                  right_thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING;
-                  right_thumbnail->alpha  = 1.0f;
-               }
-
-               if (request_left)
-               {
-                  gfx_thumbnail_reset(left_thumbnail);
-                  left_thumbnail->status  = GFX_THUMBNAIL_STATUS_MISSING;
-                  left_thumbnail->alpha   = 1.0f;
-               }
-
-               return;
-            }
-
-            /* Request image load */
-            if (request_right)
-               gfx_thumbnail_request(
-                     path_data, GFX_THUMBNAIL_RIGHT, playlist, idx, right_thumbnail,
-                     gfx_thumbnail_upscale_threshold,
-                     network_on_demand_thumbnails);
-
-            if (request_left)
-               gfx_thumbnail_request(
-                     path_data, GFX_THUMBNAIL_LEFT, playlist, idx, left_thumbnail,
-                     gfx_thumbnail_upscale_threshold,
-                     network_on_demand_thumbnails);
-         }
-      }
-   }
-   else
-   {
-      /* Entry is off-screen
-       * > If status is GFX_THUMBNAIL_STATUS_UNKNOWN,
-       *   thumbnail is already in a blank state - but we
-       *   must ensure that delay timer is set to zero
-       * > In all other cases, reset thumbnail */
-      if (right_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
-         right_thumbnail->delay_timer = 0.0f;
-      else
-         gfx_thumbnail_reset(right_thumbnail);
-
-      if (left_thumbnail->status == GFX_THUMBNAIL_STATUS_UNKNOWN)
-         left_thumbnail->delay_timer = 0.0f;
-      else
-         gfx_thumbnail_reset(left_thumbnail);
-   }
-}
-
-/* Thumbnail rendering */
-
-/* Determines the actual screen dimensions of a
- * thumbnail when centred with aspect correct
- * scaling within a rectangle of (width x height) */
-void gfx_thumbnail_get_draw_dimensions(
-      gfx_thumbnail_t *thumbnail,
-      unsigned width, unsigned height, float scale_factor,
-      float *draw_width, float *draw_height)
-{
-   float core_aspect;
-   float display_aspect;
-   float thumbnail_aspect;
-   video_driver_state_t *video_st = video_state_get_ptr();
-
-   /* Sanity check */
-   if (   !thumbnail 
-       || (width             < 1) 
-       || (height            < 1)
-       || (thumbnail->width  < 1) 
-       || (thumbnail->height < 1))
-   {
-      *draw_width  = 0.0f;
-      *draw_height = 0.0f;
-      return;
-   }
-
-   /* Account for display/thumbnail/core aspect ratio
-    * differences */
-   display_aspect   = (float)width            / (float)height;
-   thumbnail_aspect = (float)thumbnail->width / (float)thumbnail->height;
-   core_aspect      = ((thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT) 
-         && video_st && video_st->av_info.geometry.aspect_ratio > 0)
-               ? video_st->av_info.geometry.aspect_ratio
-               : thumbnail_aspect;
-
-   if (thumbnail_aspect > display_aspect)
-   {
-      *draw_width  = (float)width;
-      *draw_height = (float)thumbnail->height * (*draw_width / (float)thumbnail->width);
-
-      if (thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
-      {
-         *draw_height = *draw_height * (thumbnail_aspect / core_aspect);
-
-         if (*draw_height > height)
-         {
-            *draw_height = (float)height;
-            *draw_width  = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
-            *draw_width  = *draw_width / (thumbnail_aspect / core_aspect);
-         }
-      }
-   }
-   else
-   {
-      *draw_height = (float)height;
-      *draw_width  = (float)thumbnail->width * (*draw_height / (float)thumbnail->height);
-
-      if (thumbnail->flags & GFX_THUMB_FLAG_CORE_ASPECT)
-         *draw_width  = *draw_width / (thumbnail_aspect / core_aspect);
-   }
-
-   /* Account for scale factor
-    * > Side note: We cannot use the gfx_display_ctx_draw_t
-    *   'scale_factor' parameter for scaling thumbnails,
-    *   since this clips off any part of the expanded image
-    *   that extends beyond the bounding box. But even if
-    *   it didn't, we can't get real screen dimensions
-    *   without scaling manually... */
-   *draw_width  *= scale_factor;
-   *draw_height *= scale_factor;
-}
-
-/* Draws specified thumbnail with specified alignment
- * (and aspect correct scaling) within a rectangle of
- * (width x height).
- * 'shadow' defines an optional shadow effect (may be
- * set to NULL if a shadow effect is not required).
- * NOTE: Setting scale_factor > 1.0f will increase the
- *       size of the thumbnail beyond the limits of the
- *       (width x height) rectangle (alignment + aspect
- *       correct scaling is preserved). Use with caution */
-
-void gfx_thumbnail_draw(
-      void *userdata,
-      unsigned video_width,
-      unsigned video_height,
-      gfx_thumbnail_t *thumbnail,
-      float x, float y, unsigned width, unsigned height,
-      enum gfx_thumbnail_alignment alignment,
-      float alpha, float scale_factor,
-      gfx_thumbnail_shadow_t *shadow)
-{
-   gfx_display_t            *p_disp  = disp_get_ptr();
-   gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
-   /* Sanity check */
-   if (
-            !thumbnail
-         || !dispctx
-         || (width         < 1)
-         || (height        < 1)
-         || (alpha        <= 0.0f)
-         || (scale_factor <= 0.0f)
-      )
-      return;
-
-   /* Only draw thumbnail if it is available... */
-   if (thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
-   {
-      gfx_display_ctx_draw_t draw;
-      struct video_coords coords;
-      math_matrix_4x4 mymat;
-      float draw_width;
-      float draw_height;
-      float draw_x;
-      float draw_y;
-      float thumbnail_alpha     = thumbnail->alpha * alpha;
-      float thumbnail_color[16] = {
-         1.0f, 1.0f, 1.0f, 1.0f,
-         1.0f, 1.0f, 1.0f, 1.0f,
-         1.0f, 1.0f, 1.0f, 1.0f,
-         1.0f, 1.0f, 1.0f, 1.0f
-      };
-
-      /* Set thumbnail opacity */
-      if (thumbnail_alpha <= 0.0f)
-         return;
-      if (thumbnail_alpha < 1.0f)
-         gfx_display_set_alpha(thumbnail_color, thumbnail_alpha);
-
-      /* Get thumbnail dimensions */
-      gfx_thumbnail_get_draw_dimensions(
-            thumbnail, width, height, scale_factor,
-            &draw_width, &draw_height);
-
-      if (dispctx->blend_begin)
-         dispctx->blend_begin(userdata);
-
-      if (!p_disp->dispctx->handles_transform)
-      {
-         /* Perform 'rotation' step
-          * > Note that rotation does not actually work...
-          * > It rotates the image all right, but distorts it
-          *   to fit the aspect of the bounding box while clipping
-          *   off any 'corners' that extend beyond the bounding box
-          * > Since the result is visual garbage, we disable
-          *   rotation entirely
-          * > But we still have to call gfx_display_rotate_z(),
-          *   or nothing will be drawn...
-          */
-         float cosine             = 1.0f; /* cos(rad)  = cos(0)  = 1.0f */
-         float sine               = 0.0f; /* sine(rad) = sine(0) = 0.0f */
-         gfx_display_rotate_z(p_disp, &mymat, cosine, sine, userdata);
-      }
-
-      /* Configure draw object
-       * > Note: Colour, width/height and position must
-       *   be set *after* drawing any shadow effects */
-      coords.vertices      = 4;
-      coords.vertex        = NULL;
-      coords.tex_coord     = NULL;
-      coords.lut_tex_coord = NULL;
-
-      draw.scale_factor    = 1.0f;
-      draw.rotation        = 0.0f;
-      draw.coords          = &coords;
-      draw.matrix_data     = &mymat;
-      draw.texture         = thumbnail->texture;
-      draw.prim_type       = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
-      draw.pipeline_id     = 0;
-
-      /* Set thumbnail alignment within bounding box */
-      switch (alignment)
-      {
-         case GFX_THUMBNAIL_ALIGN_TOP:
-            /* Centred horizontally */
-            draw_x = x + ((float)width - draw_width) / 2.0f;
-            /* Drawn at top of bounding box */
-            draw_y = (float)video_height - y - draw_height;
-            break;
-         case GFX_THUMBNAIL_ALIGN_BOTTOM:
-            /* Centred horizontally */
-            draw_x = x + ((float)width - draw_width) / 2.0f;
-            /* Drawn at bottom of bounding box */
-            draw_y = (float)video_height - y - (float)height;
-            break;
-         case GFX_THUMBNAIL_ALIGN_LEFT:
-            /* Drawn at left side of bounding box */
-            draw_x = x;
-            /* Centred vertically */
-            draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
-            break;
-         case GFX_THUMBNAIL_ALIGN_RIGHT:
-            /* Drawn at right side of bounding box */
-            draw_x = x + (float)width - draw_width;
-            /* Centred vertically */
-            draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
-            break;
-         case GFX_THUMBNAIL_ALIGN_CENTRE:
-         default:
-            /* Centred both horizontally and vertically */
-            draw_x = x + ((float)width - draw_width) / 2.0f;
-            draw_y = (float)video_height - y - draw_height - ((float)height - draw_height) / 2.0f;
-            break;
-      }
-
-      /* Draw shadow effect, if required */
-      if (shadow)
-      {
-         /* Sanity check */
-         if ((shadow->type != GFX_THUMBNAIL_SHADOW_NONE) &&
-               (shadow->alpha > 0.0f))
-         {
-            float shadow_width;
-            float shadow_height;
-            float shadow_x;
-            float shadow_y;
-            float shadow_color[16] = {
-               0.0f, 0.0f, 0.0f, 1.0f,
-               0.0f, 0.0f, 0.0f, 1.0f,
-               0.0f, 0.0f, 0.0f, 1.0f,
-               0.0f, 0.0f, 0.0f, 1.0f
-            };
-            float shadow_alpha     = thumbnail_alpha;
-
-            /* Set shadow opacity */
-            if (shadow->alpha < 1.0f)
-               shadow_alpha *= shadow->alpha;
-
-            gfx_display_set_alpha(shadow_color, shadow_alpha);
-
-            /* Configure shadow based on effect type
-             * > Not using a switch() here, since we've
-             *   already eliminated GFX_THUMBNAIL_SHADOW_NONE */
-            if (shadow->type == GFX_THUMBNAIL_SHADOW_OUTLINE)
-            {
-               shadow_width  = draw_width  + (float)(shadow->outline.width * 2);
-               shadow_height = draw_height + (float)(shadow->outline.width * 2);
-               shadow_x      = draw_x - (float)shadow->outline.width;
-               shadow_y      = draw_y - (float)shadow->outline.width;
-            }
-            /* Default: GFX_THUMBNAIL_SHADOW_DROP */
-            else
-            {
-               shadow_width  = draw_width;
-               shadow_height = draw_height;
-               shadow_x      = draw_x + shadow->drop.x_offset;
-               shadow_y      = draw_y - shadow->drop.y_offset;
-            }
-
-            /* Apply shadow draw object configuration */
-            coords.color = (const float*)shadow_color;
-            draw.width   = (unsigned)shadow_width;
-            draw.height  = (unsigned)shadow_height;
-            draw.x       = shadow_x;
-            draw.y       = shadow_y;
-
-            /* Draw shadow */
-            if (draw.height > 0 && draw.width > 0)
-               if (dispctx->draw)
-                  dispctx->draw(&draw, userdata, video_width, video_height);
-         }
-      }
-
-      /* Final thumbnail draw object configuration */
-      coords.color = (const float*)thumbnail_color;
-      draw.width   = (unsigned)draw_width;
-      draw.height  = (unsigned)draw_height;
-      draw.x       = draw_x;
-      draw.y       = draw_y;
-
-      /* Draw thumbnail */
-      if (draw.height > 0 && draw.width > 0)
-         if (dispctx->draw)
-            dispctx->draw(&draw, userdata, video_width, video_height);
-
-      if (dispctx->blend_end)
-         dispctx->blend_end(userdata);
-   }
-}

+ 0 - 337
app/src/main/cpp/gfx/gfx_thumbnail.h

@@ -1,337 +0,0 @@
-/* Copyright  (C) 2010-2019 The RetroArch team
- *
- * ---------------------------------------------------------------------------------------
- * The following license statement only applies to this file (gfx_thumbnail.c).
- * ---------------------------------------------------------------------------------------
- *
- * Permission is hereby granted, free of charge,
- * to any person obtaining a copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#ifndef __GFX_THUMBNAIL_H
-#define __GFX_THUMBNAIL_H
-
-#include <retro_common_api.h>
-#include <libretro.h>
-
-#include <boolean.h>
-
-#include "gfx_animation.h"
-#include "gfx_thumbnail_path.h"
-
-RETRO_BEGIN_DECLS
-
-/* Defines the current status of an entry
- * thumbnail texture */
-enum gfx_thumbnail_status
-{
-   GFX_THUMBNAIL_STATUS_UNKNOWN = 0,
-   GFX_THUMBNAIL_STATUS_PENDING,
-   GFX_THUMBNAIL_STATUS_AVAILABLE,
-   GFX_THUMBNAIL_STATUS_MISSING
-};
-
-/* Defines thumbnail alignment within
- * gfx_thumbnail_draw() bounding box */
-enum gfx_thumbnail_alignment
-{
-   GFX_THUMBNAIL_ALIGN_CENTRE = 0,
-   GFX_THUMBNAIL_ALIGN_TOP,
-   GFX_THUMBNAIL_ALIGN_BOTTOM,
-   GFX_THUMBNAIL_ALIGN_LEFT,
-   GFX_THUMBNAIL_ALIGN_RIGHT
-};
-
-/* Defines all possible thumbnail shadow
- * effect types */
-enum gfx_thumbnail_shadow_type
-{
-   GFX_THUMBNAIL_SHADOW_NONE = 0,
-   GFX_THUMBNAIL_SHADOW_DROP,
-   GFX_THUMBNAIL_SHADOW_OUTLINE
-};
-
-enum gfx_thumbnail_flags
-{
-   GFX_THUMB_FLAG_FADE_ACTIVE = (1 << 0),
-   GFX_THUMB_FLAG_CORE_ASPECT = (1 << 1)
-};
-
-/* Holds all runtime parameters associated with
- * an entry thumbnail */
-typedef struct
-{
-   uintptr_t texture;
-   unsigned width;
-   unsigned height;
-   float alpha;
-   float delay_timer;
-   enum gfx_thumbnail_status status;
-   uint8_t flags;
-} gfx_thumbnail_t;
-
-/* Holds all configuration parameters associated
- * with a thumbnail shadow effect */
-typedef struct
-{
-   struct
-   {
-      unsigned width;
-   } outline;
-   float alpha;
-   struct
-   {
-      float x_offset;
-      float y_offset;
-   } drop;
-   enum gfx_thumbnail_shadow_type type;
-} gfx_thumbnail_shadow_t;
-
-/* Structure containing all gfx_thumbnail
- * variables */
-struct gfx_thumbnail_state
-{
-   /* Due to the asynchronous nature of thumbnail
-    * loading, it is quite possible to trigger a load
-    * then navigate to a different menu list before
-    * the load is complete/handled. As an additional
-    * safety check, we therefore tag the current menu
-    * list with counter value that is incremented whenever
-    * a list is cleared/set. This is sent as userdata when
-    * requesting a thumbnail, and the upload is only
-    * handled if the tag matches the most recent value
-    * at the time when the load completes */
-   uint64_t list_id;
-
-   /* When streaming thumbnails, to minimise the processing
-    * of unnecessary images (i.e. when scrolling rapidly through
-    * playlists), we delay loading until an entry has been on screen
-    * for at least gfx_thumbnail_delay ms */
-   float stream_delay;
-
-   /* Duration in ms of the thumbnail 'fade in' animation */
-   float fade_duration;
-
-   /* When true, 'fade in' animation will also be
-    * triggered for missing thumbnails */
-   bool fade_missing;
-};
-
-typedef struct gfx_thumbnail_state gfx_thumbnail_state_t;
-
-
-/* Setters */
-
-/* When streaming thumbnails, sets time in ms that an
- * entry must be on screen before an image load is
- * requested
- * > if 'delay' is negative, default value is set */
-void gfx_thumbnail_set_stream_delay(float delay);
-
-/* Sets duration in ms of the thumbnail 'fade in'
- * animation
- * > If 'duration' is negative, default value is set */
-void gfx_thumbnail_set_fade_duration(float duration);
-
-/* Specifies whether 'fade in' animation should be
- * triggered for missing thumbnails
- * > When 'true', allows menu driver to animate
- *   any 'thumbnail unavailable' notifications */
-void gfx_thumbnail_set_fade_missing(bool fade_missing);
-
-/* Core interface */
-
-/* When called, prevents the handling of any pending
- * thumbnail load requests
- * >> **MUST** be called before deleting any gfx_thumbnail_t
- *    objects passed to gfx_thumbnail_request() or
- *    gfx_thumbnail_process_stream(), otherwise
- *    heap-use-after-free errors *will* occur */
-void gfx_thumbnail_cancel_pending_requests(void);
-
-/* Requests loading of the specified thumbnail
- * - If operation fails, 'thumbnail->status' will be set to
- *   MUI_THUMBNAIL_STATUS_MISSING
- * - If operation is successful, 'thumbnail->status' will be
- *   set to MUI_THUMBNAIL_STATUS_PENDING
- * 'thumbnail' will be populated with texture info/metadata
- * once the image load is complete
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- *         and gfx_thumbnail_set_content*()
- * NOTE 2: 'playlist' and 'idx' are only required here for
- *         on-demand thumbnail download support
- *         (an annoyance...) */ 
-void gfx_thumbnail_request(
-      gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id,
-      playlist_t *playlist, size_t idx, gfx_thumbnail_t *thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails);
-
-/* Requests loading of a specific thumbnail image file
- * (may be used, for example, to load savestate images)
- * - If operation fails, 'thumbnail->status' will be set to
- *   MUI_THUMBNAIL_STATUS_MISSING
- * - If operation is successful, 'thumbnail->status' will be
- *   set to MUI_THUMBNAIL_STATUS_PENDING
- * 'thumbnail' will be populated with texture info/metadata
- * once the image load is complete */
-void gfx_thumbnail_request_file(
-      const char *file_path, gfx_thumbnail_t *thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold);
-
-/* Resets (and free()s the current texture of) the
- * specified thumbnail */
-void gfx_thumbnail_reset(gfx_thumbnail_t *thumbnail);
-
-/* Stream processing */
-
-/* Requests loading of the specified thumbnail via
- * the stream interface
- * - Must be called on each frame for the duration
- *   that specified thumbnail is on-screen
- * - Actual load request is deferred by currently
- *   set stream delay
- * - Function becomes a no-op once load request is
- *   made
- * - Thumbnails loaded via this function must be
- *   deleted manually via gfx_thumbnail_reset()
- *   when they move off-screen
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- *         and gfx_thumbnail_set_content*()
- * NOTE 2: 'playlist' and 'idx' are only required here for
- *         on-demand thumbnail download support
- *         (an annoyance...)
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has a *single* thumbnail.
- *         If each entry has two thumbnails, use
- *         gfx_thumbnail_request_streams() for improved
- *         performance */
-void gfx_thumbnail_request_stream(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      enum gfx_thumbnail_id thumbnail_id,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails);
-
-/* Requests loading of the specified thumbnails via
- * the stream interface
- * - Must be called on each frame for the duration
- *   that specified thumbnails are on-screen
- * - Actual load request is deferred by currently
- *   set stream delay
- * - Function becomes a no-op once load request is
- *   made
- * - Thumbnails loaded via this function must be
- *   deleted manually via gfx_thumbnail_reset()
- *   when they move off-screen
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- *         and gfx_thumbnail_set_content*()
- * NOTE 2: 'playlist' and 'idx' are only required here for
- *         on-demand thumbnail download support
- *         (an annoyance...)
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has *two* thumbnails.
- *         If each entry only has a single thumbnail, use
- *         gfx_thumbnail_request_stream() for improved
- *         performance */
-void gfx_thumbnail_request_streams(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *right_thumbnail,
-      gfx_thumbnail_t *left_thumbnail,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails);
-
-/* Handles streaming of the specified thumbnail as it moves
- * on/off screen
- * - Must be called each frame for every on-screen entry
- * - Must be called once for each entry as it moves off-screen
- *   (or can be called each frame - overheads are small)
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- * NOTE 2: This function calls gfx_thumbnail_set_content*()
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has a *single* thumbnail.
- *         If each entry has two thumbnails, use
- *         gfx_thumbnail_process_streams() for improved
- *         performance */
-void gfx_thumbnail_process_stream(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      enum gfx_thumbnail_id thumbnail_id,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *thumbnail,
-      bool on_screen,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails);
-
-/* Handles streaming of the specified thumbnails as they move
- * on/off screen
- * - Must be called each frame for every on-screen entry
- * - Must be called once for each entry as it moves off-screen
- *   (or can be called each frame - overheads are small)
- * NOTE 1: Must be called *after* gfx_thumbnail_set_system()
- * NOTE 2: This function calls gfx_thumbnail_set_content*()
- * NOTE 3: This function is intended for use in situations
- *         where each menu entry has *two* thumbnails.
- *         If each entry only has a single thumbnail, use
- *         gfx_thumbnail_process_stream() for improved
- *         performance */
-void gfx_thumbnail_process_streams(
-      gfx_thumbnail_path_data_t *path_data,
-      gfx_animation_t *p_anim,
-      playlist_t *playlist, size_t idx,
-      gfx_thumbnail_t *right_thumbnail,
-      gfx_thumbnail_t *left_thumbnail,
-      bool on_screen,
-      unsigned gfx_thumbnail_upscale_threshold,
-      bool network_on_demand_thumbnails);
-
-/* Thumbnail rendering */
-
-/* Determines the actual screen dimensions of a
- * thumbnail when centred with aspect correct
- * scaling within a rectangle of (width x height) */
-void gfx_thumbnail_get_draw_dimensions(
-      gfx_thumbnail_t *thumbnail,
-      unsigned width, unsigned height, float scale_factor,
-      float *draw_width, float *draw_height);
-
-/* Draws specified thumbnail with specified alignment
- * (and aspect correct scaling) within a rectangle of
- * (width x height).
- * 'shadow' defines an optional shadow effect (may be
- * set to NULL if a shadow effect is not required).
- * NOTE: Setting scale_factor > 1.0f will increase the
- *       size of the thumbnail beyond the limits of the
- *       (width x height) rectangle (alignment + aspect
- *       correct scaling is preserved). Use with caution */
-void gfx_thumbnail_draw(
-      void *userdata,
-      unsigned video_width,
-      unsigned video_height,
-      gfx_thumbnail_t *thumbnail,
-      float x, float y, unsigned width, unsigned height,
-      enum gfx_thumbnail_alignment alignment,
-      float alpha, float scale_factor,
-      gfx_thumbnail_shadow_t *shadow);
-
-gfx_thumbnail_state_t *gfx_thumb_get_ptr(void);
-
-RETRO_END_DECLS
-
-#endif

+ 0 - 830
app/src/main/cpp/gfx/gfx_thumbnail_path.c

@@ -1,830 +0,0 @@
-/* Copyright  (C) 2010-2019 The RetroArch team
- *
- * ---------------------------------------------------------------------------------------
- * The following license statement only applies to this file (gfx_thumbnail_path.c).
- * ---------------------------------------------------------------------------------------
- *
- * Permission is hereby granted, free of charge,
- * to any person obtaining a copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include <string/stdstring.h>
-#include <file/file_path.h>
-#include <lists/file_list.h>
-
-#include "../configuration.h"
-#include "../msg_hash.h"
-#include "../paths.h"
-#include "../file_path_special.h"
-
-#include "gfx_thumbnail_path.h"
-
-/* Used fixed size char arrays here, just to avoid
- * the inconvenience of having to calloc()/free()
- * each individual entry by hand... */
-struct gfx_thumbnail_path_data
-{
-   enum playlist_thumbnail_mode playlist_right_mode;
-   enum playlist_thumbnail_mode playlist_left_mode;
-   size_t playlist_index;
-   char system[PATH_MAX_LENGTH];
-   char content_path[PATH_MAX_LENGTH];
-   char content_label[PATH_MAX_LENGTH];
-   char content_core_name[PATH_MAX_LENGTH];
-   char content_db_name[PATH_MAX_LENGTH];
-   char content_img[PATH_MAX_LENGTH];
-   char right_path[PATH_MAX_LENGTH];
-   char left_path[PATH_MAX_LENGTH];
-};
-
-/* Resets thumbnail path data
- * (blanks all internal string containers) */
-void gfx_thumbnail_path_reset(gfx_thumbnail_path_data_t *path_data)
-{
-   if (!path_data)
-      return;
-   
-   path_data->system[0]            = '\0';
-   path_data->content_path[0]      = '\0';
-   path_data->content_label[0]     = '\0';
-   path_data->content_core_name[0] = '\0';
-   path_data->content_db_name[0]   = '\0';
-   path_data->content_img[0]       = '\0';
-   path_data->right_path[0]        = '\0';
-   path_data->left_path[0]         = '\0';
-   
-   path_data->playlist_right_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_left_mode  = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-}
-
-/* Initialisation */
-
-/* Creates new thumbnail path data container.
- * Returns handle to new gfx_thumbnail_path_data_t object.
- * on success, otherwise NULL.
- * Note: Returned object must be free()d */
-gfx_thumbnail_path_data_t *gfx_thumbnail_path_init(void)
-{
-   gfx_thumbnail_path_data_t *path_data = (gfx_thumbnail_path_data_t*)
-      malloc(sizeof(*path_data));
-   if (!path_data)
-      return NULL;
-
-   gfx_thumbnail_path_reset(path_data);
-   
-   return path_data;
-}
-
-
-/* Utility Functions */
-
-/* Fetches the thumbnail subdirectory (Named_Snaps,
- * Named_Titles, Named_Boxarts) corresponding to the
- * specified 'type index' (1, 2, 3).
- * Returns true if 'type index' is valid */
-bool gfx_thumbnail_get_sub_directory(
-      unsigned type_idx, const char **sub_directory)
-{
-   if (!sub_directory)
-      return false;
-   
-   switch (type_idx)
-   {
-      case 1:
-         *sub_directory = "Named_Snaps";
-         return true;
-      case 2:
-         *sub_directory = "Named_Titles";
-         return true;
-      case 3:
-         *sub_directory = "Named_Boxarts";
-         return true;
-      case 0:
-      default:
-         break;
-   }
-   
-   return false;
-}
-
-/* Returns currently set thumbnail 'type' (Named_Snaps,
- * Named_Titles, Named_Boxarts) for specified thumbnail
- * identifier (right, left) */
-static const char *gfx_thumbnail_get_type(
-      settings_t *settings,
-      gfx_thumbnail_path_data_t *path_data,
-      enum gfx_thumbnail_id thumbnail_id)
-{
-   unsigned        type          = 0;
-   unsigned gfx_thumbnails       = settings->uints.gfx_thumbnails;
-   unsigned menu_left_thumbnails = settings->uints.menu_left_thumbnails;
-   const char *val_off           = msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF);
-   
-   if (!path_data)
-      return val_off;
-   
-   switch (thumbnail_id)
-   {
-      case GFX_THUMBNAIL_RIGHT:
-         if (path_data->playlist_right_mode != PLAYLIST_THUMBNAIL_MODE_DEFAULT)
-            type = (unsigned)path_data->playlist_right_mode - 1;
-         else
-            type = gfx_thumbnails;
-         break;
-      case GFX_THUMBNAIL_LEFT:
-         if (path_data->playlist_left_mode != PLAYLIST_THUMBNAIL_MODE_DEFAULT)
-            type = (unsigned)path_data->playlist_left_mode - 1;
-         else
-            type = menu_left_thumbnails;
-         break;
-      default:
-         return val_off;
-   }
-   
-   switch (type)
-   {
-      case 1:
-         return "Named_Snaps";
-      case 2:
-         return "Named_Titles";
-      case 3:
-         return "Named_Boxarts";
-      case 0:
-      default:
-         break;
-   }
-   
-   return val_off;
-}
-
-/* Returns true if specified thumbnail is enabled
- * (i.e. if 'type' is not equal to MENU_ENUM_LABEL_VALUE_OFF) */
-bool gfx_thumbnail_is_enabled(gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id)
-{
-   settings_t          *settings = config_get_ptr();
-   unsigned gfx_thumbnails       = settings->uints.gfx_thumbnails;
-   unsigned menu_left_thumbnails = settings->uints.menu_left_thumbnails;
-   
-   if (!path_data)
-      return false;
-   
-   switch (thumbnail_id)
-   {
-      case GFX_THUMBNAIL_RIGHT:
-         if (path_data->playlist_right_mode != PLAYLIST_THUMBNAIL_MODE_DEFAULT)
-            return path_data->playlist_right_mode != PLAYLIST_THUMBNAIL_MODE_OFF;
-         return gfx_thumbnails != 0;
-      case GFX_THUMBNAIL_LEFT:
-         if (path_data->playlist_left_mode != PLAYLIST_THUMBNAIL_MODE_DEFAULT)
-            return path_data->playlist_left_mode != PLAYLIST_THUMBNAIL_MODE_OFF;
-         return menu_left_thumbnails != 0;
-      default:
-         break;
-   }
-   
-   return false;
-}
-
-/* Setters */
-
-/* Fills content_img field of path_data using existing
- * content_label field (for internal use only) */
-static void fill_content_img(gfx_thumbnail_path_data_t *path_data)
-{
-   char *scrub_char_pointer = NULL;
-   
-   /* Copy source label string */
-   strlcpy(path_data->content_img,
-         path_data->content_label, sizeof(path_data->content_img));
-   
-   /* Scrub characters that are not cross-platform and/or violate the
-    * No-Intro filename standard:
-    * http://datomatic.no-intro.org/stuff/The%20Official%20No-Intro%20Convention%20(20071030).zip
-    * Replace these characters in the entry name with underscores */
-   while ((scrub_char_pointer = 
-            strpbrk(path_data->content_img, "&*/:`\"<>?\\|")))
-      *scrub_char_pointer = '_';
-   
-   /* Add PNG extension */
-   strlcat(path_data->content_img, ".png", sizeof(path_data->content_img));
-}
-
-/* Sets current 'system' (default database name).
- * Returns true if 'system' is valid.
- * If playlist is provided, extracts system-specific
- * thumbnail assignment metadata (required for accurate
- * usage of gfx_thumbnail_is_enabled())
- * > Used as a fallback when individual content lacks an
- *   associated database name */
-bool gfx_thumbnail_set_system(gfx_thumbnail_path_data_t *path_data,
-      const char *system, playlist_t *playlist)
-{
-   if (!path_data)
-      return false;
-   
-   /* When system is updated, must regenerate right/left
-    * thumbnail paths */
-   path_data->right_path[0]       = '\0';
-   path_data->left_path[0]        = '\0';
-   
-   /* 'Reset' path_data system string */
-   path_data->system[0]           = '\0';
-   
-   /* Must also reset playlist thumbnail display modes */
-   path_data->playlist_right_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_left_mode  = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   
-   if (string_is_empty(system))
-      return false;
-   
-   /* Hack: There is only one MAME thumbnail repo,
-    * so filter any input starting with 'MAME...' */
-   if (strncmp(system, "MAME", 4) == 0)
-   {
-      path_data->system[0] = path_data->system[2] = 'M';
-      path_data->system[1] = 'A';
-      path_data->system[3] = 'E';
-      path_data->system[4] = '\0';
-   }
-   else
-      strlcpy(path_data->system, system, sizeof(path_data->system));
-   
-   /* Addendum: Now that we have per-playlist thumbnail display
-    * modes, we must extract them here - otherwise
-    * gfx_thumbnail_is_enabled() will go out of sync */
-   if (playlist)
-   {
-      const char *playlist_path    = playlist_get_conf_path(playlist);
-      
-      /* Note: This is not considered an error
-       * (just means that input playlist is ignored) */
-      if (!string_is_empty(playlist_path))
-      {
-         const char *playlist_file = path_basename_nocompression(playlist_path);
-         /* Note: This is not considered an error
-          * (just means that input playlist is ignored) */
-         if (!string_is_empty(playlist_file))
-         {
-            /* Check for history/favourites playlists */
-            bool playlist_valid =
-               (string_is_equal(system, "history") &&
-                string_is_equal(playlist_file,
-                   FILE_PATH_CONTENT_HISTORY)) ||
-               (string_is_equal(system, "favorites") &&
-                string_is_equal(playlist_file,
-                   FILE_PATH_CONTENT_FAVORITES));
-
-            if (!playlist_valid)
-            {
-               /* This means we have to work a little harder
-                * i.e. check whether the cached playlist file
-                * matches the database name */
-               char *playlist_name = NULL;
-               char tmp[PATH_MAX_LENGTH];
-               strlcpy(tmp, playlist_file, sizeof(tmp));
-               playlist_name  = path_remove_extension(tmp);
-               playlist_valid = string_is_equal(playlist_name, system);
-            }
-
-            /* If we have a valid playlist, extract thumbnail modes */
-            if (playlist_valid)
-            {
-               path_data->playlist_right_mode =
-                  playlist_get_thumbnail_mode(playlist, PLAYLIST_THUMBNAIL_RIGHT);
-               path_data->playlist_left_mode =
-                  playlist_get_thumbnail_mode(playlist, PLAYLIST_THUMBNAIL_LEFT);
-            }
-         }
-      }
-   }
-   
-   return true;
-}
-
-/* Sets current thumbnail content according to the specified label.
- * Returns true if content is valid */
-bool gfx_thumbnail_set_content(gfx_thumbnail_path_data_t *path_data, const char *label)
-{
-   if (!path_data)
-      return false;
-   
-   /* When content is updated, must regenerate right/left
-    * thumbnail paths */
-   path_data->right_path[0]        = '\0';
-   path_data->left_path[0]         = '\0';
-   
-   /* 'Reset' path_data content strings */
-   path_data->content_path[0]      = '\0';
-   path_data->content_label[0]     = '\0';
-   path_data->content_core_name[0] = '\0';
-   path_data->content_db_name[0]   = '\0';
-   path_data->content_img[0]       = '\0';
-   
-   /* Must also reset playlist thumbnail display modes */
-   path_data->playlist_right_mode  = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_left_mode   = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_index       = 0;
-   
-   if (string_is_empty(label))
-      return false;
-   
-   /* Cache content label */
-   strlcpy(path_data->content_label, label, sizeof(path_data->content_label));
-   
-   /* Determine content image name */
-   fill_content_img(path_data);
-   
-   /* Have to set content path to *something*...
-    * Just use label value (it doesn't matter) */
-   strlcpy(path_data->content_path, label, sizeof(path_data->content_path));
-
-   /* Redundant error check... */
-   return !string_is_empty(path_data->content_img);
-}
-
-/* Sets current thumbnail content to the specified image.
- * Returns true if content is valid */
-bool gfx_thumbnail_set_content_image(
-      gfx_thumbnail_path_data_t *path_data,
-      const char *img_dir, const char *img_name)
-{
-   char *content_img_no_ext = NULL;
-   
-   if (!path_data)
-      return false;
-   
-   /* When content is updated, must regenerate right/left
-    * thumbnail paths */
-   path_data->right_path[0]        = '\0';
-   path_data->left_path[0]         = '\0';
-   
-   /* 'Reset' path_data content strings */
-   path_data->content_path[0]      = '\0';
-   path_data->content_label[0]     = '\0';
-   path_data->content_core_name[0] = '\0';
-   path_data->content_db_name[0]   = '\0';
-   path_data->content_img[0]       = '\0';
-   
-   /* Must also reset playlist thumbnail display modes */
-   path_data->playlist_right_mode  = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_left_mode   = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_index       = 0;
-   
-   if (string_is_empty(img_dir) || string_is_empty(img_name))
-      return false;
-   
-   if (path_is_media_type(img_name) != RARCH_CONTENT_IMAGE)
-      return false;
-   
-   /* Cache content image name */
-   strlcpy(path_data->content_img,
-            img_name, sizeof(path_data->content_img));
-   
-   /* Get image label */
-   content_img_no_ext = path_remove_extension(path_data->content_img);
-   if (!string_is_empty(content_img_no_ext))
-      strlcpy(path_data->content_label,
-            content_img_no_ext, sizeof(path_data->content_label));
-   else
-      strlcpy(path_data->content_label,
-            path_data->content_img, sizeof(path_data->content_label));
-   
-   /* Set file path */
-   fill_pathname_join_special(path_data->content_path,
-      img_dir, img_name, sizeof(path_data->content_path));
-   
-   /* Set core name to "imageviewer" */
-   strlcpy(
-         path_data->content_core_name,
-         "imageviewer", sizeof(path_data->content_core_name));
-   
-   /* Set database name (arbitrarily) to "_images_"
-    * (required for compatibility with gfx_thumbnail_update_path(),
-    * but not actually used...) */
-   strlcpy(path_data->content_db_name,
-         "_images_", sizeof(path_data->content_db_name));
-   
-   /* Redundant error check */
-   return !string_is_empty(path_data->content_path);
-}
-
-/* Sets current thumbnail content to the specified playlist entry.
- * Returns true if content is valid.
- * > Note: It is always best to use playlists when setting
- *   thumbnail content, since there is no guarantee that the
- *   corresponding menu entry label will contain a useful
- *   identifier (it may be 'tainted', e.g. with the current
- *   core name). 'Real' labels should be extracted from source */
-bool gfx_thumbnail_set_content_playlist(
-      gfx_thumbnail_path_data_t *path_data, playlist_t *playlist, size_t idx)
-{
-   const char *content_path           = NULL;
-   const char *content_label          = NULL;
-   const char *core_name              = NULL;
-   const char *db_name                = NULL;
-   const struct playlist_entry *entry = NULL;
-   
-   if (!path_data)
-      return false;
-   
-   /* When content is updated, must regenerate right/left
-    * thumbnail paths */
-   path_data->right_path[0]           = '\0';
-   path_data->left_path[0]            = '\0';
-   
-   /* 'Reset' path_data content strings */
-   path_data->content_path[0]         = '\0';
-   path_data->content_label[0]        = '\0';
-   path_data->content_core_name[0]    = '\0';
-   path_data->content_db_name[0]      = '\0';
-   path_data->content_img[0]          = '\0';
-   
-   /* Must also reset playlist thumbnail display modes */
-   path_data->playlist_right_mode     = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_left_mode      = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   path_data->playlist_index          = 0;
-   
-   if (!playlist)
-      return false;
-   
-   if (idx >= playlist_get_size(playlist))
-      return false;
-   
-   /* Read playlist values */
-   playlist_get_index(playlist, idx, &entry);
-
-   if (!entry)
-      return false;
-
-   content_path  = entry->path;
-   content_label = entry->label;
-   core_name     = entry->core_name;
-   db_name       = entry->db_name;
-
-   /* Content without a path is invalid by definition */
-   if (string_is_empty(content_path))
-      return false;
-   
-   /* Cache content path
-    * (This is required for imageviewer, history and favourites content) */
-   strlcpy(path_data->content_path,
-            content_path, sizeof(path_data->content_path));
-   
-   /* Cache core name
-    * (This is required for imageviewer content) */
-   if (!string_is_empty(core_name))
-      strlcpy(path_data->content_core_name,
-            core_name, sizeof(path_data->content_core_name));
-   
-   /* Get content label */
-   if (!string_is_empty(content_label))
-      strlcpy(path_data->content_label,
-            content_label, sizeof(path_data->content_label));
-   else
-      fill_pathname(path_data->content_label,
-            path_basename_nocompression(content_path),
-            "", sizeof(path_data->content_label));
-   
-   /* Determine content image name */
-   fill_content_img(path_data);
-
-   /* Store playlist index */
-   path_data->playlist_index = idx;
-   
-   /* Redundant error check... */
-   if (string_is_empty(path_data->content_img))
-      return false;
-   
-   /* Thumbnail image name is done -> now check if
-    * per-content database name is defined */
-   if (!string_is_empty(db_name))
-   {
-      /* Hack: There is only one MAME thumbnail repo,
-       * so filter any input starting with 'MAME...' */
-      if (strncmp(db_name, "MAME", 4) == 0)
-      {
-         path_data->content_db_name[0] = path_data->content_db_name[2] = 'M';
-         path_data->content_db_name[1] = 'A';
-         path_data->content_db_name[3] = 'E';
-         path_data->content_db_name[4] = '\0';
-      }
-      else
-      {
-         char *db_name_no_ext = NULL;
-         char tmp_buf[PATH_MAX_LENGTH];
-         /* Remove .lpl extension
-          * > path_remove_extension() requires a char * (not const)
-          *   so have to use a temporary buffer... */
-         strlcpy(tmp_buf, db_name, sizeof(tmp_buf));
-         db_name_no_ext = path_remove_extension(tmp_buf);
-         
-         if (!string_is_empty(db_name_no_ext))
-            strlcpy(path_data->content_db_name,
-                  db_name_no_ext, sizeof(path_data->content_db_name));
-         else
-            strlcpy(path_data->content_db_name,
-                  tmp_buf, sizeof(path_data->content_db_name));
-      }
-   }
-   
-   /* Playlist entry is valid -> it is now 'safe' to
-    * extract any remaining playlist metadata
-    * (i.e. thumbnail display modes) */
-   path_data->playlist_right_mode =
-         playlist_get_thumbnail_mode(playlist, PLAYLIST_THUMBNAIL_RIGHT);
-   path_data->playlist_left_mode =
-         playlist_get_thumbnail_mode(playlist, PLAYLIST_THUMBNAIL_LEFT);
-   
-   return true;
-}
-
-/* Updaters */
-
-/* Updates path for specified thumbnail identifier (right, left).
- * Must be called after:
- * - gfx_thumbnail_set_system()
- * - gfx_thumbnail_set_content*()
- * ...and before:
- * - gfx_thumbnail_get_path()
- * Returns true if generated path is valid */
-bool gfx_thumbnail_update_path(
-      gfx_thumbnail_path_data_t *path_data,
-      enum gfx_thumbnail_id thumbnail_id)
-{
-   char content_dir[PATH_MAX_LENGTH];
-   settings_t *settings       = config_get_ptr();
-   const char *system_name    = NULL;
-   char *thumbnail_path       = NULL;
-   const char *dir_thumbnails = NULL;
-   
-   if (!path_data)
-      return false;
-   
-   /* Determine which path we are updating... */
-   switch (thumbnail_id)
-   {
-      case GFX_THUMBNAIL_RIGHT:
-         thumbnail_path = path_data->right_path;
-         break;
-      case GFX_THUMBNAIL_LEFT:
-         thumbnail_path = path_data->left_path;
-         break;
-      default:
-         return false;
-   }
-   
-   content_dir[0]    = '\0';
-
-   if (settings)
-      dir_thumbnails = settings->paths.directory_thumbnails;
-   
-   /* Sundry error checking */
-   if (string_is_empty(dir_thumbnails))
-      return false;
-   
-   if (!gfx_thumbnail_is_enabled(path_data, thumbnail_id))
-      return false;
-   
-   /* Generate new path */
-   
-   /* > Check path_data for empty strings */
-   if (       string_is_empty(path_data->content_path) 
-       ||     string_is_empty(path_data->content_img)
-       || (   string_is_empty(path_data->system)
-           && string_is_empty(path_data->content_db_name)))
-      return false;
-   
-   /* > Get current system */
-   if (string_is_empty(path_data->content_db_name))
-   {
-      /* If this is a content history or favorites playlist
-       * then the current 'path_data->system' string is
-       * meaningless. In this case, we fall back to the
-       * content directory name */
-      if (string_is_equal(path_data->system, "history") ||
-          string_is_equal(path_data->system, "favorites"))
-      {
-         if (!gfx_thumbnail_get_content_dir(
-                  path_data, content_dir, sizeof(content_dir)))
-            return false;
-         
-         system_name = content_dir;
-      }
-      else
-         system_name = path_data->system;
-   }
-   else
-      system_name = path_data->content_db_name;
-   
-   /* > Special case: thumbnail for imageviewer content
-    *   is the image file itself */
-   if (string_is_equal(system_name, "images_history") ||
-       string_is_equal(path_data->content_core_name, "imageviewer"))
-   {
-      /* imageviewer content is identical for left and right thumbnails */
-      if (path_is_media_type(path_data->content_path) == RARCH_CONTENT_IMAGE)
-         strlcpy(thumbnail_path,
-            path_data->content_path, PATH_MAX_LENGTH * sizeof(char));
-   }
-   else
-   {
-      char tmp_buf[PATH_MAX_LENGTH];
-      const char *type           = gfx_thumbnail_get_type(settings,
-            path_data, thumbnail_id);
-      /* > Normal content: assemble path */
-      
-      /* >> Base + system name */
-      fill_pathname_join_special(thumbnail_path, dir_thumbnails,
-            system_name, PATH_MAX_LENGTH * sizeof(char));
-      
-      /* >> Add type */
-      fill_pathname_join_special(tmp_buf, thumbnail_path, type, sizeof(tmp_buf));
-      
-      /* >> Add content image */
-      thumbnail_path[0] = '\0';
-      fill_pathname_join_special(thumbnail_path, tmp_buf,
-            path_data->content_img, PATH_MAX_LENGTH * sizeof(char));
-   }
-   
-   /* Final error check - is cached path empty? */
-   return !string_is_empty(thumbnail_path);
-}
-
-/* Getters */
-
-/* Fetches the current thumbnail file path of the
- * specified thumbnail 'type'.
- * Returns true if path is valid. */
-bool gfx_thumbnail_get_path(
-      gfx_thumbnail_path_data_t *path_data,
-      enum gfx_thumbnail_id thumbnail_id, const char **path)
-{
-   char *thumbnail_path = NULL;
-   
-   if (!path_data || !path)
-      return false;
-   
-   switch (thumbnail_id)
-   {
-      case GFX_THUMBNAIL_RIGHT:
-         if (!string_is_empty(path_data->right_path))
-         {
-            thumbnail_path = path_data->right_path;
-            *path          = thumbnail_path;
-            return true;
-         }
-         break;
-      case GFX_THUMBNAIL_LEFT:
-         if (!string_is_empty(path_data->left_path))
-         {
-            thumbnail_path = path_data->left_path;
-            *path          = thumbnail_path;
-            return true;
-         }
-         break;
-      default:
-         break;
-   }
-   
-   return false;
-}
-
-/* Fetches current 'system' (default database name).
- * Returns true if 'system' is valid. */
-bool gfx_thumbnail_get_system(
-      gfx_thumbnail_path_data_t *path_data, const char **system)
-{
-   if (!path_data || !system)
-      return false;
-   if (string_is_empty(path_data->system))
-      return false;
-   
-   *system = path_data->system;
-   
-   return true;
-}
-
-/* Fetches current content path.
- * Returns true if content path is valid. */
-bool gfx_thumbnail_get_content_path(
-      gfx_thumbnail_path_data_t *path_data, const char **content_path)
-{
-   if (!path_data || !content_path)
-      return false;
-   if (string_is_empty(path_data->content_path))
-      return false;
-   
-   *content_path = path_data->content_path;
-   
-   return true;
-}
-
-/* Fetches current thumbnail label.
- * Returns true if label is valid. */
-bool gfx_thumbnail_get_label(
-      gfx_thumbnail_path_data_t *path_data, const char **label)
-{
-   if (!path_data || !label)
-      return false;
-   if (string_is_empty(path_data->content_label))
-      return false;
-   
-   *label = path_data->content_label;
-   
-   return true;
-}
-
-/* Fetches current thumbnail core name.
- * Returns true if core name is valid. */
-bool gfx_thumbnail_get_core_name(
-      gfx_thumbnail_path_data_t *path_data, const char **core_name)
-{
-   if (!path_data || !core_name)
-      return false;
-   if (string_is_empty(path_data->content_core_name))
-      return false;
-   
-   *core_name = path_data->content_core_name;
-   
-   return true;
-}
-
-/* Fetches current database name.
- * Returns true if database name is valid. */
-bool gfx_thumbnail_get_db_name(
-      gfx_thumbnail_path_data_t *path_data, const char **db_name)
-{
-   if (!path_data || !db_name)
-      return false;
-   if (string_is_empty(path_data->content_db_name))
-      return false;
-   
-   *db_name = path_data->content_db_name;
-   
-   return true;
-}
-
-/* Fetches current thumbnail image name
- * (name is the same for all thumbnail types).
- * Returns true if image name is valid. */
-bool gfx_thumbnail_get_img_name(
-      gfx_thumbnail_path_data_t *path_data, const char **img_name)
-{
-   if (!path_data || !img_name)
-      return false;
-   if (string_is_empty(path_data->content_img))
-      return false;
-   
-   *img_name = path_data->content_img;
-   
-   return true;
-}
-
-/* Fetches current content directory.
- * Returns true if content directory is valid. */
-bool gfx_thumbnail_get_content_dir(
-      gfx_thumbnail_path_data_t *path_data, char *content_dir, size_t len)
-{
-   size_t path_length;
-   char tmp_buf[PATH_MAX_LENGTH];
-   const char *last_slash        = NULL;
-   
-   if (!path_data || string_is_empty(path_data->content_path))
-      return false;
-   
-   if (!(last_slash = find_last_slash(path_data->content_path)))
-      return false;
-   
-   path_length = last_slash + 1 - path_data->content_path;
-   
-   if (!((path_length > 1) && (path_length < PATH_MAX_LENGTH)))
-      return false;
-
-   strlcpy(tmp_buf, path_data->content_path, path_length * sizeof(char));
-   strlcpy(content_dir, path_basename_nocompression(tmp_buf), len);
-   
-   return !string_is_empty(content_dir);
-}
-
-/* Fetches current playlist index. */
-size_t gfx_thumbnail_get_playlist_index(
-      gfx_thumbnail_path_data_t *path_data)
-{
-   return (path_data) ? path_data->playlist_index : 0;
-}

+ 0 - 157
app/src/main/cpp/gfx/gfx_thumbnail_path.h

@@ -1,157 +0,0 @@
-/* Copyright  (C) 2010-2019 The RetroArch team
- *
- * ---------------------------------------------------------------------------------------
- * The following license statement only applies to this file (gfx_thumbnail_path.c).
- * ---------------------------------------------------------------------------------------
- *
- * Permission is hereby granted, free of charge,
- * to any person obtaining a copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#ifndef __GFX_THUMBNAIL_PATH_H
-#define __GFX_THUMBNAIL_PATH_H
-
-#include <retro_common_api.h>
-#include <libretro.h>
-
-#include <boolean.h>
-
-#include "../playlist.h"
-
-RETRO_BEGIN_DECLS
-
-/* Note: This implementation reflects the current
- * setup of:
- * - menu_driver_set_thumbnail_system()
- * - menu_driver_set_thumbnail_content()
- * - menu_driver_update_thumbnail_path()
- * This is absolutely not the best way to handle things,
- * but I have no interest in rewriting the existing
- * menu code... */
-
-enum gfx_thumbnail_id
-{
-   GFX_THUMBNAIL_RIGHT = 0,
-   GFX_THUMBNAIL_LEFT
-};
-
-/* Prevent direct access to gfx_thumbnail_path_data_t members */
-typedef struct gfx_thumbnail_path_data gfx_thumbnail_path_data_t;
-
-/* Initialisation */
-
-/* Creates new thumbnail path data container.
- * Returns handle to new gfx_thumbnail_path_data_t object.
- * on success, otherwise NULL.
- * Note: Returned object must be free()d */
-gfx_thumbnail_path_data_t *gfx_thumbnail_path_init(void);
-
-/* Resets thumbnail path data
- * (blanks all internal string containers) */
-void gfx_thumbnail_path_reset(gfx_thumbnail_path_data_t *path_data);
-
-/* Utility Functions */
-
-/* Fetches the thumbnail subdirectory (Named_Snaps,
- * Named_Titles, Named_Boxarts) corresponding to the
- * specified 'type index' (1, 2, 3).
- * Returns true if 'type index' is valid */
-bool gfx_thumbnail_get_sub_directory(unsigned type_idx, const char **sub_directory);
-
-/* Returns true if specified thumbnail is enabled
- * (i.e. if 'type' is not equal to MENU_ENUM_LABEL_VALUE_OFF) */
-bool gfx_thumbnail_is_enabled(gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id);
-
-/* Setters */
-
-/* Sets current 'system' (default database name).
- * Returns true if 'system' is valid.
- * If playlist is provided, extracts system-specific
- * thumbnail assignment metadata (required for accurate
- * usage of gfx_thumbnail_is_enabled())
- * > Used as a fallback when individual content lacks an
- *   associated database name */
-bool gfx_thumbnail_set_system(gfx_thumbnail_path_data_t *path_data, const char *system, playlist_t *playlist);
-
-/* Sets current thumbnail content according to the specified label.
- * Returns true if content is valid */
-bool gfx_thumbnail_set_content(gfx_thumbnail_path_data_t *path_data, const char *label);
-
-/* Sets current thumbnail content to the specified image.
- * Returns true if content is valid */
-bool gfx_thumbnail_set_content_image(gfx_thumbnail_path_data_t *path_data, const char *img_dir, const char *img_name);
-
-/* Sets current thumbnail content to the specified playlist entry.
- * Returns true if content is valid.
- * > Note: It is always best to use playlists when setting
- *   thumbnail content, since there is no guarantee that the
- *   corresponding menu entry label will contain a useful
- *   identifier (it may be 'tainted', e.g. with the current
- *   core name). 'Real' labels should be extracted from source */
-bool gfx_thumbnail_set_content_playlist(gfx_thumbnail_path_data_t *path_data, playlist_t *playlist, size_t idx);
-
-/* Updaters */
-
-/* Updates path for specified thumbnail identifier (right, left).
- * Must be called after:
- * - gfx_thumbnail_set_system()
- * - gfx_thumbnail_set_content*()
- * ...and before:
- * - gfx_thumbnail_get_path()
- * Returns true if generated path is valid */
-bool gfx_thumbnail_update_path(gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id);
-
-/* Getters */
-
-/* Fetches the current thumbnail file path of the
- * specified thumbnail 'type'.
- * Returns true if path is valid. */
-bool gfx_thumbnail_get_path(gfx_thumbnail_path_data_t *path_data, enum gfx_thumbnail_id thumbnail_id, const char **path);
-
-/* Fetches current 'system' (default database name).
- * Returns true if 'system' is valid. */
-bool gfx_thumbnail_get_system(gfx_thumbnail_path_data_t *path_data, const char **system);
-
-/* Fetches current content path.
- * Returns true if content path is valid. */
-bool gfx_thumbnail_get_content_path(gfx_thumbnail_path_data_t *path_data, const char **content_path);
-
-/* Fetches current thumbnail label.
- * Returns true if label is valid. */
-bool gfx_thumbnail_get_label(gfx_thumbnail_path_data_t *path_data, const char **label);
-
-/* Fetches current thumbnail core name.
- * Returns true if core name is valid. */
-bool gfx_thumbnail_get_core_name(gfx_thumbnail_path_data_t *path_data, const char **core_name);
-
-/* Fetches current database name.
- * Returns true if database name is valid. */
-bool gfx_thumbnail_get_db_name(gfx_thumbnail_path_data_t *path_data, const char **db_name);
-
-/* Fetches current thumbnail image name
- * (name is the same for all thumbnail types).
- * Returns true if image name is valid. */
-bool gfx_thumbnail_get_img_name(gfx_thumbnail_path_data_t *path_data, const char **img_name);
-
-/* Fetches current content directory.
- * Returns true if content directory is valid. */
-bool gfx_thumbnail_get_content_dir(gfx_thumbnail_path_data_t *path_data, char *content_dir, size_t len);
-
-/* Fetches current playlist index. */
-size_t gfx_thumbnail_get_playlist_index(gfx_thumbnail_path_data_t *path_data);
-
-RETRO_END_DECLS
-
-#endif

+ 8 - 0
app/src/main/cpp/griffin/griffin.c

@@ -892,8 +892,10 @@ DRIVERS
 #endif
 #include "../gfx/gfx_animation.c"
 #include "../gfx/gfx_display.c"
+#ifdef HAVE_GFX_THUBNAIL
 #include "../gfx/gfx_thumbnail_path.c"
 #include "../gfx/gfx_thumbnail.c"
+#endif
 #include "../gfx/video_coord_array.c"
 #ifdef HAVE_AUDIOMIXER
 #include "../libretro-common/audio/audio_mixer.c"
@@ -1218,8 +1220,10 @@ DATA RUNLOOP
 #include "../tasks/task_movie.c"
 #include "../tasks/task_image.c"
 #include "../tasks/task_file_transfer.c"
+#ifdef HAVE_PLAYLIST
 #include "../tasks/task_playlist_manager.c"
 #include "../tasks/task_manual_content_scan.c"
+#endif
 #include "../tasks/task_core_backup.c"
 #ifdef HAVE_TRANSLATE
 #include "../tasks/task_translation.c"
@@ -1245,7 +1249,9 @@ SCREENSHOTS
 /*============================================================
 PLAYLISTS
 ============================================================ */
+#ifdef HAVE_PLAYLIST
 #include "../playlist.c"
+#endif
 
 /*============================================================
 MENU
@@ -1544,7 +1550,9 @@ PLAYLIST NAME SANITIZATION
 /*============================================================
 MANUAL CONTENT SCAN
 ============================================================ */
+#ifdef HAVE_PLAYLIST
 #include "../manual_content_scan.c"
+#endif
 
 /*============================================================
 DISK CONTROL INTERFACE

+ 0 - 1400
app/src/main/cpp/manual_content_scan.c

@@ -1,1400 +0,0 @@
-/* Copyright  (C) 2010-2019 The RetroArch team
- *
- * ---------------------------------------------------------------------------------------
- * The following license statement only applies to this file (manual_content_scan.c).
- * ---------------------------------------------------------------------------------------
- *
- * Permission is hereby granted, free of charge,
- * to any person obtaining a copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#include <file/file_path.h>
-#include <file/archive_file.h>
-#include <string/stdstring.h>
-#include <lists/dir_list.h>
-#include <retro_miscellaneous.h>
-
-#include "msg_hash.h"
-#include "list_special.h"
-#include "core_info.h"
-#include "file_path_special.h"
-
-#include "frontend/frontend_driver.h"
-
-#include "manual_content_scan.h"
-
-/* Holds all configuration parameters associated
- * with a manual content scan */
-typedef struct
-{
-   enum manual_content_scan_system_name_type system_name_type;
-   enum manual_content_scan_core_type core_type;
-
-   char content_dir[PATH_MAX_LENGTH];
-   char system_name_content_dir[PATH_MAX_LENGTH];
-   char system_name_database[PATH_MAX_LENGTH];
-   char system_name_custom[PATH_MAX_LENGTH];
-   char core_name[PATH_MAX_LENGTH];
-   char core_path[PATH_MAX_LENGTH];
-   char file_exts_core[PATH_MAX_LENGTH];
-   char file_exts_custom[PATH_MAX_LENGTH];
-   char dat_file_path[PATH_MAX_LENGTH];
-
-   bool search_recursively;
-   bool search_archives;
-   bool filter_dat_content;
-   bool overwrite_playlist;
-   bool validate_entries;
-} scan_settings_t;
-
-/* TODO/FIXME - static public global variables */
-/* Static settings object
- * > Provides easy access to settings parameters
- *   when creating associated menu entries
- * > We are handling this in almost exactly the same
- *   way as the regular global 'static settings_t *configuration_settings;'
- *   object in retroarch.c. This means it is not inherently thread safe,
- *   but this should not be an issue (i.e. regular configuration_settings
- *   are not thread safe, but we only access them when pushing a
- *   task, not in the task thread itself, so all is well) */
-static scan_settings_t scan_settings = {
-   MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR, /* system_name_type */
-   MANUAL_CONTENT_SCAN_CORE_DETECT,             /* core_type */
-   "",                                          /* content_dir */
-   "",                                          /* system_name_content_dir */
-   "",                                          /* system_name_database */
-   "",                                          /* system_name_custom */
-   "",                                          /* core_name */
-   "",                                          /* core_path */
-   "",                                          /* file_exts_core */
-   "",                                          /* file_exts_custom */
-   "",                                          /* dat_file_path */
-   true,                                        /* search_recursively */
-   false,                                       /* search_archives */
-   false,                                       /* filter_dat_content */
-   false,                                       /* overwrite_playlist */
-   false                                        /* validate_entries */
-};
-
-/*****************/
-/* Configuration */
-/*****************/
-
-/* Pointer access */
-
-/* Returns a pointer to the internal
- * 'content_dir' string */
-char *manual_content_scan_get_content_dir_ptr(void)
-{
-   return scan_settings.content_dir;
-}
-
-/* Returns a pointer to the internal
- * 'system_name_custom' string */
-char *manual_content_scan_get_system_name_custom_ptr(void)
-{
-   return scan_settings.system_name_custom;
-}
-
-/* Returns size of the internal
- * 'system_name_custom' string */
-size_t manual_content_scan_get_system_name_custom_size(void)
-{
-   return sizeof(scan_settings.system_name_custom);
-}
-
-/* Returns a pointer to the internal
- * 'file_exts_custom' string */
-char *manual_content_scan_get_file_exts_custom_ptr(void)
-{
-   return scan_settings.file_exts_custom;
-}
-
-/* Returns size of the internal
- * 'file_exts_custom' string */
-size_t manual_content_scan_get_file_exts_custom_size(void)
-{
-   return sizeof(scan_settings.file_exts_custom);
-}
-
-/* Returns a pointer to the internal
- * 'dat_file_path' string */
-char *manual_content_scan_get_dat_file_path_ptr(void)
-{
-   return scan_settings.dat_file_path;
-}
-
-/* Returns size of the internal
- * 'dat_file_path' string */
-size_t manual_content_scan_get_dat_file_path_size(void)
-{
-   return sizeof(scan_settings.dat_file_path);
-}
-
-/* Returns a pointer to the internal
- * 'search_recursively' bool */
-bool *manual_content_scan_get_search_recursively_ptr(void)
-{
-   return &scan_settings.search_recursively;
-}
-
-/* Returns a pointer to the internal
- * 'search_archives' bool */
-bool *manual_content_scan_get_search_archives_ptr(void)
-{
-   return &scan_settings.search_archives;
-}
-
-/* Returns a pointer to the internal
- * 'filter_dat_content' bool */
-bool *manual_content_scan_get_filter_dat_content_ptr(void)
-{
-   return &scan_settings.filter_dat_content;
-}
-
-/* Returns a pointer to the internal
- * 'overwrite_playlist' bool */
-bool *manual_content_scan_get_overwrite_playlist_ptr(void)
-{
-   return &scan_settings.overwrite_playlist;
-}
-
-/* Returns a pointer to the internal
- * 'validate_entries' bool */
-bool *manual_content_scan_get_validate_entries_ptr(void)
-{
-   return &scan_settings.validate_entries;
-}
-
-/* Sanitisation */
-
-/* Sanitises file extensions list string:
- * > Removes period (full stop) characters
- * > Converts to lower case
- * > Trims leading/trailing whitespace */
-static void manual_content_scan_scrub_file_exts(char *file_exts)
-{
-   if (string_is_empty(file_exts))
-      return;
-
-   string_remove_all_chars(file_exts, '.');
-   string_to_lower(file_exts);
-   string_trim_whitespace(file_exts);
-}
-
-/* Removes invalid characters from
- * 'system_name_custom' string */
-void manual_content_scan_scrub_system_name_custom(void)
-{
-   char *scrub_char_pointer = NULL;
-
-   if (string_is_empty(scan_settings.system_name_custom))
-      return;
-
-   /* Scrub characters that are not cross-platform
-    * and/or violate the No-Intro filename standard:
-    * http://datomatic.no-intro.org/stuff/The%20Official%20No-Intro%20Convention%20(20071030).zip
-    * Replace these characters with underscores */
-   while ((scrub_char_pointer = 
-            strpbrk(scan_settings.system_name_custom, "&*/:`\"<>?\\|")))
-      *scrub_char_pointer = '_';
-}
-
-/* Removes period (full stop) characters from
- * 'file_exts_custom' string and converts to
- * lower case */
-void manual_content_scan_scrub_file_exts_custom(void)
-{
-   manual_content_scan_scrub_file_exts(scan_settings.file_exts_custom);
-}
-
-/* Checks 'dat_file_path' string and resets it
- * if invalid */
-enum manual_content_scan_dat_file_path_status
-      manual_content_scan_validate_dat_file_path(void)
-{
-   enum manual_content_scan_dat_file_path_status dat_file_path_status =
-         MANUAL_CONTENT_SCAN_DAT_FILE_UNSET;
-
-   /* Check if 'dat_file_path' has been set */
-   if (!string_is_empty(scan_settings.dat_file_path))
-   {
-      uint64_t file_size;
-
-      /* Check if path itself is valid */
-      if (logiqx_dat_path_is_valid(scan_settings.dat_file_path, &file_size))
-      {
-         uint64_t free_memory = frontend_driver_get_free_memory();
-         dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_OK;
-
-         /* DAT files can be *very* large...
-          * Try to enforce sane behaviour by requiring
-          * the system to have an amount of free memory
-          * at least twice the size of the DAT file...
-          * > Note that desktop (and probably mobile)
-          *   platforms should always have enough memory
-          *   for this - we're really only protecting the
-          *   console ports here */
-         if (free_memory > 0)
-         {
-            if (free_memory < (2 * file_size))
-               dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE;
-         }
-         /* This is an annoying condition - it means the
-          * current platform doesn't have a 'free_memory'
-          * implementation...
-          * Have to make some assumptions in this case:
-          * > Typically the lowest system RAM of a supported
-          *   platform in 32MB
-          * > Propose that (2 * file_size) should be no more
-          *   than 1/4 of this total RAM value */
-         else if ((2 * file_size) > (8 * 1048576))
-            dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE;
-      }
-      else
-         dat_file_path_status = MANUAL_CONTENT_SCAN_DAT_FILE_INVALID;
-   }
-
-   /* Reset 'dat_file_path' if status is anything other
-    * that 'OK' */
-   if (dat_file_path_status != MANUAL_CONTENT_SCAN_DAT_FILE_OK)
-      scan_settings.dat_file_path[0] = '\0';
-
-   return dat_file_path_status;
-}
-
-/* Menu setters */
-
-/* Sets content directory for next manual scan
- * operation.
- * Returns true if content directory is valid. */
-bool manual_content_scan_set_menu_content_dir(const char *content_dir)
-{
-   size_t len;
-   const char *dir_name = NULL;
-
-   /* Sanity check */
-   if (string_is_empty(content_dir))
-      goto error;
-
-   if (!path_is_directory(content_dir))
-      goto error;
-
-   /* Copy directory path to settings struct */
-   strlcpy(
-         scan_settings.content_dir,
-         content_dir,
-         sizeof(scan_settings.content_dir));
-
-   /* Remove trailing slash, if required */
-   len = strlen(scan_settings.content_dir);
-   if (len <= 0)
-      goto error;
-
-   if (scan_settings.content_dir[len - 1] == PATH_DEFAULT_SLASH_C())
-      scan_settings.content_dir[len - 1] = '\0';
-
-   /* Handle case where path was a single slash... */
-   if (string_is_empty(scan_settings.content_dir))
-      goto error;
-
-   /* Get directory name (used as system name
-    * when scan_settings.system_name_type ==
-    * MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR) */
-   dir_name = path_basename(scan_settings.content_dir);
-
-   if (string_is_empty(dir_name))
-      goto error;
-
-   /* Copy directory name to settings struct */
-   strlcpy(
-         scan_settings.system_name_content_dir,
-         dir_name,
-         sizeof(scan_settings.system_name_content_dir));
-
-   return true;
-
-error:
-   /* Directory is invalid - reset internal
-    * content directory and associated 'directory'
-    * system name */
-   scan_settings.content_dir[0]             = '\0';
-   scan_settings.system_name_content_dir[0] = '\0';
-   return false;
-}
-
-/* Sets system name for the next manual scan
- * operation.
- * Returns true if system name is valid.
- * NOTE:
- * > Only sets 'system_name_type' and 'system_name_database'
- * > 'system_name_content_dir' and 'system_name_custom' are
- *   (by necessity) handled elsewhere
- * > This may look fishy, but it's not - it's a menu-specific
- *   function, and this is simply the cleanest way to handle
- *   the setting... */
-bool manual_content_scan_set_menu_system_name(
-      enum manual_content_scan_system_name_type system_name_type,
-      const char *system_name)
-{
-   /* Sanity check */
-   if (system_name_type > MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE)
-      goto error;
-
-   /* Cache system name 'type' */
-   scan_settings.system_name_type = system_name_type;
-
-   /* Check if we are using a non-database name */
-   if ((scan_settings.system_name_type == MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR) ||
-       (scan_settings.system_name_type == MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM))
-      scan_settings.system_name_database[0] = '\0';
-   else
-   {
-      /* We are using a database name... */
-      if (string_is_empty(system_name))
-         goto error;
-
-      /* Copy database name to settings struct */
-      strlcpy(
-            scan_settings.system_name_database,
-            system_name,
-            sizeof(scan_settings.system_name_database));
-   }
-
-   return true;
-
-error:
-   /* Input parameters are invalid - reset internal
-    * 'system_name_type' and 'system_name_database' */
-   scan_settings.system_name_type        = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR;
-   scan_settings.system_name_database[0] = '\0';
-   return false;
-}
-
-/* Sets core name for the next manual scan
- * operation (+ core path and other associated
- * parameters).
- * Returns true if core name is valid. */
-bool manual_content_scan_set_menu_core_name(
-      enum manual_content_scan_core_type core_type,
-      const char *core_name)
-{
-   /* Sanity check */
-   if (core_type > MANUAL_CONTENT_SCAN_CORE_SET)
-      goto error;
-
-   /* Cache core 'type' */
-   scan_settings.core_type = core_type;
-
-   /* Check if we are using core autodetection */
-   if (scan_settings.core_type == MANUAL_CONTENT_SCAN_CORE_DETECT)
-   {
-      scan_settings.core_name[0]      = '\0';
-      scan_settings.core_path[0]      = '\0';
-      scan_settings.file_exts_core[0] = '\0';
-   }
-   else
-   {
-      core_info_list_t *core_info_list = NULL;
-      core_info_t *core_info           = NULL;
-      bool core_found                  = false;
-      size_t i;
-
-      /* We are using a manually set core... */
-      if (string_is_empty(core_name))
-         goto error;
-
-      /* Get core list */
-      core_info_get_list(&core_info_list);
-
-      if (!core_info_list)
-         goto error;
-
-      /* Search for the specified core name */
-      for (i = 0; i < core_info_list->count; i++)
-      {
-         core_info = NULL;
-         core_info = core_info_get(core_info_list, i);
-
-         if (core_info)
-         {
-            if (string_is_equal(core_info->display_name, core_name))
-            {
-               /* Core has been found */
-               core_found = true;
-
-               /* Copy core path to settings struct */
-               if (string_is_empty(core_info->path))
-                  goto error;
-
-               strlcpy(
-                     scan_settings.core_path,
-                     core_info->path,
-                     sizeof(scan_settings.core_path));
-
-               /* Copy core name to settings struct */
-               strlcpy(
-                     scan_settings.core_name,
-                     core_info->display_name,
-                     sizeof(scan_settings.core_name));
-
-               /* Copy supported extensions to settings
-                * struct, if required */
-               if (!string_is_empty(core_info->supported_extensions))
-               {
-                  strlcpy(
-                        scan_settings.file_exts_core,
-                        core_info->supported_extensions,
-                        sizeof(scan_settings.file_exts_core));
-
-                  /* Core info extensions are delimited by
-                   * vertical bars. For internal consistency,
-                   * replace them with spaces */
-                  string_replace_all_chars(scan_settings.file_exts_core, '|', ' ');
-
-                  /* Apply standard scrubbing/clean-up
-                   * (should not be required, but must handle the
-                   * case where a core info file is incorrectly
-                   * formatted) */
-                  manual_content_scan_scrub_file_exts(scan_settings.file_exts_core);
-               }
-               else
-                  scan_settings.file_exts_core[0] = '\0';
-
-               break;
-            }
-         }
-      }
-
-      /* Sanity check */
-      if (!core_found)
-         goto error;
-   }
-
-   return true;
-
-error:
-   /* Input parameters are invalid - reset internal
-    * core values */
-   scan_settings.core_type         = MANUAL_CONTENT_SCAN_CORE_DETECT;
-   scan_settings.core_name[0]      = '\0';
-   scan_settings.core_path[0]      = '\0';
-   scan_settings.file_exts_core[0] = '\0';
-   return false;
-}
-
-/* Sets all parameters for the next manual scan
- * operation according the to recorded values in
- * the specified playlist.
- * Returns MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK
- * if playlist contains a valid scan record. */
-enum manual_content_scan_playlist_refresh_status
-      manual_content_scan_set_menu_from_playlist(playlist_t *playlist,
-            const char *path_content_database, bool show_hidden_files)
-{
-   const char *playlist_path    = NULL;
-   const char *playlist_file    = NULL;
-   const char *content_dir      = NULL;
-   const char *core_name        = NULL;
-   const char *file_exts        = NULL;
-   const char *dat_file_path    = NULL;
-   bool search_recursively      = false;
-   bool search_archives         = false;
-   bool filter_dat_content      = false;
-#ifdef HAVE_LIBRETRODB
-   struct string_list *rdb_list = NULL;
-#endif
-   enum manual_content_scan_system_name_type
-         system_name_type       = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR;
-   enum manual_content_scan_core_type
-         core_type              = MANUAL_CONTENT_SCAN_CORE_DETECT;
-   enum manual_content_scan_playlist_refresh_status
-         playlist_status        = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK;
-   char system_name[PATH_MAX_LENGTH];
-
-   system_name[0] = '\0';
-
-   if (!playlist_scan_refresh_enabled(playlist))
-   {
-      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG;
-      goto end;
-   }
-
-   /* Read scan parameters from playlist */
-   playlist_path      = playlist_get_conf_path(playlist);
-   content_dir        = playlist_get_scan_content_dir(playlist);
-   core_name          = playlist_get_default_core_name(playlist);
-   file_exts          = playlist_get_scan_file_exts(playlist);
-   dat_file_path      = playlist_get_scan_dat_file_path(playlist);
-
-   search_recursively = playlist_get_scan_search_recursively(playlist);
-   search_archives    = playlist_get_scan_search_archives(playlist);
-   filter_dat_content = playlist_get_scan_filter_dat_content(playlist);
-
-   /* Determine system name (playlist basename
-    * without extension) */
-   if (string_is_empty(playlist_path))
-   {
-      /* Cannot happen, but would constitute a
-       * 'system name' error */
-      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
-      goto end;
-   }
-
-   if ((playlist_file = path_basename(playlist_path)))
-   {
-      strlcpy(system_name, playlist_file, sizeof(system_name));
-      path_remove_extension(system_name);
-   }
-
-   if (string_is_empty(system_name))
-   {
-      /* Cannot happen, but would constitute a
-       * 'system name' error */
-      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
-      goto end;
-   }
-
-   /* Set content directory */
-   if (!manual_content_scan_set_menu_content_dir(content_dir))
-   {
-      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR;
-      goto end;
-   }
-
-   /* Set system name */
-#ifdef HAVE_LIBRETRODB
-   /* > If platform has database support, get names
-    *   of all installed database files */
-   rdb_list = dir_list_new_special(
-         path_content_database,
-         DIR_LIST_DATABASES, NULL, show_hidden_files);
-
-   if (rdb_list && rdb_list->size)
-   {
-      size_t i;
-
-      /* Loop over database files */
-      for (i = 0; i < rdb_list->size; i++)
-      {
-         const char *rdb_path = rdb_list->elems[i].data;
-         const char *rdb_file = NULL;
-         char rdb_name[PATH_MAX_LENGTH];
-
-         rdb_name[0] = '\0';
-
-         /* Sanity check */
-         if (string_is_empty(rdb_path))
-            continue;
-
-         rdb_file = path_basename(rdb_path);
-
-         if (string_is_empty(rdb_file))
-            continue;
-
-         /* Remove file extension */
-         strlcpy(rdb_name, rdb_file, sizeof(rdb_name));
-         path_remove_extension(rdb_name);
-
-         if (string_is_empty(rdb_name))
-            continue;
-
-         /* Check whether playlist system name
-          * matches current database file */
-         if (string_is_equal(system_name, rdb_name))
-         {
-            system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE;
-            break;
-         }
-      }
-   }
-
-   string_list_free(rdb_list);
-#endif
-
-   /* > If system name does not match a database
-    *   file, then check whether it matches the
-    *   content directory name */
-   if (system_name_type !=
-         MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE)
-   {
-      /* system_name_type is set to
-       * MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR
-       * by default - so if a match is found just
-       * reset 'custom name' field */
-      if (string_is_equal(system_name,
-            scan_settings.system_name_content_dir))
-         scan_settings.system_name_custom[0] = '\0';
-      else
-      {
-         /* Playlist is using a custom system name */
-         system_name_type = MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM;
-         strlcpy(scan_settings.system_name_custom, system_name,
-               sizeof(scan_settings.system_name_custom));
-      }
-   }
-
-   if (!manual_content_scan_set_menu_system_name(
-         system_name_type, system_name))
-   {
-      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME;
-      goto end;
-   }
-
-   /* Set core path/name */
-   if (!string_is_empty(core_name) &&
-       !string_is_equal(core_name, FILE_PATH_DETECT))
-      core_type = MANUAL_CONTENT_SCAN_CORE_SET;
-
-   if (!manual_content_scan_set_menu_core_name(
-         core_type, core_name))
-   {
-      playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE;
-      goto end;
-   }
-
-   /* Set custom file extensions */
-   if (string_is_empty(file_exts))
-      scan_settings.file_exts_custom[0] = '\0';
-   else
-   {
-      strlcpy(scan_settings.file_exts_custom, file_exts,
-            sizeof(scan_settings.file_exts_custom));
-
-      /* File extensions read from playlist should
-       * be correctly formatted, with '|' characters
-       * as delimiters
-       * > For menu purposes, must replace these
-       *   delimiters with space characters
-       * > Additionally scrub the resultant string,
-       *   to handle the case where a user has
-       *   'corrupted' it by manually tampering with
-       *   the playlist file */
-      string_replace_all_chars(scan_settings.file_exts_custom, '|', ' ');
-      manual_content_scan_scrub_file_exts(scan_settings.file_exts_custom);
-   }
-
-   /* Set DAT file path */
-   if (string_is_empty(dat_file_path))
-      scan_settings.dat_file_path[0] = '\0';
-   else
-   {
-      strlcpy(scan_settings.dat_file_path, dat_file_path,
-            sizeof(scan_settings.dat_file_path));
-
-      switch (manual_content_scan_validate_dat_file_path())
-      {
-         case MANUAL_CONTENT_SCAN_DAT_FILE_INVALID:
-            playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE;
-            goto end;
-         case MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE:
-            playlist_status = MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE;
-            goto end;
-         default:
-            /* No action required */
-            break;
-      }
-   }
-
-   /* Set remaining boolean parameters */
-   scan_settings.search_recursively = search_recursively;
-   scan_settings.search_archives    = search_archives;
-   scan_settings.filter_dat_content = filter_dat_content;
-   /* When refreshing a playlist:
-    * > We never overwrite the existing file
-    * > We always validate entries in the
-    *   existing file */
-   scan_settings.overwrite_playlist = false;
-   scan_settings.validate_entries   = true;
-
-end:
-   return playlist_status;
-}
-
-/* Menu getters */
-
-/* Fetches content directory for next manual scan
- * operation.
- * Returns true if content directory is valid. */
-bool manual_content_scan_get_menu_content_dir(const char **content_dir)
-{
-   if (!content_dir)
-      return false;
-
-   if (string_is_empty(scan_settings.content_dir))
-      return false;
-
-   *content_dir = scan_settings.content_dir;
-
-   return true;
-}
-
-/* Fetches system name for the next manual scan operation.
- * Returns true if system name is valid.
- * NOTE: This corresponds to the 'System Name' value
- * displayed in menus - this is not identical to the
- * actual system name used when generating the playlist */
-bool manual_content_scan_get_menu_system_name(const char **system_name)
-{
-   if (!system_name)
-      return false;
-
-   switch (scan_settings.system_name_type)
-   {
-      case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR:
-         *system_name = msg_hash_to_str(
-               MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR);
-         return true;
-      case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM:
-         *system_name = msg_hash_to_str(
-               MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CUSTOM);
-         return true;
-      case MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE:
-         if (string_is_empty(scan_settings.system_name_database))
-            return false;
-         else
-         {
-            *system_name = scan_settings.system_name_database;
-            return true;
-         }
-      default:
-         break;
-   }
-
-   return false;
-}
-
-/* Fetches core name for the next manual scan operation.
- * Returns true if core name is valid. */
-bool manual_content_scan_get_menu_core_name(const char **core_name)
-{
-   if (!core_name)
-      return false;
-
-   switch (scan_settings.core_type)
-   {
-      case MANUAL_CONTENT_SCAN_CORE_DETECT:
-         *core_name = msg_hash_to_str(
-               MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT);
-         return true;
-      case MANUAL_CONTENT_SCAN_CORE_SET:
-         if (string_is_empty(scan_settings.core_name))
-            return false;
-         else
-         {
-            *core_name = scan_settings.core_name;
-            return true;
-         }
-      default:
-         break;
-   }
-
-   return false;
-}
-
-/* Menu utility functions */
-
-/* Creates a list of all possible 'system name' menu
- * strings, for use in 'menu_displaylist' drop-down
- * lists and 'menu_cbs_left/right'
- * > Returns NULL in the event of failure
- * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_menu_system_name_list(
-      const char *path_content_database, bool show_hidden_files)
-{
-   union string_list_elem_attr attr;
-   struct string_list *name_list = string_list_new();
-
-   /* Sanity check */
-   if (!name_list)
-      return NULL;
-
-   attr.i = 0;
-
-   /* Add 'use content directory' entry */
-   if (!string_list_append(name_list, msg_hash_to_str(
-         MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CONTENT_DIR), attr))
-      goto error;
-
-   /* Add 'use custom' entry */
-   if (!string_list_append(name_list, msg_hash_to_str(
-         MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_SYSTEM_NAME_USE_CUSTOM), attr))
-      goto error;
-
-#ifdef HAVE_LIBRETRODB
-   /* If platform has database support, get names
-    * of all installed database files */
-   {
-      /* Note: dir_list_new_special() is well behaved - the
-       * returned string list will only include database
-       * files (i.e. don't have to check for directories,
-       * or verify file extensions) */
-      struct string_list *rdb_list = dir_list_new_special(
-            path_content_database,
-            DIR_LIST_DATABASES, NULL, show_hidden_files);
-
-      if (rdb_list && rdb_list->size)
-      {
-         unsigned i;
-
-         /* Ensure database list is in alphabetical order */
-         dir_list_sort(rdb_list, true);
-
-         /* Loop over database files */
-         for (i = 0; i < rdb_list->size; i++)
-         {
-            const char *rdb_path = rdb_list->elems[i].data;
-            const char *rdb_file = NULL;
-            char rdb_name[PATH_MAX_LENGTH];
-
-            rdb_name[0] = '\0';
-
-            /* Sanity check */
-            if (string_is_empty(rdb_path))
-               continue;
-
-            rdb_file = path_basename(rdb_path);
-
-            if (string_is_empty(rdb_file))
-               continue;
-
-            /* Remove file extension */
-            strlcpy(rdb_name, rdb_file, sizeof(rdb_name));
-            path_remove_extension(rdb_name);
-
-            if (string_is_empty(rdb_name))
-               continue;
-
-            /* Add database name to list */
-            if (!string_list_append(name_list, rdb_name, attr))
-               goto error;
-         }
-      }
-
-      /* Clean up */
-      string_list_free(rdb_list);
-   }
-
-#endif
-
-   return name_list;
-
-error:
-   if (name_list)
-      string_list_free(name_list);
-   return NULL;
-}
-
-/* Creates a list of all possible 'core name' menu
- * strings, for use in 'menu_displaylist' drop-down
- * lists and 'menu_cbs_left/right'
- * > Returns NULL in the event of failure
- * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_menu_core_name_list(void)
-{
-   struct string_list *name_list    = string_list_new();
-   core_info_list_t *core_info_list = NULL;
-   union string_list_elem_attr attr;
-
-   /* Sanity check */
-   if (!name_list)
-      goto error;
-
-   attr.i = 0;
-
-   /* Add 'DETECT' entry */
-   if (!string_list_append(name_list, msg_hash_to_str(
-         MENU_ENUM_LABEL_VALUE_MANUAL_CONTENT_SCAN_CORE_NAME_DETECT), attr))
-      goto error;
-
-   /* Get core list */
-   core_info_get_list(&core_info_list);
-
-   if (core_info_list)
-   {
-      core_info_t *core_info = NULL;
-      size_t i;
-
-      /* Sort cores alphabetically */
-      core_info_qsort(core_info_list, CORE_INFO_LIST_SORT_DISPLAY_NAME);
-
-      /* Loop through cores */
-      for (i = 0; i < core_info_list->count; i++)
-      {
-         core_info = NULL;
-         core_info = core_info_get(core_info_list, i);
-
-         if (core_info)
-         {
-            if (string_is_empty(core_info->display_name))
-               continue;
-
-            /* Add core name to list */
-            if (!string_list_append(name_list, core_info->display_name, attr))
-               goto error;
-         }
-      }
-   }
-
-   return name_list;
-
-error:
-   if (name_list)
-      string_list_free(name_list);
-   return NULL;
-}
-
-/****************/
-/* Task Helpers */
-/****************/
-
-/* Parses current manual content scan settings,
- * and extracts all information required to configure
- * a manual content scan task.
- * Returns false if current settings are invalid. */
-bool manual_content_scan_get_task_config(
-      manual_content_scan_task_config_t *task_config,
-      const char *path_dir_playlist
-      )
-{
-   size_t len;
-   if (!task_config)
-      return false;
-
-   /* Ensure all 'task_config' strings are
-    * correctly initialised */
-   task_config->playlist_file[0] = '\0';
-   task_config->content_dir[0]   = '\0';
-   task_config->system_name[0]   = '\0';
-   task_config->database_name[0] = '\0';
-   task_config->core_name[0]     = '\0';
-   task_config->core_path[0]     = '\0';
-   task_config->file_exts[0]     = '\0';
-   task_config->dat_file_path[0] = '\0';
-
-   /* Get content directory */
-   if (string_is_empty(scan_settings.content_dir))
-      return false;
-
-   if (!path_is_directory(scan_settings.content_dir))
-      return false;
-
-   strlcpy(
-         task_config->content_dir,
-         scan_settings.content_dir,
-         sizeof(task_config->content_dir));
-
-   /* Get system name */
-   switch (scan_settings.system_name_type)
-   {
-      case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR:
-         if (string_is_empty(scan_settings.system_name_content_dir))
-            return false;
-
-         strlcpy(
-               task_config->system_name,
-               scan_settings.system_name_content_dir,
-               sizeof(task_config->system_name));
-
-         break;
-      case MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM:
-         if (string_is_empty(scan_settings.system_name_custom))
-            return false;
-
-         strlcpy(
-               task_config->system_name,
-               scan_settings.system_name_custom,
-               sizeof(task_config->system_name));
-
-         break;
-      case MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE:
-         if (string_is_empty(scan_settings.system_name_database))
-            return false;
-
-         strlcpy(
-               task_config->system_name,
-               scan_settings.system_name_database,
-               sizeof(task_config->system_name));
-
-         break;
-      default:
-         return false;
-   }
-
-   /* Now we have a valid system name, can generate
-    * a 'database' name... */
-   len = strlcpy(
-         task_config->database_name,
-         task_config->system_name,
-         sizeof(task_config->database_name));
-   task_config->database_name[len  ] = '.';
-   task_config->database_name[len+1] = 'l';
-   task_config->database_name[len+2] = 'p';
-   task_config->database_name[len+3] = 'l';
-   task_config->database_name[len+4] = '\0';
-
-   /* ...which can in turn be used to generate the
-    * playlist path */
-   if (string_is_empty(path_dir_playlist))
-      return false;
-
-   fill_pathname_join_special(
-         task_config->playlist_file,
-         path_dir_playlist,
-         task_config->database_name,
-         sizeof(task_config->playlist_file));
-
-   if (string_is_empty(task_config->playlist_file))
-      return false;
-
-   /* Get core name and path */
-   switch (scan_settings.core_type)
-   {
-      case MANUAL_CONTENT_SCAN_CORE_DETECT:
-         task_config->core_set = false;
-         break;
-      case MANUAL_CONTENT_SCAN_CORE_SET:
-         task_config->core_set = true;
-
-         if (string_is_empty(scan_settings.core_name))
-            return false;
-         if (string_is_empty(scan_settings.core_path))
-            return false;
-
-         strlcpy(
-               task_config->core_name,
-               scan_settings.core_name,
-               sizeof(task_config->core_name));
-
-         strlcpy(
-               task_config->core_path,
-               scan_settings.core_path,
-               sizeof(task_config->core_path));
-
-         break;
-      default:
-         return false;
-   }
-
-   /* Get file extensions list */
-   task_config->file_exts_custom_set = false;
-   if (!string_is_empty(scan_settings.file_exts_custom))
-   {
-      task_config->file_exts_custom_set = true;
-      strlcpy(
-            task_config->file_exts,
-            scan_settings.file_exts_custom,
-            sizeof(task_config->file_exts));
-   }
-   else if (scan_settings.core_type == MANUAL_CONTENT_SCAN_CORE_SET)
-      if (!string_is_empty(scan_settings.file_exts_core))
-         strlcpy(
-               task_config->file_exts,
-               scan_settings.file_exts_core,
-               sizeof(task_config->file_exts));
-
-   /* Our extension lists are space delimited
-    * > dir_list_new() expects vertical bar
-    *   delimiters, so find and replace */
-   if (!string_is_empty(task_config->file_exts))
-      string_replace_all_chars(task_config->file_exts, ' ', '|');
-
-   /* Get DAT file path */
-   if (!string_is_empty(scan_settings.dat_file_path))
-   {
-      if (!logiqx_dat_path_is_valid(scan_settings.dat_file_path, NULL))
-         return false;
-
-      strlcpy(
-            task_config->dat_file_path,
-            scan_settings.dat_file_path,
-            sizeof(task_config->dat_file_path));
-   }
-
-   /* Copy 'search recursively' setting */
-   task_config->search_recursively = scan_settings.search_recursively;
-   /* Copy 'search inside archives' setting */
-   task_config->search_archives    = scan_settings.search_archives;
-   /* Copy 'DAT file filter' setting */
-   task_config->filter_dat_content = scan_settings.filter_dat_content;
-   /* Copy 'overwrite playlist' setting */
-   task_config->overwrite_playlist = scan_settings.overwrite_playlist;
-   /* Copy 'validate_entries' setting */
-   task_config->validate_entries   = scan_settings.validate_entries;
-
-   return true;
-}
-
-/* Creates a list of all valid content in the specified
- * content directory
- * > Returns NULL in the event of failure
- * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_content_list(
-      manual_content_scan_task_config_t *task_config)
-{
-   struct string_list *dir_list = NULL;
-   bool filter_exts;
-   bool include_compressed;
-
-   /* Sanity check */
-   if (!task_config)
-      goto error;
-
-   if (string_is_empty(task_config->content_dir))
-      goto error;
-
-   /* Check whether files should be filtered by
-    * extension */
-   filter_exts = !string_is_empty(task_config->file_exts);
-
-   /* Check whether compressed files should be
-    * included in the directory list
-    * > If compressed files are already listed in
-    *   the 'file_exts' string, they will be included
-    *   automatically
-    * > If we don't have a 'file_exts' list, then all
-    *   files must be included regardless of type
-    * > If user has enabled 'search inside archives',
-    *   then compressed files must of course be included */
-   include_compressed = (!filter_exts || task_config->search_archives);
-
-   /* Get directory listing
-    * > Exclude directories and hidden files */
-   dir_list = dir_list_new(
-         task_config->content_dir,
-         filter_exts ? task_config->file_exts : NULL,
-         false, /* include_dirs */
-         false, /* include_hidden */
-         include_compressed,
-         task_config->search_recursively
-   );
-
-   /* Sanity check */
-   if (!dir_list)
-      goto error;
-
-   if (dir_list->size < 1)
-      goto error;
-
-   /* Ensure list is in alphabetical order
-    * > Not strictly required, but task status
-    *   messages will be unintuitive if we leave
-    *   the order 'random' */
-   dir_list_sort(dir_list, true);
-
-   return dir_list;
-
-error:
-   if (dir_list)
-      string_list_free(dir_list);
-   return NULL;
-}
-
-/* Converts specified content path string to 'real'
- * file path for use in playlists - i.e. handles
- * identification of content *inside* archive files.
- * Returns false if specified content is invalid. */
-static bool manual_content_scan_get_playlist_content_path(
-      manual_content_scan_task_config_t *task_config,
-      const char *content_path, int content_type,
-      char *s, size_t len)
-{
-   size_t _len;
-   struct string_list *archive_list = NULL;
-
-   /* Sanity check */
-   if (!task_config || string_is_empty(content_path))
-      return false;
-
-   if (!path_is_valid(content_path))
-      return false;
-
-   /* In all cases, base content path must be
-    * copied to @s */
-   _len = strlcpy(s, content_path, len);
-
-   /* Check whether this is an archive file
-    * requiring special attention... */
-   if ((content_type == RARCH_COMPRESSED_ARCHIVE) &&
-       task_config->search_archives)
-   {
-      bool filter_exts         = !string_is_empty(task_config->file_exts);
-      const char *archive_file = NULL;
-
-      /* Important note:
-       * > If an archive file of a particular type is
-       *   included in the task_config->file_exts list,
-       *   dir_list_new() will assign it a file type of
-       *   RARCH_PLAIN_FILE
-       * > Thus we will only reach this point if
-       *   (a) We are not filtering by extension
-       *   (b) This is an archive file type *not*
-       *       already included in the supported
-       *       extensions list
-       * > These guarantees substantially reduce the
-       *   complexity of the following code... */
-
-      /* Get archive file contents */
-      if (!(archive_list = file_archive_get_file_list(
-            content_path, filter_exts ? task_config->file_exts : NULL)))
-         goto error;
-
-      if (archive_list->size < 1)
-         goto error;
-
-      /* Get first file contained in archive */
-      dir_list_sort(archive_list, true);
-      archive_file = archive_list->elems[0].data;
-      if (string_is_empty(archive_file))
-         goto error;
-
-      /* Have to take care to ensure that we don't make
-       * a mess of arcade content...
-       * > If we are filtering by extension, then the
-       *   archive file itself is *not* valid content,
-       *   so link to the first file inside the archive
-       * > If we are not filtering by extension, then:
-       *   - If archive contains one valid file, assume
-       *     it is a compressed ROM
-       *   - If archive contains multiple files, have to
-       *     assume it is MAME/FBA-style content, where
-       *     only the archive itself is valid */
-      if (filter_exts || (archive_list->size == 1))
-      {
-         /* Build path to file inside archive */
-         s[_len  ] = '#';
-         s[_len+1] = '\0';
-         strlcat(s, archive_file, len);
-      }
-
-      string_list_free(archive_list);
-   }
-
-   return true;
-
-error:
-   if (archive_list)
-      string_list_free(archive_list);
-   return false;
-}
-
-/* Extracts content 'label' (name) from content path
- * > If a DAT file is specified, performs a lookup
- *   of content file name in an attempt to find a
- *   valid 'description' string.
- * Returns false if specified content is invalid. */
-static bool manual_content_scan_get_playlist_content_label(
-      const char *content_path, logiqx_dat_t *dat_file,
-      bool filter_dat_content,
-      char *content_label, size_t len)
-{
-   /* Sanity check */
-   if (string_is_empty(content_path))
-      return false;
-
-   /* In most cases, content label is just the
-    * filename without extension */
-   fill_pathname(content_label, path_basename(content_path), 
-         "", len);
-
-   if (string_is_empty(content_label))
-      return false;
-
-   /* Check if a DAT file has been specified */
-   if (dat_file)
-   {
-      bool content_found = false;
-      logiqx_dat_game_info_t game_info;
-
-      /* Search for current content
-       * > If content is not listed in DAT file,
-       *   use existing filename without extension */
-      if (logiqx_dat_search(dat_file, content_label, &game_info))
-      {
-         /* BIOS files should always be skipped */
-         if (game_info.is_bios)
-            return false;
-
-         /* Only include 'runnable' content */
-         if (!game_info.is_runnable)
-            return false;
-
-         /* Copy game description */
-         if (!string_is_empty(game_info.description))
-         {
-            strlcpy(content_label, game_info.description, len);
-            content_found = true;
-         }
-      }
-
-      /* If we are applying a DAT file filter,
-       * unlisted content should be skipped */
-      if (!content_found && filter_dat_content)
-         return false;
-   }
-
-   return true;
-}
-
-/* Adds specified content to playlist, if not already
- * present */
-void manual_content_scan_add_content_to_playlist(
-      manual_content_scan_task_config_t *task_config,
-      playlist_t *playlist, const char *content_path,
-      int content_type, logiqx_dat_t *dat_file)
-{
-   char playlist_content_path[PATH_MAX_LENGTH];
-
-   /* Sanity check */
-   if (!task_config || !playlist)
-      return;
-
-   /* Get 'actual' content path */
-   if (!manual_content_scan_get_playlist_content_path(
-         task_config, content_path, content_type,
-         playlist_content_path, sizeof(playlist_content_path)))
-      return;
-
-   /* Check whether content is already included
-    * in playlist */
-   if (!playlist_entry_exists(playlist, playlist_content_path))
-   {
-      struct playlist_entry entry = {0};
-      char label[PATH_MAX_LENGTH];
-
-      label[0] = '\0';
-
-      /* Get entry label */
-      if (!manual_content_scan_get_playlist_content_label(
-            playlist_content_path, dat_file,
-            task_config->filter_dat_content,
-            label, sizeof(label)))
-         return;
-
-      /* Configure playlist entry
-       * > The push function reads our entry as const,
-       *   so these casts are safe */
-      entry.path       = (char*)playlist_content_path;
-      entry.label      = label;
-      entry.core_path  = (char*)FILE_PATH_DETECT;
-      entry.core_name  = (char*)FILE_PATH_DETECT;
-      entry.crc32      = (char*)"00000000|crc";
-      entry.db_name    = task_config->database_name;
-      entry.entry_slot = 0;
-
-      /* Add entry to playlist */
-      playlist_push(playlist, &entry);
-   }
-}

+ 0 - 278
app/src/main/cpp/manual_content_scan.h

@@ -1,278 +0,0 @@
-/* Copyright  (C) 2010-2019 The RetroArch team
- *
- * ---------------------------------------------------------------------------------------
- * The following license statement only applies to this file (manual_content_scan.c).
- * ---------------------------------------------------------------------------------------
- *
- * Permission is hereby granted, free of charge,
- * to any person obtaining a copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-#ifndef __MANUAL_CONTENT_SCAN_H
-#define __MANUAL_CONTENT_SCAN_H
-
-#include <retro_common_api.h>
-#include <libretro.h>
-
-#include <boolean.h>
-
-#include <lists/string_list.h>
-#include <formats/logiqx_dat.h>
-
-#include "playlist.h"
-
-RETRO_BEGIN_DECLS
-
-/* Defines all possible system name types
- * > Use content directory name
- * > Use custom name
- * > Use database name */
-enum manual_content_scan_system_name_type
-{
-   MANUAL_CONTENT_SCAN_SYSTEM_NAME_CONTENT_DIR = 0,
-   MANUAL_CONTENT_SCAN_SYSTEM_NAME_CUSTOM,
-   MANUAL_CONTENT_SCAN_SYSTEM_NAME_DATABASE
-};
-
-/* Defines all possible core name types
- * > Autodetect core (DETECT)
- * > Use manually set core */
-enum manual_content_scan_core_type
-{
-   MANUAL_CONTENT_SCAN_CORE_DETECT = 0,
-   MANUAL_CONTENT_SCAN_CORE_SET
-};
-
-/* Defines all possible return values for
- * manual_content_scan_validate_dat_file_path() */
-enum manual_content_scan_dat_file_path_status
-{
-   MANUAL_CONTENT_SCAN_DAT_FILE_UNSET = 0,
-   MANUAL_CONTENT_SCAN_DAT_FILE_OK,
-   MANUAL_CONTENT_SCAN_DAT_FILE_INVALID,
-   MANUAL_CONTENT_SCAN_DAT_FILE_TOO_LARGE
-};
-
-/* Defines all possible return values for
- * manual_content_scan_set_menu_from_playlist() */
-enum manual_content_scan_playlist_refresh_status
-{
-   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK = 0,
-   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_MISSING_CONFIG,
-   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CONTENT_DIR,
-   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_SYSTEM_NAME,
-   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_CORE,
-   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_INVALID_DAT_FILE,
-   MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_DAT_FILE_TOO_LARGE
-};
-
-/* Holds all configuration parameters required
- * for a manual content scan task */
-typedef struct
-{
-   char playlist_file[PATH_MAX_LENGTH];
-   char content_dir[PATH_MAX_LENGTH];
-   char system_name[PATH_MAX_LENGTH];
-   char database_name[PATH_MAX_LENGTH];
-   char core_name[PATH_MAX_LENGTH];
-   char core_path[PATH_MAX_LENGTH];
-   char file_exts[PATH_MAX_LENGTH];
-   char dat_file_path[PATH_MAX_LENGTH];
-   bool core_set;
-   bool file_exts_custom_set;
-   bool search_recursively;
-   bool search_archives;
-   bool filter_dat_content;
-   bool overwrite_playlist;
-   bool validate_entries;
-} manual_content_scan_task_config_t;
-
-/*****************/
-/* Configuration */
-/*****************/
-
-/* Pointer access
- * > This is a little ugly, but it allows us to
- *   make use of standard 'menu_settings' code
- *   for several config parameters (rather than
- *   implementing unnecessary custom menu entries) */
-
-/* Returns a pointer to the internal
- * 'content_dir' string */
-char *manual_content_scan_get_content_dir_ptr(void);
-
-/* Returns a pointer to the internal
- * 'system_name_custom' string */
-char *manual_content_scan_get_system_name_custom_ptr(void);
-
-/* Returns size of the internal
- * 'system_name_custom' string */
-size_t manual_content_scan_get_system_name_custom_size(void);
-
-/* Returns a pointer to the internal
- * 'file_exts_custom' string */
-char *manual_content_scan_get_file_exts_custom_ptr(void);
-
-/* Returns size of the internal
- * 'file_exts_custom' string */
-size_t manual_content_scan_get_file_exts_custom_size(void);
-
-/* Returns a pointer to the internal
- * 'dat_file_path' string */
-char *manual_content_scan_get_dat_file_path_ptr(void);
-
-/* Returns size of the internal
- * 'dat_file_path' string */
-size_t manual_content_scan_get_dat_file_path_size(void);
-
-/* Returns a pointer to the internal
- * 'search_recursively' bool */
-bool *manual_content_scan_get_search_recursively_ptr(void);
-
-/* Returns a pointer to the internal
- * 'search_archives' bool */
-bool *manual_content_scan_get_search_archives_ptr(void);
-
-/* Returns a pointer to the internal
- * 'filter_dat_content' bool */
-bool *manual_content_scan_get_filter_dat_content_ptr(void);
-
-/* Returns a pointer to the internal
- * 'overwrite_playlist' bool */
-bool *manual_content_scan_get_overwrite_playlist_ptr(void);
-
-/* Returns a pointer to the internal
- * 'validate_entries' bool */
-bool *manual_content_scan_get_validate_entries_ptr(void);
-
-/* Sanitisation */
-
-/* Removes invalid characters from
- * 'system_name_custom' string */
-void manual_content_scan_scrub_system_name_custom(void);
-
-/* Removes period (full stop) characters from
- * 'file_exts_custom' string and converts to
- * lower case */
-void manual_content_scan_scrub_file_exts_custom(void);
-
-/* Checks 'dat_file_path' string and resets it
- * if invalid */
-enum manual_content_scan_dat_file_path_status
-      manual_content_scan_validate_dat_file_path(void);
-
-/* Menu setters */
-
-/* Sets content directory for next manual scan
- * operation.
- * Returns true if content directory is valid. */
-bool manual_content_scan_set_menu_content_dir(const char *content_dir);
-
-/* Sets system name for the next manual scan
- * operation.
- * Returns true if system name is valid.
- * NOTE:
- * > Only sets 'system_name_type' and 'system_name_database'
- * > 'system_name_content_dir' and 'system_name_custom' are
- *   (by necessity) handled elsewhere
- * > This may look fishy, but it's not - it's a menu-specific
- *   function, and this is simply the cleanest way to handle
- *   the setting... */
-bool manual_content_scan_set_menu_system_name(
-      enum manual_content_scan_system_name_type system_name_type,
-      const char *system_name);
-
-/* Sets core name for the next manual scan
- * operation (+ core path and other associated
- * parameters).
- * Returns true if core name is valid. */
-bool manual_content_scan_set_menu_core_name(
-      enum manual_content_scan_core_type core_type,
-      const char *core_name);
-
-/* Sets all parameters for the next manual scan
- * operation according the to recorded values in
- * the specified playlist.
- * Returns MANUAL_CONTENT_SCAN_PLAYLIST_REFRESH_OK
- * if playlist contains a valid scan record. */
-enum manual_content_scan_playlist_refresh_status
-      manual_content_scan_set_menu_from_playlist(playlist_t *playlist,
-            const char *path_content_database, bool show_hidden_files);
-
-/* Menu getters */
-
-/* Fetches content directory for next manual scan
- * operation.
- * Returns true if content directory is valid. */
-bool manual_content_scan_get_menu_content_dir(const char **content_dir);
-
-/* Fetches system name for the next manual scan operation.
- * Returns true if system name is valid.
- * NOTE: This corresponds to the 'System Name' value
- * displayed in menus - this is not identical to the
- * actual system name used when generating the playlist */
-bool manual_content_scan_get_menu_system_name(const char **system_name);
-
-/* Fetches core name for the next manual scan operation.
- * Returns true if core name is valid. */
-bool manual_content_scan_get_menu_core_name(const char **core_name);
-
-/* Menu utility functions */
-
-/* Creates a list of all possible 'system name' menu
- * strings, for use in 'menu_displaylist' drop-down
- * lists and 'menu_cbs_left/right'
- * > Returns NULL in the event of failure
- * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_menu_system_name_list(
-      const char *path_content_database, bool show_hidden_files);
-
-/* Creates a list of all possible 'core name' menu
- * strings, for use in 'menu_displaylist' drop-down
- * lists and 'menu_cbs_left/right'
- * > Returns NULL in the event of failure
- * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_menu_core_name_list(void);
-
-/****************/
-/* Task Helpers */
-/****************/
-
-/* Parses current manual content scan settings,
- * and extracts all information required to configure
- * a manual content scan task.
- * Returns false if current settings are invalid. */
-bool manual_content_scan_get_task_config(
-      manual_content_scan_task_config_t *task_config,
-      const char *path_dir_playlist
-      );
-
-/* Creates a list of all valid content in the specified
- * content directory
- * > Returns NULL in the event of failure
- * > Returned string list must be free()'d */
-struct string_list *manual_content_scan_get_content_list(
-      manual_content_scan_task_config_t *task_config);
-
-/* Adds specified content to playlist, if not already
- * present */
-void manual_content_scan_add_content_to_playlist(
-      manual_content_scan_task_config_t *task_config,
-      playlist_t *playlist, const char *content_path,
-      int content_type, logiqx_dat_t *dat_file);
-
-RETRO_END_DECLS
-
-#endif

+ 0 - 3510
app/src/main/cpp/playlist.c

@@ -1,3510 +0,0 @@
-/*  RetroArch - A frontend for libretro.
- *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
- *  Copyright (C) 2011-2017 - Daniel De Matteis
- *  Copyright (C) 2016-2019 - Brad Parker
- *
- *  RetroArch is free software: you can redistribute it and/or modify it under the terms
- *  of the GNU General Public License as published by the Free Software Found-
- *  ation, either version 3 of the License, or (at your option) any later version.
- *
- *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- *  PURPOSE.  See the GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along with RetroArch.
- *  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include <libretro.h>
-#include <boolean.h>
-#include <retro_miscellaneous.h>
-#include <compat/posix_string.h>
-#include <string/stdstring.h>
-#include <streams/interface_stream.h>
-#include <file/file_path.h>
-#include <file/archive_file.h>
-#include <lists/string_list.h>
-#include <formats/rjson.h>
-#include <array/rbuf.h>
-
-#include "playlist.h"
-#include "verbosity.h"
-#include "file_path_special.h"
-#include "core_info.h"
-
-#if defined(ANDROID)
-#include "play_feature_delivery/play_feature_delivery.h"
-#endif
-
-#ifndef PLAYLIST_ENTRIES
-#define PLAYLIST_ENTRIES 6
-#endif
-
-#define WINDOWS_PATH_DELIMITER '\\'
-#define POSIX_PATH_DELIMITER '/'
-
-#ifdef _WIN32
-#define LOCAL_FILE_SYSTEM_PATH_DELIMITER WINDOWS_PATH_DELIMITER
-#define USING_WINDOWS_FILE_SYSTEM
-#else
-#define LOCAL_FILE_SYSTEM_PATH_DELIMITER POSIX_PATH_DELIMITER
-#define USING_POSIX_FILE_SYSTEM
-#endif
-
-/* Holds all configuration parameters required
- * to repeat a manual content scan for a
- * previously manual-scan-generated playlist */
-typedef struct
-{
-   char *content_dir;
-   char *file_exts;
-   char *dat_file_path;
-   bool search_recursively;
-   bool search_archives;
-   bool filter_dat_content;
-} playlist_manual_scan_record_t;
-
-struct content_playlist
-{
-   char *default_core_path;
-   char *default_core_name;
-   char *base_content_directory;
-
-   struct playlist_entry *entries;
-
-   playlist_manual_scan_record_t scan_record; /* ptr alignment */
-   playlist_config_t config;                  /* size_t alignment */
-
-   enum playlist_label_display_mode label_display_mode;
-   enum playlist_thumbnail_mode right_thumbnail_mode;
-   enum playlist_thumbnail_mode left_thumbnail_mode;
-   enum playlist_sort_mode sort_mode;
-
-   bool modified;
-   bool old_format;
-   bool compressed;
-   bool cached_external;
-};
-
-typedef struct
-{
-   struct playlist_entry *current_entry;
-   char **current_string_val;
-   unsigned *current_entry_uint_val;
-   enum playlist_label_display_mode *current_meta_label_display_mode_val;
-   enum playlist_thumbnail_mode *current_meta_thumbnail_mode_val;
-   enum playlist_sort_mode *current_meta_sort_mode_val;
-   bool *current_meta_bool_val;
-   playlist_t *playlist;
-
-   unsigned array_depth;
-   unsigned object_depth;
-
-   bool in_items;
-   bool in_subsystem_roms;
-   bool capacity_exceeded;
-   bool out_of_memory;
-} JSONContext;
-
-/* TODO/FIXME - global state - perhaps move outside this file */
-static playlist_t *playlist_cached = NULL;
-
-typedef int (playlist_sort_fun_t)(
-      const struct playlist_entry *a,
-      const struct playlist_entry *b);
-
-/* TODO/FIXME - hack for allowing the explore view to switch 
- * over to a playlist item */
-void playlist_set_cached_external(playlist_t* pl)
-{
-   playlist_free_cached();
-   if (!pl)
-      return;
-
-   playlist_cached = pl;
-   playlist_cached->cached_external = true;
-}
-
-/* Convenience function: copies specified playlist
- * path to specified playlist configuration object */
-void playlist_config_set_path(playlist_config_t *config, const char *path)
-{
-   if (!config)
-      return;
-
-   if (!string_is_empty(path))
-      strlcpy(config->path, path, sizeof(config->path));
-   else
-      config->path[0] = '\0';
-}
-
-/* Convenience function: copies base content directory
- * path to specified playlist configuration object.
- * Also sets autofix_paths boolean, depending on base 
- * content directory value */
-void playlist_config_set_base_content_directory(
-      playlist_config_t* config, const char* path)
-{
-   if (!config)
-      return;
-
-   config->autofix_paths = !string_is_empty(path);
-   if (config->autofix_paths)
-      strlcpy(config->base_content_directory, path,
-            sizeof(config->base_content_directory));
-   else
-      config->base_content_directory[0] = '\0';
-}
-
-
-/* Creates a copy of the specified playlist configuration.
- * Returns false in the event of an error */
-bool playlist_config_copy(const playlist_config_t *src,
-      playlist_config_t *dst)
-{
-   if (!src || !dst)
-      return false;
-
-   strlcpy(dst->path, src->path, sizeof(dst->path));
-   strlcpy(dst->base_content_directory, src->base_content_directory,
-         sizeof(dst->base_content_directory));
-
-   dst->capacity            = src->capacity;
-   dst->old_format          = src->old_format;
-   dst->compress            = src->compress;
-   dst->fuzzy_archive_match = src->fuzzy_archive_match;
-   dst->autofix_paths       = src->autofix_paths;
-
-   return true;
-}
-
-/* Returns internal playlist configuration object
- * of specified playlist.
- * Returns NULL it the event of an error. */
-playlist_config_t *playlist_get_config(playlist_t *playlist)
-{
-   if (!playlist)
-      return NULL;
-
-   return &playlist->config;
-}
-
-static void path_replace_base_path_and_convert_to_local_file_system(
-      char *out_path, const char *in_path,
-      const char *in_oldrefpath, const char *in_refpath,
-      size_t size)
-{
-   size_t in_oldrefpath_length = strlen(in_oldrefpath);
-   size_t in_refpath_length    = strlen(in_refpath);
-
-   /* If entry path is inside playlist base path,
-    * replace it with new base content directory */
-   if (string_starts_with_size(in_path, in_oldrefpath, in_oldrefpath_length))
-   {
-      memcpy(out_path, in_refpath, in_refpath_length);
-      memcpy(out_path + in_refpath_length, in_path + in_oldrefpath_length,
-            strlen(in_path) - in_oldrefpath_length + 1);
-
-#ifdef USING_WINDOWS_FILE_SYSTEM
-      /* If we are running under a Windows filesystem,
-       * '/' characters are not allowed anywhere. 
-       * We replace with '\' and hope for the best... */
-      string_replace_all_chars(out_path,
-            POSIX_PATH_DELIMITER, WINDOWS_PATH_DELIMITER);
-#endif
-
-#ifdef USING_POSIX_FILE_SYSTEM
-      /* Under POSIX filesystem, we replace '\' characters with '/' */
-      string_replace_all_chars(out_path,
-            WINDOWS_PATH_DELIMITER, POSIX_PATH_DELIMITER);
-#endif
-   }
-   else
-      strlcpy(out_path, in_path, size);
-}
-
-/* Generates a case insensitive hash for the
- * specified path string */
-static uint32_t playlist_path_hash(const char *path)
-{
-   unsigned char c;
-   uint32_t hash = (uint32_t)0x811c9dc5;
-   while ((c = (unsigned char)*(path++)) != '\0')
-      hash = ((hash * (uint32_t)0x01000193) ^ (uint32_t)((c >= 'A' && c <= 'Z') ? (c | 0x20) : c));
-   return (hash ? hash : 1);
-}
-
-static void playlist_path_id_free(playlist_path_id_t *path_id)
-{
-   if (!path_id)
-      return;
-
-   if (path_id->archive_path &&
-       (path_id->archive_path != path_id->real_path))
-      free(path_id->archive_path);
-
-   if (path_id->real_path)
-      free(path_id->real_path);
-
-   free(path_id);
-}
-
-static playlist_path_id_t *playlist_path_id_init(const char *path)
-{
-   playlist_path_id_t *path_id  = (playlist_path_id_t*)malloc(sizeof(*path_id));
-
-   if (!path_id)
-      return NULL;
-
-   path_id->real_path           = NULL;
-   path_id->archive_path        = NULL;
-   path_id->real_path_hash      = 0;
-   path_id->archive_path_hash   = 0;
-   path_id->is_archive          = false;
-   path_id->is_in_archive       = false;
-
-   if (!string_is_empty(path))
-   {
-      char real_path[PATH_MAX_LENGTH];
-      const char *archive_delim = NULL;
-      /* Get real path */
-      strlcpy(real_path, path, sizeof(real_path));
-      playlist_resolve_path(PLAYLIST_SAVE, false, real_path,
-            sizeof(real_path));
-
-      path_id->real_path      = strdup(real_path);
-      path_id->real_path_hash = playlist_path_hash(real_path);
-
-      /* Check archive status */
-      path_id->is_archive     = path_is_compressed_file(real_path);
-      archive_delim           = path_get_archive_delim(real_path);
-
-      /* If path refers to a file inside an archive,
-       * extract the path of the parent archive */
-      if (archive_delim)
-      {
-         char archive_path[PATH_MAX_LENGTH];
-         size_t len                  = (1 + archive_delim - real_path);
-         if (len >= PATH_MAX_LENGTH)
-            len                      = PATH_MAX_LENGTH;
-         strlcpy(archive_path, real_path, len * sizeof(char));
-
-         path_id->archive_path       = strdup(archive_path);
-         path_id->archive_path_hash  = playlist_path_hash(archive_path);
-         path_id->is_in_archive      = true;
-      }
-      else if (path_id->is_archive)
-      {
-         path_id->archive_path       = path_id->real_path;
-         path_id->archive_path_hash  = path_id->real_path_hash;
-      }
-   }
-
-   return path_id;
-}
-
-/**
- * playlist_path_equal:
- * @real_path           : 'Real' search path, generated by path_resolve_realpath()
- * @entry_path          : Existing playlist entry 'path' value
- *
- * Returns 'true' if real_path matches entry_path
- * (Taking into account relative paths, case insensitive
- * filesystems, 'incomplete' archive paths)
- **/
-static bool playlist_path_equal(const char *real_path,
-      const char *entry_path, const playlist_config_t *config)
-{
-   bool real_path_is_compressed;
-   bool entry_real_path_is_compressed;
-   char entry_real_path[PATH_MAX_LENGTH];
-
-   /* Sanity check */
-   if (string_is_empty(real_path)  ||
-       string_is_empty(entry_path) ||
-       !config)
-      return false;
-
-   /* Get entry 'real' path */
-   strlcpy(entry_real_path, entry_path, sizeof(entry_real_path));
-   playlist_resolve_path(PLAYLIST_LOAD, false, entry_real_path, sizeof(entry_real_path));
-   path_resolve_realpath(entry_real_path, sizeof(entry_real_path), true);
-
-   if (string_is_empty(entry_real_path))
-      return false;
-
-   /* First pass comparison */
-#ifdef _WIN32
-   /* Handle case-insensitive operating systems*/
-   if (string_is_equal_noncase(real_path, entry_real_path))
-      return true;
-#else
-   if (string_is_equal(real_path, entry_real_path))
-      return true;
-#endif
-
-#ifdef RARCH_INTERNAL
-   /* If fuzzy matching is disabled, we can give up now */
-   if (!config->fuzzy_archive_match)
-      return false;
-#endif
-
-   /* If we reach this point, we have to work
-    * harder...
-    * Need to handle a rather awkward archive file
-    * case where:
-    * - playlist path contains a properly formatted
-    *   [archive_path][delimiter][rom_file]
-    * - search path is just [archive_path]
-    * ...or vice versa.
-    * This pretty much always happens when a playlist
-    * is generated via scan content (which handles the
-    * archive paths correctly), but the user subsequently
-    * loads an archive file via the command line or some
-    * external launcher (where the [delimiter][rom_file]
-    * part is almost always omitted) */
-   real_path_is_compressed       = path_is_compressed_file(real_path);
-   entry_real_path_is_compressed = path_is_compressed_file(entry_real_path);
-
-   if ((real_path_is_compressed  && !entry_real_path_is_compressed) ||
-       (!real_path_is_compressed && entry_real_path_is_compressed))
-   {
-      const char *compressed_path_a  = real_path_is_compressed ? real_path       : entry_real_path;
-      const char *full_path          = real_path_is_compressed ? entry_real_path : real_path;
-      const char *delim              = path_get_archive_delim(full_path);
-
-      if (delim)
-      {
-         char compressed_path_b[PATH_MAX_LENGTH];
-         unsigned len = (unsigned)(1 + delim - full_path);
-
-         strlcpy(compressed_path_b, full_path,
-               (len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char));
-
-#ifdef _WIN32
-         /* Handle case-insensitive operating systems*/
-         if (string_is_equal_noncase(compressed_path_a, compressed_path_b))
-            return true;
-#else
-         if (string_is_equal(compressed_path_a, compressed_path_b))
-            return true;
-#endif
-      }
-   }
-
-   return false;
-}
-
-/**
- * playlist_path_matches_entry:
- * @path_id           : Path identity, containing 'real' path,
- *                      hash and archive status information
- * @entry             : Playlist entry to compare with path_id
- *
- * Returns 'true' if 'path_id' matches path information
- * contained in specified 'entry'. Will update path_id
- * cache inside specified 'entry', if not already present.
- **/
-static bool playlist_path_matches_entry(playlist_path_id_t *path_id,
-      struct playlist_entry *entry, const playlist_config_t *config)
-{
-   /* Sanity check */
-   if (!path_id ||
-       !entry ||
-       !config)
-      return false;
-
-   /* Check whether entry contains a path ID cache */
-   if (!entry->path_id)
-   {
-      if (!(entry->path_id = playlist_path_id_init(entry->path)))
-         return false;
-   }
-
-   /* Ensure we have valid real_path strings */
-   if (string_is_empty(path_id->real_path) ||
-       string_is_empty(entry->path_id->real_path))
-      return false;
-
-   /* First pass comparison */
-   if (path_id->real_path_hash ==
-         entry->path_id->real_path_hash)
-   {
-#ifdef _WIN32
-      /* Handle case-insensitive operating systems*/
-      if (string_is_equal_noncase(path_id->real_path,
-            entry->path_id->real_path))
-         return true;
-#else
-      if (string_is_equal(path_id->real_path,
-            entry->path_id->real_path))
-         return true;
-#endif
-   }
-
-#ifdef RARCH_INTERNAL
-   /* If fuzzy matching is disabled, we can give up now */
-   if (!config->fuzzy_archive_match)
-      return false;
-#endif
-
-   /* If we reach this point, we have to work
-    * harder...
-    * Need to handle a rather awkward archive file
-    * case where:
-    * - playlist path contains a properly formatted
-    *   [archive_path][delimiter][rom_file]
-    * - search path is just [archive_path]
-    * ...or vice versa.
-    * This pretty much always happens when a playlist
-    * is generated via scan content (which handles the
-    * archive paths correctly), but the user subsequently
-    * loads an archive file via the command line or some
-    * external launcher (where the [delimiter][rom_file]
-    * part is almost always omitted) */
-   if (((path_id->is_archive        && !path_id->is_in_archive)        && entry->path_id->is_in_archive) ||
-       ((entry->path_id->is_archive && !entry->path_id->is_in_archive) && path_id->is_in_archive))
-   {
-      /* Ensure we have valid parent archive path
-       * strings */
-      if (string_is_empty(path_id->archive_path) ||
-          string_is_empty(entry->path_id->archive_path))
-         return false;
-
-      if (path_id->archive_path_hash ==
-            entry->path_id->archive_path_hash)
-      {
-#ifdef _WIN32
-         /* Handle case-insensitive operating systems*/
-         if (string_is_equal_noncase(path_id->archive_path,
-               entry->path_id->archive_path))
-            return true;
-#else
-         if (string_is_equal(path_id->archive_path,
-               entry->path_id->archive_path))
-            return true;
-#endif
-      }
-   }
-
-   return false;
-}
-
-/**
- * playlist_core_path_equal:
- * @real_core_path  : 'Real' search path, generated by path_resolve_realpath()
- * @entry_core_path : Existing playlist entry 'core path' value
- * @config          : Playlist config parameters
- *
- * Returns 'true' if real_core_path matches entry_core_path
- * (Taking into account relative paths, case insensitive
- * filesystems)
- **/
-static bool playlist_core_path_equal(const char *real_core_path, const char *entry_core_path, const playlist_config_t *config)
-{
-   char entry_real_core_path[PATH_MAX_LENGTH];
-
-   /* Sanity check */
-   if (string_is_empty(real_core_path) || string_is_empty(entry_core_path))
-      return false;
-
-   /* Get entry 'real' core path */
-   strlcpy(entry_real_core_path, entry_core_path, sizeof(entry_real_core_path));
-   if (!string_is_equal(entry_real_core_path, FILE_PATH_DETECT) &&
-       !string_is_equal(entry_real_core_path, FILE_PATH_BUILTIN))
-      playlist_resolve_path(PLAYLIST_SAVE, true, entry_real_core_path,
-            sizeof(entry_real_core_path));
-
-   if (string_is_empty(entry_real_core_path))
-      return false;
-
-#ifdef _WIN32
-   /* Handle case-insensitive operating systems*/
-   if (string_is_equal_noncase(real_core_path, entry_real_core_path))
-      return true;
-#else
-   if (string_is_equal(real_core_path, entry_real_core_path))
-      return true;
-#endif
-
-   if (config->autofix_paths &&
-       core_info_core_file_id_is_equal(real_core_path, entry_core_path))
-      return true;
-
-   return false;
-}
-
-uint32_t playlist_get_size(playlist_t *playlist)
-{
-   if (!playlist)
-      return 0;
-   return (uint32_t)RBUF_LEN(playlist->entries);
-}
-
-char *playlist_get_conf_path(playlist_t *playlist)
-{
-   if (!playlist)
-      return NULL;
-   return playlist->config.path;
-}
-
-/**
- * playlist_get_index:
- * @playlist            : Playlist handle.
- * @idx                 : Index of playlist entry.
- * @path                : Path of playlist entry.
- * @core_path           : Core path of playlist entry.
- * @core_name           : Core name of playlist entry.
- *
- * Gets values of playlist index:
- **/
-void playlist_get_index(playlist_t *playlist,
-      size_t idx,
-      const struct playlist_entry **entry)
-{
-   if (!playlist || !entry || (idx >= RBUF_LEN(playlist->entries)))
-      return;
-
-   *entry = &playlist->entries[idx];
-}
-
-/**
- * playlist_free_entry:
- * @entry               : Playlist entry handle.
- *
- * Frees playlist entry.
- **/
-static void playlist_free_entry(struct playlist_entry *entry)
-{
-   if (!entry)
-      return;
-
-   if (entry->path)
-      free(entry->path);
-   if (entry->label)
-      free(entry->label);
-   if (entry->core_path)
-      free(entry->core_path);
-   if (entry->core_name)
-      free(entry->core_name);
-   if (entry->db_name)
-      free(entry->db_name);
-   if (entry->crc32)
-      free(entry->crc32);
-   if (entry->subsystem_ident)
-      free(entry->subsystem_ident);
-   if (entry->subsystem_name)
-      free(entry->subsystem_name);
-   if (entry->runtime_str)
-      free(entry->runtime_str);
-   if (entry->last_played_str)
-      free(entry->last_played_str);
-   if (entry->subsystem_roms)
-      string_list_free(entry->subsystem_roms);
-   if (entry->path_id)
-      playlist_path_id_free(entry->path_id);
-
-   entry->path               = NULL;
-   entry->label              = NULL;
-   entry->core_path          = NULL;
-   entry->core_name          = NULL;
-   entry->db_name            = NULL;
-   entry->crc32              = NULL;
-   entry->subsystem_ident    = NULL;
-   entry->subsystem_name     = NULL;
-   entry->runtime_str        = NULL;
-   entry->last_played_str    = NULL;
-   entry->subsystem_roms     = NULL;
-   entry->path_id            = NULL;
-   entry->entry_slot         = 0;
-   entry->runtime_status     = PLAYLIST_RUNTIME_UNKNOWN;
-   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;
-}
-
-/**
- * playlist_delete_index:
- * @playlist            : Playlist handle.
- * @idx                 : Index of playlist entry.
- *
- * Delete the entry at the index:
- **/
-void playlist_delete_index(playlist_t *playlist,
-      size_t idx)
-{
-   size_t len;
-   struct playlist_entry *entry_to_delete;
-
-   if (!playlist)
-      return;
-
-   len = RBUF_LEN(playlist->entries);
-   if (idx >= len)
-      return;
-
-   /* Free unwanted entry */
-   entry_to_delete = (struct playlist_entry *)(playlist->entries + idx);
-   if (entry_to_delete)
-      playlist_free_entry(entry_to_delete);
-
-   /* Shift remaining entries to fill the gap */
-   memmove(playlist->entries + idx, playlist->entries + idx + 1,
-         (len - 1 - idx) * sizeof(struct playlist_entry));
-
-   RBUF_RESIZE(playlist->entries, len - 1);
-
-   playlist->modified = true;
-}
-
-/**
- * playlist_delete_by_path:
- * @playlist            : Playlist handle.
- * @search_path         : Content path.
- *
- * Deletes all entries with content path
- * matching 'search_path'
- **/
-void playlist_delete_by_path(playlist_t *playlist,
-      const char *search_path)
-{
-   playlist_path_id_t *path_id = NULL;
-   size_t i                    = 0;
-
-   if (!playlist || string_is_empty(search_path))
-      return;
-
-   if (!(path_id = playlist_path_id_init(search_path)))
-      return;
-
-   while (i < RBUF_LEN(playlist->entries))
-   {
-      if (!playlist_path_matches_entry(path_id,
-            &playlist->entries[i], &playlist->config))
-      {
-         i++;
-         continue;
-      }
-
-      /* Paths are equal - delete entry */
-      playlist_delete_index(playlist, i);
-
-      /* Entries are shifted up by the delete
-       * operation - *do not* increment i */
-   }
-
-   playlist_path_id_free(path_id);
-}
-
-void playlist_get_index_by_path(playlist_t *playlist,
-      const char *search_path,
-      const struct playlist_entry **entry)
-{
-   playlist_path_id_t *path_id = NULL;
-   size_t i, len;
-
-   if (!playlist || !entry || string_is_empty(search_path))
-      return;
-
-   if (!(path_id = playlist_path_id_init(search_path)))
-      return;
-
-   for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-   {
-      if (!playlist_path_matches_entry(path_id,
-            &playlist->entries[i], &playlist->config))
-         continue;
-
-      *entry = &playlist->entries[i];
-      break;
-   }
-
-   playlist_path_id_free(path_id);
-}
-
-bool playlist_entry_exists(playlist_t *playlist,
-      const char *path)
-{
-   playlist_path_id_t *path_id = NULL;
-   size_t i, len;
-
-   if (!playlist || string_is_empty(path))
-      return false;
-
-   if (!(path_id = playlist_path_id_init(path)))
-      return false;
-
-   for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-   {
-      if (playlist_path_matches_entry(path_id,
-            &playlist->entries[i], &playlist->config))
-      {
-         playlist_path_id_free(path_id);
-         return true;
-      }
-   }
-
-   playlist_path_id_free(path_id);
-   return false;
-}
-
-void playlist_update(playlist_t *playlist, size_t idx,
-      const struct playlist_entry *update_entry)
-{
-   struct playlist_entry *entry = NULL;
-
-   if (!playlist || idx >= RBUF_LEN(playlist->entries))
-      return;
-
-   entry            = &playlist->entries[idx];
-
-   if (update_entry->path && (update_entry->path != entry->path))
-   {
-      if (entry->path)
-         free(entry->path);
-      entry->path        = strdup(update_entry->path);
-
-      if (entry->path_id)
-      {
-         playlist_path_id_free(entry->path_id);
-         entry->path_id  = NULL;
-      }
-
-      playlist->modified = true;
-   }
-
-   if (update_entry->label && (update_entry->label != entry->label))
-   {
-      if (entry->label)
-         free(entry->label);
-      entry->label       = strdup(update_entry->label);
-      playlist->modified = true;
-   }
-
-   if (update_entry->core_path && (update_entry->core_path != entry->core_path))
-   {
-      if (entry->core_path)
-         free(entry->core_path);
-      entry->core_path   = NULL;
-      entry->core_path   = strdup(update_entry->core_path);
-      playlist->modified = true;
-   }
-
-   if (update_entry->core_name && (update_entry->core_name != entry->core_name))
-   {
-      if (entry->core_name)
-         free(entry->core_name);
-      entry->core_name   = strdup(update_entry->core_name);
-      playlist->modified = true;
-   }
-
-   if (update_entry->db_name && (update_entry->db_name != entry->db_name))
-   {
-      if (entry->db_name)
-         free(entry->db_name);
-      entry->db_name     = strdup(update_entry->db_name);
-      playlist->modified = true;
-   }
-
-   if (update_entry->crc32 && (update_entry->crc32 != entry->crc32))
-   {
-      if (entry->crc32)
-         free(entry->crc32);
-      entry->crc32       = strdup(update_entry->crc32);
-      playlist->modified = true;
-   }
-}
-
-void playlist_update_runtime(playlist_t *playlist, size_t idx,
-      const struct playlist_entry *update_entry,
-      bool register_update)
-{
-   struct playlist_entry *entry = NULL;
-
-   if (!playlist || idx >= RBUF_LEN(playlist->entries))
-      return;
-
-   entry            = &playlist->entries[idx];
-
-   if (update_entry->path && (update_entry->path != entry->path))
-   {
-      if (entry->path)
-         free(entry->path);
-      entry->path        = strdup(update_entry->path);
-
-      if (entry->path_id)
-      {
-         playlist_path_id_free(entry->path_id);
-         entry->path_id  = NULL;
-      }
-
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->core_path && (update_entry->core_path != entry->core_path))
-   {
-      if (entry->core_path)
-         free(entry->core_path);
-      entry->core_path   = NULL;
-      entry->core_path   = strdup(update_entry->core_path);
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->runtime_status != entry->runtime_status)
-   {
-      entry->runtime_status = update_entry->runtime_status;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->runtime_hours != entry->runtime_hours)
-   {
-      entry->runtime_hours = update_entry->runtime_hours;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->runtime_minutes != entry->runtime_minutes)
-   {
-      entry->runtime_minutes = update_entry->runtime_minutes;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->runtime_seconds != entry->runtime_seconds)
-   {
-      entry->runtime_seconds = update_entry->runtime_seconds;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->last_played_year != entry->last_played_year)
-   {
-      entry->last_played_year = update_entry->last_played_year;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->last_played_month != entry->last_played_month)
-   {
-      entry->last_played_month = update_entry->last_played_month;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->last_played_day != entry->last_played_day)
-   {
-      entry->last_played_day = update_entry->last_played_day;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->last_played_hour != entry->last_played_hour)
-   {
-      entry->last_played_hour = update_entry->last_played_hour;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->last_played_minute != entry->last_played_minute)
-   {
-      entry->last_played_minute = update_entry->last_played_minute;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->last_played_second != entry->last_played_second)
-   {
-      entry->last_played_second = update_entry->last_played_second;
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->runtime_str && (update_entry->runtime_str != entry->runtime_str))
-   {
-      if (entry->runtime_str)
-         free(entry->runtime_str);
-      entry->runtime_str = NULL;
-      entry->runtime_str = strdup(update_entry->runtime_str);
-      playlist->modified = playlist->modified || register_update;
-   }
-
-   if (update_entry->last_played_str && (update_entry->last_played_str != entry->last_played_str))
-   {
-      if (entry->last_played_str)
-         free(entry->last_played_str);
-      entry->last_played_str = NULL;
-      entry->last_played_str = strdup(update_entry->last_played_str);
-      playlist->modified = playlist->modified || register_update;
-   }
-}
-
-bool playlist_push_runtime(playlist_t *playlist,
-      const struct playlist_entry *entry)
-{
-   playlist_path_id_t *path_id = NULL;
-   size_t i, len;
-   char real_core_path[PATH_MAX_LENGTH];
-
-   if (!playlist || !entry)
-      goto error;
-
-   if (string_is_empty(entry->core_path))
-   {
-      RARCH_ERR("Cannot push NULL or empty core path into the playlist.\n");
-      goto error;
-   }
-
-   /* Get path ID */
-   if (!(path_id = playlist_path_id_init(entry->path)))
-      goto error;
-
-   /* Get 'real' core path */
-   strlcpy(real_core_path, entry->core_path, sizeof(real_core_path));
-   if (!string_is_equal(real_core_path, FILE_PATH_DETECT) &&
-       !string_is_equal(real_core_path, FILE_PATH_BUILTIN))
-      playlist_resolve_path(PLAYLIST_SAVE, true, real_core_path,
-             sizeof(real_core_path));
-
-   if (string_is_empty(real_core_path))
-   {
-      RARCH_ERR("Cannot push NULL or empty core path into the playlist.\n");
-      goto error;
-   }
-
-   len = RBUF_LEN(playlist->entries);
-   for (i = 0; i < len; i++)
-   {
-      struct playlist_entry tmp;
-      bool equal_path  = (string_is_empty(path_id->real_path) &&
-            string_is_empty(playlist->entries[i].path));
-
-      equal_path       = equal_path || playlist_path_matches_entry(
-            path_id, &playlist->entries[i], &playlist->config);
-
-      if (!equal_path)
-         continue;
-
-      /* Core name can have changed while still being the same core.
-       * Differentiate based on the core path only. */
-      if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path, &playlist->config))
-         continue;
-
-      /* If top entry, we don't want to push a new entry since
-       * the top and the entry to be pushed are the same. */
-      if (i == 0)
-         goto error;
-
-      /* Seen it before, bump to top. */
-      tmp = playlist->entries[i];
-      memmove(playlist->entries + 1, playlist->entries,
-            i * sizeof(struct playlist_entry));
-      playlist->entries[0] = tmp;
-
-      goto success;
-   }
-
-   if (playlist->config.capacity == 0)
-      goto error;
-
-   if (len == playlist->config.capacity)
-   {
-      struct playlist_entry *last_entry = &playlist->entries[len - 1];
-      playlist_free_entry(last_entry);
-      len--;
-   }
-   else
-   {
-      /* Allocate memory to fit one more item and resize the buffer */
-      if (!RBUF_TRYFIT(playlist->entries, len + 1))
-         goto error; /* out of memory */
-      RBUF_RESIZE(playlist->entries, len + 1);
-   }
-
-   if (playlist->entries)
-   {
-      memmove(playlist->entries + 1, playlist->entries,
-            len * sizeof(struct playlist_entry));
-
-      playlist->entries[0].path            = NULL;
-      playlist->entries[0].core_path       = NULL;
-
-      if (!string_is_empty(path_id->real_path))
-         playlist->entries[0].path         = strdup(path_id->real_path);
-      playlist->entries[0].path_id         = path_id;
-      path_id                              = NULL;
-
-      if (!string_is_empty(real_core_path))
-         playlist->entries[0].core_path    = strdup(real_core_path);
-
-      playlist->entries[0].runtime_status = entry->runtime_status;
-      playlist->entries[0].runtime_hours = entry->runtime_hours;
-      playlist->entries[0].runtime_minutes = entry->runtime_minutes;
-      playlist->entries[0].runtime_seconds = entry->runtime_seconds;
-      playlist->entries[0].last_played_year = entry->last_played_year;
-      playlist->entries[0].last_played_month = entry->last_played_month;
-      playlist->entries[0].last_played_day = entry->last_played_day;
-      playlist->entries[0].last_played_hour = entry->last_played_hour;
-      playlist->entries[0].last_played_minute = entry->last_played_minute;
-      playlist->entries[0].last_played_second = entry->last_played_second;
-
-      playlist->entries[0].runtime_str        = NULL;
-      playlist->entries[0].last_played_str    = NULL;
-
-      if (!string_is_empty(entry->runtime_str))
-         playlist->entries[0].runtime_str     = strdup(entry->runtime_str);
-      if (!string_is_empty(entry->last_played_str))
-         playlist->entries[0].last_played_str = strdup(entry->last_played_str);
-   }
-
-success:
-   if (path_id)
-      playlist_path_id_free(path_id);
-   playlist->modified = true;
-   return true;
-
-error:
-   if (path_id)
-      playlist_path_id_free(path_id);
-   return false;
-}
-
-/**
- * playlist_resolve_path:
- * @mode      : PLAYLIST_LOAD or PLAYLIST_SAVE
- * @is_core   : Set true if path to be resolved is a core file
- * @path      : The path to be modified
- *
- * Resolves the path of an item, such as the content path or path to the core, to a format
- * appropriate for saving or loading depending on the @mode parameter
- *
- * Can be platform specific. File paths for saving can be abbreviated to avoid saving absolute
- * paths, as the base directory (home or application dir) may change after each subsequent
- * install (iOS)
-**/
-void playlist_resolve_path(enum playlist_file_mode mode,
-      bool is_core, char *path, size_t len)
-{
-#ifdef HAVE_COCOATOUCH
-   char tmp[PATH_MAX_LENGTH];
-
-   if (mode == PLAYLIST_LOAD)
-   {
-      fill_pathname_expand_special(tmp, path, sizeof(tmp));
-      strlcpy(path, tmp, len);
-   }
-   else
-   {
-      /* iOS needs to call realpath here since the call
-       * above fails due to possibly buffer related issues.
-       * Try to expand the path to ensure that it gets saved
-       * correctly. The path can be abbreviated if saving to
-       * a playlist from another playlist (ex: content history to favorites)
-       */
-      char tmp2[PATH_MAX_LENGTH];
-      fill_pathname_expand_special(tmp, path, sizeof(tmp));
-      realpath(tmp, tmp2);
-      fill_pathname_abbreviate_special(path, tmp2, len);
-   }
-#else
-   bool resolve_symlinks = true;
-
-   if (mode == PLAYLIST_LOAD)
-      return;
-
-#if defined(ANDROID)
-   /* Can't resolve symlinks when dealing with cores
-    * installed via play feature delivery, because the
-    * source files have non-standard file names (which
-    * will not be recognised by regular core handling
-    * routines) */
-   if (is_core)
-      resolve_symlinks = !play_feature_delivery_enabled();
-#endif
-
-   path_resolve_realpath(path, len, resolve_symlinks);
-#endif
-}
-
-/**
- * playlist_content_path_is_valid:
- * @path      : Content path
- *
- * Checks whether specified playlist content path
- * refers to an existent file. Handles all playlist
- * content path 'types' (i.e. can validate paths
- * referencing files inside archives).
- *
- * Returns true if file referenced by content
- * path exists on the host filesystem.
- **/
-bool playlist_content_path_is_valid(const char *path)
-{
-   /* Sanity check */
-   if (string_is_empty(path))
-      return false;
-
-   /* If content is inside an archive, special
-    * handling is required... */
-   if (path_contains_compressed_file(path))
-   {
-      char archive_path[PATH_MAX_LENGTH];
-      const char *delim                  = path_get_archive_delim(path);
-      size_t len                         = 0;
-      struct string_list *archive_list   = NULL;
-      const char *content_file           = NULL;
-      bool content_found                 = false;
-
-      if (!delim)
-         return false;
-
-      /* Get path of 'parent' archive file */
-      len = (size_t)(1 + delim - path);
-      strlcpy(archive_path, path,
-            (len < PATH_MAX_LENGTH ? len : PATH_MAX_LENGTH) * sizeof(char));
-
-      /* Check if archive itself exists */
-      if (!path_is_valid(archive_path))
-         return false;
-
-      /* Check if file exists inside archive */
-      if (!(archive_list = file_archive_get_file_list(archive_path, NULL)))
-         return false;
-
-      /* > Get playlist entry content file name
-       *   (sans archive file path) */
-      content_file = delim;
-      content_file++;
-
-      if (!string_is_empty(content_file))
-      {
-         size_t i;
-
-         /* > Loop over archive file contents */
-         for (i = 0; i < archive_list->size; i++)
-         {
-            const char *archive_file = archive_list->elems[i].data;
-
-            if (string_is_empty(archive_file))
-               continue;
-
-            if (string_is_equal(content_file, archive_file))
-            {
-               content_found = true;
-               break;
-            }
-         }
-      }
-
-      /* Clean up */
-      string_list_free(archive_list);
-
-      return content_found;
-   }
-   /* This is a 'normal' path - just check if
-    * it's valid */
-   return path_is_valid(path);
-}
-
-/**
- * playlist_push:
- * @playlist        	   : Playlist handle.
- *
- * Push entry to top of playlist.
- **/
-bool playlist_push(playlist_t *playlist,
-      const struct playlist_entry *entry)
-{
-   size_t i, len;
-   char real_core_path[PATH_MAX_LENGTH];
-   playlist_path_id_t *path_id = NULL;
-   const char *core_name       = entry->core_name;
-   bool entry_updated          = false;
-
-   if (!playlist || !entry)
-      goto error;
-
-   if (string_is_empty(entry->core_path))
-   {
-      RARCH_ERR("Cannot push NULL or empty core path into the playlist.\n");
-      goto error;
-   }
-
-   /* Get path ID */
-   if (!(path_id = playlist_path_id_init(entry->path)))
-      goto error;
-
-   /* Get 'real' core path */
-   strlcpy(real_core_path, entry->core_path, sizeof(real_core_path));
-   if (!string_is_equal(real_core_path, FILE_PATH_DETECT) &&
-       !string_is_equal(real_core_path, FILE_PATH_BUILTIN))
-      playlist_resolve_path(PLAYLIST_SAVE, true, real_core_path,
-             sizeof(real_core_path));
-
-   if (string_is_empty(real_core_path))
-   {
-      RARCH_ERR("Cannot push NULL or empty core path into the playlist.\n");
-      goto error;
-   }
-
-   if (string_is_empty(core_name))
-   {
-      static char base_path[255] = {0};
-      fill_pathname_base(base_path, real_core_path, sizeof(base_path));
-      path_remove_extension(base_path);
-
-      core_name = base_path;
-
-      if (string_is_empty(core_name))
-      {
-         RARCH_ERR("Cannot push NULL or empty core name into the playlist.\n");
-         goto error;
-      }
-   }
-
-   len = RBUF_LEN(playlist->entries);
-   for (i = 0; i < len; i++)
-   {
-      struct playlist_entry tmp;
-      bool equal_path  = (string_is_empty(path_id->real_path) &&
-            string_is_empty(playlist->entries[i].path));
-
-      equal_path       = equal_path || playlist_path_matches_entry(
-            path_id, &playlist->entries[i], &playlist->config);
-
-      if (!equal_path)
-         continue;
-
-      /* Core name can have changed while still being the same core.
-       * Differentiate based on the core path only. */
-      if (!playlist_core_path_equal(real_core_path, playlist->entries[i].core_path, &playlist->config))
-         continue;
-
-      if (     !string_is_empty(entry->subsystem_ident)
-            && !string_is_empty(playlist->entries[i].subsystem_ident)
-            && !string_is_equal(playlist->entries[i].subsystem_ident, entry->subsystem_ident))
-         continue;
-
-      if (      string_is_empty(entry->subsystem_ident)
-            && !string_is_empty(playlist->entries[i].subsystem_ident))
-         continue;
-
-      if (    !string_is_empty(entry->subsystem_ident)
-            && string_is_empty(playlist->entries[i].subsystem_ident))
-         continue;
-
-      if (     !string_is_empty(entry->subsystem_name)
-            && !string_is_empty(playlist->entries[i].subsystem_name)
-            && !string_is_equal(playlist->entries[i].subsystem_name, entry->subsystem_name))
-         continue;
-
-      if (      string_is_empty(entry->subsystem_name)
-            && !string_is_empty(playlist->entries[i].subsystem_name))
-         continue;
-
-      if (     !string_is_empty(entry->subsystem_name)
-            &&  string_is_empty(playlist->entries[i].subsystem_name))
-         continue;
-
-      if (entry->subsystem_roms)
-      {
-         unsigned j;
-         const struct string_list *roms = playlist->entries[i].subsystem_roms;
-         bool                   unequal = false;
-
-         if (entry->subsystem_roms->size != roms->size)
-            continue;
-
-         for (j = 0; j < entry->subsystem_roms->size; j++)
-         {
-            char real_rom_path[PATH_MAX_LENGTH];
-
-            if (!string_is_empty(entry->subsystem_roms->elems[j].data))
-            {
-               strlcpy(real_rom_path, entry->subsystem_roms->elems[j].data, sizeof(real_rom_path));
-               path_resolve_realpath(real_rom_path, sizeof(real_rom_path), true);
-            }
-            else
-               real_rom_path[0] = '\0';
-
-            if (!playlist_path_equal(real_rom_path, roms->elems[j].data,
-                     &playlist->config))
-            {
-               unequal = true;
-               break;
-            }
-         }
-
-         if (unequal)
-            continue;
-      }
-
-      if (playlist->entries[i].entry_slot != entry->entry_slot)
-      {
-         playlist->entries[i].entry_slot  = entry->entry_slot;
-         entry_updated                    = true;
-      }
-
-      /* If content was previously loaded via file browser
-       * or command line, certain entry values will be missing.
-       * If we are now loading the same content from a playlist,
-       * fill in any blanks */
-      if (!playlist->entries[i].label && !string_is_empty(entry->label))
-      {
-         playlist->entries[i].label       = strdup(entry->label);
-         entry_updated                    = true;
-      }
-      if (!playlist->entries[i].crc32 && !string_is_empty(entry->crc32))
-      {
-         playlist->entries[i].crc32       = strdup(entry->crc32);
-         entry_updated                    = true;
-      }
-      if (!playlist->entries[i].db_name && !string_is_empty(entry->db_name))
-      {
-         playlist->entries[i].db_name     = strdup(entry->db_name);
-         entry_updated                    = true;
-      }
-
-      /* If top entry, we don't want to push a new entry since
-       * the top and the entry to be pushed are the same. */
-      if (i == 0)
-      {
-         if (entry_updated)
-            goto success;
-
-         goto error;
-      }
-
-      /* Seen it before, bump to top. */
-      tmp = playlist->entries[i];
-      memmove(playlist->entries + 1, playlist->entries,
-            i * sizeof(struct playlist_entry));
-      playlist->entries[0] = tmp;
-
-      goto success;
-   }
-
-   if (playlist->config.capacity == 0)
-      goto error;
-
-   if (len == playlist->config.capacity)
-   {
-      struct playlist_entry *last_entry = &playlist->entries[len - 1];
-      playlist_free_entry(last_entry);
-      len--;
-   }
-   else
-   {
-      /* Allocate memory to fit one more item and resize the buffer */
-      if (!RBUF_TRYFIT(playlist->entries, len + 1))
-         goto error; /* out of memory */
-      RBUF_RESIZE(playlist->entries, len + 1);
-   }
-
-   if (playlist->entries)
-   {
-      memmove(playlist->entries + 1, playlist->entries,
-            len * 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].subsystem_ident    = NULL;
-      playlist->entries[0].subsystem_name     = NULL;
-      playlist->entries[0].runtime_str        = NULL;
-      playlist->entries[0].last_played_str    = NULL;
-      playlist->entries[0].subsystem_roms     = NULL;
-      playlist->entries[0].path_id            = NULL;
-      playlist->entries[0].runtime_status     = PLAYLIST_RUNTIME_UNKNOWN;
-      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_id->real_path))
-         playlist->entries[0].path            = strdup(path_id->real_path);
-      playlist->entries[0].path_id            = path_id;
-      path_id                                 = NULL;
-
-      playlist->entries[0].entry_slot         = entry->entry_slot;
-
-      if (!string_is_empty(entry->label))
-         playlist->entries[0].label           = strdup(entry->label);
-      if (!string_is_empty(real_core_path))
-         playlist->entries[0].core_path       = strdup(real_core_path);
-      if (!string_is_empty(core_name))
-         playlist->entries[0].core_name       = strdup(core_name);
-      if (!string_is_empty(entry->db_name))
-         playlist->entries[0].db_name         = strdup(entry->db_name);
-      if (!string_is_empty(entry->crc32))
-         playlist->entries[0].crc32           = strdup(entry->crc32);
-      if (!string_is_empty(entry->subsystem_ident))
-         playlist->entries[0].subsystem_ident = strdup(entry->subsystem_ident);
-      if (!string_is_empty(entry->subsystem_name))
-         playlist->entries[0].subsystem_name  = strdup(entry->subsystem_name);
-
-      if (entry->subsystem_roms)
-      {
-         union string_list_elem_attr attributes = {0};
-
-         playlist->entries[0].subsystem_roms    = string_list_new();
-
-         for (i = 0; i < entry->subsystem_roms->size; i++)
-            string_list_append(playlist->entries[0].subsystem_roms, entry->subsystem_roms->elems[i].data, attributes);
-      }
-   }
-
-success:
-   if (path_id)
-      playlist_path_id_free(path_id);
-   playlist->modified = true;
-   return true;
-
-error:
-   if (path_id)
-      playlist_path_id_free(path_id);
-   return false;
-}
-
-void playlist_write_runtime_file(playlist_t *playlist)
-{
-   size_t i, len;
-   intfstream_t *file  = NULL;
-   rjsonwriter_t* writer;
-
-   if (!playlist || !playlist->modified)
-      return;
-
-   if (!(file = intfstream_open_file(playlist->config.path,
-         RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE)))
-   {
-      RARCH_ERR("Failed to write to playlist file: \"%s\".\n", playlist->config.path);
-      return;
-   }
-
-   if (!(writer = rjsonwriter_open_stream(file)))
-   {
-      RARCH_ERR("Failed to create JSON writer\n");
-      goto end;
-   }
-
-   rjsonwriter_raw(writer, "{", 1);
-   rjsonwriter_raw(writer, "\n", 1);
-   rjsonwriter_add_spaces(writer, 2);
-   rjsonwriter_add_string(writer, "version");
-   rjsonwriter_raw(writer, ":", 1);
-   rjsonwriter_raw(writer, " ", 1);
-   rjsonwriter_add_string(writer, "1.0");
-   rjsonwriter_raw(writer, ",", 1);
-   rjsonwriter_raw(writer, "\n", 1);
-   rjsonwriter_add_spaces(writer, 2);
-   rjsonwriter_add_string(writer, "items");
-   rjsonwriter_raw(writer, ":", 1);
-   rjsonwriter_raw(writer, " ", 1);
-   rjsonwriter_raw(writer, "[", 1);
-   rjsonwriter_raw(writer, "\n", 1);
-
-   for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-   {
-      rjsonwriter_add_spaces(writer, 4);
-      rjsonwriter_raw(writer, "{", 1);
-
-      rjsonwriter_raw(writer, "\n", 1);
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "path");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_add_string(writer, playlist->entries[i].path);
-      rjsonwriter_raw(writer, ",", 1);
-
-      rjsonwriter_raw(writer, "\n", 1);
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "core_path");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_add_string(writer, playlist->entries[i].core_path);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "runtime_hours");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].runtime_hours);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "runtime_minutes");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].runtime_minutes);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "runtime_seconds");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].runtime_seconds);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "last_played_year");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].last_played_year);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "last_played_month");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].last_played_month);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "last_played_day");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].last_played_day);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "last_played_hour");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].last_played_hour);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "last_played_minute");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].last_played_minute);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 6);
-      rjsonwriter_add_string(writer, "last_played_second");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%u", playlist->entries[i].last_played_second);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 4);
-      rjsonwriter_raw(writer, "}", 1);
-
-      if (i < len - 1)
-         rjsonwriter_raw(writer, ",", 1);
-
-      rjsonwriter_raw(writer, "\n", 1);
-   }
-
-   rjsonwriter_add_spaces(writer, 2);
-   rjsonwriter_raw(writer, "]", 1);
-   rjsonwriter_raw(writer, "\n", 1);
-   rjsonwriter_raw(writer, "}", 1);
-   rjsonwriter_raw(writer, "\n", 1);
-   rjsonwriter_free(writer);
-
-   playlist->modified        = false;
-   playlist->old_format      = false;
-   playlist->compressed      = false;
-
-   RARCH_LOG("[Playlist]: Written to playlist file: \"%s\".\n", playlist->config.path);
-end:
-   intfstream_close(file);
-   free(file);
-}
-
-void playlist_write_file(playlist_t *playlist)
-{
-   size_t i, len;
-   intfstream_t *file = NULL;
-   bool compressed    = false;
-
-   /* Playlist will be written if any of the
-    * following are true:
-    * > 'modified' flag is set
-    * > Current playlist format (old/new) does not
-    *   match requested
-    * > Current playlist compression status does
-    *   not match requested */
-   if (!playlist ||
-       !(playlist->modified ||
-#if defined(HAVE_ZLIB)
-        (playlist->compressed != playlist->config.compress) ||
-#endif
-        (playlist->old_format != playlist->config.old_format)))
-      return;
-
-#if defined(HAVE_ZLIB)
-   if (playlist->config.compress)
-      file = intfstream_open_rzip_file(playlist->config.path,
-            RETRO_VFS_FILE_ACCESS_WRITE);
-   else
-#endif
-      file = intfstream_open_file(playlist->config.path,
-            RETRO_VFS_FILE_ACCESS_WRITE,
-            RETRO_VFS_FILE_ACCESS_HINT_NONE);
-
-   if (!file)
-   {
-      RARCH_ERR("Failed to write to playlist file: \"%s\".\n", playlist->config.path);
-      return;
-   }
-
-   /* Get current file compression state */
-   compressed = intfstream_is_compressed(file);
-
-#ifdef RARCH_INTERNAL
-   if (playlist->config.old_format)
-   {
-      for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-         intfstream_printf(file, "%s\n%s\n%s\n%s\n%s\n%s\n",
-               playlist->entries[i].path      ? playlist->entries[i].path      : "",
-               playlist->entries[i].label     ? playlist->entries[i].label     : "",
-               playlist->entries[i].core_path ? playlist->entries[i].core_path : "",
-               playlist->entries[i].core_name ? playlist->entries[i].core_name : "",
-               playlist->entries[i].crc32     ? playlist->entries[i].crc32     : "",
-               playlist->entries[i].db_name   ? playlist->entries[i].db_name   : ""
-               );
-
-      /* Add metadata lines
-       * > We add these at the end of the file to prevent
-       *   breakage if the playlist is loaded with an older
-       *   version of RetroArch */
-      intfstream_printf(
-            file,
-            "default_core_path = \"%s\"\n"
-            "default_core_name = \"%s\"\n"
-            "label_display_mode = \"%d\"\n"
-            "thumbnail_mode = \"%d|%d\"\n"
-            "sort_mode = \"%d\"\n",
-            playlist->default_core_path ? playlist->default_core_path : "",
-            playlist->default_core_name ? playlist->default_core_name : "",
-            playlist->label_display_mode,
-            playlist->right_thumbnail_mode, playlist->left_thumbnail_mode,
-            playlist->sort_mode);
-
-      playlist->old_format = true;
-   }
-   else
-#endif
-   {
-      rjsonwriter_t* writer = rjsonwriter_open_stream(file);
-      if (!writer)
-      {
-         RARCH_ERR("Failed to create JSON writer\n");
-         goto end;
-      }
-      /*  When compressing playlists, human readability
-       *   is not a factor - can skip all indentation
-       *   and new line characters */
-      if (compressed)
-         rjsonwriter_set_options(writer, RJSONWRITER_OPTION_SKIP_WHITESPACE);
-
-      rjsonwriter_raw(writer, "{", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "version");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_add_string(writer, "1.5");
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "default_core_path");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_add_string(writer, playlist->default_core_path);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "default_core_name");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_add_string(writer, playlist->default_core_name);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      if (!string_is_empty(playlist->base_content_directory))
-      {
-         rjsonwriter_add_spaces(writer, 2);
-         rjsonwriter_add_string(writer, "base_content_directory");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->base_content_directory);
-         rjsonwriter_raw(writer, ",", 1);
-         rjsonwriter_raw(writer, "\n", 1);
-      }
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "label_display_mode");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%d", (int)playlist->label_display_mode);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "right_thumbnail_mode");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%d", (int)playlist->right_thumbnail_mode);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "left_thumbnail_mode");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%d", (int)playlist->left_thumbnail_mode);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "sort_mode");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_rawf(writer, "%d", (int)playlist->sort_mode);
-      rjsonwriter_raw(writer, ",", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      if (!string_is_empty(playlist->scan_record.content_dir))
-      {
-         rjsonwriter_add_spaces(writer, 2);
-         rjsonwriter_add_string(writer, "scan_content_dir");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->scan_record.content_dir);
-         rjsonwriter_raw(writer, ",", 1);
-         rjsonwriter_raw(writer, "\n", 1);
-
-         rjsonwriter_add_spaces(writer, 2);
-         rjsonwriter_add_string(writer, "scan_file_exts");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->scan_record.file_exts);
-         rjsonwriter_raw(writer, ",", 1);
-         rjsonwriter_raw(writer, "\n", 1);
-
-         rjsonwriter_add_spaces(writer, 2);
-         rjsonwriter_add_string(writer, "scan_dat_file_path");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->scan_record.dat_file_path);
-         rjsonwriter_raw(writer, ",", 1);
-         rjsonwriter_raw(writer, "\n", 1);
-
-         rjsonwriter_add_spaces(writer, 2);
-         rjsonwriter_add_string(writer, "scan_search_recursively");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         {
-            bool value = playlist->scan_record.search_recursively;
-            rjsonwriter_raw(writer, (value ? "true" : "false"), (value ? 4 : 5));
-         }
-         rjsonwriter_raw(writer, ",", 1);
-         rjsonwriter_raw(writer, "\n", 1);
-
-         rjsonwriter_add_spaces(writer, 2);
-         rjsonwriter_add_string(writer, "scan_search_archives");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         {
-            bool value = playlist->scan_record.search_archives;
-            rjsonwriter_raw(writer, (value ? "true" : "false"), (value ? 4 : 5));
-         }
-         rjsonwriter_raw(writer, ",", 1);
-         rjsonwriter_raw(writer, "\n", 1);
-
-         rjsonwriter_add_spaces(writer, 2);
-         rjsonwriter_add_string(writer, "scan_filter_dat_content");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         {
-            bool value = playlist->scan_record.filter_dat_content;
-            rjsonwriter_raw(writer, (value ? "true" : "false"), (value ? 4 : 5));
-         }
-         rjsonwriter_raw(writer, ",", 1);
-         rjsonwriter_raw(writer, "\n", 1);
-      }
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_add_string(writer, "items");
-      rjsonwriter_raw(writer, ":", 1);
-      rjsonwriter_raw(writer, " ", 1);
-      rjsonwriter_raw(writer, "[", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-      {
-         rjsonwriter_add_spaces(writer, 4);
-         rjsonwriter_raw(writer, "{", 1);
-
-         rjsonwriter_raw(writer, "\n", 1);
-         rjsonwriter_add_spaces(writer, 6);
-         rjsonwriter_add_string(writer, "path");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->entries[i].path);
-         rjsonwriter_raw(writer, ",", 1);
-
-         if (playlist->entries[i].entry_slot)
-         {
-            rjsonwriter_raw(writer, "\n", 1);
-            rjsonwriter_add_spaces(writer, 6);
-            rjsonwriter_add_string(writer, "entry_slot");
-            rjsonwriter_raw(writer, ":", 1);
-            rjsonwriter_raw(writer, " ", 1);
-            rjsonwriter_rawf(writer, "%d", (int)playlist->entries[i].entry_slot);
-            rjsonwriter_raw(writer, ",", 1);
-         }
-
-         rjsonwriter_raw(writer, "\n", 1);
-         rjsonwriter_add_spaces(writer, 6);
-         rjsonwriter_add_string(writer, "label");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->entries[i].label);
-         rjsonwriter_raw(writer, ",", 1);
-
-         rjsonwriter_raw(writer, "\n", 1);
-         rjsonwriter_add_spaces(writer, 6);
-         rjsonwriter_add_string(writer, "core_path");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->entries[i].core_path);
-         rjsonwriter_raw(writer, ",", 1);
-
-         rjsonwriter_raw(writer, "\n", 1);
-         rjsonwriter_add_spaces(writer, 6);
-         rjsonwriter_add_string(writer, "core_name");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->entries[i].core_name);
-         rjsonwriter_raw(writer, ",", 1);
-
-         rjsonwriter_raw(writer, "\n", 1);
-         rjsonwriter_add_spaces(writer, 6);
-         rjsonwriter_add_string(writer, "crc32");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->entries[i].crc32);
-         rjsonwriter_raw(writer, ",", 1);
-
-         rjsonwriter_raw(writer, "\n", 1);
-         rjsonwriter_add_spaces(writer, 6);
-         rjsonwriter_add_string(writer, "db_name");
-         rjsonwriter_raw(writer, ":", 1);
-         rjsonwriter_raw(writer, " ", 1);
-         rjsonwriter_add_string(writer, playlist->entries[i].db_name);
-
-         if (!string_is_empty(playlist->entries[i].subsystem_ident))
-         {
-            rjsonwriter_raw(writer, ",", 1);
-            rjsonwriter_raw(writer, "\n", 1);
-            rjsonwriter_add_spaces(writer, 6);
-            rjsonwriter_add_string(writer, "subsystem_ident");
-            rjsonwriter_raw(writer, ":", 1);
-            rjsonwriter_raw(writer, " ", 1);
-            rjsonwriter_add_string(writer, playlist->entries[i].subsystem_ident);
-         }
-
-         if (!string_is_empty(playlist->entries[i].subsystem_name))
-         {
-            rjsonwriter_raw(writer, ",", 1);
-            rjsonwriter_raw(writer, "\n", 1);
-            rjsonwriter_add_spaces(writer, 6);
-            rjsonwriter_add_string(writer, "subsystem_name");
-            rjsonwriter_raw(writer, ":", 1);
-            rjsonwriter_raw(writer, " ", 1);
-            rjsonwriter_add_string(writer, playlist->entries[i].subsystem_name);
-         }
-
-         if (  playlist->entries[i].subsystem_roms &&
-               playlist->entries[i].subsystem_roms->size > 0)
-         {
-            unsigned j;
-
-            rjsonwriter_raw(writer, ",", 1);
-            rjsonwriter_raw(writer, "\n", 1);
-            rjsonwriter_add_spaces(writer, 6);
-            rjsonwriter_add_string(writer, "subsystem_roms");
-            rjsonwriter_raw(writer, ":", 1);
-            rjsonwriter_raw(writer, " ", 1);
-            rjsonwriter_raw(writer, "[", 1);
-            rjsonwriter_raw(writer, "\n", 1);
-
-            for (j = 0; j < playlist->entries[i].subsystem_roms->size; j++)
-            {
-               const struct string_list *roms = playlist->entries[i].subsystem_roms;
-               rjsonwriter_add_spaces(writer, 8);
-               rjsonwriter_add_string(writer,
-                     !string_is_empty(roms->elems[j].data)
-                     ? roms->elems[j].data
-                     : "");
-
-               if (j < playlist->entries[i].subsystem_roms->size - 1)
-               {
-                  rjsonwriter_raw(writer, ",", 1);
-                  rjsonwriter_raw(writer, "\n", 1);
-               }
-            }
-
-            rjsonwriter_raw(writer, "\n", 1);
-            rjsonwriter_add_spaces(writer, 6);
-            rjsonwriter_raw(writer, "]", 1);
-         }
-
-         rjsonwriter_raw(writer, "\n", 1);
-
-         rjsonwriter_add_spaces(writer, 4);
-         rjsonwriter_raw(writer, "}", 1);
-
-         if (i < len - 1)
-            rjsonwriter_raw(writer, ",", 1);
-
-         rjsonwriter_raw(writer, "\n", 1);
-      }
-
-      rjsonwriter_add_spaces(writer, 2);
-      rjsonwriter_raw(writer, "]", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-      rjsonwriter_raw(writer, "}", 1);
-      rjsonwriter_raw(writer, "\n", 1);
-
-      if (!rjsonwriter_free(writer))
-      {
-         RARCH_ERR("Failed to write to playlist file: \"%s\".\n", playlist->config.path);
-      }
-
-      playlist->old_format = false;
-   }
-
-   playlist->modified   = false;
-   playlist->compressed = compressed;
-
-   RARCH_LOG("[Playlist]: Written to playlist file: \"%s\".\n", playlist->config.path);
-end:
-   intfstream_close(file);
-   free(file);
-}
-
-/**
- * playlist_free:
- * @playlist            : Playlist handle.
- *
- * Frees playlist handle.
- */
-void playlist_free(playlist_t *playlist)
-{
-   size_t i, len;
-
-   if (!playlist)
-      return;
-
-   if (playlist->default_core_path)
-      free(playlist->default_core_path);
-   playlist->default_core_path = NULL;
-
-   if (playlist->default_core_name)
-      free(playlist->default_core_name);
-   playlist->default_core_name = NULL;
-
-   if (playlist->base_content_directory)
-      free(playlist->base_content_directory);
-   playlist->base_content_directory = NULL;
-
-   if (playlist->scan_record.content_dir)
-      free(playlist->scan_record.content_dir);
-   playlist->scan_record.content_dir = NULL;
-
-   if (playlist->scan_record.file_exts)
-      free(playlist->scan_record.file_exts);
-   playlist->scan_record.file_exts = NULL;
-
-   if (playlist->scan_record.dat_file_path)
-      free(playlist->scan_record.dat_file_path);
-   playlist->scan_record.dat_file_path = NULL;
-
-   if (playlist->entries)
-   {
-      for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-      {
-         struct playlist_entry *entry = &playlist->entries[i];
-
-         if (entry)
-            playlist_free_entry(entry);
-      }
-
-      RBUF_FREE(playlist->entries);
-   }
-
-   free(playlist);
-}
-
-/**
- * playlist_clear:
- * @playlist        	   : Playlist handle.
- *
- * Clears all playlist entries in playlist.
- **/
-void playlist_clear(playlist_t *playlist)
-{
-   size_t i, len;
-   if (!playlist)
-      return;
-
-   for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-   {
-      struct playlist_entry *entry = &playlist->entries[i];
-
-      if (entry)
-         playlist_free_entry(entry);
-   }
-   RBUF_CLEAR(playlist->entries);
-}
-
-/**
- * playlist_size:
- * @playlist        	   : Playlist handle.
- *
- * Gets size of playlist.
- * Returns: size of playlist.
- **/
-size_t playlist_size(playlist_t *playlist)
-{
-   if (!playlist)
-      return 0;
-   return RBUF_LEN(playlist->entries);
-}
-
-/**
- * playlist_capacity:
- * @playlist        	   : Playlist handle.
- *
- * Gets maximum capacity of playlist.
- * Returns: maximum capacity of playlist.
- **/
-size_t playlist_capacity(playlist_t *playlist)
-{
-   if (!playlist)
-      return 0;
-   return playlist->config.capacity;
-}
-
-static bool JSONStartArrayHandler(void *context)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   pCtx->array_depth++;
-
-   return true;
-}
-
-static bool JSONEndArrayHandler(void *context)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   pCtx->array_depth--;
-
-   if (pCtx->in_items && pCtx->array_depth == 0 && pCtx->object_depth <= 1)
-      pCtx->in_items = false;
-   else if (pCtx->in_subsystem_roms && pCtx->array_depth <= 1 && pCtx->object_depth <= 2)
-      pCtx->in_subsystem_roms = false;
-
-   return true;
-}
-
-static bool JSONStartObjectHandler(void *context)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   pCtx->object_depth++;
-
-   if (pCtx->in_items && pCtx->object_depth == 2)
-   {
-      if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded)
-      {
-         size_t len = RBUF_LEN(pCtx->playlist->entries);
-         if (len < pCtx->playlist->config.capacity)
-         {
-            /* Allocate memory to fit one more item but don't resize the
-             * buffer just yet, wait until JSONEndObjectHandler for that */
-            if (!RBUF_TRYFIT(pCtx->playlist->entries, len + 1))
-            {
-               pCtx->out_of_memory     = true;
-               return false;
-            }
-            pCtx->current_entry = &pCtx->playlist->entries[len];
-            memset(pCtx->current_entry, 0, sizeof(*pCtx->current_entry));
-         }
-         else
-         {
-            /* Hit max item limit.
-             * Note: We can't just abort here, since there may
-             * be more metadata to read at the end of the file... */
-            RARCH_WARN("JSON file contains more entries than current playlist capacity. Excess entries will be discarded.\n");
-            pCtx->capacity_exceeded  = true;
-            pCtx->current_entry      = NULL;
-            /* In addition, since we are discarding excess entries,
-             * the playlist must be flagged as being modified
-             * (i.e. the playlist is not the same as when it was
-             * last saved to disk...) */
-            pCtx->playlist->modified = true;
-         }
-      }
-   }
-
-   return true;
-}
-
-static bool JSONEndObjectHandler(void *context)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   if (pCtx->in_items && pCtx->object_depth == 2)
-   {
-      if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded)
-         RBUF_RESIZE(pCtx->playlist->entries,
-               RBUF_LEN(pCtx->playlist->entries) + 1);
-   }
-
-   pCtx->object_depth--;
-
-   return true;
-}
-
-static bool JSONStringHandler(void *context, const char *pValue, size_t length)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   if (pCtx->in_items && pCtx->in_subsystem_roms && pCtx->object_depth == 2 && pCtx->array_depth == 2)
-   {
-      if (length && !string_is_empty(pValue))
-      {
-         union string_list_elem_attr attr = {0};
-
-         if (!pCtx->current_entry->subsystem_roms)
-            pCtx->current_entry->subsystem_roms = string_list_new();
-
-         string_list_append(pCtx->current_entry->subsystem_roms, pValue, attr);
-      }
-   }
-   else if (pCtx->in_items && pCtx->object_depth == 2)
-   {
-      if (pCtx->array_depth == 1)
-      {
-         if (pCtx->current_string_val && length && !string_is_empty(pValue))
-         {
-            if (*pCtx->current_string_val)
-                free(*pCtx->current_string_val);
-             *pCtx->current_string_val = strdup(pValue);
-         }
-      }
-   }
-   else if (pCtx->object_depth == 1)
-   {
-      if (pCtx->array_depth == 0)
-      {
-         if (pCtx->current_string_val && length && !string_is_empty(pValue))
-         {
-            /* handle any top-level playlist metadata here */
-            if (*pCtx->current_string_val)
-                free(*pCtx->current_string_val);
-            *pCtx->current_string_val = strdup(pValue);
-         }
-      }
-   }
-
-   pCtx->current_string_val = NULL;
-
-   return true;
-}
-
-static bool JSONNumberHandler(void *context, const char *pValue, size_t length)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   if (pCtx->in_items && pCtx->object_depth == 2)
-   {
-      if (pCtx->array_depth == 1 && length && !string_is_empty(pValue))
-      {
-         if (pCtx->current_entry_uint_val)
-            *pCtx->current_entry_uint_val = (unsigned)strtoul(pValue, NULL, 10);
-      }
-   }
-   else if (pCtx->object_depth == 1)
-   {
-      if (pCtx->array_depth == 0)
-      {
-         if (length && !string_is_empty(pValue))
-         {
-            /* handle any top-level playlist metadata here */
-            if (pCtx->current_meta_label_display_mode_val)
-               *pCtx->current_meta_label_display_mode_val = (enum playlist_label_display_mode)strtoul(pValue, NULL, 10);
-            else if (pCtx->current_meta_thumbnail_mode_val)
-               *pCtx->current_meta_thumbnail_mode_val = (enum playlist_thumbnail_mode)strtoul(pValue, NULL, 10);
-            else if (pCtx->current_meta_sort_mode_val)
-               *pCtx->current_meta_sort_mode_val = (enum playlist_sort_mode)strtoul(pValue, NULL, 10);
-         }
-      }
-   }
-
-   pCtx->current_entry_uint_val              = NULL;
-   pCtx->current_meta_label_display_mode_val = NULL;
-   pCtx->current_meta_thumbnail_mode_val     = NULL;
-   pCtx->current_meta_sort_mode_val          = NULL;
-
-   return true;
-}
-
-static bool JSONBoolHandler(void *context, bool value)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   if (!pCtx->in_items &&
-       (pCtx->object_depth == 1) &&
-       (pCtx->array_depth == 0) &&
-       pCtx->current_meta_bool_val)
-      *pCtx->current_meta_bool_val = value;
-
-   pCtx->current_meta_bool_val = NULL;
-
-   return true;
-}
-
-static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t length)
-{
-   JSONContext *pCtx = (JSONContext *)context;
-
-   if (pCtx->in_items && pCtx->object_depth == 2)
-   {
-      if (pCtx->array_depth == 1)
-      {
-         if (pCtx->current_string_val)
-         {
-            /* something went wrong */
-            return false;
-         }
-
-         if (length && !pCtx->capacity_exceeded)
-         {
-            pCtx->current_string_val     = NULL;
-            pCtx->current_entry_uint_val = NULL;
-            pCtx->in_subsystem_roms      = false;
-            switch (pValue[0])
-            {
-               case 'c':
-                     if (string_is_equal(pValue, "core_name"))
-                        pCtx->current_string_val = &pCtx->current_entry->core_name;
-                     else if (string_is_equal(pValue, "core_path"))
-                        pCtx->current_string_val = &pCtx->current_entry->core_path;
-                     else if (string_is_equal(pValue, "crc32"))
-                        pCtx->current_string_val = &pCtx->current_entry->crc32;
-                     break;
-               case 'd':
-                     if (string_is_equal(pValue, "db_name"))
-                        pCtx->current_string_val = &pCtx->current_entry->db_name;
-                     break;
-               case 'e':
-                     if (string_is_equal(pValue, "entry_slot"))
-                        pCtx->current_entry_uint_val = &pCtx->current_entry->entry_slot;
-                     break;
-               case 'l':
-                     if (string_is_equal(pValue, "label"))
-                        pCtx->current_string_val = &pCtx->current_entry->label;
-                     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_month"))
-                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month;
-                     else if (string_is_equal(pValue, "last_played_second"))
-                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second;
-                     else if (string_is_equal(pValue, "last_played_year"))
-                        pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year;
-                     break;
-               case 'p':
-                     if (string_is_equal(pValue, "path"))
-                        pCtx->current_string_val = &pCtx->current_entry->path;
-                     break;
-               case 'r':
-                     if (string_is_equal(pValue, "runtime_hours"))
-                        pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_hours;
-                     else if (string_is_equal(pValue, "runtime_minutes"))
-                        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;
-                     break;
-               case 's':
-                     if (string_is_equal(pValue, "subsystem_ident"))
-                        pCtx->current_string_val = &pCtx->current_entry->subsystem_ident;
-                     else if (string_is_equal(pValue, "subsystem_name"))
-                        pCtx->current_string_val = &pCtx->current_entry->subsystem_name;
-                     else if (string_is_equal(pValue, "subsystem_roms"))
-                        pCtx->in_subsystem_roms = true;
-                     break;
-            }
-         }
-      }
-   }
-   else if (pCtx->object_depth == 1 && pCtx->array_depth == 0 && length)
-   {
-      pCtx->current_string_val                  = NULL;
-      pCtx->current_meta_label_display_mode_val = NULL;
-      pCtx->current_meta_thumbnail_mode_val     = NULL;
-      pCtx->current_meta_sort_mode_val          = NULL;
-      pCtx->current_meta_bool_val               = NULL;
-      pCtx->in_items                            = false;
-
-      switch (pValue[0])
-      {
-         case 'b':
-            if (string_is_equal(pValue, "base_content_directory"))
-               pCtx->current_string_val = &pCtx->playlist->base_content_directory;
-            break;
-         case 'd':
-            if (string_is_equal(pValue,      "default_core_path"))
-               pCtx->current_string_val = &pCtx->playlist->default_core_path;
-            else if (string_is_equal(pValue, "default_core_name"))
-               pCtx->current_string_val = &pCtx->playlist->default_core_name;
-            break;
-         case 'i':
-            if (string_is_equal(pValue, "items"))
-               pCtx->in_items = true;
-            break;
-         case 'l':
-            if (string_is_equal(pValue,      "label_display_mode"))
-               pCtx->current_meta_label_display_mode_val = &pCtx->playlist->label_display_mode;
-            else if (string_is_equal(pValue, "left_thumbnail_mode"))
-               pCtx->current_meta_thumbnail_mode_val     = &pCtx->playlist->left_thumbnail_mode;
-            break;
-         case 'r':
-            if (string_is_equal(pValue, "right_thumbnail_mode"))
-               pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode;
-            break;
-         case 's':
-            if (string_is_equal(pValue,      "scan_content_dir"))
-               pCtx->current_string_val         = &pCtx->playlist->scan_record.content_dir;
-            else if (string_is_equal(pValue, "scan_file_exts"))
-               pCtx->current_string_val         = &pCtx->playlist->scan_record.file_exts;
-            else if (string_is_equal(pValue, "scan_dat_file_path"))
-               pCtx->current_string_val         = &pCtx->playlist->scan_record.dat_file_path;
-            else if (string_is_equal(pValue, "scan_search_recursively"))
-               pCtx->current_meta_bool_val      = &pCtx->playlist->scan_record.search_recursively;
-            else if (string_is_equal(pValue, "scan_search_archives"))
-               pCtx->current_meta_bool_val      = &pCtx->playlist->scan_record.search_archives;
-            else if (string_is_equal(pValue, "scan_filter_dat_content"))
-               pCtx->current_meta_bool_val      = &pCtx->playlist->scan_record.filter_dat_content;
-            else if (string_is_equal(pValue, "sort_mode"))
-               pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode;
-            break;
-      }
-   }
-
-   return true;
-}
-
-static void playlist_get_old_format_metadata_value(
-      char *metadata_line, char *value, size_t len)
-{
-   char *end   = NULL;
-   char *start = strchr(metadata_line, '\"');
-
-   if (!start)
-      return;
-
-   start++;
-   end         = strchr(start, '\"');
-
-   if (!end)
-      return;
-
-   *end        = '\0';
-   strlcpy(value, start, len);
-}
-
-static bool playlist_read_file(playlist_t *playlist)
-{
-   unsigned i;
-   int test_char;
-   bool res = true;
-#if defined(HAVE_ZLIB)
-      /* Always use RZIP interface when reading playlists
-       * > this will automatically handle uncompressed
-       *   data */
-   intfstream_t *file   = intfstream_open_rzip_file(
-         playlist->config.path,
-         RETRO_VFS_FILE_ACCESS_READ);
-#else
-   intfstream_t *file   = intfstream_open_file(
-         playlist->config.path,
-         RETRO_VFS_FILE_ACCESS_READ,
-         RETRO_VFS_FILE_ACCESS_HINT_NONE);
-#endif
-
-   /* If playlist file does not exist,
-    * create an empty playlist instead */
-   if (!file)
-      return true;
-
-   playlist->compressed = intfstream_is_compressed(file);
-
-   /* Detect format of playlist
-    * > Read file until we find the first printable
-    *   non-whitespace ASCII character */
-   do
-   {
-	   /* Read error or EOF (end of file) */
-      if ((test_char = intfstream_getc(file)) == EOF)
-         goto end;
-   }while (!isgraph(test_char) || test_char > 0x7F);
-
-   playlist->old_format = (test_char != '{');
-
-   /* Reset file to start */
-   intfstream_rewind(file);
-
-   if (!playlist->old_format)
-   {
-      rjson_t* parser;
-      JSONContext context = {0};
-      context.playlist    = playlist;
-
-      if (!(parser = rjson_open_stream(file)))
-      {
-         RARCH_ERR("Failed to create JSON parser\n");
-         goto end;
-      }
-
-      rjson_set_options(parser,
-              RJSON_OPTION_ALLOW_UTF8BOM
-            | RJSON_OPTION_ALLOW_COMMENTS
-            | RJSON_OPTION_ALLOW_UNESCAPED_CONTROL_CHARACTERS
-            | RJSON_OPTION_REPLACE_INVALID_ENCODING);
-
-      if (rjson_parse(parser, &context,
-            JSONObjectMemberHandler,
-            JSONStringHandler,
-            JSONNumberHandler,
-            JSONStartObjectHandler,
-            JSONEndObjectHandler,
-            JSONStartArrayHandler,
-            JSONEndArrayHandler,
-            JSONBoolHandler,
-            NULL) /* Unused null handler */
-            != RJSON_DONE)
-      {
-         if (context.out_of_memory)
-         {
-            RARCH_WARN("Ran out of memory while parsing JSON playlist\n");
-            res = false;
-         }
-         else
-         {
-            RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n",
-                  rjson_get_source_context_len(parser),
-                  rjson_get_source_context_buf(parser));
-            RARCH_WARN("Error: Invalid JSON at line %d, column %d - %s.\n",
-                  (int)rjson_get_source_line(parser),
-                  (int)rjson_get_source_column(parser),
-                  (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error"));
-         }
-      }
-      rjson_free(parser);
-   }
-   else
-   {
-      size_t len = RBUF_LEN(playlist->entries);
-      char line_buf[PLAYLIST_ENTRIES][PATH_MAX_LENGTH] = {{0}};
-
-      /* Unnecessary, but harmless */
-      for (i = 0; i < PLAYLIST_ENTRIES; i++)
-         line_buf[i][0] = '\0';
-
-      /* Read playlist entries */
-      while (len < playlist->config.capacity)
-      {
-         size_t i;
-         size_t lines_read = 0;
-
-         /* Attempt to read the next 'PLAYLIST_ENTRIES'
-          * lines from the file */
-         for (i = 0; i < PLAYLIST_ENTRIES; i++)
-         {
-            *line_buf[i] = '\0';
-
-            if (!intfstream_gets(file, line_buf[i], sizeof(line_buf[i])))
-               break;
-            /* Ensure line is NULL terminated, regardless of
-             * Windows or Unix line endings */
-            string_replace_all_chars(line_buf[i], '\r', '\0');
-            string_replace_all_chars(line_buf[i], '\n', '\0');
-
-            lines_read++;
-         }
-
-         /* If a 'full set' of lines were read, then this
-          * is a valid playlist entry */
-         if (lines_read >= PLAYLIST_ENTRIES)
-         {
-            struct playlist_entry* entry;
-
-            if (!RBUF_TRYFIT(playlist->entries, len + 1))
-            {
-               res = false; /* out of memory */
-               goto end;
-            }
-            RBUF_RESIZE(playlist->entries, len + 1);
-            entry = &playlist->entries[len++];
-
-            memset(entry, 0, sizeof(*entry));
-
-            /* path */
-            if (!string_is_empty(line_buf[0]))
-               entry->path      = strdup(line_buf[0]);
-
-            /* label */
-            if (!string_is_empty(line_buf[1]))
-               entry->label     = strdup(line_buf[1]);
-
-            /* core_path */
-            if (!string_is_empty(line_buf[2]))
-               entry->core_path = strdup(line_buf[2]);
-
-            /* core_name */
-            if (!string_is_empty(line_buf[3]))
-               entry->core_name = strdup(line_buf[3]);
-
-            /* crc32 */
-            if (!string_is_empty(line_buf[4]))
-               entry->crc32     = strdup(line_buf[4]);
-
-            /* db_name */
-            if (!string_is_empty(line_buf[5]))
-               entry->db_name   = strdup(line_buf[5]);
-         }
-         /* If fewer than 'PLAYLIST_ENTRIES' lines were
-          * read, then this is metadata */
-         else
-         {
-            char default_core_path[PATH_MAX_LENGTH];
-            char default_core_name[PATH_MAX_LENGTH];
-
-            default_core_path[0] = '\0';
-            default_core_name[0] = '\0';
-
-            /* Get default_core_path */
-            if (lines_read < 1)
-               break;
-
-            if (strncmp("default_core_path",
-                     line_buf[0],
-                     STRLEN_CONST("default_core_path")) == 0)
-               playlist_get_old_format_metadata_value(
-                     line_buf[0], default_core_path, sizeof(default_core_path));
-
-            /* Get default_core_name */
-            if (lines_read < 2)
-               break;
-
-            if (strncmp("default_core_name",
-                     line_buf[1],
-                     STRLEN_CONST("default_core_name")) == 0)
-               playlist_get_old_format_metadata_value(
-                     line_buf[1], default_core_name, sizeof(default_core_name));
-
-            /* > Populate default core path/name, if required
-             *   (if one is empty, the other should be ignored) */
-            if (!string_is_empty(default_core_path) &&
-                !string_is_empty(default_core_name))
-            {
-               playlist->default_core_path = strdup(default_core_path);
-               playlist->default_core_name = strdup(default_core_name);
-            }
-
-            /* Get label_display_mode */
-            if (lines_read < 3)
-               break;
-
-            if (strncmp("label_display_mode",
-                     line_buf[2],
-                     STRLEN_CONST("label_display_mode")) == 0)
-            {
-               unsigned display_mode;
-               char display_mode_str[4] = {0};
-
-               playlist_get_old_format_metadata_value(
-                     line_buf[2], display_mode_str, sizeof(display_mode_str));
-
-               display_mode = string_to_unsigned(display_mode_str);
-
-               if (display_mode <= LABEL_DISPLAY_MODE_KEEP_REGION_AND_DISC_INDEX)
-                  playlist->label_display_mode = (enum playlist_label_display_mode)display_mode;
-            }
-
-            /* Get thumbnail modes */
-            if (lines_read < 4)
-               break;
-
-            if (strncmp("thumbnail_mode",
-                     line_buf[3],
-                     STRLEN_CONST("thumbnail_mode")) == 0)
-            {
-               char thumbnail_mode_str[8]          = {0};
-               struct string_list thumbnail_modes  = {0};
-
-               playlist_get_old_format_metadata_value(
-                     line_buf[3], thumbnail_mode_str,
-                     sizeof(thumbnail_mode_str));
-               string_list_initialize(&thumbnail_modes);
-               if (string_split_noalloc(&thumbnail_modes,
-                        thumbnail_mode_str, "|"))
-               {
-                  if (thumbnail_modes.size == 2)
-                  {
-                     /* Right thumbnail mode */
-                     unsigned thumbnail_mode = string_to_unsigned(
-                           thumbnail_modes.elems[0].data);
-                     if (thumbnail_mode <= PLAYLIST_THUMBNAIL_MODE_BOXARTS)
-                        playlist->right_thumbnail_mode = (enum playlist_thumbnail_mode)thumbnail_mode;
-
-                     /* Left thumbnail mode */
-                     thumbnail_mode = string_to_unsigned(
-                           thumbnail_modes.elems[1].data);
-                     if (thumbnail_mode <= PLAYLIST_THUMBNAIL_MODE_BOXARTS)
-                        playlist->left_thumbnail_mode = (enum playlist_thumbnail_mode)thumbnail_mode;
-                  }
-               }
-               string_list_deinitialize(&thumbnail_modes);
-            }
-
-            /* Get sort_mode */
-            if (lines_read < 5)
-               break;
-
-            if (strncmp("sort_mode",
-                     line_buf[4],
-                     STRLEN_CONST("sort_mode")) == 0)
-            {
-               unsigned sort_mode;
-               char sort_mode_str[4] = {0};
-
-               playlist_get_old_format_metadata_value(
-                     line_buf[4], sort_mode_str, sizeof(sort_mode_str));
-
-               sort_mode = string_to_unsigned(sort_mode_str);
-
-               if (sort_mode <= PLAYLIST_SORT_MODE_OFF)
-                  playlist->sort_mode = (enum playlist_sort_mode)sort_mode;
-            }
-
-            /* All metadata parsed -> end of file */
-            break;
-         }
-      }
-   }
-
-end:
-   intfstream_close(file);
-   free(file);
-   return res;
-}
-
-void playlist_free_cached(void)
-{
-   if (playlist_cached && !playlist_cached->cached_external)
-      playlist_free(playlist_cached);
-   playlist_cached = NULL;
-}
-
-playlist_t *playlist_get_cached(void)
-{
-   if (playlist_cached)
-      return playlist_cached;
-   return NULL;
-}
-
-bool playlist_init_cached(const playlist_config_t *config)
-{
-   playlist_t *playlist = playlist_init(config);
-   if (!playlist)
-      return false;
-
-   /* If playlist format/compression state
-    * does not match requested settings, update
-    * file on disk immediately */
-   if (
-#if defined(HAVE_ZLIB)
-       (playlist->compressed != playlist->config.compress) ||
-#endif
-       (playlist->old_format != playlist->config.old_format))
-      playlist_write_file(playlist);
-
-   playlist_cached      = playlist;
-   return true;
-}
-
-/**
- * playlist_init:
- * @config            	: Playlist configuration object.
- *
- * Creates and initializes a playlist.
- *
- * Returns: handle to new playlist if successful, otherwise NULL
- **/
-playlist_t *playlist_init(const playlist_config_t *config)
-{
-   playlist_t           *playlist = (playlist_t*)malloc(sizeof(*playlist));
-   if (!playlist)
-      goto error;
-
-   /* Set initial values */
-   playlist->modified               = false;
-   playlist->old_format             = false;
-   playlist->compressed             = false;
-   playlist->cached_external        = false;
-   playlist->default_core_name      = NULL;
-   playlist->default_core_path      = NULL;
-   playlist->base_content_directory = NULL;
-   playlist->entries                = NULL;
-   playlist->label_display_mode     = LABEL_DISPLAY_MODE_DEFAULT;
-   playlist->right_thumbnail_mode   = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   playlist->left_thumbnail_mode    = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-   playlist->sort_mode              = PLAYLIST_SORT_MODE_DEFAULT;
-
-   playlist->scan_record.search_recursively = false;
-   playlist->scan_record.search_archives    = false;
-   playlist->scan_record.filter_dat_content = false;
-   playlist->scan_record.content_dir        = NULL;
-   playlist->scan_record.file_exts          = NULL;
-   playlist->scan_record.dat_file_path      = NULL;
-
-   /* Cache configuration parameters */
-   if (!playlist_config_copy(config, &playlist->config))
-      goto error;
-
-   /* Attempt to read any existing playlist file */
-   if (!playlist_read_file(playlist))
-      goto error;
-
-   /* Try auto-fixing paths if enabled, and playlist
-    * base content directory is different */
-   if (config->autofix_paths &&
-       !string_is_equal(playlist->base_content_directory,
-            config->base_content_directory))
-   {
-      if (!string_is_empty(playlist->base_content_directory))
-      {
-         size_t i, j, len;
-         char tmp_entry_path[PATH_MAX_LENGTH];
-
-         for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++)
-         {
-            struct playlist_entry* entry = &playlist->entries[i];
-
-            if (!entry || string_is_empty(entry->path))
-               continue;
-
-            /* Fix entry path */
-            tmp_entry_path[0] = '\0';
-            path_replace_base_path_and_convert_to_local_file_system(
-                  tmp_entry_path, entry->path,
-                  playlist->base_content_directory, playlist->config.base_content_directory,
-                  sizeof(tmp_entry_path));
-
-            free(entry->path);
-            entry->path = strdup(tmp_entry_path);
-
-            /* Fix subsystem roms paths*/
-            if (entry->subsystem_roms && (entry->subsystem_roms->size > 0))
-            {
-               struct string_list* subsystem_roms_new_paths = string_list_new();
-               union string_list_elem_attr attributes = { 0 };
-
-               if (!subsystem_roms_new_paths)
-                  goto error;
-
-               for (j = 0; j < entry->subsystem_roms->size; j++)
-               {
-                  const char* subsystem_rom_path = entry->subsystem_roms->elems[j].data;
-
-                  if (string_is_empty(subsystem_rom_path))
-                     continue;
-
-                  tmp_entry_path[0] = '\0';
-                  path_replace_base_path_and_convert_to_local_file_system(
-                        tmp_entry_path, subsystem_rom_path,
-                        playlist->base_content_directory, playlist->config.base_content_directory,
-                        sizeof(tmp_entry_path));
-                  string_list_append(subsystem_roms_new_paths, tmp_entry_path, attributes);
-               }
-
-               string_list_free(entry->subsystem_roms);
-               entry->subsystem_roms = subsystem_roms_new_paths;
-            }
-         }
-
-         /* Fix scan record content directory */
-         if (!string_is_empty(playlist->scan_record.content_dir))
-         {
-            tmp_entry_path[0] = '\0';
-            path_replace_base_path_and_convert_to_local_file_system(
-                  tmp_entry_path, playlist->scan_record.content_dir,
-                  playlist->base_content_directory, playlist->config.base_content_directory,
-                  sizeof(tmp_entry_path));
-
-            free(playlist->scan_record.content_dir);
-            playlist->scan_record.content_dir = strdup(tmp_entry_path);
-         }
-
-         /* Fix scan record arcade DAT file */
-         if (!string_is_empty(playlist->scan_record.dat_file_path))
-         {
-            tmp_entry_path[0] = '\0';
-            path_replace_base_path_and_convert_to_local_file_system(
-                  tmp_entry_path, playlist->scan_record.dat_file_path,
-                  playlist->base_content_directory, playlist->config.base_content_directory,
-                  sizeof(tmp_entry_path));
-
-            free(playlist->scan_record.dat_file_path);
-            playlist->scan_record.dat_file_path = strdup(tmp_entry_path);
-         }
-      }
-
-      /* Update playlist base content directory*/
-      if (playlist->base_content_directory)
-         free(playlist->base_content_directory);
-      playlist->base_content_directory = strdup(playlist->config.base_content_directory);
-
-      /* Save playlist */
-      playlist->modified = true;
-      playlist_write_file(playlist);
-   }
-
-   return playlist;
-
-error:
-   playlist_free(playlist);
-   return NULL;
-}
-
-static int playlist_qsort_func(const struct playlist_entry *a,
-      const struct playlist_entry *b)
-{
-   char *a_str            = NULL;
-   char *b_str            = NULL;
-   char *a_fallback_label = NULL;
-   char *b_fallback_label = NULL;
-   int ret                = 0;
-
-   if (!a || !b)
-      goto end;
-
-   a_str                  = a->label;
-   b_str                  = b->label;
-
-   /* It is quite possible for playlist labels
-    * to be blank. If that is the case, have to use
-    * filename as a fallback (this is slow, but we
-    * have no other option...) */
-   if (string_is_empty(a_str))
-   {
-      if (!(a_fallback_label = (char*)calloc(PATH_MAX_LENGTH, sizeof(char))))
-         goto end;
-
-      if (!string_is_empty(a->path))
-         fill_pathname(a_fallback_label,
-               path_basename_nocompression(a->path),
-               "", PATH_MAX_LENGTH * sizeof(char));
-      /* If filename is also empty, use core name
-       * instead -> this matches the behaviour of
-       * menu_displaylist_parse_playlist() */
-      else if (!string_is_empty(a->core_name))
-         strlcpy(a_fallback_label, a->core_name, PATH_MAX_LENGTH * sizeof(char));
-
-      /* If both filename and core name are empty,
-       * then have to compare an empty string
-       * -> again, this is to match the behaviour of
-       * menu_displaylist_parse_playlist() */
-
-      a_str = a_fallback_label;
-   }
-
-   if (string_is_empty(b_str))
-   {
-      if (!(b_fallback_label = (char*)calloc(PATH_MAX_LENGTH, sizeof(char))))
-         goto end;
-
-      if (!string_is_empty(b->path))
-         fill_pathname(b_fallback_label,
-               path_basename_nocompression(b->path), "",
-               PATH_MAX_LENGTH * sizeof(char));
-      else if (!string_is_empty(b->core_name))
-         strlcpy(b_fallback_label, b->core_name, PATH_MAX_LENGTH * sizeof(char));
-
-      b_str = b_fallback_label;
-   }
-
-   ret = strcasecmp(a_str, b_str);
-
-end:
-
-   a_str = NULL;
-   b_str = NULL;
-
-   if (a_fallback_label)
-   {
-      free(a_fallback_label);
-      a_fallback_label = NULL;
-   }
-
-   if (b_fallback_label)
-   {
-      free(b_fallback_label);
-      b_fallback_label = NULL;
-   }
-
-   return ret;
-}
-
-void playlist_qsort(playlist_t *playlist)
-{
-   /* Avoid inadvertent sorting if 'sort mode'
-    * has been set explicitly to PLAYLIST_SORT_MODE_OFF */
-   if (!playlist ||
-       (playlist->sort_mode == PLAYLIST_SORT_MODE_OFF) ||
-       !playlist->entries)
-      return;
-
-   qsort(playlist->entries, RBUF_LEN(playlist->entries),
-         sizeof(struct playlist_entry),
-         (int (*)(const void *, const void *))playlist_qsort_func);
-}
-
-void command_playlist_push_write(
-      playlist_t *playlist,
-      const struct playlist_entry *entry)
-{
-   if (playlist && playlist_push(playlist, entry))
-      playlist_write_file(playlist);
-}
-
-void command_playlist_update_write(
-      playlist_t *plist,
-      size_t idx,
-      const struct playlist_entry *entry)
-{
-   playlist_t *playlist = plist ? plist : playlist_get_cached();
-
-   if (!playlist)
-      return;
-
-   playlist_update(
-         playlist,
-         idx,
-         entry);
-
-   playlist_write_file(playlist);
-}
-
-bool playlist_index_is_valid(playlist_t *playlist, size_t idx,
-      const char *path, const char *core_path)
-{
-   if (!playlist)
-      return false;
-
-   if (idx >= RBUF_LEN(playlist->entries))
-      return false;
-
-   return playlist_path_equal(path, playlist->entries[idx].path, &playlist->config) &&
-          string_is_equal(path_basename_nocompression(playlist->entries[idx].core_path), path_basename_nocompression(core_path));
-}
-
-bool playlist_entries_are_equal(
-      const struct playlist_entry *entry_a,
-      const struct playlist_entry *entry_b,
-      const playlist_config_t *config)
-{
-   char real_path_a[PATH_MAX_LENGTH];
-   char real_core_path_a[PATH_MAX_LENGTH];
-
-   /* Sanity check */
-   if (!entry_a || !entry_b || !config)
-      return false;
-
-   if (   string_is_empty(entry_a->path)
-       && string_is_empty(entry_a->core_path)
-       && string_is_empty(entry_b->path)
-       && string_is_empty(entry_b->core_path))
-      return true;
-
-   /* Check content paths */
-   if (!string_is_empty(entry_a->path))
-   {
-      strlcpy(real_path_a, entry_a->path, sizeof(real_path_a));
-      path_resolve_realpath(real_path_a, sizeof(real_path_a), true);
-   }
-   else
-      real_path_a[0]      = '\0';
-
-   if (!playlist_path_equal(
-         real_path_a, entry_b->path, config))
-      return false;
-
-   /* Check core paths */
-   if (!string_is_empty(entry_a->core_path))
-   {
-      strlcpy(real_core_path_a, entry_a->core_path, sizeof(real_core_path_a));
-      if (!string_is_equal(real_core_path_a, FILE_PATH_DETECT) &&
-          !string_is_equal(real_core_path_a, FILE_PATH_BUILTIN))
-         playlist_resolve_path(PLAYLIST_SAVE, true,
-               real_core_path_a, sizeof(real_core_path_a));
-   }
-   else
-      real_core_path_a[0] = '\0';
-
-   return playlist_core_path_equal(real_core_path_a, entry_b->core_path, config);
-}
-
-/* Returns true if entries at specified indices
- * of specified playlist have identical content
- * and core paths */
-bool playlist_index_entries_are_equal(
-      playlist_t *playlist, size_t idx_a, size_t idx_b)
-{
-   struct playlist_entry *entry_a = NULL;
-   struct playlist_entry *entry_b = NULL;
-   size_t len;
-
-   if (!playlist)
-      return false;
-
-   len = RBUF_LEN(playlist->entries);
-
-   if ((idx_a >= len) || (idx_b >= len))
-      return false;
-
-   /* Fetch entries */
-   entry_a = &playlist->entries[idx_a];
-   entry_b = &playlist->entries[idx_b];
-
-   if (!entry_a || !entry_b)
-      return false;
-
-   /* Initialise path ID for entry A, if required
-    * (entry B will be handled inside
-    * playlist_path_matches_entry()) */
-   if (!entry_a->path_id)
-      entry_a->path_id = playlist_path_id_init(entry_a->path);
-
-   return playlist_path_matches_entry(
-         entry_a->path_id, entry_b, &playlist->config);
-}
-
-void playlist_get_crc32(playlist_t *playlist, size_t idx,
-      const char **crc32)
-{
-   if (!playlist || idx >= RBUF_LEN(playlist->entries))
-      return;
-
-   if (crc32)
-      *crc32 = playlist->entries[idx].crc32;
-}
-
-void playlist_get_db_name(playlist_t *playlist, size_t idx,
-      const char **db_name)
-{
-   if (!playlist || idx >= RBUF_LEN(playlist->entries))
-      return;
-
-   if (db_name)
-   {
-      if (!string_is_empty(playlist->entries[idx].db_name))
-         *db_name = playlist->entries[idx].db_name;
-      else
-      {
-         const char *conf_path_basename = path_basename_nocompression(playlist->config.path);
-
-         /* Only use file basename if this is a 'collection' playlist
-          * (i.e. ignore history/favourites) */
-         if (
-                  !string_is_empty(conf_path_basename)
-               && !string_ends_with_size(conf_path_basename, "_history.lpl",
-                        strlen(conf_path_basename), STRLEN_CONST("_history.lpl"))
-               && !string_is_equal(conf_path_basename,
-                        FILE_PATH_CONTENT_FAVORITES)
-            )
-            *db_name = conf_path_basename;
-      }
-   }
-}
-
-const char *playlist_get_default_core_path(playlist_t *playlist)
-{
-   if (!playlist)
-      return NULL;
-   return playlist->default_core_path;
-}
-
-const char *playlist_get_default_core_name(playlist_t *playlist)
-{
-   if (!playlist)
-      return NULL;
-   return playlist->default_core_name;
-}
-
-enum playlist_label_display_mode playlist_get_label_display_mode(playlist_t *playlist)
-{
-   if (!playlist)
-      return LABEL_DISPLAY_MODE_DEFAULT;
-   return playlist->label_display_mode;
-}
-
-enum playlist_thumbnail_mode playlist_get_thumbnail_mode(
-      playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id)
-{
-   if (playlist)
-   {
-      if (thumbnail_id == PLAYLIST_THUMBNAIL_RIGHT)
-         return playlist->right_thumbnail_mode;
-      else if (thumbnail_id == PLAYLIST_THUMBNAIL_LEFT)
-         return playlist->left_thumbnail_mode;
-   }
-   /* Fallback */
-   return PLAYLIST_THUMBNAIL_MODE_DEFAULT;
-}
-
-enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist)
-{
-   if (!playlist)
-      return PLAYLIST_SORT_MODE_DEFAULT;
-   return playlist->sort_mode;
-}
-
-const char *playlist_get_scan_content_dir(playlist_t *playlist)
-{
-   if (!playlist)
-      return NULL;
-   return playlist->scan_record.content_dir;
-}
-
-const char *playlist_get_scan_file_exts(playlist_t *playlist)
-{
-   if (!playlist)
-      return NULL;
-   return playlist->scan_record.file_exts;
-}
-
-const char *playlist_get_scan_dat_file_path(playlist_t *playlist)
-{
-   if (!playlist)
-      return NULL;
-   return playlist->scan_record.dat_file_path;
-}
-
-bool playlist_get_scan_search_recursively(playlist_t *playlist)
-{
-   if (!playlist)
-      return false;
-   return playlist->scan_record.search_recursively;
-}
-
-bool playlist_get_scan_search_archives(playlist_t *playlist)
-{
-   if (!playlist)
-      return false;
-   return playlist->scan_record.search_archives;
-}
-
-bool playlist_get_scan_filter_dat_content(playlist_t *playlist)
-{
-   if (!playlist)
-      return false;
-   return playlist->scan_record.filter_dat_content;
-}
-
-bool playlist_scan_refresh_enabled(playlist_t *playlist)
-{
-   if (!playlist)
-      return false;
-   return !string_is_empty(playlist->scan_record.content_dir);
-}
-
-void playlist_set_default_core_path(playlist_t *playlist,
-      const char *core_path)
-{
-   char real_core_path[PATH_MAX_LENGTH];
-
-   if (!playlist || string_is_empty(core_path))
-      return;
-
-   /* Get 'real' core path */
-   strlcpy(real_core_path, core_path, sizeof(real_core_path));
-   if (!string_is_equal(real_core_path, FILE_PATH_DETECT) &&
-       !string_is_equal(real_core_path, FILE_PATH_BUILTIN))
-       playlist_resolve_path(PLAYLIST_SAVE, true,
-             real_core_path, sizeof(real_core_path));
-
-   if (string_is_empty(real_core_path))
-      return;
-
-   if (!string_is_equal(playlist->default_core_path, real_core_path))
-   {
-      if (playlist->default_core_path)
-         free(playlist->default_core_path);
-      playlist->default_core_path  = strdup(real_core_path);
-      playlist->modified           = true;
-   }
-}
-
-void playlist_set_default_core_name(
-      playlist_t *playlist, const char *core_name)
-{
-   if (!playlist || string_is_empty(core_name))
-      return;
-
-   if (!string_is_equal(playlist->default_core_name, core_name))
-   {
-      if (playlist->default_core_name)
-         free(playlist->default_core_name);
-      playlist->default_core_name  = strdup(core_name);
-      playlist->modified           = true;
-   }
-}
-
-void playlist_set_label_display_mode(playlist_t *playlist,
-      enum playlist_label_display_mode label_display_mode)
-{
-   if (playlist && playlist->label_display_mode != label_display_mode)
-   {
-      playlist->label_display_mode = label_display_mode;
-      playlist->modified           = true;
-   }
-}
-
-void playlist_set_thumbnail_mode(
-      playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id,
-      enum playlist_thumbnail_mode thumbnail_mode)
-{
-   if (!playlist)
-      return;
-
-   switch (thumbnail_id)
-   {
-      case PLAYLIST_THUMBNAIL_RIGHT:
-         playlist->right_thumbnail_mode = thumbnail_mode;
-         playlist->modified             = true;
-         break;
-      case PLAYLIST_THUMBNAIL_LEFT:
-         playlist->left_thumbnail_mode = thumbnail_mode;
-         playlist->modified            = true;
-         break;
-   }
-}
-
-void playlist_set_sort_mode(playlist_t *playlist,
-      enum playlist_sort_mode sort_mode)
-{
-   if (playlist && playlist->sort_mode != sort_mode)
-   {
-      playlist->sort_mode = sort_mode;
-      playlist->modified  = true;
-   }
-}
-
-void playlist_set_scan_content_dir(playlist_t *playlist, const char *content_dir)
-{
-   bool current_string_empty;
-   bool new_string_empty;
-
-   if (!playlist)
-      return;
-
-   current_string_empty = string_is_empty(playlist->scan_record.content_dir);
-   new_string_empty     = string_is_empty(content_dir);
-
-   /* Check whether string value has changed
-    * (note that a NULL or empty argument will
-    * unset the playlist value) */
-   if (( current_string_empty && !new_string_empty) ||
-       (!current_string_empty &&  new_string_empty) ||
-       !string_is_equal(playlist->scan_record.content_dir, content_dir))
-      playlist->modified = true;
-   else
-      return; /* Strings are identical; do nothing */
-
-   if (playlist->scan_record.content_dir)
-   {
-      free(playlist->scan_record.content_dir);
-      playlist->scan_record.content_dir = NULL;
-   }
-
-   if (!new_string_empty)
-      playlist->scan_record.content_dir = strdup(content_dir);
-}
-
-void playlist_set_scan_file_exts(playlist_t *playlist, const char *file_exts)
-{
-   bool current_string_empty;
-   bool new_string_empty;
-
-   if (!playlist)
-      return;
-
-   current_string_empty = string_is_empty(playlist->scan_record.file_exts);
-   new_string_empty     = string_is_empty(file_exts);
-
-   /* Check whether string value has changed
-    * (note that a NULL or empty argument will
-    * unset the playlist value) */
-   if (   ( current_string_empty && !new_string_empty)
-       || (!current_string_empty &&  new_string_empty)
-       || !string_is_equal(playlist->scan_record.file_exts, file_exts))
-      playlist->modified = true;
-   else
-      return; /* Strings are identical; do nothing */
-
-   if (playlist->scan_record.file_exts)
-   {
-      free(playlist->scan_record.file_exts);
-      playlist->scan_record.file_exts = NULL;
-   }
-
-   if (!new_string_empty)
-      playlist->scan_record.file_exts = strdup(file_exts);
-}
-
-void playlist_set_scan_dat_file_path(playlist_t *playlist, const char *dat_file_path)
-{
-   bool current_string_empty;
-   bool new_string_empty;
-
-   if (!playlist)
-      return;
-
-   current_string_empty = string_is_empty(playlist->scan_record.dat_file_path);
-   new_string_empty     = string_is_empty(dat_file_path);
-
-   /* Check whether string value has changed
-    * (note that a NULL or empty argument will
-    * unset the playlist value) */
-   if (( current_string_empty && !new_string_empty) ||
-       (!current_string_empty &&  new_string_empty) ||
-       !string_is_equal(playlist->scan_record.dat_file_path, dat_file_path))
-      playlist->modified = true;
-   else
-      return; /* Strings are identical; do nothing */
-
-   if (playlist->scan_record.dat_file_path)
-   {
-      free(playlist->scan_record.dat_file_path);
-      playlist->scan_record.dat_file_path = NULL;
-   }
-
-   if (!new_string_empty)
-      playlist->scan_record.dat_file_path = strdup(dat_file_path);
-}
-
-void playlist_set_scan_search_recursively(playlist_t *playlist, bool search_recursively)
-{
-   if (playlist && playlist->scan_record.search_recursively != search_recursively)
-   {
-      playlist->scan_record.search_recursively = search_recursively;
-      playlist->modified = true;
-   }
-}
-
-void playlist_set_scan_search_archives(playlist_t *playlist, bool search_archives)
-{
-   if (playlist && playlist->scan_record.search_archives != search_archives)
-   {
-      playlist->scan_record.search_archives = search_archives;
-      playlist->modified = true;
-   }
-}
-
-void playlist_set_scan_filter_dat_content(playlist_t *playlist, bool filter_dat_content)
-{
-   if (playlist && playlist->scan_record.filter_dat_content != filter_dat_content)
-   {
-      playlist->scan_record.filter_dat_content = filter_dat_content;
-      playlist->modified = true;
-   }
-}
-
-/* Returns true if specified entry has a valid
- * core association (i.e. a non-empty string
- * other than DETECT) */
-bool playlist_entry_has_core(const struct playlist_entry *entry)
-{
-   if (  !entry
-       || string_is_empty(entry->core_path)
-       || string_is_empty(entry->core_name)
-       || string_is_equal(entry->core_path, FILE_PATH_DETECT)
-       || string_is_equal(entry->core_name, FILE_PATH_DETECT))
-      return false;
-   return true;
-}
-
-/* Fetches core info object corresponding to the
- * currently associated core of the specified
- * playlist entry.
- * Returns NULL if entry does not have a valid
- * core association */
-core_info_t *playlist_entry_get_core_info(const struct playlist_entry* entry)
-{
-   if (playlist_entry_has_core(entry))
-   {
-      core_info_t *core_info = NULL;
-      /* Search for associated core */
-      if (core_info_find(entry->core_path, &core_info))
-         return core_info;
-   }
-   return NULL;
-}
-
-/* Fetches core info object corresponding to the
- * currently associated default core of the
- * specified playlist.
- * Returns NULL if playlist does not have a valid
- * default core association */
-core_info_t *playlist_get_default_core_info(playlist_t* playlist)
-{
-   core_info_t *core_info = NULL;
-
-   if (  !playlist
-       || string_is_empty(playlist->default_core_path)
-       || string_is_empty(playlist->default_core_name)
-       || string_is_equal(playlist->default_core_path, FILE_PATH_DETECT)
-       || string_is_equal(playlist->default_core_name, FILE_PATH_DETECT))
-      return NULL;
-
-   /* Search for associated core */
-   if (core_info_find(playlist->default_core_path, &core_info))
-      return core_info;
-
-   return NULL;
-}

+ 0 - 406
app/src/main/cpp/playlist.h

@@ -1,406 +0,0 @@
-/*  RetroArch - A frontend for libretro.
- *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
- *  Copyright (C) 2011-2017 - Daniel De Matteis
- *  Copyright (C) 2016-2019 - Brad Parker
- *
- *  RetroArch is free software: you can redistribute it and/or modify it under the terms
- *  of the GNU General Public License as published by the Free Software Found-
- *  ation, either version 3 of the License, or (at your option) any later version.
- *
- *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- *  PURPOSE.  See the GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along with RetroArch.
- *  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef _PLAYLIST_H__
-#define _PLAYLIST_H__
-
-#include <stddef.h>
-
-#include <retro_common_api.h>
-#include <boolean.h>
-#include <lists/string_list.h>
-
-#include "core_info.h"
-
-RETRO_BEGIN_DECLS
-
-/* Default maximum playlist size */
-#define COLLECTION_SIZE 0x7FFFFFFF
-
-typedef struct content_playlist playlist_t;
-
-enum playlist_runtime_status
-{
-   PLAYLIST_RUNTIME_UNKNOWN = 0,
-   PLAYLIST_RUNTIME_MISSING,
-   PLAYLIST_RUNTIME_VALID
-};
-
-enum playlist_file_mode
-{
-   PLAYLIST_LOAD = 0,
-   PLAYLIST_SAVE
-};
-
-enum playlist_label_display_mode
-{
-   LABEL_DISPLAY_MODE_DEFAULT = 0,
-   LABEL_DISPLAY_MODE_REMOVE_PARENTHESES,
-   LABEL_DISPLAY_MODE_REMOVE_BRACKETS,
-   LABEL_DISPLAY_MODE_REMOVE_PARENTHESES_AND_BRACKETS,
-   LABEL_DISPLAY_MODE_KEEP_REGION,
-   LABEL_DISPLAY_MODE_KEEP_DISC_INDEX,
-   LABEL_DISPLAY_MODE_KEEP_REGION_AND_DISC_INDEX
-};
-
-enum playlist_thumbnail_mode
-{
-   PLAYLIST_THUMBNAIL_MODE_DEFAULT = 0,
-   PLAYLIST_THUMBNAIL_MODE_OFF,
-   PLAYLIST_THUMBNAIL_MODE_SCREENSHOTS,
-   PLAYLIST_THUMBNAIL_MODE_TITLE_SCREENS,
-   PLAYLIST_THUMBNAIL_MODE_BOXARTS
-};
-
-enum playlist_sort_mode
-{
-   PLAYLIST_SORT_MODE_DEFAULT = 0,
-   PLAYLIST_SORT_MODE_ALPHABETICAL,
-   PLAYLIST_SORT_MODE_OFF
-};
-
-/* TODO/FIXME - since gfx_thumbnail_path.h has now
- * been divorced from the menu code, perhaps jdgleaver
- * can refactor this? */
-
-/* Note: We already have a left/right enum defined
- * in gfx_thumbnail_path.h - but we can't include
- * menu code here, so have to make a 'duplicate'... */
-enum playlist_thumbnail_id
-{
-   PLAYLIST_THUMBNAIL_RIGHT = 0,
-   PLAYLIST_THUMBNAIL_LEFT
-};
-
-/* Holds all parameters required to uniquely
- * identify a playlist content path */
-typedef struct
-{
-   char *real_path;
-   char *archive_path;
-   uint32_t real_path_hash;
-   uint32_t archive_path_hash;
-   bool is_archive;
-   bool is_in_archive;
-} playlist_path_id_t;
-
-struct playlist_entry
-{
-   char *path;
-   char *label;
-   char *core_path;
-   char *core_name;
-   char *db_name;
-   char *crc32;
-   char *subsystem_ident;
-   char *subsystem_name;
-   char *runtime_str;
-   char *last_played_str;
-   struct string_list *subsystem_roms;
-   playlist_path_id_t *path_id;
-   unsigned entry_slot;
-   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;
-   enum playlist_runtime_status runtime_status;
-};
-
-/* Holds all configuration parameters required
- * when initialising/saving playlists */
-typedef struct
-{
-   size_t capacity;
-   bool old_format;
-   bool compress;
-   bool fuzzy_archive_match;
-   bool autofix_paths;   
-   char path[PATH_MAX_LENGTH];
-   char base_content_directory[PATH_MAX_LENGTH];
-} playlist_config_t;
-
-/* Convenience function: copies specified playlist
- * path to specified playlist configuration object */
-void playlist_config_set_path(playlist_config_t *config, const char *path);
-
-/* Convenience function: copies base content directory
- * path to specified playlist configuration object */
-void playlist_config_set_base_content_directory(playlist_config_t* config, const char* path);
-
-/* Creates a copy of the specified playlist configuration.
- * Returns false in the event of an error */
-bool playlist_config_copy(const playlist_config_t *src, playlist_config_t *dst);
-
-/* Returns internal playlist configuration object
- * of specified playlist.
- * Returns NULL it the event of an error. */
-playlist_config_t *playlist_get_config(playlist_t *playlist);
-
-/**
- * playlist_init:
- * @config            	: Playlist configuration object.
- *
- * Creates and initializes a playlist.
- *
- * Returns: handle to new playlist if successful, otherwise NULL
- **/
-playlist_t *playlist_init(const playlist_config_t *config);
-
-/**
- * playlist_free:
- * @playlist        	   : Playlist handle.
- *
- * Frees playlist handle.
- */
-void playlist_free(playlist_t *playlist);
-
-/**
- * playlist_clear:
- * @playlist        	   : Playlist handle.
- *
- * Clears all playlist entries in playlist.
- **/
-void playlist_clear(playlist_t *playlist);
-
-/**
- * playlist_size:
- * @playlist        	   : Playlist handle.
- *
- * Gets size of playlist.
- * Returns: size of playlist.
- **/
-size_t playlist_size(playlist_t *playlist);
-
-/**
- * playlist_capacity:
- * @playlist        	   : Playlist handle.
- *
- * Gets maximum capacity of playlist.
- * Returns: maximum capacity of playlist.
- **/
-size_t playlist_capacity(playlist_t *playlist);
-
-/**
- * playlist_get_index:
- * @playlist               : Playlist handle.
- * @idx                 : Index of playlist entry.
- *
- * Gets values of playlist index:
- **/
-void playlist_get_index(playlist_t *playlist,
-      size_t idx,
-      const struct playlist_entry **entry);
-
-/**
- * playlist_delete_index:
- * @playlist               : Playlist handle.
- * @idx                 : Index of playlist entry.
- *
- * Deletes the entry at index:
- **/
-void playlist_delete_index(playlist_t *playlist,
-      size_t idx);
-
-/**
- * playlist_delete_by_path:
- * @playlist            : Playlist handle.
- * @search_path         : Content path.
- *
- * Deletes all entries with content path
- * matching 'search_path'
- **/
-void playlist_delete_by_path(playlist_t *playlist,
-      const char *search_path);
-
-/**
- * playlist_resolve_path:
- * @mode      : PLAYLIST_LOAD or PLAYLIST_SAVE
- * @is_core   : Set true if path to be resolved is a core file
- * @path      : The path to be modified
- *
- * Resolves the path of an item, such as the content path or path to the core, to a format
- * appropriate for saving or loading depending on the @mode parameter
- *
- * Can be platform specific. File paths for saving can be abbreviated to avoid saving absolute
- * paths, as the base directory (home or application dir) may change after each subsequent
- * install (iOS)
- **/
-void playlist_resolve_path(enum playlist_file_mode mode,
-      bool is_core, char *path, size_t len);
-
-/**
- * playlist_content_path_is_valid:
- * @path      : Content path
- *
- * Checks whether specified playlist content path
- * refers to an existent file. Handles all playlist
- * content path 'types' (i.e. can validate paths
- * referencing files inside archives).
- *
- * Returns true if file referenced by content
- * path exists on the host filesystem.
- **/
-bool playlist_content_path_is_valid(const char *path);
-
-/**
- * playlist_push:
- * @playlist        	   : Playlist handle.
- *
- * Push entry to top of playlist.
- **/
-bool playlist_push(playlist_t *playlist,
-      const struct playlist_entry *entry);
-
-bool playlist_push_runtime(playlist_t *playlist,
-      const struct playlist_entry *entry);
-
-void playlist_update(playlist_t *playlist, size_t idx,
-      const struct playlist_entry *update_entry);
-
-/* 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 struct playlist_entry *update_entry,
-      bool register_update);
-
-void playlist_get_index_by_path(playlist_t *playlist,
-      const char *search_path,
-      const struct playlist_entry **entry);
-
-bool playlist_entry_exists(playlist_t *playlist,
-      const char *path);
-
-char *playlist_get_conf_path(playlist_t *playlist);
-
-uint32_t playlist_get_size(playlist_t *playlist);
-
-void playlist_write_file(playlist_t *playlist);
-
-void playlist_write_runtime_file(playlist_t *playlist);
-
-void playlist_qsort(playlist_t *playlist);
-
-void playlist_free_cached(void);
-
-playlist_t *playlist_get_cached(void);
-
-/* If current on-disk playlist file referenced
- * by 'config->path' does not match requested
- * 'old format' or 'compression' state, file will
- * be updated automatically
- * > Since this function is called whenever a
- *   playlist is browsed via the menu, this is
- *   a simple method for ensuring that files
- *   are always kept synced with user settings */
-bool playlist_init_cached(const playlist_config_t *config);
-
-void command_playlist_push_write(
-      playlist_t *playlist,
-      const struct playlist_entry *entry);
-
-void command_playlist_update_write(
-      playlist_t *playlist,
-      size_t idx,
-      const struct playlist_entry *entry);
-
-/* Returns true if specified playlist index matches
- * specified content/core paths */
-bool playlist_index_is_valid(playlist_t *playlist, size_t idx,
-      const char *path, const char *core_path);
-
-/* Returns true if specified playlist entries have
- * identical content and core paths */
-bool playlist_entries_are_equal(
-      const struct playlist_entry *entry_a,
-      const struct playlist_entry *entry_b,
-      const playlist_config_t *config);
-
-/* Returns true if entries at specified indices
- * of specified playlist have identical content
- * and core paths */
-bool playlist_index_entries_are_equal(
-      playlist_t *playlist, size_t idx_a, size_t idx_b);
-
-void playlist_get_crc32(playlist_t *playlist, size_t idx,
-      const char **crc32);
-
-/* If db_name is empty, 'returns' playlist file basename */
-void playlist_get_db_name(playlist_t *playlist, size_t idx,
-      const char **db_name);
-
-const char *playlist_get_default_core_path(playlist_t *playlist);
-const char *playlist_get_default_core_name(playlist_t *playlist);
-enum playlist_label_display_mode playlist_get_label_display_mode(playlist_t *playlist);
-enum playlist_thumbnail_mode playlist_get_thumbnail_mode(
-      playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id);
-enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist);
-const char *playlist_get_scan_content_dir(playlist_t *playlist);
-const char *playlist_get_scan_file_exts(playlist_t *playlist);
-const char *playlist_get_scan_dat_file_path(playlist_t *playlist);
-bool playlist_get_scan_search_recursively(playlist_t *playlist);
-bool playlist_get_scan_search_archives(playlist_t *playlist);
-bool playlist_get_scan_filter_dat_content(playlist_t *playlist);
-bool playlist_scan_refresh_enabled(playlist_t *playlist);
-
-void playlist_set_default_core_path(playlist_t *playlist, const char *core_path);
-void playlist_set_default_core_name(playlist_t *playlist, const char *core_name);
-void playlist_set_label_display_mode(playlist_t *playlist, enum playlist_label_display_mode label_display_mode);
-void playlist_set_thumbnail_mode(
-      playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id, enum playlist_thumbnail_mode thumbnail_mode);
-void playlist_set_sort_mode(playlist_t *playlist, enum playlist_sort_mode sort_mode);
-void playlist_set_scan_content_dir(playlist_t *playlist, const char *content_dir);
-void playlist_set_scan_file_exts(playlist_t *playlist, const char *file_exts);
-void playlist_set_scan_dat_file_path(playlist_t *playlist, const char *dat_file_path);
-void playlist_set_scan_search_recursively(playlist_t *playlist, bool search_recursively);
-void playlist_set_scan_search_archives(playlist_t *playlist, bool search_archives);
-void playlist_set_scan_filter_dat_content(playlist_t *playlist, bool filter_dat_content);
-
-/* Returns true if specified entry has a valid
- * core association (i.e. a non-empty string
- * other than DETECT) */
-bool playlist_entry_has_core(const struct playlist_entry *entry);
-
-/* Fetches core info object corresponding to the
- * currently associated core of the specified
- * playlist entry.
- * Returns NULL if entry does not have a valid
- * core association */
-core_info_t *playlist_entry_get_core_info(const struct playlist_entry* entry);
-
-/* Fetches core info object corresponding to the
- * currently associated default core of the
- * specified playlist.
- * Returns NULL if playlist does not have a valid
- * default core association */
-core_info_t *playlist_get_default_core_info(playlist_t* playlist);
-
-void playlist_set_cached_external(playlist_t* pl);
-
-RETRO_END_DECLS
-
-#endif

+ 14 - 2
app/src/main/cpp/retroarch.c

@@ -3022,6 +3022,7 @@ bool command_event(enum event_command cmd, void *data)
          }
          break;
       case CMD_EVENT_HISTORY_DEINIT:
+#ifdef HAVE_PLAYLIST
          if (g_defaults.content_history)
          {
             playlist_write_file(g_defaults.content_history);
@@ -3052,9 +3053,11 @@ bool command_event(enum event_command cmd, void *data)
             playlist_free(g_defaults.image_history);
          }
          g_defaults.image_history = NULL;
+#endif
 #endif
          break;
       case CMD_EVENT_HISTORY_INIT:
+#ifdef HAVE_PLAYLIST
          {
             playlist_config_t playlist_config;
             const char *_msg                       = NULL;
@@ -3115,6 +3118,7 @@ bool command_event(enum event_command cmd, void *data)
                   g_defaults.image_history, PLAYLIST_SORT_MODE_OFF);
 #endif
          }
+#endif
          break;
       case CMD_EVENT_CORE_INFO_DEINIT:
          core_info_deinit_list();
@@ -3359,6 +3363,7 @@ bool command_event(enum event_command cmd, void *data)
          }
          break;
       case CMD_EVENT_ADD_TO_FAVORITES:
+#ifdef HAVE_PLAYLIST
          {
             struct string_list *str_list = (struct string_list*)data;
 
@@ -3403,9 +3408,11 @@ bool command_event(enum event_command cmd, void *data)
                }
             }
 
-            break;
          }
+#endif
+           break;
       case CMD_EVENT_RESET_CORE_ASSOCIATION:
+#ifdef HAVE_PLAYLIST
          {
             const char *core_name          = "DETECT";
             const char *core_path          = "DETECT";
@@ -3433,8 +3440,9 @@ bool command_event(enum event_command cmd, void *data)
 #endif
 
             runloop_msg_queue_push(msg_hash_to_str(MSG_RESET_CORE_ASSOCIATION), 1, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
-            break;
          }
+#endif
+           break;
       case CMD_EVENT_RESTART_RETROARCH:
          if (!frontend_driver_set_fork(FRONTEND_FORK_RESTART))
             return false;
@@ -4682,7 +4690,9 @@ int rarch_main(int argc, char *argv[], void *data)
 
    libretro_free_system_info(&runloop_st->system.info);
    command_event(CMD_EVENT_HISTORY_DEINIT, NULL);
+#ifdef HAVE_PLAYLIST
    retroarch_favorites_deinit();
+#endif
 
    retroarch_config_init();
 
@@ -7247,6 +7257,7 @@ enum retro_language retroarch_get_language_from_iso(const char *iso639)
    return lang;
 }
 
+#ifdef HAVE_PLAYLIST
 void retroarch_favorites_init(void)
 {
    settings_t *settings                = config_get_ptr();
@@ -7295,6 +7306,7 @@ void retroarch_favorites_deinit(void)
    playlist_free(g_defaults.content_favorites);
    g_defaults.content_favorites = NULL;
 }
+#endif
 
 #ifdef HAVE_ACCESSIBILITY
 bool accessibility_speak_priority(

+ 2 - 0
app/src/main/cpp/retroarch.h

@@ -126,9 +126,11 @@ void retroarch_menu_running_finished(bool quit);
 
 enum retro_language retroarch_get_language_from_iso(const char *lang);
 
+#ifdef HAVE_PLAYLIST
 void retroarch_favorites_init(void);
 
 void retroarch_favorites_deinit(void);
+#endif
 
 /* Audio */
 

+ 15 - 7
app/src/main/cpp/runloop.c

@@ -137,7 +137,9 @@
 #include "audio/audio_driver.h"
 #include "gfx/gfx_animation.h"
 #include "gfx/gfx_display.h"
+#ifdef HAVE_GFX_THUBNAIL
 #include "gfx/gfx_thumbnail.h"
+#endif
 #include "gfx/video_filter.h"
 
 #include "input/input_osk.h"
@@ -6888,21 +6890,27 @@ int runloop_iterate(void)
             && !netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL);
 #endif
 
-      if (want_runahead)
+      if (want_runahead) {
+         RARCH_LOG("[DEBUG]: %s:%d", __FILE_NAME__, __LINE__);
          runahead_run(
-               runloop_st,
-               run_ahead_num_frames,
-               run_ahead_hide_warnings,
-               run_ahead_secondary_instance);
-      else if (runloop_st->preempt_data)
+                 runloop_st,
+                 run_ahead_num_frames,
+                 run_ahead_hide_warnings,
+                 run_ahead_secondary_instance);
+      } else if (runloop_st->preempt_data) {
+         RARCH_LOG("[DEBUG]: %s:%d", __FILE_NAME__, __LINE__);
          preempt_run(runloop_st->preempt_data, runloop_st);
-      else
+      } else
 #endif
+      {
+         RARCH_LOG("[DEBUG]: %s:%d", __FILE_NAME__, __LINE__);
          core_run();
+      }
    }
 
    /* Increment runtime tick counter after each call to
     * core_run() or run_ahead() */
+   RARCH_LOG("[DEBUG]: %s:%d", __FILE_NAME__, __LINE__);
    runloop_st->core_runtime_usec += runloop_core_runtime_tick(
          runloop_st,
          slowmotion_ratio,

+ 2 - 0
app/src/main/cpp/runtime_file.c

@@ -1271,6 +1271,7 @@ void runtime_log_convert_usec2hms(retro_time_t usec,
 
 /* Updates specified playlist entry runtime values with
  * contents of associated log file */
+#ifdef HAVE_PLAYLIST
 void runtime_update_playlist(
       playlist_t *playlist, size_t idx,
       const char *dir_runtime_log,
@@ -1373,6 +1374,7 @@ void runtime_update_playlist(
    /* Update playlist */
    playlist_update_runtime(playlist, idx, &update_entry, false);
 }
+#endif
 
 #if defined(HAVE_MENU)
 /* Contentless cores manipulation */

+ 4 - 1
app/src/main/cpp/runtime_file.h

@@ -29,7 +29,9 @@
 #include <time.h>
 #include <boolean.h>
 
+#ifdef HAVE_PLAYLIST
 #include "playlist.h"
+#endif
 #include "runtime_file_defines.h"
 
 RETRO_BEGIN_DECLS
@@ -147,7 +149,7 @@ void runtime_log_save(runtime_log_t *runtime_log);
 void runtime_log_convert_usec2hms(retro_time_t usec, unsigned *hours, unsigned *minutes, unsigned *seconds);
 
 /* Playlist manipulation */
-
+#ifdef HAVE_PLAYLIST
 /* Updates specified playlist entry runtime values with
  * contents of associated log file */
 void runtime_update_playlist(
@@ -157,6 +159,7 @@ void runtime_update_playlist(
       bool log_per_core,
       enum playlist_sublabel_last_played_style_type timedate_style,
       enum playlist_sublabel_last_played_date_separator_type date_separator);
+#endif
 
 #if defined(HAVE_MENU)
 /* Contentless cores manipulation */

+ 8 - 0
app/src/main/cpp/tasks/task_content.c

@@ -92,7 +92,9 @@
 #include "../file_path_special.h"
 #include "../frontend/frontend.h"
 #include "../msg_hash.h"
+#ifdef HAVE_PLAYLIST
 #include "../playlist.h"
+#endif
 #include "../paths.h"
 #include "../retroarch.h"
 #include "../runloop.h"
@@ -1489,7 +1491,9 @@ static bool content_load(content_ctx_info_t *info,
 #endif
 
    command_event(CMD_EVENT_HISTORY_INIT, NULL);
+#ifdef HAVE_PLAYLIST
    retroarch_favorites_init();
+#endif
    command_event(CMD_EVENT_RESUME, NULL);
    command_event(CMD_EVENT_VIDEO_SET_ASPECT_RATIO, NULL);
 
@@ -1540,6 +1544,7 @@ void menu_content_environment_get(int *argc, char *argv[],
          : path_get(RARCH_PATH_CORE);
 }
 
+#ifdef HAVE_PLAYLIST
 /**
  * task_push_to_history_list:
  *
@@ -1689,6 +1694,7 @@ static void task_push_to_history_list(
       }
    }
 }
+#endif
 
 #ifndef HAVE_DYNAMIC
 /**
@@ -2222,7 +2228,9 @@ bool task_push_start_current_core(content_ctx_info_t *content_info)
       goto end;
    }
 
+#ifdef HAVE_PLAYLIST
    task_push_to_history_list(p_content, true, false, false);
+#endif
 
 #ifdef HAVE_MENU
    /* Push Quick Menu onto menu stack */

+ 18 - 2
app/src/main/cpp/tasks/task_database.c

@@ -34,7 +34,9 @@
 
 #include "../file_path_special.h"
 #include "../msg_hash.h"
+#ifdef HAVE_PLAYLIST
 #include "../playlist.h"
+#endif
 #ifdef RARCH_INTERNAL
 #include "../configuration.h"
 #include "../ui/ui_companion_driver.h"
@@ -72,7 +74,9 @@ typedef struct db_handle
    char *fullpath;
    database_info_handle_t *handle;
    database_state_handle_t state;
+#ifdef HAVE_PLAYLIST
    playlist_config_t playlist_config; /* size_t alignment */
+#endif
    unsigned status;
    uint8_t flags;
 } db_handle_t;
@@ -703,7 +707,9 @@ static int database_info_list_iterate_found_match(
    char* entry_path_str           = (char*)malloc(str_len);
    char* entry_label              = (char*)malloc(str_len);
    char *hash                     = NULL;
+#ifdef HAVE_PLAYLIST
    playlist_t   *playlist         = NULL;
+#endif
    const char         *db_path    =
       database_info_get_current_name(db_state);
    const char         *entry_path =
@@ -725,8 +731,10 @@ static int database_info_list_iterate_found_match(
       fill_pathname_join_special(db_playlist_path, _db->playlist_directory,
             db_playlist_base_str, str_len);
 
+#ifdef HAVE_PLAYLIST
    playlist_config_set_path(&_db->playlist_config, db_playlist_path);
    playlist = playlist_init(&_db->playlist_config);
+#endif
 
    if (!string_is_empty(db_state->serial))
    {
@@ -777,6 +785,7 @@ static int database_info_list_iterate_found_match(
    fprintf(stderr, "entry path str: %s\n", entry_path_str);
 #endif
 
+#ifdef HAVE_PLAYLIST
    if (!playlist_entry_exists(playlist, entry_path_str))
    {
       struct playlist_entry entry;
@@ -811,6 +820,7 @@ static int database_info_list_iterate_found_match(
 
    playlist_write_file(playlist);
    playlist_free(playlist);
+#endif
 
    database_info_list_free(db_state->info);
    free(db_state->info);
@@ -955,8 +965,9 @@ static int task_database_iterate_playlist_lutro(
       const char *path)
 {
    char db_playlist_path[PATH_MAX_LENGTH];
+#ifdef HAVE_PLAYLIST
    playlist_t   *playlist  = NULL;
-
+#endif
    db_playlist_path[0]     = '\0';
 
    if (!string_is_empty(_db->playlist_directory))
@@ -964,6 +975,7 @@ static int task_database_iterate_playlist_lutro(
             _db->playlist_directory,
             "Lutro.lpl", sizeof(db_playlist_path));
 
+#ifdef HAVE_PLAYLIST
    playlist_config_set_path(&_db->playlist_config, db_playlist_path);
    playlist = playlist_init(&_db->playlist_config);
 
@@ -1002,7 +1014,7 @@ static int task_database_iterate_playlist_lutro(
 
    playlist_write_file(playlist);
    playlist_free(playlist);
-
+#endif
    return 0;
 }
 
@@ -1348,17 +1360,21 @@ bool task_push_dbscan(
    t->progress_cb                          = task_database_progress_cb;
    if (settings->bools.scan_without_core_match)
       db->flags |= DB_HANDLE_FLAG_SCAN_WITHOUT_CORE_MATCH;
+#ifdef HAVE_PLAYLIST
    db->playlist_config.capacity            = COLLECTION_SIZE;
    db->playlist_config.old_format          = settings->bools.playlist_use_old_format;
    db->playlist_config.compress            = settings->bools.playlist_compression;
    db->playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
    playlist_config_set_base_content_directory(&db->playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
+#endif
 #else
+#ifdef HAVE_PLAYLIST
    db->playlist_config.capacity            = COLLECTION_SIZE;
    db->playlist_config.old_format          = false;
    db->playlist_config.compress            = false;
    db->playlist_config.fuzzy_archive_match = false;
    playlist_config_set_base_content_directory(&db->playlist_config, NULL);
+#endif
 #endif
    if (db_dir_show_hidden_files)
       db->flags |= DB_HANDLE_FLAG_SHOW_HIDDEN_FILES;

+ 0 - 614
app/src/main/cpp/tasks/task_manual_content_scan.c

@@ -1,614 +0,0 @@
-/*  RetroArch - A frontend for libretro.
- *  Copyright (C) 2011-2017 - Daniel De Matteis
- *  Copyright (C) 2014-2017 - Jean-André Santoni
- *  Copyright (C) 2016-2019 - Brad Parker
- *  Copyright (C)      2019 - James Leaver
- *
- *  RetroArch is free software: you can redistribute it and/or modify it under the terms
- *  of the GNU General Public License as published by the Free Software Found-
- *  ation, either version 3 of the License, or (at your option) any later version.
- *
- *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- *  PURPOSE.  See the GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along with RetroArch.
- *  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <boolean.h>
-
-#include <string/stdstring.h>
-#include <lists/string_list.h>
-#include <file/file_path.h>
-#include <formats/logiqx_dat.h>
-#include <formats/m3u_file.h>
-
-#include "tasks_internal.h"
-
-#include "../msg_hash.h"
-#include "../playlist.h"
-#include "../manual_content_scan.h"
-
-#ifdef RARCH_INTERNAL
-#ifdef HAVE_MENU
-#include "../menu/menu_driver.h"
-#endif
-#endif
-
-enum manual_scan_status
-{
-   MANUAL_SCAN_BEGIN = 0,
-   MANUAL_SCAN_ITERATE_CLEAN,
-   MANUAL_SCAN_ITERATE_CONTENT,
-   MANUAL_SCAN_ITERATE_M3U,
-   MANUAL_SCAN_END
-};
-
-typedef struct manual_scan_handle
-{
-   manual_content_scan_task_config_t *task_config;
-   playlist_t *playlist;
-   struct string_list *file_exts_list;
-   struct string_list *content_list;
-   logiqx_dat_t *dat_file;
-   struct string_list *m3u_list;
-   playlist_config_t playlist_config; /* size_t alignment */
-   size_t playlist_size;
-   size_t playlist_index;
-   size_t content_list_size;
-   size_t content_list_index;
-   size_t m3u_index;
-   enum manual_scan_status status;
-} manual_scan_handle_t;
-
-/* Frees task handle + all constituent objects */
-static void free_manual_content_scan_handle(manual_scan_handle_t *manual_scan)
-{
-   if (!manual_scan)
-      return;
-
-   if (manual_scan->task_config)
-   {
-      free(manual_scan->task_config);
-      manual_scan->task_config = NULL;
-   }
-
-   if (manual_scan->playlist)
-   {
-      playlist_free(manual_scan->playlist);
-      manual_scan->playlist = NULL;
-   }
-
-   if (manual_scan->file_exts_list)
-   {
-      string_list_free(manual_scan->file_exts_list);
-      manual_scan->file_exts_list = NULL;
-   }
-
-   if (manual_scan->content_list)
-   {
-      string_list_free(manual_scan->content_list);
-      manual_scan->content_list = NULL;
-   }
-
-   if (manual_scan->m3u_list)
-   {
-      string_list_free(manual_scan->m3u_list);
-      manual_scan->m3u_list = NULL;
-   }
-
-   if (manual_scan->dat_file)
-   {
-      logiqx_dat_free(manual_scan->dat_file);
-      manual_scan->dat_file = NULL;
-   }
-
-   free(manual_scan);
-   manual_scan = NULL;
-}
-
-static void cb_task_manual_content_scan(
-      retro_task_t *task, void *task_data,
-      void *user_data, const char *err)
-{
-   manual_scan_handle_t *manual_scan = NULL;
-   playlist_t *cached_playlist       = playlist_get_cached();
-#if defined(RARCH_INTERNAL) && defined(HAVE_MENU)
-   struct menu_state *menu_st        = menu_state_get_ptr();
-   if (!task)
-      goto end;
-#else
-   if (!task)
-      return;
-#endif
-
-   if (!(manual_scan = (manual_scan_handle_t*)task->state))
-   {
-#if defined(RARCH_INTERNAL) && defined(HAVE_MENU)
-      goto end;
-#else
-      return;
-#endif
-   }
-
-   /* If the manual content scan task has modified the
-    * currently cached playlist, then it must be re-cached
-    * (otherwise changes will be lost if the currently
-    * cached playlist is saved to disk for any reason...) */
-   if (cached_playlist)
-   {
-      if (string_is_equal(
-            manual_scan->playlist_config.path,
-            playlist_get_conf_path(cached_playlist)))
-      {
-         playlist_config_t playlist_config;
-
-         /* Copy configuration of cached playlist
-          * (could use manual_scan->playlist_config,
-          * but doing it this way guarantees that
-          * the cached playlist is preserved in
-          * its original state) */
-         if (playlist_config_copy(
-               playlist_get_config(cached_playlist),
-               &playlist_config))
-         {
-            playlist_free_cached();
-            playlist_init_cached(&playlist_config);
-         }
-      }
-   }
-
-#if defined(RARCH_INTERNAL) && defined(HAVE_MENU)
-end:
-   /* When creating playlists, the playlist tabs of
-    * any active menu driver must be refreshed */
-   if (menu_st->driver_ctx->environ_cb)
-      menu_st->driver_ctx->environ_cb(MENU_ENVIRON_RESET_HORIZONTAL_LIST,
-            NULL, menu_st->userdata);
-#endif
-}
-
-static void task_manual_content_scan_free(retro_task_t *task)
-{
-   manual_scan_handle_t *manual_scan = NULL;
-
-   if (!task)
-      return;
-
-   manual_scan = (manual_scan_handle_t*)task->state;
-
-   free_manual_content_scan_handle(manual_scan);
-}
-
-static void task_manual_content_scan_handler(retro_task_t *task)
-{
-   manual_scan_handle_t *manual_scan = NULL;
-
-   if (!task)
-      goto task_finished;
-
-   if (!(manual_scan = (manual_scan_handle_t*)task->state))
-      goto task_finished;
-
-   if (task_get_cancelled(task))
-      goto task_finished;
-
-   switch (manual_scan->status)
-   {
-      case MANUAL_SCAN_BEGIN:
-         {
-            /* Get allowed file extensions list */
-            if (!string_is_empty(manual_scan->task_config->file_exts))
-               manual_scan->file_exts_list = string_split(
-                     manual_scan->task_config->file_exts, "|");
-
-            /* Get content list */
-            if (!(manual_scan->content_list 
-                     = manual_content_scan_get_content_list(
-                        manual_scan->task_config)))
-            {
-               runloop_msg_queue_push(
-                     msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_INVALID_CONTENT),
-                     1, 100, true,
-                     NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
-               goto task_finished;
-            }
-
-            manual_scan->content_list_size = manual_scan->content_list->size;
-
-            /* Load DAT file, if required */
-            if (!string_is_empty(manual_scan->task_config->dat_file_path))
-            {
-               if (!(manual_scan->dat_file =
-                     logiqx_dat_init(
-                        manual_scan->task_config->dat_file_path)))
-               {
-                  runloop_msg_queue_push(
-                        msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_DAT_FILE_LOAD_ERROR),
-                        1, 100, true,
-                        NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
-                  goto task_finished;
-               }
-            }
-
-            /* Open playlist */
-            if (!(manual_scan->playlist =
-                     playlist_init(&manual_scan->playlist_config)))
-               goto task_finished;
-
-            /* Reset playlist, if required */
-            if (manual_scan->task_config->overwrite_playlist)
-               playlist_clear(manual_scan->playlist);
-
-            /* Get initial playlist size */
-            manual_scan->playlist_size = 
-               playlist_size(manual_scan->playlist);
-
-            /* Set default core, if required */
-            if (manual_scan->task_config->core_set)
-            {
-               playlist_set_default_core_path(manual_scan->playlist,
-                     manual_scan->task_config->core_path);
-               playlist_set_default_core_name(manual_scan->playlist,
-                     manual_scan->task_config->core_name);
-            }
-
-            /* Record remaining scan parameters to enable
-             * subsequent 'refresh playlist' operations */
-            playlist_set_scan_content_dir(manual_scan->playlist,
-                  manual_scan->task_config->content_dir);
-            playlist_set_scan_file_exts(manual_scan->playlist,
-                  manual_scan->task_config->file_exts_custom_set ?
-                        manual_scan->task_config->file_exts : NULL);
-            playlist_set_scan_dat_file_path(manual_scan->playlist,
-                  manual_scan->task_config->dat_file_path);
-            playlist_set_scan_search_recursively(manual_scan->playlist,
-                  manual_scan->task_config->search_recursively);
-            playlist_set_scan_search_archives(manual_scan->playlist,
-                  manual_scan->task_config->search_archives);
-            playlist_set_scan_filter_dat_content(manual_scan->playlist,
-                  manual_scan->task_config->filter_dat_content);
-
-            /* All good - can start iterating
-             * > If playlist has content and 'validate
-             *   entries' is enabled, go to clean-up phase
-             * > Otherwise go straight to content scan phase */
-            if (manual_scan->task_config->validate_entries &&
-                (manual_scan->playlist_size > 0))
-               manual_scan->status = MANUAL_SCAN_ITERATE_CLEAN;
-            else
-               manual_scan->status = MANUAL_SCAN_ITERATE_CONTENT;
-         }
-         break;
-      case MANUAL_SCAN_ITERATE_CLEAN:
-         {
-            const struct playlist_entry *entry = NULL;
-            bool delete_entry                  = false;
-
-            /* Get current entry */
-            playlist_get_index(manual_scan->playlist,
-                  manual_scan->playlist_index, &entry);
-
-            if (entry)
-            {
-               const char *entry_file     = NULL;
-               const char *entry_file_ext = NULL;
-               char task_title[PATH_MAX_LENGTH];
-
-               /* Update progress display */
-               task_free_title(task);
-
-               strlcpy(task_title,
-                     msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_PLAYLIST_CLEANUP),
-                     sizeof(task_title));
-
-               if (!string_is_empty(entry->path) &&
-                   (entry_file = path_basename(entry->path)))
-                  strlcat(task_title, entry_file, sizeof(task_title));
-
-               task_set_title(task, strdup(task_title));
-               task_set_progress(task, (manual_scan->playlist_index * 100) /
-                     manual_scan->playlist_size);
-
-               /* Check whether playlist content exists on
-                * the filesystem */
-               if (!playlist_content_path_is_valid(entry->path))
-                  delete_entry = true;
-               /* If file exists, check whether it has a
-                * permitted file extension */
-               else if (manual_scan->file_exts_list &&
-                        (entry_file_ext = path_get_extension(entry->path)) &&
-                        !string_list_find_elem_prefix(
-                              manual_scan->file_exts_list,
-                              ".", entry_file_ext))
-                  delete_entry = true;
-
-               if (delete_entry)
-               {
-                  /* Invalid content - delete entry */
-                  playlist_delete_index(manual_scan->playlist,
-                        manual_scan->playlist_index);
-
-                  /* Update playlist_size */
-                  manual_scan->playlist_size = playlist_size(manual_scan->playlist);
-               }
-            }
-
-            /* Increment entry index *if* current entry still
-             * exists (i.e. if entry was deleted, current index
-             * will already point to the *next* entry) */
-            if (!delete_entry)
-               manual_scan->playlist_index++;
-
-            if (manual_scan->playlist_index >=
-                  manual_scan->playlist_size)
-               manual_scan->status = MANUAL_SCAN_ITERATE_CONTENT;
-         }
-         break;
-      case MANUAL_SCAN_ITERATE_CONTENT:
-         {
-            const char *content_path = manual_scan->content_list->elems[
-                  manual_scan->content_list_index].data;
-            int content_type         = manual_scan->content_list->elems[
-                  manual_scan->content_list_index].attr.i;
-
-            if (!string_is_empty(content_path))
-            {
-               char task_title[PATH_MAX_LENGTH];
-               const char *content_file = path_basename(content_path);
-
-               /* Update progress display */
-               task_free_title(task);
-
-               strlcpy(task_title,
-                     msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_IN_PROGRESS),
-                     sizeof(task_title));
-
-               if (!string_is_empty(content_file))
-                  strlcat(task_title, content_file, sizeof(task_title));
-
-               task_set_title(task, strdup(task_title));
-               task_set_progress(task,
-                     (manual_scan->content_list_index * 100) /
-                     manual_scan->content_list_size);
-
-               /* Add content to playlist */
-               manual_content_scan_add_content_to_playlist(
-                     manual_scan->task_config, manual_scan->playlist,
-                     content_path, content_type, manual_scan->dat_file);
-
-               /* If this is an M3U file, add it to the
-                * M3U list for later processing */
-               if (m3u_file_is_m3u(content_path))
-               {
-                  union string_list_elem_attr attr;
-                  attr.i = 0;
-                  /* Note: If string_list_append() fails, there is
-                   * really nothing we can do. The M3U file will
-                   * just be ignored... */
-                  string_list_append(
-                        manual_scan->m3u_list, content_path, attr);
-               }
-            }
-
-            /* Increment content index */
-            manual_scan->content_list_index++;
-            if (manual_scan->content_list_index >=
-                  manual_scan->content_list_size)
-            {
-               /* Check whether we have any M3U files
-                * to process */
-               if (manual_scan->m3u_list->size > 0)
-                  manual_scan->status = MANUAL_SCAN_ITERATE_M3U;
-               else
-                  manual_scan->status = MANUAL_SCAN_END;
-            }
-         }
-         break;
-      case MANUAL_SCAN_ITERATE_M3U:
-         {
-            const char *m3u_path = manual_scan->m3u_list->elems[
-                  manual_scan->m3u_index].data;
-
-            if (!string_is_empty(m3u_path))
-            {
-               char task_title[PATH_MAX_LENGTH];
-               const char *m3u_name = path_basename_nocompression(m3u_path);
-               m3u_file_t *m3u_file = NULL;
-
-               /* Update progress display */
-               task_free_title(task);
-
-               strlcpy(task_title,
-                     msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP),
-                     sizeof(task_title));
-
-               if (!string_is_empty(m3u_name))
-                  strlcat(task_title, m3u_name, sizeof(task_title));
-
-               task_set_title(task, strdup(task_title));
-               task_set_progress(task, (manual_scan->m3u_index * 100) /
-                     manual_scan->m3u_list->size);
-
-               /* Load M3U file */
-               if ((m3u_file = m3u_file_init(m3u_path)))
-               {
-                  size_t i;
-
-                  /* Loop over M3U entries */
-                  for (i = 0; i < m3u_file_get_size(m3u_file); i++)
-                  {
-                     m3u_file_entry_t *m3u_entry = NULL;
-
-                     /* Delete any playlist items matching the
-                      * content path of the M3U entry */
-                     if (m3u_file_get_entry(m3u_file, i, &m3u_entry))
-                        playlist_delete_by_path(
-                              manual_scan->playlist, m3u_entry->full_path);
-                  }
-
-                  m3u_file_free(m3u_file);
-               }
-            }
-
-            /* Increment M3U file index */
-            manual_scan->m3u_index++;
-            if (manual_scan->m3u_index >= manual_scan->m3u_list->size)
-               manual_scan->status = MANUAL_SCAN_END;
-         }
-         break;
-      case MANUAL_SCAN_END:
-         {
-            char task_title[PATH_MAX_LENGTH];
-
-            /* Ensure playlist is alphabetically sorted
-             * > Override user settings here */
-            playlist_set_sort_mode(manual_scan->playlist, PLAYLIST_SORT_MODE_DEFAULT);
-            playlist_qsort(manual_scan->playlist);
-
-            /* Save playlist changes to disk */
-            playlist_write_file(manual_scan->playlist);
-
-            /* Update progress display */
-            task_free_title(task);
-
-            strlcpy(
-                  task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_END),
-                  sizeof(task_title));
-            strlcat(task_title, manual_scan->task_config->system_name,
-                  sizeof(task_title));
-
-            task_set_title(task, strdup(task_title));
-         }
-         /* fall-through */
-      default:
-         task_set_progress(task, 100);
-         goto task_finished;
-   }
-   
-   return;
-   
-task_finished:
-
-   if (task)
-      task_set_finished(task, true);
-}
-
-static bool task_manual_content_scan_finder(retro_task_t *task, void *user_data)
-{
-   manual_scan_handle_t *manual_scan = NULL;
-
-   if (!task || !user_data)
-      return false;
-   if (task->handler != task_manual_content_scan_handler)
-      return false;
-   if (!(manual_scan = (manual_scan_handle_t*)task->state))
-      return false;
-   return string_is_equal(
-         (const char*)user_data, manual_scan->playlist_config.path);
-}
-
-bool task_push_manual_content_scan(
-      const playlist_config_t *playlist_config,
-      const char *playlist_directory)
-{
-   task_finder_data_t find_data;
-   char task_title[PATH_MAX_LENGTH];
-   retro_task_t *task                = NULL;
-   manual_scan_handle_t *manual_scan = NULL;
-
-   /* Sanity check */
-   if (  !playlist_config
-       || string_is_empty(playlist_directory))
-      return false;
-
-   if (!(manual_scan = (manual_scan_handle_t*)
-         calloc(1, sizeof(manual_scan_handle_t))))
-      return false;
-
-   /* Configure handle */
-   manual_scan->task_config         = NULL;
-   manual_scan->playlist            = NULL;
-   manual_scan->file_exts_list      = NULL;
-   manual_scan->content_list        = NULL;
-   manual_scan->dat_file            = NULL;
-   manual_scan->playlist_size       = 0;
-   manual_scan->playlist_index      = 0;
-   manual_scan->content_list_size   = 0;
-   manual_scan->content_list_index  = 0;
-   manual_scan->status              = MANUAL_SCAN_BEGIN;
-   manual_scan->m3u_index           = 0;
-   manual_scan->m3u_list            = string_list_new();
-
-   if (!manual_scan->m3u_list)
-      goto error;
-
-   /* > Get current manual content scan configuration */
-   if (!(manual_scan->task_config = (manual_content_scan_task_config_t*)
-         calloc(1, sizeof(manual_content_scan_task_config_t))))
-      goto error;
-
-   if ( !manual_content_scan_get_task_config(
-         manual_scan->task_config, playlist_directory))
-   {
-      runloop_msg_queue_push(
-            msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_INVALID_CONFIG),
-            1, 100, true,
-            NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
-      goto error;
-   }
-
-   /* > Cache playlist configuration */
-   if (!playlist_config_copy(playlist_config,
-         &manual_scan->playlist_config))
-      goto error;
-
-   playlist_config_set_path(
-         &manual_scan->playlist_config,
-         manual_scan->task_config->playlist_file);
-
-   /* Concurrent scanning of content to the same
-    * playlist is not allowed */
-   find_data.func     = task_manual_content_scan_finder;
-   find_data.userdata = (void*)manual_scan->playlist_config.path;
-
-   if (task_queue_find(&find_data))
-      goto error;
-
-   /* Create task */
-   if (!(task = task_init()))
-      goto error;
-
-   /* > Get task title */
-   strlcpy(
-         task_title, msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_START),
-         sizeof(task_title));
-   strlcat(task_title, manual_scan->task_config->system_name,
-         sizeof(task_title));
-
-   /* > Configure task */
-   task->handler                 = task_manual_content_scan_handler;
-   task->state                   = manual_scan;
-   task->title                   = strdup(task_title);
-   task->alternative_look        = true;
-   task->progress                = 0;
-   task->callback                = cb_task_manual_content_scan;
-   task->cleanup                 = task_manual_content_scan_free;
-
-   /* > Push task */
-   task_queue_push(task);
-
-   return true;
-
-error:
-   /* Clean up handle */
-   free_manual_content_scan_handle(manual_scan);
-   manual_scan = NULL;
-
-   return false;
-}

+ 0 - 776
app/src/main/cpp/tasks/task_playlist_manager.c

@@ -1,776 +0,0 @@
-/*  RetroArch - A frontend for libretro.
- *  Copyright (C) 2011-2017 - Daniel De Matteis
- *  Copyright (C) 2014-2017 - Jean-André Santoni
- *  Copyright (C) 2016-2019 - Brad Parker
- *
- *  RetroArch is free software: you can redistribute it and/or modify it under the terms
- *  of the GNU General Public License as published by the Free Software Found-
- *  ation, either version 3 of the License, or (at your option) any later version.
- *
- *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
- *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- *  PURPOSE.  See the GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along with RetroArch.
- *  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-
-#include <string/stdstring.h>
-#include <lists/string_list.h>
-#include <file/file_path.h>
-#include <formats/m3u_file.h>
-
-#include "tasks_internal.h"
-
-#include "../msg_hash.h"
-#include "../file_path_special.h"
-#include "../playlist.h"
-#include "../core_info.h"
-
-enum pl_manager_status
-{
-   PL_MANAGER_BEGIN = 0,
-   PL_MANAGER_ITERATE_ENTRY_RESET_CORE,
-   PL_MANAGER_ITERATE_ENTRY_VALIDATE,
-   PL_MANAGER_VALIDATE_END,
-   PL_MANAGER_ITERATE_ENTRY_CHECK_DUPLICATE,
-   PL_MANAGER_CHECK_DUPLICATE_END,
-   PL_MANAGER_ITERATE_FETCH_M3U,
-   PL_MANAGER_ITERATE_CLEAN_M3U,
-   PL_MANAGER_END
-};
-
-typedef struct pl_manager_handle
-{
-   struct string_list *m3u_list;
-   char *playlist_name;
-   playlist_t *playlist;
-   size_t list_size;
-   size_t list_index;
-   size_t m3u_index;
-   playlist_config_t playlist_config; /* size_t alignment */
-   enum pl_manager_status status;
-} pl_manager_handle_t;
-
-/*********************/
-/* Utility Functions */
-/*********************/
-
-static void free_pl_manager_handle(pl_manager_handle_t *pl_manager)
-{
-   if (!pl_manager)
-      return;
-   
-   if (pl_manager->m3u_list)
-   {
-      string_list_free(pl_manager->m3u_list);
-      pl_manager->m3u_list = NULL;
-   }
-   
-   if (!string_is_empty(pl_manager->playlist_name))
-   {
-      free(pl_manager->playlist_name);
-      pl_manager->playlist_name = NULL;
-   }
-   
-   if (pl_manager->playlist)
-   {
-      playlist_free(pl_manager->playlist);
-      pl_manager->playlist = NULL;
-   }
-   
-   free(pl_manager);
-   pl_manager = NULL;
-}
-
-static void cb_task_pl_manager(
-      retro_task_t *task, void *task_data,
-      void *user_data, const char *err)
-{
-   pl_manager_handle_t *pl_manager = NULL;
-   playlist_t *cached_playlist     = playlist_get_cached();
-
-   /* If no playlist is currently cached, no action
-    * is required */
-   if (!task || !cached_playlist)
-      return;
-
-   pl_manager = (pl_manager_handle_t*)task->state;
-
-   if (!pl_manager)
-      return;
-
-   /* If the playlist manager task has modified the
-    * currently cached playlist, then it must be re-cached
-    * (otherwise changes will be lost if the currently
-    * cached playlist is saved to disk for any reason...) */
-   if (string_is_equal(
-         pl_manager->playlist_config.path,
-         playlist_get_conf_path(cached_playlist)))
-   {
-      playlist_config_t playlist_config;
-
-      /* Copy configuration of cached playlist
-       * (could use pl_manager->playlist_config,
-       * but doing it this way guarantees that
-       * the cached playlist is preserved in
-       * its original state) */
-      if (playlist_config_copy(
-            playlist_get_config(cached_playlist),
-            &playlist_config))
-      {
-         playlist_free_cached();
-         playlist_init_cached(&playlist_config);
-      }
-   }
-}
-
-static void task_pl_manager_free(retro_task_t *task)
-{
-   pl_manager_handle_t *pl_manager = NULL;
-
-   if (!task)
-      return;
-
-   pl_manager = (pl_manager_handle_t*)task->state;
-
-   free_pl_manager_handle(pl_manager);
-}
-
-/**************************/
-/* Reset Associated Cores */
-/**************************/
-
-static void task_pl_manager_reset_cores_handler(retro_task_t *task)
-{
-   pl_manager_handle_t *pl_manager = NULL;
-   
-   if (!task)
-      goto task_finished;
-   
-   pl_manager = (pl_manager_handle_t*)task->state;
-   
-   if (!pl_manager)
-      goto task_finished;
-   
-   if (task_get_cancelled(task))
-      goto task_finished;
-   
-   switch (pl_manager->status)
-   {
-      case PL_MANAGER_BEGIN:
-         /* Load playlist */
-         if (!path_is_valid(pl_manager->playlist_config.path))
-            goto task_finished;
-
-         pl_manager->playlist = playlist_init(&pl_manager->playlist_config);
-
-         if (!pl_manager->playlist)
-            goto task_finished;
-
-         pl_manager->list_size = playlist_size(pl_manager->playlist);
-
-         if (pl_manager->list_size < 1)
-            goto task_finished;
-
-         /* All good - can start iterating */
-         pl_manager->status = PL_MANAGER_ITERATE_ENTRY_RESET_CORE;
-         break;
-      case PL_MANAGER_ITERATE_ENTRY_RESET_CORE:
-         {
-            const struct playlist_entry *entry = NULL;
-            
-            /* Get current entry */
-            playlist_get_index(
-                  pl_manager->playlist, pl_manager->list_index, &entry);
-            
-            if (entry)
-            {
-               struct playlist_entry update_entry = {0};
-               char task_title[PATH_MAX_LENGTH];
-               /* Update progress display */
-               task_free_title(task);
-               strlcpy(
-                     task_title,
-		     msg_hash_to_str(MSG_PLAYLIST_MANAGER_RESETTING_CORES),
-                     sizeof(task_title));
-               
-               if (!string_is_empty(entry->label))
-                  strlcat(task_title, entry->label, sizeof(task_title));
-               else if (!string_is_empty(entry->path))
-               {
-                  char entry_name[PATH_MAX_LENGTH];
-                  fill_pathname_base(entry_name, entry->path, sizeof(entry_name));
-                  path_remove_extension(entry_name);
-                  strlcat(task_title, entry_name, sizeof(task_title));
-               }
-               
-               task_set_title(task, strdup(task_title));
-               task_set_progress(task, (pl_manager->list_index * 100) / pl_manager->list_size);
-               
-               /* Reset core association
-                * > The update function reads our entry as const,
-                *   so these casts are safe */
-               update_entry.core_path = (char*)"DETECT";
-               update_entry.core_name = (char*)"DETECT";
-               
-               playlist_update(
-                     pl_manager->playlist, pl_manager->list_index, &update_entry);
-            }
-            
-            /* Increment entry index */
-            pl_manager->list_index++;
-            if (pl_manager->list_index >= pl_manager->list_size)
-               pl_manager->status = PL_MANAGER_END;
-         }
-         break;
-      case PL_MANAGER_END:
-         {
-            char task_title[PATH_MAX_LENGTH];
-            /* Save playlist changes to disk */
-            playlist_write_file(pl_manager->playlist);
-            /* Update progress display */
-            task_free_title(task);
-            strlcpy(
-                  task_title, msg_hash_to_str(MSG_PLAYLIST_MANAGER_CORES_RESET),
-                  sizeof(task_title));
-            strlcat(task_title, pl_manager->playlist_name, sizeof(task_title));
-            
-            task_set_title(task, strdup(task_title));
-         }
-         /* fall-through */
-      default:
-         task_set_progress(task, 100);
-         goto task_finished;
-   }
-   
-   return;
-   
-task_finished:
-   
-   if (task)
-      task_set_finished(task, true);
-}
-
-static bool task_pl_manager_reset_cores_finder(
-      retro_task_t *task, void *user_data)
-{
-   pl_manager_handle_t *pl_manager = NULL;
-   
-   if (!task || !user_data)
-      return false;
-   
-   if (task->handler != task_pl_manager_reset_cores_handler)
-      return false;
-   
-   if (!(pl_manager = (pl_manager_handle_t*)task->state))
-      return false;
-   
-   return string_is_equal((const char*)user_data,
-         pl_manager->playlist_config.path);
-}
-
-bool task_push_pl_manager_reset_cores(const playlist_config_t *playlist_config)
-{
-   task_finder_data_t find_data;
-   char playlist_name[PATH_MAX_LENGTH];
-   char task_title[PATH_MAX_LENGTH];
-   retro_task_t *task              = task_init();
-   pl_manager_handle_t *pl_manager = (pl_manager_handle_t*)
-      calloc(1, sizeof(pl_manager_handle_t));
-   /* Sanity check */
-   if (!playlist_config || !task || !pl_manager)
-      goto error;
-   if (string_is_empty(playlist_config->path))
-      goto error;
-   
-   fill_pathname_base(playlist_name,
-         playlist_config->path, sizeof(playlist_name));
-   path_remove_extension(playlist_name);
-   
-   if (string_is_empty(playlist_name))
-      goto error;
-   
-   /* Concurrent management of the same playlist
-    * is not allowed */
-   find_data.func                = task_pl_manager_reset_cores_finder;
-   find_data.userdata            = (void*)playlist_config->path;
-   
-   if (task_queue_find(&find_data))
-      goto error;
-   
-   /* Configure handle */
-   if (!playlist_config_copy(playlist_config, &pl_manager->playlist_config))
-      goto error;
-   
-   pl_manager->playlist_name       = strdup(playlist_name);
-   pl_manager->playlist            = NULL;
-   pl_manager->list_size           = 0;
-   pl_manager->list_index          = 0;
-   pl_manager->m3u_list            = NULL;
-   pl_manager->m3u_index           = 0;
-   pl_manager->status              = PL_MANAGER_BEGIN;
-   
-   /* Configure task */
-   strlcpy(
-         task_title,
-	 msg_hash_to_str(MSG_PLAYLIST_MANAGER_RESETTING_CORES),
-         sizeof(task_title));
-   strlcat(task_title, playlist_name, sizeof(task_title));
-   
-   task->handler                 = task_pl_manager_reset_cores_handler;
-   task->state                   = pl_manager;
-   task->title                   = strdup(task_title);
-   task->alternative_look        = true;
-   task->progress                = 0;
-   task->callback                = cb_task_pl_manager;
-   task->cleanup                 = task_pl_manager_free;
-   
-   task_queue_push(task);
-   
-   return true;
-   
-error:
-   
-   if (task)
-   {
-      free(task);
-      task = NULL;
-   }
-   
-   free_pl_manager_handle(pl_manager);
-   pl_manager = NULL;
-   
-   return false;
-}
-
-/******************/
-/* Clean Playlist */
-/******************/
-
-static void pl_manager_validate_core_association(
-      playlist_t *playlist, size_t entry_index,
-      const char *core_path, const char *core_name)
-{
-   struct playlist_entry update_entry = {0};
-   
-   /* Sanity check */
-   if (!playlist)
-      return;
-   
-   if (entry_index >= playlist_size(playlist))
-      return;
-   
-   if (string_is_empty(core_path))
-      goto reset_core;
-   
-   /* Handle 'DETECT' entries */
-   if (string_is_equal(core_path, "DETECT"))
-   {
-      if (!string_is_equal(core_name, "DETECT"))
-         goto reset_core;
-   }
-   /* Handle 'builtin' entries */
-   else if (string_is_equal(core_path, "builtin"))
-   {
-      if (string_is_empty(core_name))
-         goto reset_core;
-   }
-   /* Handle file path entries */
-   else if (!path_is_valid(core_path))
-      goto reset_core;
-   else
-   {
-      char core_display_name[PATH_MAX_LENGTH];
-      core_info_t *core_info = NULL;
-      
-      /* Search core info */
-      if (core_info_find(core_path, &core_info) &&
-          !string_is_empty(core_info->display_name))
-         strlcpy(core_display_name, core_info->display_name,
-               sizeof(core_display_name));
-      else
-         core_display_name[0] = '\0';
-      
-      /* If core_display_name string is empty, it means the
-       * core wasn't found -> reset association */
-      if (string_is_empty(core_display_name))
-         goto reset_core;
-      
-      /* ...Otherwise, check that playlist entry
-       * core name is correct */
-      if (!string_is_equal(core_name, core_display_name))
-      {
-         update_entry.core_name = core_display_name;
-         playlist_update(playlist, entry_index, &update_entry);
-      }
-   }
-   
-   return;
-   
-reset_core:
-   /* The update function reads our entry as const,
-    * so these casts are safe */
-   update_entry.core_path = (char*)"DETECT";
-   update_entry.core_name = (char*)"DETECT";
-   
-   playlist_update(playlist, entry_index, &update_entry);
-}
-
-static void task_pl_manager_clean_playlist_handler(retro_task_t *task)
-{
-   pl_manager_handle_t *pl_manager = NULL;
-   
-   if (!task)
-      goto task_finished;
-   
-   pl_manager = (pl_manager_handle_t*)task->state;
-   
-   if (!pl_manager)
-      goto task_finished;
-   
-   if (task_get_cancelled(task))
-      goto task_finished;
-   
-   switch (pl_manager->status)
-   {
-      case PL_MANAGER_BEGIN:
-         {
-            /* Load playlist */
-            if (!path_is_valid(pl_manager->playlist_config.path))
-               goto task_finished;
-            
-            pl_manager->playlist = playlist_init(&pl_manager->playlist_config);
-            
-            if (!pl_manager->playlist)
-               goto task_finished;
-            
-            pl_manager->list_size = playlist_size(pl_manager->playlist);
-            
-            if (pl_manager->list_size < 1)
-               goto task_finished;
-            
-            /* All good - can start iterating */
-            pl_manager->status = PL_MANAGER_ITERATE_ENTRY_VALIDATE;
-         }
-         break;
-      case PL_MANAGER_ITERATE_ENTRY_VALIDATE:
-         {
-            const struct playlist_entry *entry = NULL;
-            bool entry_deleted                 = false;
-            
-            /* Update progress display */
-            task_set_progress(task, (pl_manager->list_index * 100) / pl_manager->list_size);
-            
-            /* Get current entry */
-            playlist_get_index(
-                  pl_manager->playlist, pl_manager->list_index, &entry);
-            
-            if (entry)
-            {
-               /* Check whether playlist content exists on
-                * the filesystem */
-               if (!playlist_content_path_is_valid(entry->path))
-               {
-                  /* Invalid content - delete entry */
-                  playlist_delete_index(pl_manager->playlist, pl_manager->list_index);
-                  entry_deleted = true;
-                  
-                  /* Update list_size */
-                  pl_manager->list_size = playlist_size(pl_manager->playlist);
-               }
-               /* Content is valid - check if core is valid */
-               else
-                  pl_manager_validate_core_association(
-                        pl_manager->playlist, pl_manager->list_index,
-                        entry->core_path, entry->core_name);
-            }
-            
-            /* Increment entry index *if* current entry still
-             * exists (i.e. if entry was deleted, current index
-             * will already point to the *next* entry) */
-            if (!entry_deleted)
-               pl_manager->list_index++;
-            
-            if (pl_manager->list_index >= pl_manager->list_size)
-               pl_manager->status = PL_MANAGER_VALIDATE_END;
-         }
-         break;
-      case PL_MANAGER_VALIDATE_END:
-         /* Sanity check - if all (or all but one)
-          * playlist entries were removed during the
-          * 'validate' phase, we can stop now */
-         if (pl_manager->list_size < 2)
-         {
-            pl_manager->status = PL_MANAGER_END;
-            break;
-         }
-
-         /* ...otherwise, reset index counter and
-          * start the duplicates check */
-         pl_manager->list_index = 0;
-         pl_manager->status = PL_MANAGER_ITERATE_ENTRY_CHECK_DUPLICATE;
-         break;
-      case PL_MANAGER_ITERATE_ENTRY_CHECK_DUPLICATE:
-         {
-            bool entry_deleted = false;
-            size_t i;
-            
-            /* Update progress display */
-            task_set_progress(task, (pl_manager->list_index * 100) / pl_manager->list_size);
-            
-            /* Check whether the content + core paths of the
-             * current entry match those of any subsequent
-             * entry */
-            for (i = pl_manager->list_index + 1; i < pl_manager->list_size; i++)
-            {
-               if (playlist_index_entries_are_equal(pl_manager->playlist,
-                     pl_manager->list_index, i))
-               {
-                  /* Duplicate found - delete entry */
-                  playlist_delete_index(pl_manager->playlist, pl_manager->list_index);
-                  entry_deleted = true;
-                  
-                  /* Update list_size */
-                  pl_manager->list_size = playlist_size(pl_manager->playlist);
-                  break;
-               }
-            }
-            
-            /* Increment entry index *if* current entry still
-             * exists (i.e. if entry was deleted, current index
-             * will already point to the *next* entry) */
-            if (!entry_deleted)
-               pl_manager->list_index++;
-            
-            if (pl_manager->list_index + 1 >= pl_manager->list_size)
-               pl_manager->status = PL_MANAGER_CHECK_DUPLICATE_END;
-         }
-         break;
-      case PL_MANAGER_CHECK_DUPLICATE_END:
-         /* Sanity check - if all (or all but one)
-          * playlist entries were removed during the
-          * 'check duplicate' phase, we can stop now */
-         if (pl_manager->list_size < 2)
-         {
-            pl_manager->status = PL_MANAGER_END;
-            break;
-         }
-
-         /* ...otherwise, reset index counter and
-          * start building the M3U file list */
-         pl_manager->list_index = 0;
-         pl_manager->status = PL_MANAGER_ITERATE_FETCH_M3U;
-         break;
-      case PL_MANAGER_ITERATE_FETCH_M3U:
-         {
-            const struct playlist_entry *entry = NULL;
-            
-            /* Update progress display */
-            task_set_progress(task, (pl_manager->list_index * 100) / pl_manager->list_size);
-            
-            /* Get current entry */
-            playlist_get_index(
-                  pl_manager->playlist, pl_manager->list_index, &entry);
-            
-            if (entry)
-            {
-               /* If this is an M3U file, add it to the
-                * M3U list for later processing */
-               if (m3u_file_is_m3u(entry->path))
-               {
-                  union string_list_elem_attr attr;
-                  attr.i = 0;
-                  /* Note: If string_list_append() fails, there is
-                   * really nothing we can do. The M3U file will
-                   * just be ignored... */
-                  string_list_append(
-                        pl_manager->m3u_list, entry->path, attr);
-               }
-            }
-            
-            /* Increment entry index */
-            pl_manager->list_index++;
-            
-            if (pl_manager->list_index >= pl_manager->list_size)
-            {
-               /* Check whether we have any M3U files
-                * to process */
-               if (pl_manager->m3u_list->size > 0)
-                  pl_manager->status = PL_MANAGER_ITERATE_CLEAN_M3U;
-               else
-                  pl_manager->status = PL_MANAGER_END;
-            }
-         }
-         break;
-      case PL_MANAGER_ITERATE_CLEAN_M3U:
-         {
-            const char *m3u_path =
-                  pl_manager->m3u_list->elems[pl_manager->m3u_index].data;
-            
-            if (!string_is_empty(m3u_path))
-            {
-               m3u_file_t *m3u_file = NULL;
-               
-               /* Update progress display */
-               task_set_progress(task, (pl_manager->m3u_index * 100) / pl_manager->m3u_list->size);
-               
-               /* Load M3U file */
-               m3u_file = m3u_file_init(m3u_path);
-               
-               if (m3u_file)
-               {
-                  size_t i;
-                  
-                  /* Loop over M3U entries */
-                  for (i = 0; i < m3u_file_get_size(m3u_file); i++)
-                  {
-                     m3u_file_entry_t *m3u_entry = NULL;
-
-                     /* Delete any playlist items matching the
-                      * content path of the M3U entry */
-                     if (m3u_file_get_entry(m3u_file, i, &m3u_entry))
-                        playlist_delete_by_path(
-                              pl_manager->playlist, m3u_entry->full_path);
-                  }
-                  
-                  m3u_file_free(m3u_file);
-               }
-            }
-            
-            /* Increment M3U file index */
-            pl_manager->m3u_index++;
-            if (pl_manager->m3u_index >= pl_manager->m3u_list->size)
-               pl_manager->status = PL_MANAGER_END;
-         }
-         break;
-      case PL_MANAGER_END:
-         {
-            char task_title[PATH_MAX_LENGTH];
-            /* Save playlist changes to disk */
-            playlist_write_file(pl_manager->playlist);
-            /* Update progress display */
-            task_free_title(task);
-            strlcpy(
-                  task_title, msg_hash_to_str(MSG_PLAYLIST_MANAGER_PLAYLIST_CLEANED),
-                  sizeof(task_title));
-            strlcat(task_title, pl_manager->playlist_name, sizeof(task_title));
-            
-            task_set_title(task, strdup(task_title));
-         }
-         /* fall-through */
-      default:
-         task_set_progress(task, 100);
-         goto task_finished;
-   }
-   
-   return;
-   
-task_finished:
-   
-   if (task)
-      task_set_finished(task, true);
-}
-
-static bool task_pl_manager_clean_playlist_finder(
-      retro_task_t *task, void *user_data)
-{
-   pl_manager_handle_t *pl_manager = NULL;
-   
-   if (!task || !user_data)
-      return false;
-   
-   if (task->handler != task_pl_manager_clean_playlist_handler)
-      return false;
-   
-   pl_manager = (pl_manager_handle_t*)task->state;
-   if (!pl_manager)
-      return false;
-   
-   return string_is_equal((const char*)user_data,
-         pl_manager->playlist_config.path);
-}
-
-bool task_push_pl_manager_clean_playlist(
-      const playlist_config_t *playlist_config)
-{
-   task_finder_data_t find_data;
-   char playlist_name[PATH_MAX_LENGTH];
-   char task_title[PATH_MAX_LENGTH];
-   retro_task_t *task              = task_init();
-   pl_manager_handle_t *pl_manager = (pl_manager_handle_t*)
-      calloc(1, sizeof(pl_manager_handle_t));
-   /* Sanity check */
-   if (!playlist_config || !task || !pl_manager)
-      goto error;
-   if (string_is_empty(playlist_config->path))
-      goto error;
-   
-   fill_pathname_base(playlist_name,
-         playlist_config->path, sizeof(playlist_name));
-   path_remove_extension(playlist_name);
-   
-   if (string_is_empty(playlist_name))
-      goto error;
-   
-   /* Concurrent management of the same playlist
-    * is not allowed */
-   find_data.func                = task_pl_manager_clean_playlist_finder;
-   find_data.userdata            = (void*)playlist_config->path;
-   
-   if (task_queue_find(&find_data))
-      goto error;
-   
-   /* Configure handle */
-   if (!playlist_config_copy(playlist_config, &pl_manager->playlist_config))
-      goto error;
-   
-   pl_manager->playlist_name       = strdup(playlist_name);
-   pl_manager->playlist            = NULL;
-   pl_manager->list_size           = 0;
-   pl_manager->list_index          = 0;
-   pl_manager->m3u_list            = string_list_new();
-   pl_manager->m3u_index           = 0;
-   pl_manager->status              = PL_MANAGER_BEGIN;
-   
-   if (!pl_manager->m3u_list)
-      goto error;
-   
-   /* Configure task */
-   strlcpy(
-         task_title,
-	 msg_hash_to_str(MSG_PLAYLIST_MANAGER_CLEANING_PLAYLIST),
-         sizeof(task_title));
-   strlcat(task_title, playlist_name, sizeof(task_title));
-   
-   task->handler                 = task_pl_manager_clean_playlist_handler;
-   task->state                   = pl_manager;
-   task->title                   = strdup(task_title);
-   task->alternative_look        = true;
-   task->progress                = 0;
-   task->callback                = cb_task_pl_manager;
-   task->cleanup                 = task_pl_manager_free;
-   
-   task_queue_push(task);
-   
-   return true;
-   
-error:
-   
-   if (task)
-   {
-      free(task);
-      task = NULL;
-   }
-   
-   free_pl_manager_handle(pl_manager);
-   pl_manager = NULL;
-   
-   return false;
-}

+ 2 - 0
app/src/main/cpp/tasks/task_screenshot.c

@@ -134,6 +134,7 @@ static void task_screenshot_handler(retro_task_t *task)
    ret = screenshot_dump_direct(state);
 
    /* Push screenshot to image history playlist */
+#ifdef HAVE_PLAYLIST
 #ifdef HAVE_IMAGEVIEWER
    if (       ret
          && !(state->flags & SS_TASK_FLAG_SILENCE)
@@ -149,6 +150,7 @@ static void task_screenshot_handler(retro_task_t *task)
 
       command_playlist_push_write(g_defaults.image_history, &entry);
    }
+#endif
 #endif
 
    task_set_progress(task, 100);

+ 6 - 0
app/src/main/cpp/tasks/tasks_internal.h

@@ -33,7 +33,9 @@
 #include "../core_updater_list.h"
 #endif
 
+#ifdef HAVE_PLAYLIST
 #include "../playlist.h"
+#endif
 
 /* Required for task_push_core_backup() */
 #include "../core_backup.h"
@@ -163,8 +165,10 @@ bool task_push_core_restore(const char *backup_path,
       const char *dir_libretro,
       bool *core_loaded);
 
+#ifdef HAVE_PLAYLIST
 bool task_push_pl_manager_reset_cores(const playlist_config_t *playlist_config);
 bool task_push_pl_manager_clean_playlist(const playlist_config_t *playlist_config);
+#endif
 
 bool task_push_image_load(const char *fullpath,
       bool supports_rgba, unsigned upscale_threshold,
@@ -179,9 +183,11 @@ bool task_push_dbscan(
       retro_task_callback_t cb);
 #endif
 
+#ifdef HAVE_PLAYLIST
 bool task_push_manual_content_scan(
       const playlist_config_t *playlist_config,
       const char *playlist_directory);
+#endif
 
 #ifdef HAVE_OVERLAY
 bool task_push_overlay_load_default(