/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    QC_Dir.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/QC_Dir.h"
#include "qore/intern/QC_File.h"
#include "qore/intern/QoreHashNodeIntern.h"
#include "qore/intern/qore_qd_private.h"

#include <cerrno>
#include <sys/types.h>

QoreListNode* qore_qd_private::list(ExceptionSink *xsink, int stat_filter, const QoreString *regex, int regex_options, bool full) const {
    AutoLocker al(m);

    if (dirname.empty()) {
        xsink->raiseException("DIR-READ-ERROR", "cannot list directory; no directory is set");
        return nullptr;
    }

    SimpleRefHolder<QoreRegex> re(0);

    if (regex) {
        re = new QoreRegex(*regex, regex_options, xsink);
        if (*xsink)
            return nullptr;
    }

    // avoid memory leaks...
    ReferenceHolder<QoreListNode> lst(new QoreListNode(autoTypeInfo), xsink);

    DIR *dptr = opendir(dirname.c_str());
    if (!dptr) {
        xsink->raiseErrnoException("DIR-READ-FAILURE", errno, "error opening directory '%s' for reading", dirname.c_str());
        return nullptr;
    }
    ON_BLOCK_EXIT(closedir, dptr);

    struct dirent *de;
    while ((de = readdir(dptr))) {
        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
            continue;

        // if there is a regular expression, see if the name matches
        if (regex) {
            QoreString targ(de->d_name, enc);
            bool b = re->exec(&targ, xsink);
            if (*xsink)
                return nullptr;
            if (!b)
                continue;
        }

        // if we are filtering out directories, then we have to stat the file
        if (full || stat_filter != -1) {
            QoreString fname(dirname);
            fname.concat(QORE_DIR_SEP);
            fname.concat(de->d_name);
            struct stat buf;
            if (!full) {
                if (::stat(fname.getBuffer(), &buf)) {
                    // ignore the file if we cannot stat it, instead of returning
                    continue;
                }

                if (!(buf.st_mode & stat_filter))
                  continue;

                lst->push(new QoreStringNode(de->d_name, enc), xsink);
                continue;
            }
#ifdef HAVE_LSTAT
            if (::lstat(fname.getBuffer(), &buf)) {
                // ignore the file if we cannot stat it, instead of returning
                continue;
            }

            SimpleRefHolder<QoreStringNode> lpath;

            if (S_ISLNK(buf.st_mode)) {
                {
                    char lbuf[QORE_PATH_MAX + 1];
                    qore_offset_t len = readlink(fname.getBuffer(), lbuf, QORE_PATH_MAX);
                    if (len < 0) {
                        xsink->raiseErrnoException("DIR-READ-FAILURE", errno, "readlink('%s') failed", fname.getBuffer());
                        return 0;
                    }
                    assert(len <= QORE_PATH_MAX);
                    lbuf[len] = '\0';
                    lpath = new QoreStringNode(lbuf);
                }

                if (::stat(fname.getBuffer(), &buf)) {
                    // ignore the file if we cannot stat it, instead of returning
                    continue;
                }
            }
#else
            if (::stat(fname.getBuffer(), &buf)) {
                // ignore the file if we cannot stat it, instead of returning
                continue;
            }
#endif
            if (stat_filter != -1 && !(buf.st_mode & stat_filter))
                continue;
            QoreHashNode* h = stat_to_hash(buf, hashdeclDirStatInfo);
            qore_hash_private::get(*h)->setKeyValueIntern("name", new QoreStringNode(de->d_name, enc));
#ifdef HAVE_LSTAT
            if (*lpath)
                qore_hash_private::get(*h)->setKeyValueIntern("link", lpath.release());
#endif
            lst->push(h, xsink);
            continue;
        }

        // not full, no filter
        lst->push(new QoreStringNode(de->d_name, enc), xsink);
        continue;
    }

    return lst.release();
}

