summaryrefslogtreecommitdiffstats
path: root/source/core/slang-io.h
blob: 73dfd31d79c8cde7870f340395d12661fa8aa55c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#ifndef SLANG_CORE_IO_H
#define SLANG_CORE_IO_H

#include "slang-blob.h"
#include "slang-secure-crt.h"
#include "slang-stream.h"
#include "slang-string.h"
#include "slang-text-io.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);
    static bool createDirectoryRecursive(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 current working directory
    /// @return The path in platform native format. Returns empty string if failed.
    static String getCurrentPath();

    /// 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);

    /// Remove a file or directory at specified path. The directory can be non-empty.
    /// @param path
    /// @return SLANG_OK if file or directory is removed
    static SlangResult removeNonEmpty(const String& path);

    static bool equals(String path1, String path2);

    /// Turn `path` into a relative path from base.
    static String getRelativePath(String base, 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;
};

} // namespace Slang

#endif