mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-24 00:20:01 +00:00
898 lines
28 KiB
C
898 lines
28 KiB
C
/* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include <string/stdstring.h>
|
|
#include <lists/string_list.h>
|
|
#include <file/file_path.h>
|
|
#include <file/archive_file.h>
|
|
#include <formats/m3u_file.h>
|
|
|
|
#include "tasks_internal.h"
|
|
|
|
#include "../configuration.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
|
|
{
|
|
bool use_old_format;
|
|
bool compress;
|
|
bool fuzzy_archive_match;
|
|
enum pl_manager_status status;
|
|
size_t list_size;
|
|
size_t list_index;
|
|
struct string_list *m3u_list;
|
|
size_t m3u_index;
|
|
char *playlist_path;
|
|
char *playlist_name;
|
|
playlist_t *playlist;
|
|
} 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_path))
|
|
{
|
|
free(pl_manager->playlist_path);
|
|
pl_manager->playlist_path = 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_path,
|
|
playlist_get_conf_path(cached_playlist)))
|
|
{
|
|
playlist_free_cached();
|
|
playlist_init_cached(
|
|
pl_manager->playlist_path, COLLECTION_SIZE,
|
|
pl_manager->use_old_format, pl_manager->compress);
|
|
}
|
|
}
|
|
|
|
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_path))
|
|
goto task_finished;
|
|
|
|
pl_manager->playlist = playlist_init(pl_manager->playlist_path, COLLECTION_SIZE);
|
|
|
|
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];
|
|
|
|
task_title[0] = '\0';
|
|
|
|
/* 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];
|
|
entry_name[0] = '\0';
|
|
|
|
fill_pathname_base_noext(entry_name, entry->path, sizeof(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];
|
|
|
|
task_title[0] = '\0';
|
|
|
|
/* Save playlist changes to disk */
|
|
playlist_write_file(
|
|
pl_manager->playlist,
|
|
pl_manager->use_old_format,
|
|
pl_manager->compress);
|
|
|
|
/* 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;
|
|
|
|
pl_manager = (pl_manager_handle_t*)task->state;
|
|
if (!pl_manager)
|
|
return false;
|
|
|
|
return string_is_equal((const char*)user_data, pl_manager->playlist_path);
|
|
}
|
|
|
|
bool task_push_pl_manager_reset_cores(const char *playlist_path)
|
|
{
|
|
task_finder_data_t find_data;
|
|
char playlist_name[PATH_MAX_LENGTH];
|
|
char task_title[PATH_MAX_LENGTH];
|
|
settings_t *settings = config_get_ptr();
|
|
retro_task_t *task = task_init();
|
|
pl_manager_handle_t *pl_manager = (pl_manager_handle_t*)calloc(1, sizeof(pl_manager_handle_t));
|
|
|
|
playlist_name[0] = '\0';
|
|
task_title[0] = '\0';
|
|
|
|
/* Sanity check */
|
|
if (!task || !pl_manager || !settings)
|
|
goto error;
|
|
|
|
if (string_is_empty(playlist_path))
|
|
goto error;
|
|
|
|
fill_pathname_base_noext(playlist_name, playlist_path, sizeof(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_path;
|
|
|
|
if (task_queue_find(&find_data))
|
|
goto error;
|
|
|
|
/* 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;
|
|
|
|
/* Configure handle */
|
|
pl_manager->playlist_path = strdup(playlist_path);
|
|
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;
|
|
pl_manager->use_old_format = settings->bools.playlist_use_old_format;
|
|
pl_manager->compress = settings->bools.playlist_compression;
|
|
pl_manager->fuzzy_archive_match = false; /* Not relevant here */
|
|
|
|
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 bool pl_manager_content_exists(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))
|
|
{
|
|
const char *delim = path_get_archive_delim(path);
|
|
char archive_path[PATH_MAX_LENGTH] = {0};
|
|
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 */
|
|
archive_list = file_archive_get_file_list(archive_path, NULL);
|
|
|
|
if (!archive_list)
|
|
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 */
|
|
else
|
|
return path_is_valid(path);
|
|
}
|
|
|
|
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_ctx_find_t core_info;
|
|
|
|
core_display_name[0] = '\0';
|
|
|
|
/* Search core info */
|
|
core_info.inf = NULL;
|
|
core_info.path = core_path;
|
|
|
|
if (core_info_find(&core_info) &&
|
|
!string_is_empty(core_info.inf->display_name))
|
|
strlcpy(core_display_name, core_info.inf->display_name,
|
|
sizeof(core_display_name));
|
|
|
|
/* 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_path))
|
|
goto task_finished;
|
|
|
|
pl_manager->playlist = playlist_init(pl_manager->playlist_path, COLLECTION_SIZE);
|
|
|
|
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 (!pl_manager_content_exists(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:
|
|
{
|
|
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)
|
|
{
|
|
size_t i;
|
|
|
|
/* Loop over all subsequent entries, and check
|
|
* whether content + core paths are the same */
|
|
for (i = pl_manager->list_index + 1; i < pl_manager->list_size; i++)
|
|
{
|
|
const struct playlist_entry *next_entry = NULL;
|
|
|
|
/* Get next entry */
|
|
playlist_get_index(pl_manager->playlist, i, &next_entry);
|
|
|
|
if (!next_entry)
|
|
continue;
|
|
|
|
if (playlist_entries_are_equal(
|
|
entry, next_entry, pl_manager->fuzzy_archive_match))
|
|
{
|
|
/* 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, M3U_FILE_SIZE);
|
|
|
|
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,
|
|
pl_manager->fuzzy_archive_match);
|
|
}
|
|
|
|
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];
|
|
|
|
task_title[0] = '\0';
|
|
|
|
/* Save playlist changes to disk */
|
|
playlist_write_file(
|
|
pl_manager->playlist,
|
|
pl_manager->use_old_format,
|
|
pl_manager->compress);
|
|
|
|
/* 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_path);
|
|
}
|
|
|
|
bool task_push_pl_manager_clean_playlist(const char *playlist_path)
|
|
{
|
|
task_finder_data_t find_data;
|
|
char playlist_name[PATH_MAX_LENGTH];
|
|
char task_title[PATH_MAX_LENGTH];
|
|
settings_t *settings = config_get_ptr();
|
|
retro_task_t *task = task_init();
|
|
pl_manager_handle_t *pl_manager = (pl_manager_handle_t*)calloc(1, sizeof(pl_manager_handle_t));
|
|
|
|
playlist_name[0] = '\0';
|
|
task_title[0] = '\0';
|
|
|
|
/* Sanity check */
|
|
if (!task || !pl_manager || !settings)
|
|
goto error;
|
|
|
|
if (string_is_empty(playlist_path))
|
|
goto error;
|
|
|
|
fill_pathname_base_noext(playlist_name, playlist_path, sizeof(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_path;
|
|
|
|
if (task_queue_find(&find_data))
|
|
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;
|
|
|
|
/* Configure handle */
|
|
pl_manager->playlist_path = strdup(playlist_path);
|
|
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;
|
|
pl_manager->use_old_format = settings->bools.playlist_use_old_format;
|
|
pl_manager->compress = settings->bools.playlist_compression;
|
|
pl_manager->fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
|
|
|
|
if (!pl_manager->m3u_list)
|
|
goto error;
|
|
|
|
task_queue_push(task);
|
|
|
|
return true;
|
|
|
|
error:
|
|
|
|
if (task)
|
|
{
|
|
free(task);
|
|
task = NULL;
|
|
}
|
|
|
|
free_pl_manager_handle(pl_manager);
|
|
pl_manager = NULL;
|
|
|
|
return false;
|
|
}
|