QoreHashNode* qore_qd_private::hstat(ExceptionSink *xsink) const {
    AutoLocker al(m);

    if (dirname.empty()) {
        xsink->raiseException("DIR-HSTAT-ERROR", "cannot stat; no directory is set");
        return nullptr;
    }

    struct stat sbuf;
    if (::stat(dirname.c_str(), &sbuf)) {
        xsink->raiseErrnoException("DIR-HSTAT-FAILURE", errno, "stat() call failed on '%s'", dirname.c_str());
        return nullptr;
    }

    return stat_to_hash(sbuf);
}

//! file status information hash as returned by @ref Qore::Dir::list() "Dir::list()", @ref Qore::Dir::listFiles() "Dir::listFiles()", and @ref Qore::Dir::listDirs() "Dir::listDirs()" if the appropriate arguments are used
/** @since %Qore 0.8.13
*/
hashdecl DirStatInfo {
    //! device inode number the file is on
    int dev;
    //! inode of the file
    int inode;
    //! inode protection mode
    int mode;
    //! number of hard links to this file
    int nlink;
    //! user ID of the owner
    int uid;
    //! group ID of the owner
    int gid;
    //! device type number
    int rdev;
    //! file size in bytes
    int size;
    //! last access time of the file
    date atime;
    //! last modified time of the file
    date mtime;
    //! last change time of the file's inode
    date ctime;
    //! block size; may be zero if the platform's internal %stat() (2) function does not provide this info
    int blksize;
    //! blocks allocated for the file; may be zero if the platform's internal %stat() (2) function does not provide this info
    int blocks;
    //! a string giving the file type; one of:\n - \c "BLOCK-DEVICE" \n - \c "DIRECTORY" \n - \c "CHARACTER-DEVICE" \n - \c "FIFO" \n - \c "SYMBOLIC-LINK" \n - \c "SOCKET" \n - \c "REGULAR" \n - \c "UNKNOWN"
    string type;
    //! a string giving UNIX-style permissions for the file (ex: \c "-rwxr-xr-x")
    string perm;
    //! a string giving the file or directory name
    string name;
    //! symbolic link target (if present)
    *string link;
}

//! This class implements directory handling, file listing, creating/removing subdirectories, etc
/** The %Dir class allows %Qore programs to list and manipulate directories.

    Directory objects can be created/opened with a specific character encoding. This means that any entry read from the directory will be tagged with the directory's character encoding. If no character encoding is given the @ref default_encoding "default character encoding" is assumed.

    @note This class is not available with the @ref PO_NO_FILESYSTEM parse option
 */
qclass Dir [arg=Dir* d; dom=FILESYSTEM];

//! Creates the Directory object
/** The object will point to the currect directory of the process

    @param encoding the name of the default character encoding for filenames retrieved; if this argument is not given, filename strings retrieved will be tagged with the @ref default_encoding "default character encoding" of the process

    @par Example:
    @code{.py}
Dir dir("utf-8");
    @endcode
 */
Dir::constructor(*string encoding) {
    const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
    SimpleRefHolder<Dir> d(new Dir(xsink, qe));
    if (*xsink)
        return;
    self->setPrivate(CID_DIR, d.release());
}

//! Creates a new directory object with the same character encoding specification and the same path as the original
/**
    @par Example:
    @code{.py}
Dir nd = dir.copy();
    @endcode
 */
Dir::copy() {
    SimpleRefHolder<Dir> nd(new Dir(xsink, *d));
    if (*xsink)
        return;

    self->setPrivate(CID_DIR, nd.release());
}

//! Changes the current directory of the Dir object to the path given
/** If this method returns @ref False then the directory can be created by calling Dir::create()

    @param path The path can be either an absolute path (leading with '/') or a directory relative to the actual path

    @return @ref True if the new path is openable as directory (see Dir::exists())

    @par Example:
    @code{.py}
if (!d.chdir("../doc")) {
    d.create(0755);
}
    @endcode

    @note calls to change to the current directory \c "." have no effect

    @throw DIR-CHDIR-ERROR cannot change to relative directory because no current directory is set
 */
