1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | // |
17 | // Based on code from ScummVM - Scumm Interpreter |
18 | // Copyright (C) 2002-2004 The ScummVM project |
19 | //============================================================================ |
20 | |
21 | #ifndef FS_NODE_HXX |
22 | #define FS_NODE_HXX |
23 | |
24 | #include "bspf.hxx" |
25 | |
26 | /* |
27 | * The API described in this header is meant to allow for file system browsing in a |
28 | * portable fashions. To this ends, multiple or single roots have to be supported |
29 | * (compare Unix with a single root, Windows with multiple roots C:, D:, ...). |
30 | * |
31 | * To this end, we abstract away from paths; implementations can be based on |
32 | * paths (and it's left to them whether / or \ or : is the path separator :-); |
33 | * but it is also possible to use inodes or vrefs (MacOS 9) or anything else. |
34 | * |
35 | * You may ask now: "isn't this cheating? Why do we go through all this when we use |
36 | * a path in the end anyway?!?". |
37 | * Well, for once as long as we don't provide our own file open/read/write API, we |
38 | * still have to use fopen(). Since all our targets already support fopen(), it should |
39 | * be possible to get a fopen() compatible string for any file system node. |
40 | * |
41 | * Secondly, with this abstraction layer, we still avoid a lot of complications based on |
42 | * differences in FS roots, different path separators, or even systems with no real |
43 | * paths (MacOS 9 doesn't even have the notion of a "current directory"). |
44 | * And if we ever want to support devices with no FS in the classical sense (Palm...), |
45 | * we can build upon this. |
46 | */ |
47 | |
48 | #include <functional> |
49 | |
50 | #include "bspf.hxx" |
51 | |
52 | class FilesystemNode; |
53 | class AbstractFSNode; |
54 | using AbstractFSNodePtr = shared_ptr<AbstractFSNode>; |
55 | |
56 | /** |
57 | * List of multiple file system nodes. E.g. the contents of a given directory. |
58 | * This is subclass instead of just a typedef so that we can use forward |
59 | * declarations of it in other places. |
60 | */ |
61 | class FSList : public vector<FilesystemNode> { }; |
62 | |
63 | /** |
64 | * This class acts as a wrapper around the AbstractFSNode class defined |
65 | * in backends/fs. |
66 | */ |
67 | class FilesystemNode |
68 | { |
69 | public: |
70 | /** |
71 | * Flag to tell listDir() which kind of files to list. |
72 | */ |
73 | enum class ListMode { FilesOnly, DirectoriesOnly, All }; |
74 | |
75 | /** Function used to filter the file listing. Returns true if the filename |
76 | should be included, else false.*/ |
77 | using NameFilter = std::function<bool(const FilesystemNode& node)>; |
78 | |
79 | /** |
80 | * Create a new pathless FilesystemNode. Since there's no path associated |
81 | * with this node, path-related operations (i.e. exists(), isDirectory(), |
82 | * getPath()) will always return false or raise an assertion. |
83 | */ |
84 | FilesystemNode(); |
85 | |
86 | /** |
87 | * Create a new FilesystemNode referring to the specified path. This is |
88 | * the counterpart to the path() method. |
89 | * |
90 | * If path is empty or equals '~', then a node representing the |
91 | * "home directory" will be created. If that is not possible (since e.g. the |
92 | * operating system doesn't support the concept), some other directory is |
93 | * used (usually the root directory). |
94 | */ |
95 | explicit FilesystemNode(const string& path); |
96 | |
97 | /** |
98 | * Assignment operators. |
99 | */ |
100 | FilesystemNode(const FilesystemNode&) = default; |
101 | FilesystemNode& operator=(const FilesystemNode&) = default; |
102 | |
103 | /** |
104 | * Compare the name of this node to the name of another, testing for |
105 | * equality, |
106 | */ |
107 | inline bool operator==(const FilesystemNode& node) const |
108 | { |
109 | return BSPF::compareIgnoreCase(getName(), node.getName()) == 0; |
110 | } |
111 | |
112 | /** |
113 | * By default, the output operator simply outputs the fully-qualified |
114 | * pathname of the node. |
115 | */ |
116 | friend ostream& operator<<(ostream& os, const FilesystemNode& node) |
117 | { |
118 | return os << node.getPath(); |
119 | } |
120 | |
121 | /** |
122 | * Indicates whether the object referred by this path exists in the |
123 | * filesystem or not. |
124 | * |
125 | * @return bool true if the path exists, false otherwise. |
126 | */ |
127 | bool exists() const; |
128 | |
129 | /** |
130 | * Return a list of child nodes of this directory node. If called on a node |
131 | * that does not represent a directory, false is returned. |
132 | * |
133 | * @return true if successful, false otherwise (e.g. when the directory |
134 | * does not exist). |
135 | */ |
136 | bool getChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly, |
137 | const NameFilter& filter = [](const FilesystemNode&){ return true; }) const; |
138 | |
139 | /** |
140 | * Set/get a string representation of the name of the file. This is can be |
141 | * used e.g. by detection code that relies on matching the name of a given |
142 | * file. But it is *not* suitable for use with fopen / File::open, nor |
143 | * should it be archived. |
144 | * |
145 | * @return the file name |
146 | */ |
147 | const string& getName() const; |
148 | void setName(const string& name); |
149 | |
150 | /** |
151 | * Return a string representation of the file which can be passed to fopen(). |
152 | * This will usually be a 'path' (hence the name of the method), but can |
153 | * be anything that fulfills the above criterions. |
154 | * |
155 | * @return the 'path' represented by this filesystem node |
156 | */ |
157 | const string& getPath() const; |
158 | |
159 | /** |
160 | * Return a string representation of the file which contains the '~' |
161 | * symbol (if applicable), and is suitable for archiving (i.e. writing |
162 | * to the config file). |
163 | * |
164 | * @return the 'path' represented by this filesystem node |
165 | */ |
166 | string getShortPath() const; |
167 | |
168 | /** |
169 | * Determine whether this node has a parent. |
170 | */ |
171 | bool hasParent() const; |
172 | |
173 | /** |
174 | * Get the parent node of this node. If this node has no parent node, |
175 | * then it returns a duplicate of this node. |
176 | */ |
177 | FilesystemNode getParent() const; |
178 | |
179 | /** |
180 | * Indicates whether the path refers to a directory or not. |
181 | */ |
182 | bool isDirectory() const; |
183 | |
184 | /** |
185 | * Indicates whether the path refers to a real file or not. |
186 | * |
187 | * Currently, a symlink or pipe is not considered a file. |
188 | */ |
189 | bool isFile() const; |
190 | |
191 | /** |
192 | * Indicates whether the object referred by this path can be read from or not. |
193 | * |
194 | * If the path refers to a directory, readability implies being able to read |
195 | * and list the directory entries. |
196 | * |
197 | * If the path refers to a file, readability implies being able to read the |
198 | * contents of the file. |
199 | * |
200 | * @return bool true if the object can be read, false otherwise. |
201 | */ |
202 | bool isReadable() const; |
203 | |
204 | /** |
205 | * Indicates whether the object referred by this path can be written to or not. |
206 | * |
207 | * If the path refers to a directory, writability implies being able to modify |
208 | * the directory entry (i.e. rename the directory, remove it or write files |
209 | * inside of it). |
210 | * |
211 | * If the path refers to a file, writability implies being able to write data |
212 | * to the file. |
213 | * |
214 | * @return bool true if the object can be written to, false otherwise. |
215 | */ |
216 | bool isWritable() const; |
217 | |
218 | /** |
219 | * Create a directory from the current node path. |
220 | * |
221 | * @return bool true if the directory was created, false otherwise. |
222 | */ |
223 | bool makeDir(); |
224 | |
225 | /** |
226 | * Rename the current node path with the new given name. |
227 | * |
228 | * @return bool true if the node was renamed, false otherwise. |
229 | */ |
230 | bool rename(const string& newfile); |
231 | |
232 | /** |
233 | * Read data (binary format) into the given buffer. |
234 | * |
235 | * @param buffer The buffer to contain the data. |
236 | * |
237 | * @return The number of bytes read (0 in the case of failure) |
238 | * This method can throw exceptions, and should be used inside |
239 | * a try-catch block. |
240 | */ |
241 | uInt32 read(ByteBuffer& buffer) const; |
242 | |
243 | /** |
244 | * The following methods are almost exactly the same as the various |
245 | * getXXXX() methods above. Internally, they call the respective methods |
246 | * and replace the extension (if present) with the given one. If no |
247 | * extension is present, the given one is appended instead. |
248 | */ |
249 | string getNameWithExt(const string& ext) const; |
250 | string getPathWithExt(const string& ext) const; |
251 | |
252 | private: |
253 | AbstractFSNodePtr _realNode; |
254 | explicit FilesystemNode(AbstractFSNodePtr realNode); |
255 | }; |
256 | |
257 | |
258 | /** |
259 | * Abstract file system node. Private subclasses implement the actual |
260 | * functionality. |
261 | * |
262 | * Most of the methods correspond directly to methods in class FSNode, |
263 | * so if they are not documented here, look there for more information about |
264 | * the semantics. |
265 | */ |
266 | |
267 | using AbstractFSList = vector<AbstractFSNodePtr>; |
268 | |
269 | class AbstractFSNode |
270 | { |
271 | protected: |
272 | friend class FilesystemNode; |
273 | using ListMode = FilesystemNode::ListMode; |
274 | using NameFilter = FilesystemNode::NameFilter; |
275 | |
276 | public: |
277 | /** |
278 | * Assignment operators. |
279 | */ |
280 | AbstractFSNode() = default; |
281 | AbstractFSNode(const AbstractFSNode&) = default; |
282 | // AbstractFSNode(AbstractFSNode&&) = default; |
283 | AbstractFSNode& operator=(const AbstractFSNode&) = default; |
284 | // AbstractFSNode& operator=(AbstractFSNode&&) = default; |
285 | virtual ~AbstractFSNode() = default; |
286 | |
287 | /* |
288 | * Indicates whether the object referred by this path exists in the |
289 | * filesystem or not. |
290 | */ |
291 | virtual bool exists() const = 0; |
292 | |
293 | /** |
294 | * Return a list of child nodes of this directory node. If called on a node |
295 | * that does not represent a directory, false is returned. |
296 | * |
297 | * @param list List to put the contents of the directory in. |
298 | * @param mode Mode to use while listing the directory. |
299 | * |
300 | * @return true if successful, false otherwise (e.g. when the directory |
301 | * does not exist). |
302 | */ |
303 | virtual bool getChildren(AbstractFSList& list, ListMode mode) const = 0; |
304 | |
305 | /** |
306 | * Returns the last component of the path pointed by this FilesystemNode. |
307 | * |
308 | * Examples (POSIX): |
309 | * /foo/bar.txt would return /bar.txt |
310 | * /foo/bar/ would return /bar/ |
311 | * |
312 | * @note This method is very architecture dependent, please check the concrete |
313 | * implementation for more information. |
314 | */ |
315 | virtual const string& getName() const = 0; |
316 | virtual void setName(const string& name) = 0; |
317 | |
318 | /** |
319 | * Returns the 'path' of the current node, usable in fopen(). |
320 | */ |
321 | virtual const string& getPath() const = 0; |
322 | |
323 | /** |
324 | * Returns the 'path' of the current node, containing '~' and for archiving. |
325 | */ |
326 | |
327 | virtual string getShortPath() const = 0; |
328 | |
329 | /** |
330 | * Determine whether this node has a parent. |
331 | */ |
332 | virtual bool hasParent() const = 0; |
333 | |
334 | /** |
335 | * The parent node of this directory. |
336 | * The parent of the root is 'nullptr'. |
337 | */ |
338 | virtual AbstractFSNodePtr getParent() const = 0; |
339 | |
340 | /** |
341 | * Indicates whether this path refers to a directory or not. |
342 | */ |
343 | virtual bool isDirectory() const = 0; |
344 | |
345 | /** |
346 | * Indicates whether this path refers to a real file or not. |
347 | */ |
348 | virtual bool isFile() const = 0; |
349 | |
350 | /** |
351 | * Indicates whether the object referred by this path can be read from or not. |
352 | * |
353 | * If the path refers to a directory, readability implies being able to read |
354 | * and list the directory entries. |
355 | * |
356 | * If the path refers to a file, readability implies being able to read the |
357 | * contents of the file. |
358 | * |
359 | * @return bool true if the object can be read, false otherwise. |
360 | */ |
361 | virtual bool isReadable() const = 0; |
362 | |
363 | /** |
364 | * Indicates whether the object referred by this path can be written to or not. |
365 | * |
366 | * If the path refers to a directory, writability implies being able to modify |
367 | * the directory entry (i.e. rename the directory, remove it or write files |
368 | * inside of it). |
369 | * |
370 | * If the path refers to a file, writability implies being able to write data |
371 | * to the file. |
372 | * |
373 | * @return bool true if the object can be written to, false otherwise. |
374 | */ |
375 | virtual bool isWritable() const = 0; |
376 | |
377 | /** |
378 | * Create a directory from the current node path. |
379 | * |
380 | * @return bool true if the directory was created, false otherwise. |
381 | */ |
382 | virtual bool makeDir() = 0; |
383 | |
384 | /** |
385 | * Rename the current node path with the new given name. |
386 | * |
387 | * @return bool true if the node was renamed, false otherwise. |
388 | */ |
389 | virtual bool rename(const string& newfile) = 0; |
390 | |
391 | /** |
392 | * Read data (binary format) into the given buffer. |
393 | * |
394 | * @param buffer The buffer to containing the data |
395 | * This will be allocated by the method, and must be |
396 | * freed by the caller. |
397 | * @return The number of bytes read (0 in the case of failure) |
398 | * This method can throw exceptions, and should be used inside |
399 | * a try-catch block. |
400 | */ |
401 | virtual uInt32 read(ByteBuffer& buffer) const { return 0; } |
402 | }; |
403 | |
404 | #endif |
405 | |