uwp: fix dirent. Implement access() and stat()

Use UWP version of FindFirstFileEx
Implement basic access() and stat()
Add home dir to list of drives
Allow external storage folders to be added and scanned for games
This commit is contained in:
flyinghead 2021-12-01 14:40:17 +01:00
parent 6a8f1b941e
commit 280c159078
3 changed files with 302 additions and 257 deletions

View File

@ -29,6 +29,10 @@
#include <sys/stat.h>
#include <errno.h>
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PC_APP || WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
#define FindFirstFileExW FindFirstFileExFromAppW
#endif
/* Indicates that d_type field is available in dirent structure */
#define _DIRENT_HAVE_D_TYPE

View File

@ -1,257 +1,289 @@
/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast 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 2 of the License, or
(at your option) any later version.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include "nowide/config.hpp"
#include "nowide/stackstring.hpp"
#include <dirent.h>
#include <sys/stat.h>
#ifdef _MSC_VER
#include <io.h>
#define R_OK 4
typedef unsigned short mode_t;
#else
#include <unistd.h>
#endif
namespace flycast {
#if !defined(NOWIDE_WINDOWS)
using ::opendir;
using ::readdir;
using ::closedir;
using ::stat;
using ::access;
using ::mkdir;
#else
inline DIR *opendir(char const *dirname)
{
nowide::wstackstring wname;
if (!wname.convert(dirname)) {
errno = EINVAL;
return nullptr;
}
return (DIR *)::_wopendir(wname.c_str());
}
inline dirent *readdir(DIR *dirstream)
{
_WDIR *wdir = (_WDIR *)dirstream;
_wdirent *wdirent = ::_wreaddir(wdir);
if (wdirent == nullptr)
return nullptr;
nowide::stackstring name;
if (!name.convert(wdirent->d_name)) {
errno = EINVAL;
return nullptr;
}
static dirent d;
d.d_ino = wdirent->d_ino;
d.d_off = wdirent->d_off;
d.d_type = wdirent->d_type;
d.d_reclen = sizeof(dirent);
d.d_namlen = wdirent->d_namlen;
strcpy(d.d_name, name.c_str());
return &d;
}
inline int closedir(DIR *dirstream)
{
return ::_wclosedir((_WDIR *)dirstream);
}
inline int stat(const char *filename, struct stat *buf)
{
nowide::wstackstring wname;
if (!wname.convert(filename)) {
errno = EINVAL;
return -1;
}
struct _stat _st;
int rc = _wstat(wname.c_str(), &_st);
buf->st_ctime = _st.st_ctime;
buf->st_mtime = _st.st_mtime;
buf->st_atime = _st.st_atime;
buf->st_dev = _st.st_dev;
buf->st_ino = _st.st_ino;
buf->st_uid = _st.st_uid;
buf->st_gid = _st.st_gid;
buf->st_mode = _st.st_mode;
buf->st_nlink = _st.st_nlink;
buf->st_rdev = _st.st_rdev;
buf->st_size = _st.st_size;
return rc;
}
inline int access(const char *filename, int how)
{
nowide::wstackstring wname;
if (!wname.convert(filename)) {
errno = EINVAL;
return -1;
}
return ::_waccess(wname.c_str(), how);
}
inline int mkdir(const char *path, mode_t mode) {
nowide::wstackstring wpath;
if (!wpath.convert(path)) {
errno = EINVAL;
return -1;
}
return ::_wmkdir(wpath.c_str());
}
#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, const 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 (direntry->d_type == DT_DIR)
isDir = true;
else if (direntry->d_type == DT_UNKNOWN || direntry->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 {dir, root};
}
iterator end()
{
return {nullptr, root};
}
private:
const std::string& root;
};
/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast 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 2 of the License, or
(at your option) any later version.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include "nowide/config.hpp"
#include "nowide/stackstring.hpp"
#include <dirent.h>
#include <sys/stat.h>
#ifdef _MSC_VER
#include <io.h>
#define R_OK 4
typedef unsigned short mode_t;
#else
#include <unistd.h>
#endif
namespace flycast {
#if !defined(NOWIDE_WINDOWS)
using ::opendir;
using ::readdir;
using ::closedir;
using ::stat;
using ::access;
using ::mkdir;
#else
inline DIR *opendir(char const *dirname)
{
nowide::wstackstring wname;
if (!wname.convert(dirname)) {
errno = EINVAL;
return nullptr;
}
return (DIR *)::_wopendir(wname.c_str());
}
inline dirent *readdir(DIR *dirstream)
{
_WDIR *wdir = (_WDIR *)dirstream;
_wdirent *wdirent = ::_wreaddir(wdir);
if (wdirent == nullptr)
return nullptr;
nowide::stackstring name;
if (!name.convert(wdirent->d_name)) {
errno = EINVAL;
return nullptr;
}
static dirent d;
d.d_ino = wdirent->d_ino;
d.d_off = wdirent->d_off;
d.d_type = wdirent->d_type;
d.d_reclen = sizeof(dirent);
d.d_namlen = wdirent->d_namlen;
strcpy(d.d_name, name.c_str());
return &d;
}
inline int closedir(DIR *dirstream)
{
return ::_wclosedir((_WDIR *)dirstream);
}
inline int stat(const char *filename, struct stat *buf)
{
nowide::wstackstring wname;
if (!wname.convert(filename)) {
errno = EINVAL;
return -1;
}
#ifdef TARGET_UWP
WIN32_FILE_ATTRIBUTE_DATA attrs;
bool rc = GetFileAttributesExFromAppW(wname.c_str(), GetFileExInfoStandard, &attrs);
if (!rc)
return -1;
memset(buf, 0, sizeof(struct stat));
if (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
buf->st_mode = S_IFDIR;
else
buf->st_mode = S_IFREG;
buf->st_size = attrs.nFileSizeLow;
constexpr UINT64 WINDOWS_TICK = 10000000u;
constexpr UINT64 SEC_TO_UNIX_EPOCH = 11644473600llu;
buf->st_ctime = (unsigned)(((UINT64)attrs.ftCreationTime.dwLowDateTime | ((UINT64)attrs.ftCreationTime.dwHighDateTime << 32)) / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
buf->st_mtime = (unsigned)(((UINT64)attrs.ftLastWriteTime.dwLowDateTime | ((UINT64)attrs.ftLastWriteTime.dwHighDateTime << 32)) / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
buf->st_atime =(unsigned)(((UINT64)attrs.ftLastAccessTime.dwLowDateTime | ((UINT64)attrs.ftLastAccessTime.dwHighDateTime << 32)) / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
return 0;
#else
struct _stat _st;
int rc = _wstat(wname.c_str(), &_st);
buf->st_ctime = _st.st_ctime;
buf->st_mtime = _st.st_mtime;
buf->st_atime = _st.st_atime;
buf->st_dev = _st.st_dev;
buf->st_ino = _st.st_ino;
buf->st_uid = _st.st_uid;
buf->st_gid = _st.st_gid;
buf->st_mode = _st.st_mode;
buf->st_nlink = _st.st_nlink;
buf->st_rdev = _st.st_rdev;
buf->st_size = _st.st_size;
return rc;
#endif
}
inline int access(const char *filename, int how)
{
nowide::wstackstring wname;
if (!wname.convert(filename)) {
errno = EINVAL;
return -1;
}
#ifdef TARGET_UWP
WIN32_FILE_ATTRIBUTE_DATA attrs;
bool rc = GetFileAttributesExFromAppW(wname.c_str(), GetFileExInfoStandard, &attrs);
if (!rc)
return -1;
if (how != R_OK && (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
return -1;
else
return 0;
#else
return ::_waccess(wname.c_str(), how);
#endif
}
inline int mkdir(const char *path, mode_t mode) {
nowide::wstackstring wpath;
if (!wpath.convert(path)) {
errno = EINVAL;
return -1;
}
return ::_wmkdir(wpath.c_str());
}
#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, const 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 (direntry->d_type == DT_DIR)
isDir = true;
else if (direntry->d_type == DT_UNKNOWN || direntry->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 {dir, root};
}
iterator end()
{
return {nullptr, root};
}
private:
const std::string& root;
};

View File

@ -109,6 +109,15 @@ void select_file_popup(const char *prompt, StringCallback callback,
for (int i = 0; i < 32; i++)
if ((drives & (1 << i)) != 0)
subfolders.push_back(std::string(1, (char)('A' + i)) + ":\\");
#ifdef TARGET_UWP
// Add the home directory to the list of drives as it's not accessible from the root
std::string home;
const char *home_drive = nowide::getenv("HOMEDRIVE");
if (home_drive != NULL)
home = home_drive;
home += nowide::getenv("HOMEPATH");
subfolders.push_back(home);
#endif
}
else
#elif __ANDROID__