Raw File
slang-io.h
#ifndef SLANG_CORE_IO_H
#define SLANG_CORE_IO_H

#include "slang-string.h"
#include "slang-stream.h"
#include "slang-text-io.h"
#include "slang-secure-crt.h"
#include "slang-blob.h"

namespace Slang
{
    class File
    {
    public:
        static bool exists(const String& fileName);

        static SlangResult readAllText(const String& fileName, String& outString);

        static SlangResult readAllBytes(const String& fileName, List<unsigned char>& out);
        static SlangResult readAllBytes(const String& fileName, ScopedAllocation& out);

        static SlangResult writeAllText(const String& fileName, const String& text);

        static SlangResult writeAllTextIfChanged(const String& fileName, UnownedStringSlice text);

            /// Write as text in native form for the target (so typically may change line endings )
        static SlangResult writeNativeText(const String& filename, const void* data, size_t size);

        static SlangResult writeAllBytes(const String& fileName, const void* data, size_t size);

        static SlangResult remove(const String& fileName);

        static SlangResult makeExecutable(const String& fileName);

            /// Creates a temporary file typically in some way based on the prefix
            /// The file will be *created* with the outFileName, on success.
            /// It's creation in necessary to lock that particular name.
        static SlangResult generateTemporary(const UnownedStringSlice& prefix, String& outFileName);
    };

    class Path
    {
    public:
        typedef uint32_t SimplifyIntegral;

        struct SimplifyFlag
        {
            enum Enum : SimplifyIntegral
            {
                    /// Can only simplify to an absolute path. Will return an error if not possible.
                    /// Useful to constrain a path, such as when wanting something like 'chroot'.
                AbsoluteOnly = 0x1,
                    /// If the simplified path is a root path, remove the root.
                    /// Will mean that for example
                    /// "/" -> "."
                    /// "/a/.." -> "."
                    /// "/a" -> "a"
                    /// Its worth noting that a path prefixed "/" will never be returned and if *just* the root it specified
                    /// it will return as ".".
                NoRoot       = 0x2,
            };
        };

        // A more convenient typesafe way to specify the SimplifyFlag combinations
        enum SimplifyStyle : SimplifyIntegral
        {
            Normal                      = 0,
            AbsoluteOnly                = SimplifyFlag::AbsoluteOnly,
            NoRoot                      = SimplifyFlag::NoRoot,
            AbsoluteOnlyAndNoRoot       = SimplifyFlag::AbsoluteOnly | SimplifyFlag::NoRoot,
        };

        enum class Type
        {
            Unknown,
            File,
            Directory,
        };

        typedef uint32_t TypeFlags;
        struct TypeFlag
        {
            enum Enum : TypeFlags
            {
                Unknown = TypeFlags(1) << int(Type::Unknown),
                File = TypeFlags(1) << int(Type::File),
                Directory = TypeFlags(1) << int(Type::Directory),
            };
        };

        class Visitor
        {
        public:
            virtual void accept(Type type, const UnownedStringSlice& filename) = 0;
        };

        static const char kPathDelimiter = '/';

#if SLANG_WINDOWS_FAMILY
        static const char kOSCanonicalPathDelimiter = '\\';
        static const char kOSAlternativePathDelimiter = '/';

#else
        static const char kOSCanonicalPathDelimiter = '/';
        static const char kOSAlternativePathDelimiter = '/';
#endif

            /// Finds all all the items in the specified directory, that matches the pattern.
            ///
            /// @param directoryPath The directory to do the search in. If the directory is not found, SLANG_E_NOT_FOUND is returned
            /// @param pattern. The pattern to match against. The pattern matching is targtet specific (ie window matching is different to linux/unix). Passing nullptr means no matching.
            /// @return is SLANG_E_NOT_FOUND if the directoryPath is not found
        static SlangResult find(const String& directoryPath, const char* pattern, Visitor* visitor);

