mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-09 03:29:43 -10:00
m3u handling during db scan (#18467)
This commit is contained in:
1 parent
6bb72e8a4f
commit
8618e55115
1 file changed
+276
@@ -158,6 +158,7 @@ typedef struct database_state_handle
|
||||
{
|
||||
database_info_list_t *info;
|
||||
struct string_list *list;
|
||||
struct string_list *m3u_list; /* List of M3U files found during scan */
|
||||
uint8_t *buf;
|
||||
size_t list_index;
|
||||
size_t entry_index;
|
||||
@@ -377,6 +378,245 @@ static void task_database_cue_prune(database_info_handle_t *db,
|
||||
free(fd);
|
||||
}
|
||||
|
||||
/* Remove disc indicators from title string */
|
||||
/* Helper function to validate if a string is a valid disc indicator
|
||||
* Valid formats:
|
||||
* - Single/double digit: 0-99
|
||||
* - Single letter: A-Z
|
||||
* - Roman numerals: I, II, III, IV, V, VI, VII, VIII, IX, X, etc.
|
||||
* - "X of Y" format: 1 of 2, 01 of 10, etc.
|
||||
*/
|
||||
static bool is_valid_disc_indicator(const char *str, size_t len)
|
||||
{
|
||||
const char *p = str;
|
||||
const char *end = str + len;
|
||||
|
||||
if (len == 0 || len > 10) /* Sanity check */
|
||||
return false;
|
||||
|
||||
/* Check for single letter (A-Z) */
|
||||
if (len == 1 && isalpha((unsigned char)*p))
|
||||
return true;
|
||||
|
||||
/* Check for 1-2 digit number (0-99) */
|
||||
if (len <= 2 && isdigit((unsigned char)*p))
|
||||
{
|
||||
p++;
|
||||
if (p == end)
|
||||
return true; /* Single digit */
|
||||
if (isdigit((unsigned char)*p) && p + 1 == end)
|
||||
return true; /* Double digit */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check for "X of Y" pattern where X and Y are 1-2 digits */
|
||||
if (len >= 5 && isdigit((unsigned char)*p))
|
||||
{
|
||||
/* Parse first number (1-2 digits) */
|
||||
p++;
|
||||
if (p < end && isdigit((unsigned char)*p))
|
||||
p++;
|
||||
|
||||
/* Check for " of " */
|
||||
if (p + 4 <= end && strncmp(p, " of ", 4) == 0)
|
||||
{
|
||||
p += 4;
|
||||
/* Parse second number (1-2 digits) */
|
||||
if (p < end && isdigit((unsigned char)*p))
|
||||
{
|
||||
p++;
|
||||
if (p < end && isdigit((unsigned char)*p))
|
||||
p++;
|
||||
if (p == end)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check for Roman numerals (I, II, III, IV, V, VI, VII, VIII, IX, X, etc.) */
|
||||
/* Valid Roman numeral chars: I, V, X (we'll be conservative) */
|
||||
if (len >= 1 && len <= 4)
|
||||
{
|
||||
bool all_roman = true;
|
||||
const char *roman_p = str;
|
||||
while (roman_p < end)
|
||||
{
|
||||
char c = *roman_p;
|
||||
if (c != 'I' && c != 'V' && c != 'X')
|
||||
{
|
||||
all_roman = false;
|
||||
break;
|
||||
}
|
||||
roman_p++;
|
||||
}
|
||||
if (all_roman)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void remove_disc_indicators(char *title, size_t len)
|
||||
{
|
||||
char *disc_pos = NULL;
|
||||
|
||||
/* Search for common disc patterns */
|
||||
if ((disc_pos = strstr(title, " (Disc ")) ||
|
||||
(disc_pos = strstr(title, " (disc ")) ||
|
||||
(disc_pos = strstr(title, " (Disk ")) ||
|
||||
(disc_pos = strstr(title, " (disk ")))
|
||||
{
|
||||
/* Find the closing parenthesis */
|
||||
char *end_pos = strchr(disc_pos, ')');
|
||||
if (end_pos)
|
||||
{
|
||||
/* Extract the disc indicator text (between " (Disc " and ")") */
|
||||
const char *indicator_start = disc_pos + 7; /* Skip " (Disc " */
|
||||
size_t indicator_len = end_pos - indicator_start;
|
||||
|
||||
/* Validate this is actually a disc indicator, not arbitrary text */
|
||||
if (is_valid_disc_indicator(indicator_start, indicator_len))
|
||||
{
|
||||
/* Truncate at the disc indicator */
|
||||
*disc_pos = '\0';
|
||||
/* Remove trailing whitespace */
|
||||
string_trim_whitespace_right(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void task_database_iterate_m3u(
|
||||
db_handle_t *_db,
|
||||
database_state_handle_t *db_state,
|
||||
const char *m3u_path)
|
||||
{
|
||||
size_t i, j;
|
||||
bool found_match = false;
|
||||
char first_matched_db[NAME_MAX_LENGTH];
|
||||
char first_matched_crc[128];
|
||||
char collapsed_title[NAME_MAX_LENGTH];
|
||||
m3u_file_t *m3u_file = NULL;
|
||||
|
||||
first_matched_db[0] = '\0';
|
||||
first_matched_crc[0] = '\0';
|
||||
collapsed_title[0] = '\0';
|
||||
|
||||
/* Open M3U file */
|
||||
if (!(m3u_file = m3u_file_init(m3u_path)))
|
||||
{
|
||||
RARCH_ERR("[Scanner] Failed to open M3U file: %s\n", m3u_path);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Scan each referenced file and check if it's in scan_results */
|
||||
for (i = 0; i < m3u_file_get_size(m3u_file); i++)
|
||||
{
|
||||
m3u_file_entry_t *entry = NULL;
|
||||
const char *ref_path = NULL;
|
||||
|
||||
if (!m3u_file_get_entry(m3u_file, i, &entry))
|
||||
continue;
|
||||
|
||||
ref_path = entry->full_path;
|
||||
if (string_is_empty(ref_path))
|
||||
continue;
|
||||
|
||||
/* Look for this file in scan results */
|
||||
for (j = 0; j < _db->scan_results.count; j++)
|
||||
{
|
||||
scan_result_t *result = &_db->scan_results.results[j];
|
||||
char result_path_resolved[PATH_MAX_LENGTH];
|
||||
|
||||
result_path_resolved[0] = '\0';
|
||||
|
||||
if (!result->entry_path)
|
||||
continue;
|
||||
|
||||
/* Resolve the scan result path to absolute form for comparison */
|
||||
strlcpy(result_path_resolved, result->entry_path,
|
||||
sizeof(result_path_resolved));
|
||||
path_resolve_realpath(result_path_resolved,
|
||||
sizeof(result_path_resolved), false);
|
||||
|
||||
if (string_is_equal(ref_path, result_path_resolved))
|
||||
{
|
||||
/* Found a match! */
|
||||
if (!found_match)
|
||||
{
|
||||
/* First match - save the info */
|
||||
found_match = true;
|
||||
strlcpy(first_matched_db, result->db_name,
|
||||
sizeof(first_matched_db));
|
||||
strlcpy(first_matched_crc, result->db_crc,
|
||||
sizeof(first_matched_crc));
|
||||
strlcpy(collapsed_title, result->entry_label,
|
||||
sizeof(collapsed_title));
|
||||
|
||||
/* Remove disc indicator from title */
|
||||
remove_disc_indicators(collapsed_title,
|
||||
sizeof(collapsed_title));
|
||||
}
|
||||
|
||||
/* Mark this result for removal */
|
||||
/* We'll remove it by setting entry_path to NULL */
|
||||
/* and compacting the array later */
|
||||
if (result->entry_path)
|
||||
{
|
||||
free(result->entry_path);
|
||||
result->entry_path = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m3u_file_free(m3u_file);
|
||||
|
||||
/* If we found at least one match, add M3U entry */
|
||||
if (found_match)
|
||||
{
|
||||
if (!scan_results_add(&_db->scan_results, m3u_path, collapsed_title,
|
||||
first_matched_crc, first_matched_db, NULL))
|
||||
{
|
||||
RARCH_ERR("[Scanner] Failed to add M3U result: \"%s\"\n", m3u_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
RARCH_LOG("[Scanner] Matched M3U \"%s\" to \"%s\"\n",
|
||||
collapsed_title, first_matched_db);
|
||||
}
|
||||
}
|
||||
|
||||
/* Compact scan_results to remove NULL entries */
|
||||
{
|
||||
size_t write_idx = 0;
|
||||
for (i = 0; i < _db->scan_results.count; i++)
|
||||
{
|
||||
if (_db->scan_results.results[i].entry_path != NULL)
|
||||
{
|
||||
if (write_idx != i)
|
||||
_db->scan_results.results[write_idx] =
|
||||
_db->scan_results.results[i];
|
||||
write_idx++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Free any remaining allocated fields */
|
||||
if (_db->scan_results.results[i].entry_label)
|
||||
free(_db->scan_results.results[i].entry_label);
|
||||
if (_db->scan_results.results[i].db_crc)
|
||||
free(_db->scan_results.results[i].db_crc);
|
||||
if (_db->scan_results.results[i].db_name)
|
||||
free(_db->scan_results.results[i].db_name);
|
||||
if (_db->scan_results.results[i].archive_name)
|
||||
free(_db->scan_results.results[i].archive_name);
|
||||
}
|
||||
}
|
||||
_db->scan_results.count = write_idx;
|
||||
}
|
||||
}
|
||||
|
||||
static void gdi_prune(database_info_handle_t *db, const char *name)
|
||||
{
|
||||
size_t i;
|
||||
@@ -1396,6 +1636,14 @@ static void task_database_handler(retro_task_t *task)
|
||||
goto task_finished;
|
||||
}
|
||||
|
||||
/* Initialize M3U list tracking */
|
||||
dbstate->m3u_list = string_list_new();
|
||||
if (!dbstate->m3u_list)
|
||||
{
|
||||
RARCH_ERR("[Scanner] Failed to initialize M3U list\n");
|
||||
goto task_finished;
|
||||
}
|
||||
|
||||
RARCH_LOG("[Scanner] %s\"%s\"...\n", msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_START), db->fullpath);
|
||||
if (retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_DATABASE_SCAN, NULL))
|
||||
printf("%s\"%s\"...\n", msg_hash_to_str(MSG_MANUAL_CONTENT_SCAN_START), db->fullpath);
|
||||
@@ -1404,6 +1652,16 @@ static void task_database_handler(retro_task_t *task)
|
||||
break;
|
||||
case DATABASE_STATUS_ITERATE_START:
|
||||
name = database_info_get_current_element_name(dbinfo);
|
||||
|
||||
/* Check if this is an M3U file and add to list for post-processing */
|
||||
if (m3u_file_is_m3u(name))
|
||||
{
|
||||
union string_list_elem_attr attr;
|
||||
attr.i = 0;
|
||||
if (dbstate->m3u_list)
|
||||
string_list_append(dbstate->m3u_list, name, attr);
|
||||
}
|
||||
|
||||
task_database_cleanup_state(dbstate);
|
||||
dbstate->list_index = 0;
|
||||
dbstate->entry_index = 0;
|
||||
@@ -1462,6 +1720,22 @@ static void task_database_handler(retro_task_t *task)
|
||||
#else
|
||||
fprintf(stderr, "msg: %s\n", msg);
|
||||
#endif
|
||||
/* Process M3U files after main scan completes */
|
||||
if (dbstate->m3u_list && dbstate->m3u_list->size > 0)
|
||||
{
|
||||
size_t m;
|
||||
RARCH_LOG("[Scanner] Processing %u M3U files...\n",
|
||||
(unsigned)dbstate->m3u_list->size);
|
||||
|
||||
/* Scan M3U files and collapse disc entries */
|
||||
for (m = 0; m < dbstate->m3u_list->size; m++)
|
||||
{
|
||||
const char *m3u_path = dbstate->m3u_list->elems[m].data;
|
||||
if (m3u_path)
|
||||
task_database_iterate_m3u(db, dbstate, m3u_path);
|
||||
}
|
||||
}
|
||||
|
||||
/* Batch update all playlists with accumulated results */
|
||||
if (db->scan_results.count > 0)
|
||||
scan_results_batch_update_playlists(&db->scan_results, db);
|
||||
@@ -1488,6 +1762,8 @@ task_finished:
|
||||
{
|
||||
if (dbstate->list)
|
||||
dir_list_free(dbstate->list);
|
||||
if (dbstate->m3u_list)
|
||||
string_list_free(dbstate->m3u_list);
|
||||
}
|
||||
|
||||
if (db)
|
||||
|
||||
Reference in new issue
Block a user