Path: new class "Path" wraps filesystem path strings
This commit is contained in:
parent
8901514506
commit
e5039c478a
|
@ -26,6 +26,7 @@
|
|||
#include "song.h"
|
||||
#include "mpd_error.h"
|
||||
#include "Mapper.hxx"
|
||||
#include "Path.hxx"
|
||||
#include "decoder_api.h"
|
||||
#include "tag.h"
|
||||
#include "input_stream.h"
|
||||
|
@ -431,7 +432,7 @@ decoder_run(struct decoder_control *dc)
|
|||
assert(song != NULL);
|
||||
|
||||
if (song_is_file(song))
|
||||
uri = map_song_fs(song);
|
||||
uri = map_song_fs(song).Steal();
|
||||
else
|
||||
uri = song_get_uri(song);
|
||||
|
||||
|
|
|
@ -32,14 +32,12 @@
|
|||
#include <errno.h>
|
||||
|
||||
bool
|
||||
ExcludeList::LoadFile(const char *path_fs)
|
||||
ExcludeList::LoadFile(const Path &path_fs)
|
||||
{
|
||||
assert(path_fs != NULL);
|
||||
|
||||
FILE *file = fopen(path_fs, "r");
|
||||
FILE *file = fopen(path_fs.c_str(), "r");
|
||||
if (file == NULL) {
|
||||
if (errno != ENOENT) {
|
||||
char *path_utf8 = fs_charset_to_utf8(path_fs);
|
||||
char *path_utf8 = path_fs.ToUTF8();
|
||||
g_debug("Failed to open %s: %s",
|
||||
path_utf8, g_strerror(errno));
|
||||
g_free(path_utf8);
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
#include <glib.h>
|
||||
|
||||
class Path;
|
||||
|
||||
class ExcludeList {
|
||||
class Pattern {
|
||||
GPatternSpec *pattern;
|
||||
|
@ -65,7 +67,7 @@ public:
|
|||
/**
|
||||
* Loads and parses a .mpdignore file.
|
||||
*/
|
||||
bool LoadFile(const char *path_fs);
|
||||
bool LoadFile(const Path &path_fs);
|
||||
|
||||
/**
|
||||
* Checks whether one of the patterns in the .mpdignore file matches
|
||||
|
|
|
@ -238,7 +238,8 @@ glue_state_file_init(GError **error_r)
|
|||
return true;
|
||||
}
|
||||
|
||||
state_file = new StateFile(path, *global_partition, *main_loop);
|
||||
state_file = new StateFile(Path::FromUTF8(path),
|
||||
*global_partition, *main_loop);
|
||||
g_free(path);
|
||||
state_file->Read();
|
||||
return true;
|
||||
|
|
|
@ -156,67 +156,54 @@ map_to_relative_path(const char *path_utf8)
|
|||
: path_utf8;
|
||||
}
|
||||
|
||||
char *
|
||||
Path
|
||||
map_uri_fs(const char *uri)
|
||||
{
|
||||
char *uri_fs, *path_fs;
|
||||
|
||||
assert(uri != NULL);
|
||||
assert(*uri != '/');
|
||||
|
||||
if (music_dir_fs == NULL)
|
||||
return NULL;
|
||||
return Path::Null();
|
||||
|
||||
uri_fs = utf8_to_fs_charset(uri);
|
||||
if (uri_fs == NULL)
|
||||
return NULL;
|
||||
const Path uri_fs = Path::FromUTF8(uri);
|
||||
if (uri_fs.IsNull())
|
||||
return Path::Null();
|
||||
|
||||
path_fs = g_build_filename(music_dir_fs, uri_fs, NULL);
|
||||
g_free(uri_fs);
|
||||
|
||||
return path_fs;
|
||||
return Path::Build(music_dir_fs, uri_fs);
|
||||
}
|
||||
|
||||
char *
|
||||
Path
|
||||
map_directory_fs(const Directory *directory)
|
||||
{
|
||||
assert(music_dir_utf8 != NULL);
|
||||
assert(music_dir_fs != NULL);
|
||||
|
||||
if (directory->IsRoot())
|
||||
return g_strdup(music_dir_fs);
|
||||
return Path::FromFS(music_dir_fs);
|
||||
|
||||
return map_uri_fs(directory->GetPath());
|
||||
}
|
||||
|
||||
char *
|
||||
Path
|
||||
map_directory_child_fs(const Directory *directory, const char *name)
|
||||
{
|
||||
assert(music_dir_utf8 != NULL);
|
||||
assert(music_dir_fs != NULL);
|
||||
|
||||
char *name_fs, *parent_fs, *path;
|
||||
|
||||
/* check for invalid or unauthorized base names */
|
||||
if (*name == 0 || strchr(name, '/') != NULL ||
|
||||
strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
|
||||
return NULL;
|
||||
return Path::Null();
|
||||
|
||||
parent_fs = map_directory_fs(directory);
|
||||
if (parent_fs == NULL)
|
||||
return NULL;
|
||||
const Path parent_fs = map_directory_fs(directory);
|
||||
if (parent_fs.IsNull())
|
||||
return Path::Null();
|
||||
|
||||
name_fs = utf8_to_fs_charset(name);
|
||||
if (name_fs == NULL) {
|
||||
g_free(parent_fs);
|
||||
return NULL;
|
||||
}
|
||||
const Path name_fs = Path::FromUTF8(name);
|
||||
if (name_fs.IsNull())
|
||||
return Path::Null();
|
||||
|
||||
path = g_build_filename(parent_fs, name_fs, NULL);
|
||||
g_free(parent_fs);
|
||||
g_free(name_fs);
|
||||
|
||||
return path;
|
||||
return Path::Build(parent_fs, name_fs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,19 +211,17 @@ map_directory_child_fs(const Directory *directory, const char *name)
|
|||
* not have a real parent directory, only the dummy object
|
||||
* #detached_root.
|
||||
*/
|
||||
static char *
|
||||
static Path
|
||||
map_detached_song_fs(const char *uri_utf8)
|
||||
{
|
||||
char *uri_fs = utf8_to_fs_charset(uri_utf8);
|
||||
if (uri_fs == NULL)
|
||||
return NULL;
|
||||
Path uri_fs = Path::FromUTF8(uri_utf8);
|
||||
if (uri_fs.IsNull())
|
||||
return Path::Null();
|
||||
|
||||
char *path = g_build_filename(music_dir_fs, uri_fs, NULL);
|
||||
g_free(uri_fs);
|
||||
return path;
|
||||
return Path::Build(music_dir_fs, uri_fs);
|
||||
}
|
||||
|
||||
char *
|
||||
Path
|
||||
map_song_fs(const struct song *song)
|
||||
{
|
||||
assert(song_is_file(song));
|
||||
|
@ -246,7 +231,7 @@ map_song_fs(const struct song *song)
|
|||
? map_detached_song_fs(song->uri)
|
||||
: map_directory_child_fs(song->parent, song->uri);
|
||||
else
|
||||
return utf8_to_fs_charset(song->uri);
|
||||
return Path::FromUTF8(song->uri);
|
||||
}
|
||||
|
||||
char *
|
||||
|
@ -273,22 +258,17 @@ map_spl_path(void)
|
|||
return playlist_dir_fs;
|
||||
}
|
||||
|
||||
char *
|
||||
Path
|
||||
map_spl_utf8_to_fs(const char *name)
|
||||
{
|
||||
char *filename_utf8, *filename_fs, *path;
|
||||
|
||||
if (playlist_dir_fs == NULL)
|
||||
return NULL;
|
||||
return Path::Null();
|
||||
|
||||
filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
|
||||
filename_fs = utf8_to_fs_charset(filename_utf8);
|
||||
char *filename_utf8 = g_strconcat(name, PLAYLIST_FILE_SUFFIX, NULL);
|
||||
const Path filename_fs = Path::FromUTF8(filename_utf8);
|
||||
g_free(filename_utf8);
|
||||
if (filename_fs == NULL)
|
||||
return NULL;
|
||||
if (filename_fs.IsNull())
|
||||
return Path::Null();
|
||||
|
||||
path = g_build_filename(playlist_dir_fs, filename_fs, NULL);
|
||||
g_free(filename_fs);
|
||||
|
||||
return path;
|
||||
return Path::Build(playlist_dir_fs, filename_fs);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#define PLAYLIST_FILE_SUFFIX ".m3u"
|
||||
|
||||
class Path;
|
||||
struct Directory;
|
||||
struct song;
|
||||
|
||||
|
@ -75,8 +76,8 @@ map_to_relative_path(const char *path_utf8);
|
|||
* is basically done by converting the URI to the file system charset
|
||||
* and prepending the music directory.
|
||||
*/
|
||||
gcc_malloc
|
||||
char *
|
||||
gcc_pure
|
||||
Path
|
||||
map_uri_fs(const char *uri);
|
||||
|
||||
/**
|
||||
|
@ -85,8 +86,8 @@ map_uri_fs(const char *uri);
|
|||
* @param directory the directory object
|
||||
* @return the path in file system encoding, or nullptr if mapping failed
|
||||
*/
|
||||
gcc_malloc
|
||||
char *
|
||||
gcc_pure
|
||||
Path
|
||||
map_directory_fs(const Directory *directory);
|
||||
|
||||
/**
|
||||
|
@ -97,8 +98,8 @@ map_directory_fs(const Directory *directory);
|
|||
* @param name the child's name in UTF-8
|
||||
* @return the path in file system encoding, or nullptr if mapping failed
|
||||
*/
|
||||
gcc_malloc
|
||||
char *
|
||||
gcc_pure
|
||||
Path
|
||||
map_directory_child_fs(const Directory *directory, const char *name);
|
||||
|
||||
/**
|
||||
|
@ -108,8 +109,8 @@ map_directory_child_fs(const Directory *directory, const char *name);
|
|||
* @param song the song object
|
||||
* @return the path in file system encoding, or nullptr if mapping failed
|
||||
*/
|
||||
gcc_malloc
|
||||
char *
|
||||
gcc_pure
|
||||
Path
|
||||
map_song_fs(const struct song *song);
|
||||
|
||||
/**
|
||||
|
@ -138,7 +139,7 @@ map_spl_path(void);
|
|||
* @return the path in file system encoding, or nullptr if mapping failed
|
||||
*/
|
||||
gcc_pure
|
||||
char *
|
||||
Path
|
||||
map_spl_utf8_to_fs(const char *name);
|
||||
|
||||
#endif
|
||||
|
|
207
src/Path.hxx
207
src/Path.hxx
|
@ -21,7 +21,14 @@
|
|||
#define MPD_PATH_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "gcc.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#if !defined(MPD_PATH_MAX)
|
||||
|
@ -54,4 +61,204 @@ utf8_to_fs_charset(const char *path_utf8);
|
|||
|
||||
const char *path_get_fs_charset();
|
||||
|
||||
/**
|
||||
* A path name in the native file system character set.
|
||||
*/
|
||||
class Path {
|
||||
public:
|
||||
typedef char value_type;
|
||||
typedef value_type *pointer;
|
||||
typedef const value_type *const_pointer;
|
||||
|
||||
private:
|
||||
pointer value;
|
||||
|
||||
struct Donate {};
|
||||
|
||||
/**
|
||||
* Donate the allocated pointer to a new #Path object.
|
||||
*/
|
||||
constexpr Path(Donate, pointer _value):value(_value) {}
|
||||
|
||||
/**
|
||||
* Release memory allocated by the value, but do not clear the
|
||||
* value pointer.
|
||||
*/
|
||||
void Free() {
|
||||
/* free() can be optimized by gcc, while g_free() can
|
||||
not: when the compiler knows that the value is
|
||||
nullptr, it will not emit a free() call in the
|
||||
inlined destructor; however on Windows, we need to
|
||||
call g_free(), because the value has been allocated
|
||||
by GLib, and on Windows, this matters */
|
||||
#ifdef WIN32
|
||||
g_free(value);
|
||||
#else
|
||||
free(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Copy a #Path object.
|
||||
*/
|
||||
Path(const Path &other)
|
||||
:value(g_strdup(other.value)) {}
|
||||
|
||||
/**
|
||||
* Move a #Path object.
|
||||
*/
|
||||
Path(Path &&other):value(other.value) {
|
||||
other.value = nullptr;
|
||||
}
|
||||
|
||||
~Path() {
|
||||
Free();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a "nulled" instance. Its IsNull() method will
|
||||
* return true. Such an object must not be used.
|
||||
*
|
||||
* @see IsNull()
|
||||
*/
|
||||
gcc_const
|
||||
static Path Null() {
|
||||
return Path(Donate(), nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two path components with the path separator.
|
||||
*/
|
||||
gcc_pure gcc_nonnull_all
|
||||
static Path Build(const_pointer a, const_pointer b) {
|
||||
return Path(Donate(), g_build_filename(a, b, nullptr));
|
||||
}
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static Path Build(const_pointer a, const Path &b) {
|
||||
return Build(a, b.c_str());
|
||||
}
|
||||
|
||||
gcc_pure gcc_nonnull_all
|
||||
static Path Build(const Path &a, const_pointer b) {
|
||||
return Build(a.c_str(), b);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static Path Build(const Path &a, const Path &b) {
|
||||
return Build(a.c_str(), b.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a C string that is already in the filesystem
|
||||
* character set to a #Path instance.
|
||||
*/
|
||||
gcc_pure
|
||||
static Path FromFS(const_pointer fs) {
|
||||
return Path(Donate(), g_strdup(fs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UTF-8 C string to a #Path instance.
|
||||
*
|
||||
* TODO: return a "nulled" instance on error and add checks to
|
||||
* all callers
|
||||
*/
|
||||
gcc_pure
|
||||
static Path FromUTF8(const char *utf8) {
|
||||
return Path(Donate(), utf8_to_fs_charset(utf8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a #Path object.
|
||||
*/
|
||||
Path &operator=(const Path &other) {
|
||||
if (this != &other) {
|
||||
Free();
|
||||
value = g_strdup(other.value);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a #Path object.
|
||||
*/
|
||||
Path &operator=(Path &&other) {
|
||||
std::swap(value, other.value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Steal the allocated value. This object has an undefined
|
||||
* value, and the caller is response for freeing this method's
|
||||
* return value.
|
||||
*/
|
||||
pointer Steal() {
|
||||
pointer result = value;
|
||||
value = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a "nulled" instance. A "nulled" instance
|
||||
* must not be used.
|
||||
*/
|
||||
bool IsNull() const {
|
||||
return value == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this object's value, make it "nulled".
|
||||
*
|
||||
* @see IsNull()
|
||||
*/
|
||||
void SetNull() {
|
||||
Free();
|
||||
value = nullptr;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
bool empty() const {
|
||||
assert(value != nullptr);
|
||||
|
||||
return *value == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of this string in number of "value_type"
|
||||
* elements (which may not be the number of characters).
|
||||
*/
|
||||
gcc_pure
|
||||
size_t length() const {
|
||||
assert(value != nullptr);
|
||||
|
||||
return strlen(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as a const C string. The returned
|
||||
* pointer is invalidated whenever the value of life of this
|
||||
* instance ends.
|
||||
*/
|
||||
gcc_pure
|
||||
const_pointer c_str() const {
|
||||
assert(value != nullptr);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the path to UTF-8. The caller is responsible for
|
||||
* freeing the return value with g_free(). Returns nullptr on
|
||||
* error.
|
||||
*/
|
||||
char *ToUTF8() const {
|
||||
return value != nullptr
|
||||
? fs_charset_to_utf8(value)
|
||||
: nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -106,15 +106,15 @@ spl_check_name(const char *name_utf8, GError **error_r)
|
|||
return true;
|
||||
}
|
||||
|
||||
static char *
|
||||
static Path
|
||||
spl_map_to_fs(const char *name_utf8, GError **error_r)
|
||||
{
|
||||
if (spl_map(error_r) == NULL ||
|
||||
!spl_check_name(name_utf8, error_r))
|
||||
return NULL;
|
||||
return Path::Null();
|
||||
|
||||
char *path_fs = map_spl_utf8_to_fs(name_utf8);
|
||||
if (path_fs == NULL)
|
||||
Path path_fs = map_spl_utf8_to_fs(name_utf8);
|
||||
if (path_fs.IsNull())
|
||||
g_set_error_literal(error_r, playlist_quark(),
|
||||
PLAYLIST_RESULT_BAD_NAME,
|
||||
"Bad playlist name");
|
||||
|
@ -209,12 +209,11 @@ SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path,
|
|||
if (spl_map(error_r) == NULL)
|
||||
return false;
|
||||
|
||||
char *path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
FILE *file = fopen(path_fs, "w");
|
||||
g_free(path_fs);
|
||||
FILE *file = fopen(path_fs.c_str(), "w");
|
||||
if (file == NULL) {
|
||||
playlist_errno(error_r);
|
||||
return false;
|
||||
|
@ -235,8 +234,8 @@ LoadPlaylistFile(const char *utf8path, GError **error_r)
|
|||
if (spl_map(error_r) == NULL)
|
||||
return contents;
|
||||
|
||||
char *path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs.IsNull())
|
||||
return contents;
|
||||
|
||||
TextFile file(path_fs);
|
||||
|
@ -308,17 +307,14 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest,
|
|||
bool
|
||||
spl_clear(const char *utf8path, GError **error_r)
|
||||
{
|
||||
FILE *file;
|
||||
|
||||
if (spl_map(error_r) == NULL)
|
||||
return false;
|
||||
|
||||
char *path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
file = fopen(path_fs, "w");
|
||||
g_free(path_fs);
|
||||
FILE *file = fopen(path_fs.c_str(), "w");
|
||||
if (file == NULL) {
|
||||
playlist_errno(error_r);
|
||||
return false;
|
||||
|
@ -333,12 +329,11 @@ spl_clear(const char *utf8path, GError **error_r)
|
|||
bool
|
||||
spl_delete(const char *name_utf8, GError **error_r)
|
||||
{
|
||||
char *path_fs = spl_map_to_fs(name_utf8, error_r);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = spl_map_to_fs(name_utf8, error_r);
|
||||
if (path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
int ret = unlink(path_fs);
|
||||
g_free(path_fs);
|
||||
int ret = unlink(path_fs.c_str());
|
||||
if (ret < 0) {
|
||||
playlist_errno(error_r);
|
||||
return false;
|
||||
|
@ -376,17 +371,14 @@ spl_remove_index(const char *utf8path, unsigned pos, GError **error_r)
|
|||
bool
|
||||
spl_append_song(const char *utf8path, struct song *song, GError **error_r)
|
||||
{
|
||||
FILE *file;
|
||||
|
||||
if (spl_map(error_r) == NULL)
|
||||
return false;
|
||||
|
||||
char *path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = spl_map_to_fs(utf8path, error_r);
|
||||
if (path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
file = fopen(path_fs, "a");
|
||||
g_free(path_fs);
|
||||
FILE *file = fopen(path_fs.c_str(), "a");
|
||||
if (file == NULL) {
|
||||
playlist_errno(error_r);
|
||||
return false;
|
||||
|
@ -439,24 +431,24 @@ spl_append_uri(const char *url, const char *utf8file, GError **error_r)
|
|||
}
|
||||
|
||||
static bool
|
||||
spl_rename_internal(const char *from_path_fs, const char *to_path_fs,
|
||||
spl_rename_internal(const Path &from_path_fs, const Path &to_path_fs,
|
||||
GError **error_r)
|
||||
{
|
||||
if (!g_file_test(from_path_fs, G_FILE_TEST_IS_REGULAR)) {
|
||||
if (!g_file_test(from_path_fs.c_str(), G_FILE_TEST_IS_REGULAR)) {
|
||||
g_set_error_literal(error_r, playlist_quark(),
|
||||
PLAYLIST_RESULT_NO_SUCH_LIST,
|
||||
"No such playlist");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_file_test(to_path_fs, G_FILE_TEST_EXISTS)) {
|
||||
if (g_file_test(to_path_fs.c_str(), G_FILE_TEST_EXISTS)) {
|
||||
g_set_error_literal(error_r, playlist_quark(),
|
||||
PLAYLIST_RESULT_LIST_EXISTS,
|
||||
"Playlist exists already");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rename(from_path_fs, to_path_fs) < 0) {
|
||||
if (rename(from_path_fs.c_str(), to_path_fs.c_str()) < 0) {
|
||||
playlist_errno(error_r);
|
||||
return false;
|
||||
}
|
||||
|
@ -471,20 +463,13 @@ spl_rename(const char *utf8from, const char *utf8to, GError **error_r)
|
|||
if (spl_map(error_r) == NULL)
|
||||
return false;
|
||||
|
||||
char *from_path_fs = spl_map_to_fs(utf8from, error_r);
|
||||
if (from_path_fs == NULL)
|
||||
Path from_path_fs = spl_map_to_fs(utf8from, error_r);
|
||||
if (from_path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
char *to_path_fs = spl_map_to_fs(utf8to, error_r);
|
||||
if (to_path_fs == NULL) {
|
||||
g_free(from_path_fs);
|
||||
Path to_path_fs = spl_map_to_fs(utf8to, error_r);
|
||||
if (to_path_fs.IsNull())
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = spl_rename_internal(from_path_fs, to_path_fs, error_r);
|
||||
|
||||
g_free(from_path_fs);
|
||||
g_free(to_path_fs);
|
||||
|
||||
return success;
|
||||
return spl_rename_internal(from_path_fs, to_path_fs, error_r);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "PlaylistMapper.hxx"
|
||||
#include "PlaylistFile.hxx"
|
||||
#include "Mapper.hxx"
|
||||
#include "Path.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include "playlist_list.h"
|
||||
|
@ -75,19 +76,13 @@ static struct playlist_provider *
|
|||
playlist_open_in_music_dir(const char *uri, GMutex *mutex, GCond *cond,
|
||||
struct input_stream **is_r)
|
||||
{
|
||||
char *path_fs;
|
||||
|
||||
assert(uri_safe_local(uri));
|
||||
|
||||
path_fs = map_uri_fs(uri);
|
||||
if (path_fs == NULL)
|
||||
Path path = map_uri_fs(uri);
|
||||
if (path.IsNull())
|
||||
return NULL;
|
||||
|
||||
struct playlist_provider *playlist =
|
||||
playlist_open_path(path_fs, mutex, cond, is_r);
|
||||
g_free(path_fs);
|
||||
|
||||
return playlist;
|
||||
return playlist_open_path(path.c_str(), mutex, cond, is_r);
|
||||
}
|
||||
|
||||
struct playlist_provider *
|
||||
|
|
|
@ -38,62 +38,47 @@ void
|
|||
playlist_print_song(FILE *file, const struct song *song)
|
||||
{
|
||||
if (playlist_saveAbsolutePaths && song_in_database(song)) {
|
||||
char *path = map_song_fs(song);
|
||||
if (path != NULL) {
|
||||
fprintf(file, "%s\n", path);
|
||||
g_free(path);
|
||||
}
|
||||
const Path path = map_song_fs(song);
|
||||
if (!path.IsNull())
|
||||
fprintf(file, "%s\n", path.c_str());
|
||||
} else {
|
||||
char *uri = song_get_uri(song), *uri_fs;
|
||||
|
||||
uri_fs = utf8_to_fs_charset(uri);
|
||||
char *uri = song_get_uri(song);
|
||||
const Path uri_fs = Path::FromUTF8(uri);
|
||||
g_free(uri);
|
||||
|
||||
fprintf(file, "%s\n", uri_fs);
|
||||
g_free(uri_fs);
|
||||
fprintf(file, "%s\n", uri_fs.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
playlist_print_uri(FILE *file, const char *uri)
|
||||
{
|
||||
char *s;
|
||||
Path path = playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
|
||||
!g_path_is_absolute(uri)
|
||||
? map_uri_fs(uri)
|
||||
: Path::FromUTF8(uri);
|
||||
|
||||
if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
|
||||
!g_path_is_absolute(uri))
|
||||
s = map_uri_fs(uri);
|
||||
else
|
||||
s = utf8_to_fs_charset(uri);
|
||||
|
||||
if (s != NULL) {
|
||||
fprintf(file, "%s\n", s);
|
||||
g_free(s);
|
||||
}
|
||||
if (!path.IsNull())
|
||||
fprintf(file, "%s\n", path.c_str());
|
||||
}
|
||||
|
||||
enum playlist_result
|
||||
spl_save_queue(const char *name_utf8, const struct queue *queue)
|
||||
{
|
||||
char *path_fs;
|
||||
FILE *file;
|
||||
|
||||
if (map_spl_path() == NULL)
|
||||
return PLAYLIST_RESULT_DISABLED;
|
||||
|
||||
if (!spl_valid_name(name_utf8))
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
path_fs = map_spl_utf8_to_fs(name_utf8);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_spl_utf8_to_fs(name_utf8);
|
||||
if (path_fs.IsNull())
|
||||
return PLAYLIST_RESULT_BAD_NAME;
|
||||
|
||||
if (g_file_test(path_fs, G_FILE_TEST_EXISTS)) {
|
||||
g_free(path_fs);
|
||||
if (g_file_test(path_fs.c_str(), G_FILE_TEST_EXISTS))
|
||||
return PLAYLIST_RESULT_LIST_EXISTS;
|
||||
}
|
||||
|
||||
file = fopen(path_fs, "w");
|
||||
g_free(path_fs);
|
||||
FILE *file = fopen(path_fs.c_str(), "w");
|
||||
|
||||
if (file == NULL)
|
||||
return PLAYLIST_RESULT_ERRNO;
|
||||
|
|
|
@ -65,8 +65,8 @@ apply_song_metadata(struct song *dest, const struct song *src)
|
|||
return dest;
|
||||
|
||||
if (song_in_database(dest)) {
|
||||
char *path_fs = map_song_fs(dest);
|
||||
if (path_fs == NULL)
|
||||
char *path_fs = map_song_fs(dest).Steal();
|
||||
if (path_fs == nullptr)
|
||||
return dest;
|
||||
|
||||
char *path_utf8 = fs_charset_to_utf8(path_fs);
|
||||
|
|
|
@ -26,6 +26,7 @@ extern "C" {
|
|||
|
||||
#include "Directory.hxx"
|
||||
#include "Mapper.hxx"
|
||||
#include "Path.hxx"
|
||||
#include "tag.h"
|
||||
#include "input_stream.h"
|
||||
|
||||
|
@ -85,7 +86,6 @@ bool
|
|||
song_file_update(struct song *song)
|
||||
{
|
||||
const char *suffix;
|
||||
char *path_fs;
|
||||
const struct decoder_plugin *plugin;
|
||||
struct stat st;
|
||||
struct input_stream *is = NULL;
|
||||
|
@ -102,8 +102,8 @@ song_file_update(struct song *song)
|
|||
if (plugin == NULL)
|
||||
return false;
|
||||
|
||||
path_fs = map_song_fs(song);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_song_fs(song);
|
||||
if (path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
if (song->tag != NULL) {
|
||||
|
@ -111,8 +111,7 @@ song_file_update(struct song *song)
|
|||
song->tag = NULL;
|
||||
}
|
||||
|
||||
if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
|
||||
g_free(path_fs);
|
||||
if (stat(path_fs.c_str(), &st) < 0 || !S_ISREG(st.st_mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -129,7 +128,7 @@ song_file_update(struct song *song)
|
|||
do {
|
||||
/* load file tag */
|
||||
song->tag = tag_new();
|
||||
if (decoder_plugin_scan_file(plugin, path_fs,
|
||||
if (decoder_plugin_scan_file(plugin, path_fs.c_str(),
|
||||
&full_tag_handler, song->tag))
|
||||
break;
|
||||
|
||||
|
@ -143,7 +142,8 @@ song_file_update(struct song *song)
|
|||
if (is == NULL) {
|
||||
mutex = g_mutex_new();
|
||||
cond = g_cond_new();
|
||||
is = input_stream_open(path_fs, mutex, cond,
|
||||
is = input_stream_open(path_fs.c_str(),
|
||||
mutex, cond,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
@ -174,9 +174,9 @@ song_file_update(struct song *song)
|
|||
}
|
||||
|
||||
if (song->tag != NULL && tag_is_empty(song->tag))
|
||||
tag_scan_fallback(path_fs, &full_tag_handler, song->tag);
|
||||
tag_scan_fallback(path_fs.c_str(), &full_tag_handler,
|
||||
song->tag);
|
||||
|
||||
g_free(path_fs);
|
||||
return song->tag != NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "state_file"
|
||||
|
||||
StateFile::StateFile(const char *_path, Partition &_partition, EventLoop &_loop)
|
||||
:TimeoutMonitor(_loop), path(_path), partition(_partition),
|
||||
StateFile::StateFile(Path &&_path, Partition &_partition, EventLoop &_loop)
|
||||
:TimeoutMonitor(_loop), path(std::move(_path)), partition(_partition),
|
||||
prev_volume_version(0), prev_output_version(0),
|
||||
prev_playlist_version(0)
|
||||
{
|
||||
|
@ -73,7 +73,7 @@ StateFile::Read()
|
|||
|
||||
g_debug("Loading state file %s", path.c_str());
|
||||
|
||||
TextFile file(path.c_str());
|
||||
TextFile file(path);
|
||||
if (file.HasFailed()) {
|
||||
g_warning("failed to open %s: %s",
|
||||
path.c_str(), g_strerror(errno));
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#define MPD_STATE_FILE_HXX
|
||||
|
||||
#include "event/TimeoutMonitor.hxx"
|
||||
#include "Path.hxx"
|
||||
#include "gcc.h"
|
||||
|
||||
#include <string>
|
||||
|
@ -28,7 +29,7 @@
|
|||
struct Partition;
|
||||
|
||||
class StateFile final : private TimeoutMonitor {
|
||||
std::string path;
|
||||
Path path;
|
||||
|
||||
Partition &partition;
|
||||
|
||||
|
@ -40,7 +41,7 @@ class StateFile final : private TimeoutMonitor {
|
|||
prev_playlist_version;
|
||||
|
||||
public:
|
||||
StateFile(const char *path, Partition &partition, EventLoop &loop);
|
||||
StateFile(Path &&path, Partition &partition, EventLoop &loop);
|
||||
|
||||
void Read();
|
||||
void Write();
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#define MPD_TEXT_FILE_HXX
|
||||
|
||||
#include "gcc.h"
|
||||
#include "Path.hxx"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
|
@ -35,8 +36,9 @@ class TextFile {
|
|||
GString *const buffer;
|
||||
|
||||
public:
|
||||
TextFile(const char *path_fs)
|
||||
:file(fopen(path_fs, "r")), buffer(g_string_sized_new(step)) {}
|
||||
TextFile(const Path &path_fs)
|
||||
:file(fopen(path_fs.c_str(), "r")),
|
||||
buffer(g_string_sized_new(step)) {}
|
||||
|
||||
TextFile(const TextFile &other) = delete;
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "Directory.hxx"
|
||||
#include "song.h"
|
||||
#include "Mapper.hxx"
|
||||
#include "Path.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include "archive_list.h"
|
||||
|
@ -96,20 +97,19 @@ update_archive_file2(Directory *parent, const char *name,
|
|||
changed since - don't consider updating it */
|
||||
return;
|
||||
|
||||
char *path_fs = map_directory_child_fs(parent, name);
|
||||
const Path path_fs = map_directory_child_fs(parent, name);
|
||||
|
||||
/* open archive */
|
||||
GError *error = NULL;
|
||||
struct archive_file *file = archive_file_open(plugin, path_fs, &error);
|
||||
struct archive_file *file = archive_file_open(plugin, path_fs.c_str(),
|
||||
&error);
|
||||
if (file == NULL) {
|
||||
g_free(path_fs);
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_debug("archive %s opened", path_fs);
|
||||
g_free(path_fs);
|
||||
g_debug("archive %s opened", path_fs.c_str());
|
||||
|
||||
if (directory == NULL) {
|
||||
g_debug("creating archive directory: %s", name);
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "song.h"
|
||||
#include "decoder_plugin.h"
|
||||
#include "Mapper.hxx"
|
||||
#include "Path.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include "tag_handler.h"
|
||||
|
@ -84,22 +85,22 @@ update_container_file(Directory *directory,
|
|||
contdir->device = DEVICE_CONTAINER;
|
||||
db_unlock();
|
||||
|
||||
char *const pathname = map_directory_child_fs(directory, name);
|
||||
const Path pathname = map_directory_child_fs(directory, name);
|
||||
|
||||
char *vtrack;
|
||||
unsigned int tnum = 0;
|
||||
while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) {
|
||||
while ((vtrack = plugin->container_scan(pathname.c_str(), ++tnum)) != NULL) {
|
||||
struct song *song = song_file_new(vtrack, contdir);
|
||||
|
||||
// shouldn't be necessary but it's there..
|
||||
song->mtime = st->st_mtime;
|
||||
|
||||
char *child_path_fs = map_directory_child_fs(contdir, vtrack);
|
||||
const Path child_path_fs =
|
||||
map_directory_child_fs(contdir, vtrack);
|
||||
|
||||
song->tag = tag_new();
|
||||
decoder_plugin_scan_file(plugin, child_path_fs,
|
||||
decoder_plugin_scan_file(plugin, child_path_fs.c_str(),
|
||||
&add_tag_handler, song->tag);
|
||||
g_free(child_path_fs);
|
||||
|
||||
db_lock();
|
||||
contdir->AddSong(song);
|
||||
|
@ -111,8 +112,6 @@ update_container_file(Directory *directory,
|
|||
g_free(vtrack);
|
||||
}
|
||||
|
||||
g_free(pathname);
|
||||
|
||||
if (tnum == 1) {
|
||||
db_lock();
|
||||
delete_directory(contdir);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "UpdateIO.hxx"
|
||||
#include "Directory.hxx"
|
||||
#include "Mapper.hxx"
|
||||
#include "Path.hxx"
|
||||
#include "glib_compat.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
@ -31,15 +32,15 @@
|
|||
int
|
||||
stat_directory(const Directory *directory, struct stat *st)
|
||||
{
|
||||
char *path_fs = map_directory_fs(directory);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_directory_fs(directory);
|
||||
if (path_fs.IsNull())
|
||||
return -1;
|
||||
|
||||
int ret = stat(path_fs, st);
|
||||
int ret = stat(path_fs.c_str(), st);
|
||||
if (ret < 0)
|
||||
g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno));
|
||||
g_warning("Failed to stat %s: %s",
|
||||
path_fs.c_str(), g_strerror(errno));
|
||||
|
||||
g_free(path_fs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -47,23 +48,23 @@ int
|
|||
stat_directory_child(const Directory *parent, const char *name,
|
||||
struct stat *st)
|
||||
{
|
||||
char *path_fs = map_directory_child_fs(parent, name);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_directory_child_fs(parent, name);
|
||||
if (path_fs.IsNull())
|
||||
return -1;
|
||||
|
||||
int ret = stat(path_fs, st);
|
||||
int ret = stat(path_fs.c_str(), st);
|
||||
if (ret < 0)
|
||||
g_warning("Failed to stat %s: %s", path_fs, g_strerror(errno));
|
||||
g_warning("Failed to stat %s: %s",
|
||||
path_fs.c_str(), g_strerror(errno));
|
||||
|
||||
g_free(path_fs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
directory_exists(const Directory *directory)
|
||||
{
|
||||
char *path_fs = map_directory_fs(directory);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_directory_fs(directory);
|
||||
if (path_fs.IsNull())
|
||||
/* invalid path: cannot exist */
|
||||
return false;
|
||||
|
||||
|
@ -72,25 +73,19 @@ directory_exists(const Directory *directory)
|
|||
? G_FILE_TEST_IS_REGULAR
|
||||
: G_FILE_TEST_IS_DIR;
|
||||
|
||||
bool exists = g_file_test(path_fs, test);
|
||||
g_free(path_fs);
|
||||
|
||||
return exists;
|
||||
return g_file_test(path_fs.c_str(), test);
|
||||
}
|
||||
|
||||
bool
|
||||
directory_child_is_regular(const Directory *directory,
|
||||
const char *name_utf8)
|
||||
{
|
||||
char *path_fs = map_directory_child_fs(directory, name_utf8);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_directory_child_fs(directory, name_utf8);
|
||||
if (path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
struct stat st;
|
||||
bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode);
|
||||
g_free(path_fs);
|
||||
|
||||
return is_regular;
|
||||
return stat(path_fs.c_str(), &st) == 0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -104,14 +99,12 @@ directory_child_access(const Directory *directory,
|
|||
(void)mode;
|
||||
return true;
|
||||
#else
|
||||
char *path = map_directory_child_fs(directory, name);
|
||||
if (path == NULL)
|
||||
const Path path = map_directory_child_fs(directory, name);
|
||||
if (path.IsNull())
|
||||
/* something went wrong, but that isn't a permission
|
||||
problem */
|
||||
return true;
|
||||
|
||||
bool success = access(path, mode) == 0 || errno != EACCES;
|
||||
g_free(path);
|
||||
return success;
|
||||
return access(path.c_str(), mode) == 0 || errno != EACCES;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -102,27 +102,23 @@ remove_excluded_from_directory(Directory *directory,
|
|||
|
||||
Directory *child, *n;
|
||||
directory_for_each_child_safe(child, n, directory) {
|
||||
char *name_fs = utf8_to_fs_charset(child->GetName());
|
||||
const Path name_fs = Path::FromUTF8(child->GetName());
|
||||
|
||||
if (exclude_list.Check(name_fs)) {
|
||||
if (exclude_list.Check(name_fs.c_str())) {
|
||||
delete_directory(child);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
g_free(name_fs);
|
||||
}
|
||||
|
||||
struct song *song, *ns;
|
||||
directory_for_each_song_safe(song, ns, directory) {
|
||||
assert(song->parent == directory);
|
||||
|
||||
char *name_fs = utf8_to_fs_charset(song->uri);
|
||||
if (exclude_list.Check(name_fs)) {
|
||||
const Path name_fs = Path::FromUTF8(song->uri);
|
||||
if (exclude_list.Check(name_fs.c_str())) {
|
||||
delete_song(directory, song);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
g_free(name_fs);
|
||||
}
|
||||
|
||||
db_unlock();
|
||||
|
@ -145,18 +141,16 @@ purge_deleted_from_directory(Directory *directory)
|
|||
|
||||
struct song *song, *ns;
|
||||
directory_for_each_song_safe(song, ns, directory) {
|
||||
char *path;
|
||||
struct stat st;
|
||||
if ((path = map_song_fs(song)) == NULL ||
|
||||
stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
|
||||
const Path path = map_song_fs(song);
|
||||
if (path.IsNull() ||
|
||||
stat(path.c_str(), &st) < 0 || !S_ISREG(st.st_mode)) {
|
||||
db_lock();
|
||||
delete_song(directory, song);
|
||||
db_unlock();
|
||||
|
||||
modified = true;
|
||||
}
|
||||
|
||||
g_free(path);
|
||||
}
|
||||
|
||||
for (auto i = directory->playlists.begin(),
|
||||
|
@ -283,13 +277,12 @@ static bool
|
|||
skip_symlink(const Directory *directory, const char *utf8_name)
|
||||
{
|
||||
#ifndef WIN32
|
||||
char *path_fs = map_directory_child_fs(directory, utf8_name);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_directory_child_fs(directory, utf8_name);
|
||||
if (path_fs.IsNull())
|
||||
return true;
|
||||
|
||||
char buffer[MPD_PATH_MAX];
|
||||
ssize_t length = readlink(path_fs, buffer, sizeof(buffer));
|
||||
g_free(path_fs);
|
||||
ssize_t length = readlink(path_fs.c_str(), buffer, sizeof(buffer));
|
||||
if (length < 0)
|
||||
/* don't skip if this is not a symlink */
|
||||
return errno != EINVAL;
|
||||
|
@ -359,24 +352,19 @@ update_directory(Directory *directory, const struct stat *st)
|
|||
|
||||
directory_set_stat(directory, st);
|
||||
|
||||
char *path_fs = map_directory_fs(directory);
|
||||
if (path_fs == NULL)
|
||||
const Path path_fs = map_directory_fs(directory);
|
||||
if (path_fs.IsNull())
|
||||
return false;
|
||||
|
||||
DIR *dir = opendir(path_fs);
|
||||
DIR *dir = opendir(path_fs.c_str());
|
||||
if (!dir) {
|
||||
g_warning("Failed to open directory %s: %s",
|
||||
path_fs, g_strerror(errno));
|
||||
g_free(path_fs);
|
||||
path_fs.c_str(), g_strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
char *exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL);
|
||||
ExcludeList exclude_list;
|
||||
exclude_list.LoadFile(exclude_path_fs);
|
||||
g_free(exclude_path_fs);
|
||||
|
||||
g_free(path_fs);
|
||||
exclude_list.LoadFile(Path::Build(path_fs, ".mpdignore"));
|
||||
|
||||
if (!exclude_list.IsEmpty())
|
||||
remove_excluded_from_directory(directory, exclude_list);
|
||||
|
|
|
@ -68,7 +68,7 @@ SimpleDatabase::Configure(const struct config_param *param, GError **error_r)
|
|||
return false;
|
||||
}
|
||||
|
||||
path = _path;
|
||||
path = Path::FromUTF8(_path);
|
||||
free(_path);
|
||||
|
||||
return true;
|
||||
|
@ -77,6 +77,7 @@ SimpleDatabase::Configure(const struct config_param *param, GError **error_r)
|
|||
bool
|
||||
SimpleDatabase::Check(GError **error_r) const
|
||||
{
|
||||
assert(!path.IsNull());
|
||||
assert(!path.empty());
|
||||
|
||||
/* Check if the file exists */
|
||||
|
@ -153,7 +154,7 @@ SimpleDatabase::Load(GError **error_r)
|
|||
assert(!path.empty());
|
||||
assert(root != NULL);
|
||||
|
||||
TextFile file(path.c_str());
|
||||
TextFile file(path);
|
||||
if (file.HasFailed()) {
|
||||
g_set_error(error_r, simple_db_quark(), errno,
|
||||
"Failed to open database file \"%s\": %s",
|
||||
|
|
|
@ -21,17 +21,17 @@
|
|||
#define MPD_SIMPLE_DATABASE_PLUGIN_HXX
|
||||
|
||||
#include "DatabasePlugin.hxx"
|
||||
#include "Path.hxx"
|
||||
#include "gcc.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
struct Directory;
|
||||
|
||||
class SimpleDatabase : public Database {
|
||||
std::string path;
|
||||
Path path;
|
||||
|
||||
Directory *root;
|
||||
|
||||
|
@ -41,6 +41,9 @@ class SimpleDatabase : public Database {
|
|||
unsigned borrowed_song_count;
|
||||
#endif
|
||||
|
||||
SimpleDatabase()
|
||||
:path(Path::Null()) {}
|
||||
|
||||
public:
|
||||
gcc_pure
|
||||
Directory *GetRoot() {
|
||||
|
|
Loading…
Reference in New Issue