bool Dir::chdir(string path) {
    return !d->chdir(path->getBuffer(), xsink);
}

//! Returns the path of the Dir object or @ref nothing if no path is set
/** This path does not necessarily need to exist; the path is adjusted to remove \c "." and \c ".." from the path if present

    @return the path of the Dir object or @ref nothing if no path is set

    @par Example:
    @code{.py}
*string mypath = d.path();
    @endcode
 */
*string Dir::path() {
    return d->dirname();
}

//! Returns @ref True if the path in the Dir object points to a directory that already exists and is openable by the current user, @ref False otherwise
/** @return @ref True if the path in the Dir object points to a directory that already exists and is openable by the current user, @ref False otherwise

    @par Example:
    @code{.py}
if (!d.exists())
   printf("%y: does not exist or cannot be opened\n", d.path());
    @endcode
 */
bool Dir::exists() {
    return !d->checkPath();
}

//! Creates the directory tree the Dir object points to, if it does not exist
/** @param mode The mode of the directory
    @return the number of directories actually created

    @par Example:
    @code{.py}
if (!d.exists()) {
    try {
        printf("%y: does not exist; creating...\n", d.path());
        cnt = d.create(0755);
    } catch (hash<ExceptionInfo> ex) {
        stderr.printf("%s: %s", ex.err, ex.desc);
    }
}
    @endcode

    @throw DIR-CREATE-ERROR cannot create directory; no directory is set
    @throw DIR-CREATE-FAILURE error creating directory (\c arg will be assigned to the errno value)
*/
int Dir::create(softint mode = 0777) {
    return d->create((int)mode, xsink);
}

//! Changes the mode of the directory
/** @param mode The mode of the directory

    @par Example:
    @code{.py}
try {
    dir.chmod(0750);
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-CHMOD-ERROR cannot change directory's mode; no directory is set
    @throw DIR-CHMOD-FAILURE error changing directory's mode (\c arg will be assigned to the errno value)
 */
nothing Dir::chmod(softint mode) {
    d->chmod((int)mode, xsink);
}

//! Change the ownership of the directory from the userid
/** @param uid the userid of the user to change the directory to

    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @par Example:
    @code{.py}
try {
    dir.chown(uid);
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-CHOWN-ERROR cannot change directory owner; no directory is set
    @throw DIR-CHOWN-FAILURE error changing directory owner (\c arg will be assigned to the errno value)
 */
nothing Dir::chown(int uid) {
#ifdef HAVE_CHOWN
    d->chown((uid_t)uid, (gid_t)-1, xsink);
    return QoreValue();
#else
    return missing_method_error("Dir::chown", "UNIX_FILEMGT", xsink);
#endif
}

//! Change the ownership of the directory from the username
/** @param username the username of the user to change the directory to

    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @par Example:
    @code{.py}
try {
    dir.chown(username);
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-CHOWN-PARAMETER-ERROR no userid found for user
    @throw DIR-CHOWN-ERROR cannot change directory owner; no directory is set
    @throw DIR-CHOWN-FAILURE error changing directory owner (\c arg will be assigned to the errno value)
 */
nothing Dir::chown(string username) {
#ifdef HAVE_CHOWN
   uid_t uid;

   // Try getting UID for username
   int rc = q_uname2uid(username->getBuffer(), uid);
   if (rc)
      xsink->raiseException("DIR-CHOWN-PARAMETER-ERROR", "no userid found for user '%s'", username->getBuffer());
   else
      d->chown(uid, (gid_t)-1, xsink);
   return QoreValue();
#else
   return missing_method_error("Dir::chown", "UNIX_FILEMGT", xsink);
#endif
}

//! Change the group membership of the directory from the group id
/** @param gid the groupid of the user to change the directory to

    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @par Example:
    @code{.py}
try {
    dir.chgrp(gid);
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-CHOWN-ERROR cannot change directory owner; no directory is set
    @throw DIR-CHOWN-FAILURE error changing directory owner (\c arg will be assigned to the errno value)
 */
