Preset subdirs (#385)

Scanning textures/presets dirs for textures and scanning preset dir for presets.
Scanning is now done recursively, so presets and textures can be organized into subdirectories instead of needing to be flattened into a single directory.

On POSIX systems makes use of [ftw](https://linux.die.net/man/3/ftw) which should be relatively efficient. Otherwise falls back to recursing with `dirent` (for windows).

Probably should have made an autoconf check for `ftw` instead of doing `#ifdef WIN32`. 

* Scan subdirectories in presets directory

* remove preset subdir config

* Recursively scan for textures too, add c++-17 compatibility

* Refactor directory scanning code so it's reused by texture loader and preset loader. Make cross-platform (maybe)

* filescanner in makefile

* extension filter for file loader

* make extensions match up'

* need string.h

* Add FileScanner.cpp to win build (maybe)

* scan all dirs

* #ifndef #ifdef is def fun

* bogus comment

* bleh

* bleh

* itunes plugin with c++17

* EyeTunes needs to know about the FileScanner

Co-authored-by: milkdropper.com <milkdropper.com@gmail.com>
Co-authored-by: milkdropper <59471060+milkdropper@users.noreply.github.com>
This commit is contained in:
Mischa Spiegelmock
2020-07-28 22:01:56 +03:00
committed by GitHub
parent e51b2c7d63
commit e49720ecfa
27 changed files with 379 additions and 330 deletions

View File

@ -0,0 +1,180 @@
//
// FileScanner.cpp
// libprojectM
//
//
#include "FileScanner.hpp"
FileScanner::FileScanner() {}
FileScanner::FileScanner(std::vector<std::string> &rootDirs, std::vector<std::string> &extensions) : _rootDirs(rootDirs), _extensions(extensions) {}
void FileScanner::scan(ScanCallback cb) {
#ifdef WIN32
for (auto dir : _rootDirs)
scanGeneric(cb, dir.c_str());
#else
scanPosix(cb);
#endif
}
void FileScanner::handleDirectoryError(std::string dir) {
#ifdef WIN32
std::cerr << "[PresetLoader] warning: errno unsupported on win32 platforms. fix me" << std::endl;
#else
std::cerr << dir << " scan error: ";
switch ( errno )
{
case ENOENT:
std::cerr << "ENOENT error. The path \"" << dir << "\" probably does not exist. \"man open\" for more info." << std::endl;
break;
case ENOMEM:
std::cerr << "out of memory!" << std::endl;
abort();
case ENOTDIR:
std::cerr << "directory specified is not a directory! Trying to continue..." << std::endl;
break;
case ENFILE:
std::cerr << "Your system has reached its open file limit. Trying to continue..." << std::endl;
break;
case EMFILE:
std::cerr << "too many files in use by projectM! Bailing!" << std::endl;
break;
case EACCES:
std::cerr << "permissions issue reading the specified preset directory." << std::endl;
break;
default:
break;
}
#endif
}
std::string FileScanner::extensionMatches(std::string &filename) {
// returns file name without extension
// TODO: optimize me
std::string lowerCaseFileName(filename);
std::transform(lowerCaseFileName.begin(), lowerCaseFileName.end(), lowerCaseFileName.begin(), tolower);
// Remove extension
for (auto ext : _extensions)
{
size_t found = lowerCaseFileName.find(ext);
if (found != std::string::npos)
{
std::string name = filename;
name.replace(int(found), ext.size(), "");
return name;
}
}
return {};
}
// generic implementation using dirent
void FileScanner::scanGeneric(ScanCallback cb, const char *currentDir) {
DIR * m_dir;
// Allocate a new a stream given the current directory name
if ((m_dir = opendir(currentDir)) == NULL)
{
return; // no files found in here
}
struct dirent * dir_entry;
while ((dir_entry = readdir(m_dir)) != NULL)
{
// Convert char * to friendly string
std::string filename(dir_entry->d_name);
if (filename.length() > 0 && filename[0] == '.')
continue;
std::string fullPath = std::string(currentDir) + PATH_SEPARATOR + filename;
if (dir_entry->d_type == DT_DIR) {
// recurse into dir
scanGeneric(cb, fullPath.c_str());
continue;
} else if (dir_entry->d_type != DT_REG && dir_entry->d_type != DT_LNK) {
// not regular file/link
continue;
}
auto nameMatched = extensionMatches(filename);
if (! nameMatched.empty())
cb(fullPath, nameMatched);
}
if (m_dir)
{
closedir(m_dir);
m_dir = 0;
}
}
#ifndef WIN32
// more optimized posix "fts" directory traversal
int fts_compare(const FTSENT** one, const FTSENT** two) {
return (strcmp((*one)->fts_name, (*two)->fts_name));
}
#endif
void FileScanner::scanPosix(ScanCallback cb) {
#ifndef WIN32
// efficient directory traversal
FTS* fileSystem = NULL;
FTSENT *node = NULL;
// list of directories to scan
auto rootDirCount = _rootDirs.size();
char **dirList = (char **)malloc(sizeof(char*) * (rootDirCount + 1));
for (unsigned long i = 0; i < rootDirCount; i++) {
dirList[i] = (char *) _rootDirs[i].c_str();
}
dirList[rootDirCount] = NULL;
// initialize file hierarchy traversal
fileSystem = fts_open(dirList, FTS_LOGICAL|FTS_NOCHDIR|FTS_NOSTAT, &fts_compare);
if (fileSystem == NULL) {
std::string s;
for (int i = 0; i < _rootDirs.size(); i++)
s += _rootDirs[i] + ' ';
handleDirectoryError(s);
free(dirList);
return;
}
std::string path, name, nameMatched;
// traverse dirList
while( (node = fts_read(fileSystem)) != NULL) {
switch (node->fts_info) {
case FTS_F:
case FTS_SL:
case FTS_NSOK:
// found a file
path = std::string(node->fts_path);
name = std::string(node->fts_name);
// check extension
nameMatched = extensionMatches(name);
if (! nameMatched.empty())
cb(path, nameMatched);
break;
default:
break;
}
}
fts_close(fileSystem);
free(dirList);
#endif
}