            /// Returns -1 if no separator is found
        static Index findLastSeparatorIndex(String const& path) { return findLastSeparatorIndex(path.getUnownedSlice()); }
        static Index findLastSeparatorIndex(UnownedStringSlice const& path);
            /// Finds the index of the last dot in a path, else returns -1
        static Index findExtIndex(String const& path) { return findExtIndex(path.getUnownedSlice()); }
        static Index findExtIndex(UnownedStringSlice const& path);

            /// True if isn't just a name (ie has any path separator)
            /// Note this is no the same as having a 'parent' as '/thing' 'has a path', but it doesn't have a parent.
        static bool hasPath(const UnownedStringSlice& path) { return findLastSeparatorIndex(path) >= 0; }
        static bool hasPath(const String& path) { return findLastSeparatorIndex(path) >= 0; }

        static String replaceExt(const String& path, const char* newExt);
        static String getFileName(const String& path);
        static String getPathWithoutExt(const String& path);

        static String getPathExt(const String& path) { return getPathExt(path.getUnownedSlice()); }
        static UnownedStringSlice getPathExt(const UnownedStringSlice& path);

        static String getParentDirectory(const String& path);

        static String getFileNameWithoutExt(const String& path);

        static String combine(const String& path1, const String& path2);
        static String combine(const String& path1, const String& path2, const String& path3);

            /// Combine path sections and store the result in outBuilder
        static void combineIntoBuilder(const UnownedStringSlice& path1, const UnownedStringSlice& path2, StringBuilder& outBuilder);

            /// Append a path, taking into account path separators onto the end of ioBuilder
        static void append(StringBuilder& ioBuilder, const UnownedStringSlice& path);

        static bool createDirectory(const String& path);

            /// Accept either style of delimiter
        SLANG_FORCE_INLINE static bool isDelimiter(char c) { return c == '/' || c == '\\'; }

            /// True if the element appears to be a drive specification (where element is the prefix to a path that isn't a directory)
            /// @param pathPrefix The path prefix to test if it's a drive specification
        static bool isDriveSpecification(const UnownedStringSlice& pathPrefix);

            /// Splits the path into it's individual bits
            /// Absolute paths of the form "/" will become [""]
            /// Absolute paths of the form "a:/" will become ["a:", ""]
            /// A drive specification of the form "a:" will become ["a:"]
            /// Relative paths that are in effect "." will become []
        static void split(const UnownedStringSlice& path, List<UnownedStringSlice>& splitOut);

            /// Strips .. and . as much as it can
        static String simplify(const UnownedStringSlice& path);
        static String simplify(const String& path) { return simplify(path.getUnownedSlice()); }

            /// Given a path simplifies it such the the resultant path is absolute (ie contains no . or ..)
            /// Same behavior as simplify around the root
        static SlangResult simplify(const UnownedStringSlice& path, SimplifyStyle style, StringBuilder& outPath);
        static SlangResult simplify(const String& path, SimplifyStyle style, StringBuilder& outPath) { return simplify(path.getUnownedSlice(), style, outPath); }
        static SlangResult simplify(const char* path, SimplifyStyle style, StringBuilder& outPath) { return simplify(UnownedStringSlice(path), style, outPath); }

            /// Simplifies the path split up
        static void simplify(List<UnownedStringSlice>& ioSplit);

            /// Join the parts of the path to produce an output path
        static void join(const UnownedStringSlice* slices, Index count, StringBuilder& out);

            /// Returns true if the path is absolute
        static bool isAbsolute(const UnownedStringSlice& path);
        static bool isAbsolute(const String& path) { return isAbsolute(path.getUnownedSlice()); }

            /// Returns true if path contains contains an element of . or ..
        static bool hasRelativeElement(const UnownedStringSlice& path);
        static bool hasRelativeElement(const String& path) { return hasRelativeElement(path.getUnownedSlice()); }

