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
52class FilesystemNode;
53class AbstractFSNode;
54using 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 */
61class FSList : public vector<FilesystemNode> { };
62
63/**
64 * This class acts as a wrapper around the AbstractFSNode class defined
65 * in backends/fs.
66 */
67class 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
267using AbstractFSList = vector<AbstractFSNodePtr>;
268
269class 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