allow subfolders in custom texture folders

make file iterator to be used by custom tex and game scanner
added some missing nowide::getenv
This commit is contained in:
flyinghead 2021-01-19 16:58:26 +01:00
parent b47e2abb7c
commit 87e9fdd0ac
6 changed files with 191 additions and 110 deletions

View File

@ -138,7 +138,7 @@ LogManager::LogManager()
FileLogListener *listener = new FileLogListener(logPath);
if (!listener->IsValid())
{
const char *home = getenv("HOME");
const char *home = nowide::getenv("HOME");
if (home != nullptr)
{
delete listener;

View File

@ -125,3 +125,133 @@ inline int mkdir(const char *path, mode_t mode) {
#endif
}
// iterate depth-first over the files contained in a folder hierarchy
class DirectoryTree
{
public:
struct item {
std::string name;
std::string parentPath;
};
class iterator
{
private:
iterator(DIR *dir, std::string pathname) {
if (dir != nullptr)
{
dirs.push_back(dir);
pathnames.push_back(pathname);
advance();
}
}
public:
~iterator() {
for (DIR *dir : dirs)
flycast::closedir(dir);
}
const item *operator->() {
if (direntry == nullptr)
throw std::runtime_error("null iterator");
return &currentItem;
}
const item& operator*() const {
if (direntry == nullptr)
throw std::runtime_error("null iterator");
return currentItem;
}
// Prefix increment
iterator& operator++() {
advance();
return *this;
}
// Basic (in)equality implementations, just intended to work when comparing with end() or this
friend bool operator==(const iterator& a, const iterator& b) {
return a.direntry == b.direntry;
}
friend bool operator!=(const iterator& a, const iterator& b) {
return a.direntry != b.direntry;
}
private:
void advance()
{
while (!dirs.empty())
{
direntry = flycast::readdir(dirs.back());
if (direntry == nullptr)
{
flycast::closedir(dirs.back());
dirs.pop_back();
pathnames.pop_back();
continue;
}
currentItem.name = direntry->d_name;
if (currentItem.name == "." || currentItem.name == "..")
continue;
std::string childPath = pathnames.back() + "/" + currentItem.name;
bool isDir = false;
#ifndef _WIN32
if (entry->d_type == DT_DIR)
isDir = true;
else if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)
#endif
{
struct stat st;
if (flycast::stat(childPath.c_str(), &st) != 0)
continue;
if (S_ISDIR(st.st_mode))
isDir = true;
}
if (!isDir)
{
currentItem.parentPath = pathnames.back();
break;
}
DIR *childDir = flycast::opendir(childPath.c_str());
if (childDir == nullptr)
{
INFO_LOG(COMMON, "Cannot read directory '%s'", childPath.c_str());
}
else
{
dirs.push_back(childDir);
pathnames.push_back(childPath);
}
}
}
std::vector<DIR *> dirs;
std::vector<std::string> pathnames;
dirent *direntry = nullptr;
item currentItem;
friend class DirectoryTree;
};
DirectoryTree(const std::string& root) : root(root) {
}
iterator begin()
{
DIR *dir = flycast::opendir(root.c_str());
if (dir == nullptr)
INFO_LOG(COMMON, "Cannot read directory '%s'", root.c_str());
return iterator(dir, root);
}
iterator end()
{
return iterator(nullptr, root);
}
private:
const std::string& root;
};

View File

@ -20,10 +20,7 @@
#include "cfg/cfg.h"
#include "oslib/directory.h"
#include <algorithm>
#include <dirent.h>
#include <sstream>
#include <sys/stat.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_JPEG
#define STBI_ONLY_PNG
@ -41,33 +38,33 @@ void CustomTexture::LoaderThread()
BaseTextureCacheData *texture;
do {
texture = NULL;
work_queue_mutex.lock();
if (!work_queue.empty())
texture = nullptr;
{
texture = work_queue.back();
work_queue.pop_back();
std::unique_lock<std::mutex> lock(work_queue_mutex);
if (!work_queue.empty())
{
texture = work_queue.back();
work_queue.pop_back();
}
}
work_queue_mutex.unlock();
if (texture != NULL)
if (texture != nullptr)
{
texture->ComputeHash();
if (texture->custom_image_data != NULL)
if (texture->custom_image_data != nullptr)
{
free(texture->custom_image_data);
texture->custom_image_data = NULL;
texture->custom_image_data = nullptr;
}
if (!texture->dirty)
{
int width, height;
u8 *image_data = LoadCustomTexture(texture->texture_hash, width, height);
if (image_data == NULL)
if (image_data == nullptr)
{
image_data = LoadCustomTexture(texture->old_texture_hash, width, height);
}
if (image_data != NULL)
if (image_data != nullptr)
{
texture->custom_width = width;
texture->custom_height = height;
@ -77,7 +74,7 @@ void CustomTexture::LoaderThread()
texture->custom_load_in_progress--;
}
} while (texture != NULL);
} while (texture != nullptr);
wakeup_thread.Wait();
}
@ -105,8 +102,8 @@ bool CustomTexture::Init()
{
textures_path = get_readonly_data_path("textures/" + game_id) + "/";
DIR *dir = opendir(textures_path.c_str());
if (dir != NULL)
DIR *dir = flycast::opendir(textures_path.c_str());
if (dir != nullptr)
{
INFO_LOG(RENDERER, "Found custom textures directory: %s", textures_path.c_str());
custom_textures_available = true;
@ -123,9 +120,10 @@ void CustomTexture::Terminate()
if (initialized)
{
initialized = false;
work_queue_mutex.lock();
work_queue.clear();
work_queue_mutex.unlock();
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
work_queue.clear();
}
wakeup_thread.Set();
loader_thread.WaitToEnd();
texture_map.clear();
@ -154,9 +152,10 @@ void CustomTexture::LoadCustomTextureAsync(BaseTextureCacheData *texture_data)
return;
texture_data->custom_load_in_progress++;
work_queue_mutex.lock();
work_queue.insert(work_queue.begin(), texture_data);
work_queue_mutex.unlock();
{
std::unique_lock<std::mutex> lock(work_queue_mutex);
work_queue.insert(work_queue.begin(), texture_data);
}
wakeup_thread.Set();
}
@ -238,35 +237,14 @@ void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, voi
void CustomTexture::LoadMap()
{
texture_map.clear();
DIR *dir = flycast::opendir(textures_path.c_str());
if (dir == nullptr)
return;
while (true)
DirectoryTree tree(textures_path);
for (const DirectoryTree::item& item : tree)
{
struct dirent *entry = flycast::readdir(dir);
if (entry == nullptr)
break;
std::string name(entry->d_name);
if (name == "." || name == "..")
continue;
std::string child_path = textures_path + name;
#ifndef _WIN32
if (entry->d_type == DT_DIR)
continue;
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)
#endif
{
struct stat st;
if (flycast::stat(child_path.c_str(), &st) != 0)
continue;
if (S_ISDIR(st.st_mode))
continue;
}
std::string extension = get_file_extension(name);
std::string extension = get_file_extension(item.name);
if (extension != "jpg" && extension != "jpeg" && extension != "png")
continue;
std::string::size_type dotpos = name.find_last_of('.');
std::string basename = name.substr(0, dotpos);
std::string::size_type dotpos = item.name.find_last_of('.');
std::string basename = item.name.substr(0, dotpos);
char *endptr;
u32 hash = (u32)strtoll(basename.c_str(), &endptr, 16);
if (endptr - basename.c_str() < (ptrdiff_t)basename.length())
@ -274,8 +252,7 @@ void CustomTexture::LoadMap()
INFO_LOG(RENDERER, "Invalid hash %s", basename.c_str());
continue;
}
texture_map[hash] = child_path;
texture_map[hash] = item.parentPath + "/" + item.name;
}
flycast::closedir(dir);
custom_textures_available = !texture_map.empty();
}