            /// Determines the type of file at the path
            /// @param path The path to test
            /// @param outPathType Holds the object type at the path on success
            /// @return SLANG_OK on success
        static SlangResult getPathType(const String& path, SlangPathType* outPathType);

            /// Determines the canonical equivalent path to path.
            /// The path returned should reference the identical object - and two different references to the same path should return the same canonical path
            /// @param path Path to get the canonical path for
            /// @param outCanonicalPath The canonical path for 'path' is call is successful
            /// @return SLANG_OK on success
        static SlangResult getCanonical(const String& path, String& outCanonicalPath);

            /// Returns the executable path
            /// @return The path in platform native format. Returns empty string if failed.
        static String getExecutablePath();

            /// Returns the first element of the path or an empty slice if there is none
            /// This broadly equivalent to returning the first element of split
            /// @param path Path to extract first element from
            /// @return The first element of the path, or empty
        static UnownedStringSlice getFirstElement(const UnownedStringSlice& path);

            /// Remove a file or directory at specified path. The directory must be empty for it to be removed
            /// @param path
            /// @return SLANG_OK if file or directory is removed
        static SlangResult remove(const String& path);
    };

    struct URI
    {
        String uri;
        bool operator==(const URI& other) const { return uri == other.uri; }
        bool operator!=(const URI& other) const { return uri != other.uri; }

        HashCode getHashCode() const { return uri.getHashCode(); }

        bool isLocalFile() { return uri.startsWith("file://"); };
        String getPath() const;
        StringSlice getProtocol() const;

        static URI fromLocalFilePath(UnownedStringSlice path);
        static URI fromString(UnownedStringSlice uriString);
        static bool isSafeURIChar(char ch);
    };

        /// Helper class abstracting lock files.
        /// Uses LockFileEx() on windows systems and flock() on POSIX systems.
    class LockFile
    {
    public:
        enum class LockType
        {
            Exclusive,
            Shared,
        };

            /// Open the lock file. This will create the file if it doesn't exist yet.
            /// @param fileName File name to open.
            /// @return SLANG_OK on success.
        SlangResult open(const String& fileName);

            /// Closes the lock file.
        void close();

            /// Returns true if the lock file is open.
        bool isOpen() const { return m_isOpen; }

            /// Acquire the lock in non-blocking mode.
            /// @param lockType Lock type (Exclusive or Shared).
            /// @return SLANG_OK on success. SLANG_E_TIME_OUT if the lock is already held.
        SlangResult tryLock(LockType lockType = LockType::Exclusive);

            /// Acquire the lock in blocking mode.
            /// @param lockType Lock type (Exclusive or Shared).
            /// @return SLANG_OK on success.
        SlangResult lock(LockType lockType = LockType::Exclusive);

            /// Release the lock.
            /// @return SLANG_OK on success.
        SlangResult unlock();

        LockFile();
        ~LockFile();

    private:
        LockFile(const LockFile&) = delete;
        LockFile(LockFile&&) = delete;
        LockFile& operator=(const LockFile&) = delete;
        LockFile& operator=(LockFile&&) = delete;

#if SLANG_WINDOWS_FAMILY
        void* m_fileHandle;
#else
        int m_fileHandle;
#endif
        bool m_isOpen;
    };

    class LockFileGuard
    {
    public:
        LockFileGuard(LockFile& lockFile, LockFile::LockType lockType = LockFile::LockType::Exclusive)
            : m_lockFile(lockFile)
        {
            m_lockFile.lock(lockType);
        }

        ~LockFileGuard()
        {
            m_lockFile.unlock();
        }

    private:
        LockFileGuard(const LockFileGuard&) = delete;
        LockFileGuard(LockFileGuard&&) = delete;
        LockFileGuard& operator=(const LockFileGuard&) = delete;
        LockFileGuard& operator=(LockFileGuard&&) = delete;

        LockFile& m_lockFile;
    };

}

#endif
back to top