Pass theme list & its entries around by reference rather than copying them. Fix bug in async icon loading that caused icons to be loaded multiple times. Original PR by @LiquidFenrir
232 lines
7.7 KiB
C
232 lines
7.7 KiB
C
/*
|
|
* This file is part of Anemone3DS
|
|
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
|
|
*
|
|
* This program 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 Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
|
|
* * Requiring preservation of specified reasonable legal notices or
|
|
* author attributions in that material or in the Appropriate Legal
|
|
* Notices displayed by works containing it.
|
|
* * Prohibiting misrepresentation of the origin of that material,
|
|
* or requiring that modified versions of such material be marked in
|
|
* reasonable ways as different from the original version.
|
|
*/
|
|
|
|
#include "entries_list.h"
|
|
#include "loading.h"
|
|
#include "draw.h"
|
|
#include "fs.h"
|
|
#include "unicode.h"
|
|
|
|
void delete_entry(Entry_s * entry, bool is_file)
|
|
{
|
|
if(is_file)
|
|
FSUSER_DeleteFile(ArchiveSD, fsMakePath(PATH_UTF16, entry->path));
|
|
else
|
|
FSUSER_DeleteDirectoryRecursively(ArchiveSD, fsMakePath(PATH_UTF16, entry->path));
|
|
}
|
|
|
|
u32 load_data(const char * filename, const Entry_s * entry, char ** buf)
|
|
{
|
|
if(entry->is_zip)
|
|
{
|
|
return zip_file_to_buf(filename + 1, entry->path, buf); //the first character will always be '/' because of the other case
|
|
}
|
|
else
|
|
{
|
|
u16 path[0x106] = {0};
|
|
strucat(path, entry->path);
|
|
struacat(path, filename);
|
|
|
|
return file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, buf);
|
|
}
|
|
}
|
|
|
|
C2D_Image get_icon_at(Entry_List_s * list, size_t index)
|
|
{
|
|
return (C2D_Image){
|
|
.tex = &list->icons_texture,
|
|
.subtex = &list->icons_info[index].subtex,
|
|
};
|
|
}
|
|
|
|
static int compare_entries_base(const Entry_s * const a, const Entry_s * const b)
|
|
{
|
|
// entry->placeholder_color == 0 means it is not filled (no name, author, description)
|
|
// if a is filled and b is filled, return == 0
|
|
// if a is not filled and b is not filled, return == 0
|
|
// if a is not filled, return < 0
|
|
// if b is an unfilled entry, return > 0
|
|
return ((int)(a->placeholder_color != 0)) - ((int)(b->placeholder_color != 0));
|
|
}
|
|
|
|
typedef int (*sort_comparator)(const void *, const void *);
|
|
static int compare_entries_by_name(const void * a, const void * b)
|
|
{
|
|
const Entry_s * const entry_a = (const Entry_s *)a;
|
|
const Entry_s * const entry_b = (const Entry_s *)b;
|
|
const int base = compare_entries_base(entry_a, entry_b);
|
|
if(base)
|
|
return base;
|
|
|
|
return memcmp(entry_a->name, entry_b->name, 0x40 * sizeof(u16));
|
|
}
|
|
static int compare_entries_by_author(const void * a, const void * b)
|
|
{
|
|
const Entry_s * const entry_a = (const Entry_s *)a;
|
|
const Entry_s * const entry_b = (const Entry_s *)b;
|
|
const int base = compare_entries_base(entry_a, entry_b);
|
|
if(base)
|
|
return base;
|
|
|
|
return memcmp(entry_a->author, entry_b->author, 0x40 * sizeof(u16));
|
|
}
|
|
static int compare_entries_by_filename(const void * a, const void * b)
|
|
{
|
|
const Entry_s * const entry_a = (const Entry_s *)a;
|
|
const Entry_s * const entry_b = (const Entry_s *)b;
|
|
const int base = compare_entries_base(entry_a, entry_b);
|
|
if(base)
|
|
return base;
|
|
|
|
return memcmp(entry_a->path, entry_b->path, 0x106 * sizeof(u16));
|
|
}
|
|
|
|
static void sort_list(Entry_List_s * list, sort_comparator compare_entries)
|
|
{
|
|
if(list->entries != NULL && list->entries != NULL)
|
|
qsort(list->entries, list->entries_count, sizeof(Entry_s), compare_entries); //alphabet sort
|
|
}
|
|
|
|
void sort_by_name(Entry_List_s * list)
|
|
{
|
|
sort_list(list, compare_entries_by_name);
|
|
list->current_sort = SORT_NAME;
|
|
}
|
|
void sort_by_author(Entry_List_s * list)
|
|
{
|
|
sort_list(list, compare_entries_by_author);
|
|
list->current_sort = SORT_AUTHOR;
|
|
}
|
|
void sort_by_filename(Entry_List_s * list)
|
|
{
|
|
sort_list(list, compare_entries_by_filename);
|
|
list->current_sort = SORT_PATH;
|
|
}
|
|
|
|
#define LOADING_DIR_ENTRIES_COUNT 16
|
|
static FS_DirectoryEntry loading_dir_entries[LOADING_DIR_ENTRIES_COUNT];
|
|
Result load_entries(const char * loading_path, Entry_List_s * list, const InstallType loading_screen)
|
|
{
|
|
Handle dir_handle;
|
|
Result res = FSUSER_OpenDirectory(&dir_handle, ArchiveSD, fsMakePath(PATH_ASCII, loading_path));
|
|
if(R_FAILED(res))
|
|
{
|
|
DEBUG("Failed to open folder: %s\n", loading_path);
|
|
return res;
|
|
}
|
|
|
|
list_init_capacity(list, LOADING_DIR_ENTRIES_COUNT);
|
|
|
|
u32 entries_read = LOADING_DIR_ENTRIES_COUNT;
|
|
while(entries_read == LOADING_DIR_ENTRIES_COUNT)
|
|
{
|
|
res = FSDIR_Read(dir_handle, &entries_read, LOADING_DIR_ENTRIES_COUNT, loading_dir_entries);
|
|
if(R_FAILED(res))
|
|
break;
|
|
|
|
for(u32 i = 0; i < entries_read; ++i)
|
|
{
|
|
const FS_DirectoryEntry * const dir_entry = &loading_dir_entries[i];
|
|
const bool is_zip = !strcmp(dir_entry->shortExt, "ZIP");
|
|
if(!(dir_entry->attributes & FS_ATTRIBUTE_DIRECTORY) && !is_zip)
|
|
continue;
|
|
|
|
const ssize_t new_entry_index = list_add_entry(list);
|
|
if(new_entry_index < 0)
|
|
{
|
|
// out of memory: still allow use of currently loaded entries.
|
|
// Many things might die, depending on the heap layout after
|
|
entries_read = 0;
|
|
break;
|
|
}
|
|
|
|
Entry_s * const current_entry = &list->entries[new_entry_index];
|
|
memset(current_entry, 0, sizeof(Entry_s));
|
|
struacat(current_entry->path, loading_path);
|
|
strucat(current_entry->path, dir_entry->name);
|
|
current_entry->is_zip = is_zip;
|
|
}
|
|
}
|
|
|
|
FSDIR_Close(dir_handle);
|
|
|
|
list->loading_path = loading_path;
|
|
const int loading_bar_ticks = list->entries_count / 10;
|
|
|
|
for(int i = 0, j = 0; i < list->entries_count; ++i)
|
|
{
|
|
// replaces (i % loading_bar_ticks) == 0
|
|
if(++j >= loading_bar_ticks)
|
|
{
|
|
j = 0;
|
|
draw_loading_bar(i, list->entries_count, loading_screen);
|
|
}
|
|
Entry_s * const current_entry = &list->entries[i];
|
|
char * buf = NULL;
|
|
u32 buflen = load_data("/info.smdh", current_entry, &buf);
|
|
parse_smdh(buflen == sizeof(Icon_s) ? (Icon_s *)buf : NULL, current_entry, current_entry->path + strlen(loading_path));
|
|
free(buf);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void list_init_capacity(Entry_List_s * list, const int init_capacity)
|
|
{
|
|
list->entries = malloc(init_capacity * sizeof(Entry_s));
|
|
list->entries_capacity = init_capacity;
|
|
}
|
|
|
|
#define LIST_CAPACITY_THRESHOLD 512
|
|
ssize_t list_add_entry(Entry_List_s * list)
|
|
{
|
|
if(list->entries_count == list->entries_capacity)
|
|
{
|
|
int next_capacity = list->entries_capacity;
|
|
// expand by doubling until we hit LIST_CAPACITY_THRESHOLD
|
|
// then simply increment by that, to have less extra space leftover
|
|
if(next_capacity < LIST_CAPACITY_THRESHOLD)
|
|
{
|
|
next_capacity *= 2;
|
|
}
|
|
else
|
|
{
|
|
next_capacity += LIST_CAPACITY_THRESHOLD;
|
|
}
|
|
|
|
Entry_s * const new_list = realloc(list->entries, next_capacity * sizeof(Entry_s));
|
|
if(new_list == NULL)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
list->entries = new_list;
|
|
list->entries_capacity = next_capacity;
|
|
}
|
|
|
|
return list->entries_count++;
|
|
}
|