View File

@ -24,6 +24,7 @@
#include <string>
#include <vector>
#include <map>
#include <mutex>
class CustomTexture {
public:

View File

@ -55,7 +55,7 @@ class GameScanner
void add_game_directory(const std::string& path)
{
//printf("Exploring %s\n", path.c_str());
// FIXME this won't work anymore
if (game_list.size() == 0)
{
++empty_folders_scanned;
@ -67,63 +67,36 @@ class GameScanner
content_path_looks_incorrect = false;
}
DIR *dir = flycast::opendir(path.c_str());
if (dir == NULL)
return;
while (running)
{
dirent *entry = flycast::readdir(dir);
if (entry == NULL)
break;
std::string name(entry->d_name);
if (name == "." || name == "..")
DirectoryTree tree(path);
for (const DirectoryTree::item& item : tree)
{
std::string name(item.name);
std::string child_path = item.parentPath + "/" + name;
std::string extension = get_file_extension(name);
if (extension == "zip" || extension == "7z")
{
std::string basename = get_file_basename(name);
string_tolower(basename);
auto it = arcade_games.find(basename);
if (it == arcade_games.end())
continue;
name = name + " (" + std::string(it->second->description) + ")";
}
else if (extension == "chd" || extension == "gdi")
{
// Hide arcade gdroms
std::string basename = get_file_basename(name);
string_tolower(basename);
if (arcade_gdroms.count(basename) != 0)
continue;
}
else if ((settings.dreamcast.HideLegacyNaomiRoms
|| (extension != "bin" && extension != "lst" && extension != "dat"))
&& extension != "cdi" && extension != "cue")
continue;
std::string child_path = path + "/" + name;
bool is_dir = false;
#ifndef _WIN32
if (entry->d_type == DT_DIR)
is_dir = true;
if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK)
#endif
{
struct stat st;
if (flycast::stat(child_path.c_str(), &st) != 0)
continue;
if (S_ISDIR(st.st_mode))
is_dir = true;
}
if (is_dir)
{
add_game_directory(child_path);
}
else
{
std::string extension = get_file_extension(name);
if (extension == "zip" || extension == "7z")
{
std::string basename = get_file_basename(name);
string_tolower(basename);
auto it = arcade_games.find(basename);
if (it == arcade_games.end())
continue;
name = name + " (" + std::string(it->second->description) + ")";
}
else if (extension == "chd" || extension == "gdi")
{
// Hide arcade gdroms
std::string basename = get_file_basename(name);
string_tolower(basename);
if (arcade_gdroms.count(basename) != 0)
continue;
}
else if ((settings.dreamcast.HideLegacyNaomiRoms
|| (extension != "bin" && extension != "lst" && extension != "dat"))
&& extension != "cdi" && extension != "cue")
continue;
insert_game(GameMedia{ name, child_path });
}
insert_game(GameMedia{ name, child_path });
}
flycast::closedir(dir);
}
public:

View File

@ -111,7 +111,7 @@ void select_directory_popup(const char *prompt, float scaling, StringCallback ca
if (select_current_directory == PSEUDO_ROOT)
{
error_message = "Storage Locations";
const char *home = getenv("REICAST_HOME");
const char *home = nowide::getenv("REICAST_HOME");
while (home != NULL)
{
const char *pcolon = strchr(home, ':');