nothing Dir::chgrp(int gid) {
#ifdef HAVE_CHOWN
   d->chown((uid_t)-1, (gid_t)gid, xsink);
   return QoreValue();
#else
   return missing_method_error("Dir::chgrp", "UNIX_FILEMGT", xsink);
#endif
}

//! Change the group membership of the directory
/** @param groupname the group name of the group to change the directory to

    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_FILEMGT

    @par Example:
    @code{.py}
try {
    dir.chgrp(groupname);
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-CHGRP-PARAMETER-ERROR no userid found for user
    @throw DIR-CHOWN-ERROR cannot change directory owner; no directory is set
    @throw DIR-CHOWN-FAILURE error changing directory owner (\c arg will be assigned to the errno value)
 */
nothing Dir::chgrp(string groupname) {
#ifdef HAVE_CHOWN
   gid_t gid;

   // Try getting GID for name
   int rc = q_gname2gid(groupname->getBuffer(), gid);
   if (rc)
      xsink->raiseException("DIR-CHGRP-PARAMETER-ERROR", "no groupid found for group '%s'", groupname->getBuffer());
   else
      d->chown((uid_t)-1, gid, xsink);
   return QoreValue();
#else
   return missing_method_error("Dir::chgrp", "UNIX_FILEMGT", xsink);
#endif
}

//! Creates a direct subdirectory in the Dir object's current path
/** @param subdir The subdirectory name to create; only direct subdirectories are allowed; directory separator characters are not allowed
    @param mode The mode of the directory

    @par Example:
    @code{.py}
try {
    dir.mkdir("subdir", 0750);
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-MKDIR-PARAMETER-ERROR only direct subdirectories are allowed
    @throw DIR-MKDIR-FAILURE error creating directory (\c arg will be assigned to the errno value)
 */
nothing Dir::mkdir(string subdir, softint mode = 0777) {
   // check if there is a path delimiter
   if (strchr(subdir->getBuffer(), QORE_DIR_SEP))
      xsink->raiseException("DIR-MKDIR-PARAMETER-ERROR", "only single, direct subdirectories are allowed");
   else
      d->mkdir(xsink, subdir->getBuffer(), (int)mode);
}

//! Removes a direct subdirectory from the Dir object's current path
/** @param subdir The subdirectory name to remove; only direct subdirectories are allowed; directory separator characters are not allowed

    @par Example:
    @code{.py}
try {
    dir.rmdir("subdir");
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-RMDIR-PARAMETER-ERROR only direct subdirectories are allowed
    @throw DIR-RMDIR-FAILURE error removing directory (\c arg will be assigned to the errno value)

    @see @ref Qore::rmdir()
 */
nothing Dir::rmdir(string subdir) {
   // check if there is a path delimiter
   if (strchr(subdir->getBuffer(), QORE_DIR_SEP))
      xsink->raiseException("DIR-RMDIR-PARAMETER-ERROR", "only direct subdirectories are allowed");
   else
      d->rmdir(subdir->getBuffer(), xsink);
}

//! Get all entries in this directory, except \c "." and \c ".." directories; if any errors occur an exception is thrown
/** This method uses \c stat() system call internally to get detailed info about directory entries if the \c full parameter is @ref True "True". If a stat() call fails for an entry, the entry is skipped and information about it is not included in the returned list.

    @param full if @ref True "True" then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @returns a list of all entries in the directory (except \c "." and \c ".." directories); if \a full is @ref True "True", then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @par Example:
    @code{.py}
try {
    map printf("entry: %s\n", $1), d.list();
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-READ-ERROR no directory is set
    @throw DIR-READ-FAILURE error reading directory (\c arg will be assigned to the errno value)

    @since %Qore 0.8.8 added the \a full parameter
 */
list<auto> Dir::list(bool full = False) {
   return d->list(xsink, -1, 0, 0, full);
}

//! Gets all entries in the directory that match the given regular expression (except \c "." and \c ".." directories); if any errors occur an exception is thrown
/** This method uses \c stat() system call internally to get info about directory entries. If a stat() call fails for an entry, the entry is skipped and information about it is not included in the returned list.

    @param regex a regular expression string used to filter the arguments (note that this is not a glob, but rather a regular expression string)
    @param regex_options optional bitwise-or'ed @ref regex_constants "regex option constants"
    @param full if @ref True "True" then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @return a list of all entries in the directory that match the given regular expression (except \c "." and \c ".." directories); if \a full is @ref True "True", then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @par Example:
    @code{.py}
try {
    foreach string e in (d.list("\\.txt$")) {
        printf("entry: %s\n");
    }
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-READ-ERROR no directory is set
    @throw DIR-READ-FAILURE error reading directory (\c arg will be assigned to the errno value)
    @throw REGEX-COMPILATION-ERROR error in regular expression
    @throw REGEX-OPTION-ERROR regex option argument contains invalid option bits

    @since %Qore 0.8.8 added the \a full parameter
 */
list<auto> Dir::list(string regex, softint regex_options = 0, softbool full = False) {
   return d->list(xsink, -1, regex, (int)regex_options, full);
}

//! Retrieves all files in this directory; if any errors occur an exception is thrown
/** This method uses \c stat() system call internally to get info about directory entries. If a stat() call fails for a file, the file is skipped and information about this file is not included in the returned list.

    @param full if @ref True "True" then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @return a list of all files in the current directory of the object; if \a full is @ref True "True", then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @par Example:
    @code{.py}
try {
    foreach string e in (d.listFiles()) {
        printf("entry: %s\n");
    }
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-READ-ERROR no directory is set
    @throw DIR-READ-FAILURE error reading directory (\c arg will be assigned to the errno value)

    @since %Qore 0.8.8 added the \a full parameter
 */
list<auto> Dir::listFiles(bool full = False) {
   return d->list(xsink, S_IFMT^S_IFDIR, 0, 0, full);
}

//! Retrieves all files in the directory that match the given regular expression; if any errors occur an exception is thrown
/** This method uses \c stat() system call internally to get info about directory entries. If a stat() call fails for a file, the file is skipped and information about this file is not included in the returned list.

    @param regex a regular expression string used to filter the arguments (note that this is not a glob, but rather a regular expression string)
    @param regex_options optional bitwise-or'ed @ref regex_constants "regex option constants"
    @param full if @ref True "True" then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @return a list of all files in the directory that match the given regular expression; if \a full is @ref True "True", then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @par Example:
    @code{.py}
try {
    foreach string e in (d.listFiles("\\.txt$")) {
        printf("entry: %s\n");
    }
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-READ-ERROR no directory is set
    @throw DIR-READ-FAILURE error reading directory (\c arg will be assigned to the errno value)
    @throw REGEX-COMPILATION-ERROR error in regular expression
    @throw REGEX-OPTION-ERROR regex option argument contains invalid option bits

    @since %Qore 0.8.8 added the \a full parameter
 */
list<auto> Dir::listFiles(string regex, softint regex_options = 0, softbool full = False) {
   return d->list(xsink, S_IFMT^S_IFDIR, regex, (int)regex_options, full);
}

//! Retrieves all subdirectory entries in this directory, except \c "." and \c ".." directories; if any errors occur an exception is thrown
/** This method uses \c stat() system call internally to get info about directory entries. If a stat() call fails for an entry, the entry is skipped and information about it is not included in the returned list.

    @param full if @ref True "True" then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @return a list of all subdirectory entries in this directory, except \c "." and \c ".." directories; if \a full is @ref True "True", then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @par Example:
    @code{.py}
try {
    foreach string e in (d.listDirs()) {
        printf("entry: %s\n");
    }
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-READ-ERROR no directory is set
    @throw DIR-READ-FAILURE error reading directory (\c arg will be assigned to the errno value)

    @since %Qore 0.8.8 added the \a full parameter
 */
list<auto> Dir::listDirs(bool full = False) {
   return d->list(xsink, S_IFDIR, 0, 0, full);
}

//! Gets all subdirectory entries in the directory that match the given regular expression (except \c "." and \c ".." directories); if any errors occur an exception is thrown
/** This method uses \c stat() system call internally to get info about directory entries. If a stat() call fails for an entry, the entry is skipped and information about it is not included in the returned list.

    @param regex a regular expression string used to filter the arguments (note that this is not a glob, but rather a regular expression string)
    @param regex_options optional bitwise-or'ed @ref regex_constants "regex option constants"
    @param full if @ref True "True" then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @return a list of all subdirectory entries in the directory that match the given regular expression (except \c "." and \c ".." directories); if \a full is @ref True "True", then the return value is a list of @ref Qore::DirStatInfo "DirStatInfo" hashes, otherwise a simple list of string names is returned

    @par Example:
    @code{.py}
try {
    foreach string e in (d.listDirs("^pgsql-")) {
        printf("entry: %s\n");
    }
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-READ-ERROR no directory is set
    @throw DIR-READ-FAILURE error reading directory (\c arg will be assigned to the errno value)
    @throw REGEX-COMPILATION-ERROR error in regular expression
    @throw REGEX-OPTION-ERROR regex option argument contains invalid option bits

    @since %Qore 0.8.8 added the \a full parameter
 */
list<auto> Dir::listDirs(string regex, softint regex_options = 0, softbool full = False) {
   return d->list(xsink, S_IFDIR, regex, (int)regex_options, full);
}

//! Create and open a File object in the current directory of the Dir object
/** This method uses the File::open2() method to open the file.

    @param filename The filename for the file which must be in the current directory (no path separator characters are allowed)
    @param flags Flags that determine the way the file is accessed, see @ref file_open_constants for more information
    @param mode Permission bits for when the file is to be created
    @param encoding The name of the default character encoding for this file; if this argument is not given, the file will be tagged with the @ref default_encoding "default character encoding" for the process

    @par Example:
    @code{.py}
# open a file for writing in the directory and set the mode to 0644 and the encoding to UTF-8
File f = d.openFile("myfile.txt", O_CREAT|O_WRONLY, 0644, "utf-8");
    @endcode

    @throw DIR-OPENFILE-PARAMETER-ERROR only direct subdirectory names without path separators are allowed

    @note see File::open2() for additional exceptions that can be thrown opening the file
 */
File Dir::openFile(string filename, int flags = O_RDONLY, int mode = 0666, *string encoding) {
   if (strchr(filename->getBuffer(), QORE_DIR_SEP))
      return xsink->raiseException("DIR-OPENFILE-PARAMETER-ERROR", "only filenames without path (i.e. without '%c' characters) are allowed", QORE_DIR_SEP);

   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;

   // open the file with exception
   ReferenceHolder<File> f(new File(qe), xsink);
   std::string path = d->getPath(filename->getBuffer());

   if (f->open2(xsink, path.c_str(), flags, mode, qe)) {
      assert(*xsink);
      return QoreValue();
   }

   // create the QoreObject and set the File object as private data of the class tagged with the CID_FILE class ID
   return new QoreObject(QC_FILE, getProgram(), f.release());
}

//! Get a Dir object as an subdir entry of the current directory
/** @param subdir The name of the subdirectory for the new Dir object (which must be in the current directory of the current Dir object; no path separator characters are allowed)
    @param encoding The name of the default character encoding for the new Dir object; if this argument is not given, the new Dir object will be tagged with the character encoding of the current Dir object

    @return The Dir object created for the directory

    @par Example:
    @code{.py}
Dir sd = d.openDir("mysubdir");
    @endcode

    @throw DIR-OPENDIR-PARAMETER-ERROR only direct subdirectory names without path separators are allowed
 */
Dir Dir::openDir(string subdir, *string encoding) {
   // check if there is a path delimiter in
   if (strchr(subdir->getBuffer(), QORE_DIR_SEP))
      return xsink->raiseException("DIR-OPENDIR-PARAMETER-ERROR", "only direct directory names without path (i.e. without '%c' characters) are allowed", QORE_DIR_SEP);

   const QoreEncoding *qe = encoding ? QEM.findCreate(encoding) : d->getEncoding();
   ReferenceHolder<Dir> dc(new Dir(xsink, qe, d->getPath(subdir->getBuffer()).c_str()), xsink);
   // create the qoreObject and set the Dir object as private data of the class tagged with the CID_DIR class ID
   return new QoreObject(QC_DIR, getProgram(), dc.release());
}

//! Remove the file with the given name in the Dir object's directory
/** If any errors occur unlinking the file, then an exception occurs

    @param file Remove the file with the given name in the Dir object's directory

    @return @ref True if the file was present and could be removed, @ref False if the file did not exist

    @par Example:
    @code{.py}
try {
    bool b = dir.removeFile(filename);
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-REMOVEFILE-PARAMETER-ERROR only filenames without path (i.e. without path separator characters) are allowed
    @throw DIR-REMOVEFILE-FAILURE the unlink() function returned an error (\c arg will be assigned to the errno value)
 */
bool Dir::removeFile(string file) {
   // check if there is a path delimiter in
   if (strchr(file->getBuffer(), QORE_DIR_SEP))
      return xsink->raiseException("DIR-REMOVEFILE-PARAMETER-ERROR", "only filenames without path (i.e. without '%c' characters) are allowed", QORE_DIR_SEP);

   std::string path = d->getPath(file->getBuffer());
   errno = 0; // clear errno flag
   if (unlink(path.c_str()) && errno != ENOENT)
      return xsink->raiseErrnoException("DIR-REMOVEFILE-FAILURE", errno, "error removing file '%s'", file->getBuffer());

   // if an errno was set it must be ENOENT at this point.
   // so we return that no file is removed
   return errno ? false : true;
}

//! Returns a list of file status information for the current directory
/** If any errors occur an exception will be thrown

    @return a @ref stat_list giving information about the current directory

    @par Example:
    @code{.py}
try {
    list<auto> l = dir.stat();
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-STAT-ERROR no directory is set
    @throw DIR-STAT-FAILURE error stat'ing directory (\c arg will be assigned to the errno value)
 */
list<auto> Dir::stat() {
   return d->stat(xsink);
}

//! Returns a @ref Qore::StatInfo "StatInfo" hash of file status information for the current directory
/** If any errors occur an exception will be thrown

    @return a @ref Qore::StatInfo "StatInfo" hash giving information about the current directory

    @par Example:
    @code{.py}
try {
    hash<StatInfo> h = dir.hstat();
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-HSTAT-ERROR no directory is set
    @throw DIR-HSTAT-FAILURE error stat'ing directory (\c arg will be assigned to the errno value)
 */
hash<StatInfo> Dir::hstat() {
    return d->hstat(xsink);
}

//! Returns a @ref FilesystemInfo hash for the current directory
/** @return a @ref FilesystemInfo hash for the current directory

    @par Platform Availability:
    @ref Qore::Option::HAVE_STATVFS

    @par Example:
    @code{.py}
try {
    hash<FilesystemInfo> h = dir.statvfs();
} catch (hash<ExceptionInfo> ex) {
    stderr.printf("%s: %s", ex.err, ex.desc);
}
    @endcode

    @throw DIR-STATVFS-ERROR no directory is set
    @throw DIR-STATVFS-FAILURE error in statvfs (\c arg will be assigned to the errno value)
 */
hash<FilesystemInfo> Dir::statvfs() {
#ifdef Q_HAVE_STATVFS
    return d->statvfs(xsink);
#else
    return missing_method_error("Dir::statvfs", "STATVFS", xsink);
#endif
}
