More reworking of the file selection mechanism.

- removed GameList, and integrated functionality directly into 'FSList' (which was mostly doing the same thing)
- have FSNode::getChildren() relabel directories, instead of Browser and Launcher dialogs
- MD5 calculations in ROM launcher are now cached, instead of being recalculated each time a directory is left and re-entered

Windows and macOS likely broken for a moment; this will be fixed next.
This commit is contained in:
Stephen Anthony 2019-06-22 21:09:42 -02:30
parent 786f3b2a49
commit bcca945951
15 changed files with 145 additions and 266 deletions

View File

@ -134,8 +134,8 @@ void FilesystemNodeZIP::setFlags(const string& zipfile,
{
_path += ("/" + _virtualPath);
_shortPath += ("/" + _virtualPath);
_name = lastPathComponent(_path);
}
_name = lastPathComponent(_path);
_error = zip_error::NONE;
if(!_realNode->isFile())
@ -145,8 +145,7 @@ void FilesystemNodeZIP::setFlags(const string& zipfile,
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode,
bool hidden) const
bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode) const
{
// Files within ZIP archives don't contain children
if(!isDirectory() || _error != zip_error::NONE)

View File

@ -48,9 +48,11 @@ class FilesystemNodeZIP : public AbstractFSNode
explicit FilesystemNodeZIP(const string& path);
bool exists() const override { return _realNode && _realNode->exists(); }
const string& getName() const override { return _name; }
const string& getName() const override { return _name; }
void setName(const string& name) override { _name = name; }
const string& getPath() const override { return _path; }
string getShortPath() const override { return _shortPath; }
bool hasParent() const override { return true; }
bool isDirectory() const override { return _isDirectory; }
bool isFile() const override { return _isFile; }
bool isReadable() const override { return _realNode && _realNode->isReadable(); }
@ -62,7 +64,7 @@ class FilesystemNodeZIP : public AbstractFSNode
bool rename(const string& newfile) override { return false; }
//////////////////////////////////////////////////////////
bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const override;
bool getChildren(AbstractFSList& list, ListMode mode) const override;
AbstractFSNodePtr getParent() const override;
uInt32 read(ByteBuffer& image) const override;

View File

@ -37,7 +37,7 @@ FilesystemNode::FilesystemNode(const string& p)
{
// Is this potentially a ZIP archive?
#if defined(ZIP_SUPPORT)
if(BSPF::containsIgnoreCase(p, ".zip"))
if (BSPF::containsIgnoreCase(p, ".zip"))
_realNode = FilesystemNodeFactory::create(p, FilesystemNodeFactory::Type::ZIP);
else
#endif
@ -51,7 +51,7 @@ bool FilesystemNode::exists() const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, bool hidden) const
bool FilesystemNode::getChildren(FSList& fslist, ListMode mode) const
{
if (!_realNode || !_realNode->isDirectory())
return false;
@ -59,11 +59,48 @@ bool FilesystemNode::getChildren(FSList& fslist, ListMode mode, bool hidden) con
AbstractFSList tmp;
tmp.reserve(fslist.capacity());
if (!_realNode->getChildren(tmp, mode, hidden))
if (!_realNode->getChildren(tmp, mode))
return false;
std::sort(tmp.begin(), tmp.end(),
[](const AbstractFSNodePtr& node1, const AbstractFSNodePtr& node2)
{
if (node1->isDirectory() != node2->isDirectory())
return node1->isDirectory();
else
return BSPF::compareIgnoreCase(node1->getName(), node2->getName()) < 0;
}
);
// Add parent node, if it is valid to do so
if (hasParent())
{
FilesystemNode parent = getParent();
parent.setName(" [..]");
fslist.emplace_back(parent);
}
// And now add the rest of the entries
for (const auto& i: tmp)
fslist.emplace_back(FilesystemNode(i));
{
#if defined(ZIP_SUPPORT)
// Force ZIP c'tor to be called
if (BSPF::endsWithIgnoreCase(i->getPath(), ".zip"))
{
AbstractFSNodePtr ptr = FilesystemNodeFactory::create(i->getPath(),
FilesystemNodeFactory::Type::ZIP);
fslist.emplace_back(FilesystemNode(ptr));
}
else
#endif
{
// Make directories stand out
if (i->isDirectory())
i->setName(" [" + i->getName() + "]");
fslist.emplace_back(FilesystemNode(i));
}
}
return true;
}
@ -74,6 +111,13 @@ const string& FilesystemNode::getName() const
return _realNode->getName();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FilesystemNode::setName(const string& name)
{
_realNode->setName(name);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string& FilesystemNode::getPath() const
{
@ -109,7 +153,7 @@ string FilesystemNode::getPathWithExt(const string& ext) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNode::hasParent() const
{
return _realNode ? (_realNode->getParent() != nullptr) : false;
return _realNode ? _realNode->hasParent() : false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -164,23 +208,23 @@ uInt32 FilesystemNode::read(ByteBuffer& image) const
uInt32 size = 0;
// File must actually exist
if(!(exists() && isReadable()))
if (!(exists() && isReadable()))
throw runtime_error("File not found/readable");
// First let the private subclass attempt to open the file
if((size = _realNode->read(image)) > 0)
if ((size = _realNode->read(image)) > 0)
return size;
// Otherwise, the default behaviour is to read from a normal C++ ifstream
image = make_unique<uInt8[]>(512 * 1024);
ifstream in(getPath(), std::ios::binary);
if(in)
if (in)
{
in.seekg(0, std::ios::end);
std::streampos length = in.tellg();
in.seekg(0, std::ios::beg);
if(length == 0)
if (length == 0)
throw runtime_error("Zero-byte file");
size = std::min(uInt32(length), 512u * 1024u);

View File

@ -94,18 +94,6 @@ class FilesystemNode
FilesystemNode(const FilesystemNode&) = default;
FilesystemNode& operator=(const FilesystemNode&) = default;
/**
* Compare the name of this node to the name of another. Directories
* go before normal files.
*/
inline bool operator<(const FilesystemNode& node) const
{
if (isDirectory() != node.isDirectory())
return isDirectory();
return BSPF::compareIgnoreCase(getName(), node.getName()) < 0;
}
/**
* Compare the name of this node to the name of another, testing for
* equality,
@ -139,11 +127,10 @@ class FilesystemNode
* @return true if successful, false otherwise (e.g. when the directory
* does not exist).
*/
bool getChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly,
bool hidden = false) const;
bool getChildren(FSList& fslist, ListMode mode = ListMode::DirectoriesOnly) const;
/**
* Return a string representation of the name of the file. This is can be
* Set/get a string representation of the name of the file. This is can be
* used e.g. by detection code that relies on matching the name of a given
* file. But it is *not* suitable for use with fopen / File::open, nor
* should it be archived.
@ -151,6 +138,7 @@ class FilesystemNode
* @return the file name
*/
const string& getName() const;
void setName(const string& name);
/**
* Return a string representation of the file which can be passed to fopen().
@ -300,12 +288,11 @@ class AbstractFSNode
*
* @param list List to put the contents of the directory in.
* @param mode Mode to use while listing the directory.
* @param hidden Whether to include hidden files or not in the results.
*
* @return true if successful, false otherwise (e.g. when the directory
* does not exist).
*/
virtual bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const = 0;
virtual bool getChildren(AbstractFSList& list, ListMode mode) const = 0;
/**
* Returns the last component of the path pointed by this FilesystemNode.
@ -318,6 +305,7 @@ class AbstractFSNode
* implementation for more information.
*/
virtual const string& getName() const = 0;
virtual void setName(const string& name) = 0;
/**
* Returns the 'path' of the current node, usable in fopen().
@ -330,6 +318,17 @@ class AbstractFSNode
virtual string getShortPath() const = 0;
/**
* Determine whether this node has a parent.
*/
virtual bool hasParent() const = 0;
/**
* The parent node of this directory.
* The parent of the root is 'nullptr'.
*/
virtual AbstractFSNodePtr getParent() const = 0;
/**
* Indicates whether this path refers to a directory or not.
*/
@ -392,12 +391,6 @@ class AbstractFSNode
* a try-catch block.
*/
virtual uInt32 read(ByteBuffer& buffer) const { return 0; }
/**
* The parent node of this directory.
* The parent of the root is the root itself.
*/
virtual AbstractFSNodePtr getParent() const = 0;
};
#endif

View File

@ -18,16 +18,21 @@
#include "ScrollBarWidget.hxx"
#include "FileListWidget.hxx"
#include "Bankswitch.hxx"
#include "MD5.hxx"
#include "bspf.hxx"
/**
TODO:
- extension handling not handled
- add lambda filter to selectively choose files based on pattern
- history of selected folders/files
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileListWidget::FileListWidget(GuiObject* boss, const GUI::Font& font,
int x, int y, int w, int h)
: StringListWidget(boss, font, x, y, w, h),
_fsmode(FilesystemNode::ListMode::All),
_selectedPos(0)
_selected(0)
{
// This widget is special, in that it catches signals and redirects them
setTarget(this);
@ -45,36 +50,15 @@ void FileListWidget::setLocation(const FilesystemNode& node, string select)
_node = _node.getParent();
}
// Start with empty list
_gameList.clear();
// Read in the data from the file system
FSList content;
content.reserve(512);
_node.getChildren(content, _fsmode);
// Add '[..]' to indicate previous folder
if(_node.hasParent())
_gameList.appendGame(" [..]", _node.getParent().getPath(), "", true);
// Now add the directory entries
for(const auto& file: content)
{
string name = file.getName();
bool isDir = file.isDirectory();
if(isDir)
name = " [" + name + "]";
else if(!BSPF::endsWithIgnoreCase(name, _extension))
continue;
_gameList.appendGame(name, file.getPath(), "", isDir);
}
_gameList.sortByName();
// Read in the data from the file system (start with an empty list)
_fileList.clear();
_fileList.reserve(512);
_node.getChildren(_fileList, _fsmode);
// Now fill the list widget with the contents of the GameList
StringList l;
for(uInt32 i = 0; i < _gameList.size(); ++i)
l.push_back(_gameList.name(i));
for(const auto& file: _fileList)
l.push_back(file.getName());
setList(l);
setSelected(select);
@ -96,20 +80,7 @@ void FileListWidget::selectParent()
void FileListWidget::reload()
{
if(_node.exists())
setLocation(_node, _gameList.name(_selectedPos));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string& FileListWidget::selectedMD5()
{
if(_selected.isDirectory() || !Bankswitch::isValidRomName(_selected))
return EmptyString;
// Make sure we have a valid md5 for this ROM
if(_gameList.md5(_selectedPos) == "")
_gameList.setMd5(_selectedPos, MD5::hash(_selected));
return _gameList.md5(_selectedPos);
setLocation(_node, selected().getName());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -123,19 +94,16 @@ void FileListWidget::handleCommand(CommandSender* sender, int cmd, int data, int
case ListWidget::kSelectionChangedCmd:
cmd = ItemChanged;
_selected = FilesystemNode(_gameList.path(data));
_selectedPos = data;
_selected = data;
break;
case ListWidget::kActivatedCmd:
case ListWidget::kDoubleClickedCmd:
if(_gameList.isDir(data))
_selected = data;
if(selected().isDirectory())
{
cmd = ItemChanged;
if(_gameList.name(data) == " [..]")
selectParent();
else
setLocation(FilesystemNode(_gameList.path(data)));
setLocation(selected());
}
else
cmd = ItemActivated;

View File

@ -21,7 +21,6 @@
class CommandSender;
#include "FSNode.hxx"
#include "GameList.hxx"
#include "StringListWidget.hxx"
/**
@ -64,26 +63,19 @@ class FileListWidget : public StringListWidget
void reload();
/** Gets current node(s) */
const FilesystemNode& selected() const { return _selected; }
const FilesystemNode& currentDir() const { return _node; }
/** Gets MD5sum of the current node, if it is a file, and caches the result.
Otherwise, does nothing.
@return MD5sum of selected file, else EmptyString
*/
const string& selectedMD5();
const FilesystemNode& selected() const { return _fileList[_selected]; }
const FilesystemNode& currentDir() const { return _node; }
private:
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
private:
FilesystemNode::ListMode _fsmode;
FilesystemNode _node, _selected;
GameList _gameList;
FilesystemNode _node;
FSList _fileList;
string _extension;
uInt32 _selectedPos;
uInt32 _selected;
private:
// Following constructors and assignment operators not supported

View File

@ -1,58 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// Based on code from KStella - Stella frontend
// Copyright (C) 2003-2005 Stephen Anthony
//============================================================================
#include <cctype>
#include <algorithm>
#include "GameList.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void GameList::sortByName()
{
if(myArray.size() < 2)
return;
auto cmp = [](const Entry& a, const Entry& b)
{
// directories always first
if(a._isdir != b._isdir)
return a._isdir;
auto it1 = a._name.cbegin(), it2 = b._name.cbegin();
// Account for ending ']' character in directory entries
auto end1 = a._isdir ? a._name.cend() - 1 : a._name.cend();
auto end2 = b._isdir ? b._name.cend() - 1 : b._name.cend();
// Stop when either string's end has been reached
while((it1 != end1) && (it2 != end2))
{
if(toupper(*it1) != toupper(*it2)) // letters differ?
return toupper(*it1) < toupper(*it2);
// proceed to the next character in each string
++it1;
++it2;
}
return a._name.size() < b._name.size();
};
sort(myArray.begin(), myArray.end(), cmp);
}

View File

@ -1,75 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// Based on code from KStella - Stella frontend
// Copyright (C) 2003-2005 Stephen Anthony
//============================================================================
#ifndef GAME_LIST_HXX
#define GAME_LIST_HXX
#include "bspf.hxx"
/**
Holds the list of game info for the ROM launcher.
*/
class GameList
{
public:
GameList() = default;
const string& name(uInt32 i) const
{ return i < myArray.size() ? myArray[i]._name : EmptyString; }
const string& path(uInt32 i) const
{ return i < myArray.size() ? myArray[i]._path : EmptyString; }
const string& md5(uInt32 i) const
{ return i < myArray.size() ? myArray[i]._md5 : EmptyString; }
bool isDir(uInt32 i) const
{ return i < myArray.size() ? myArray[i]._isdir: false; }
void setMd5(uInt32 i, const string& md5)
{ myArray[i]._md5 = md5; }
uInt32 size() const { return uInt32(myArray.size()); }
void clear() { myArray.clear(); }
void appendGame(const string& name, const string& path, const string& md5,
bool isDir = false) {
myArray.emplace_back(name, path, md5, isDir);
}
void sortByName();
private:
struct Entry {
string _name;
string _path;
string _md5;
bool _isdir;
Entry(string name, string path, string md5, bool isdir)
: _name(name), _path(path), _md5(md5), _isdir(isdir) { }
};
vector<Entry> myArray;
private:
// Following constructors and assignment operators not supported
GameList(const GameList&) = delete;
GameList(GameList&&) = delete;
GameList& operator=(const GameList&) = delete;
GameList& operator=(GameList&&) = delete;
};
#endif

View File

@ -16,6 +16,7 @@
//============================================================================
#include "bspf.hxx"
#include "Bankswitch.hxx"
#include "BrowserDialog.hxx"
#include "ContextMenu.hxx"
#include "DialogContainer.hxx"
@ -23,7 +24,7 @@
#include "EditTextWidget.hxx"
#include "FileListWidget.hxx"
#include "FSNode.hxx"
#include "GameList.hxx"
#include "MD5.hxx"
#include "OptionsDialog.hxx"
#include "GlobalPropsDialog.hxx"
#include "StellaSettingsDialog.hxx"
@ -46,7 +47,7 @@
TODO:
- show all files / only ROMs
- connect to 'matchPattern'
- history of selected folders/files
- create lambda filter to pass these to FileListWidget
*/
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -246,7 +247,19 @@ const string& LauncherDialog::selectedRom() const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string& LauncherDialog::selectedRomMD5()
{
return myList->selectedMD5();
if(currentNode().isDirectory() || !Bankswitch::isValidRomName(currentNode()))
return EmptyString;
// Attempt to conserve memory
if(myMD5List.size() > 500)
myMD5List.clear();
// Lookup MD5, and if not present, cache it
auto iter = myMD5List.find(currentNode().getPath());
if(iter == myMD5List.end())
myMD5List[currentNode().getPath()] = MD5::hash(currentNode());
return myMD5List[currentNode().getPath()];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -612,7 +625,7 @@ void LauncherDialog::handleCommand(CommandSender* sender, int cmd,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void LauncherDialog::loadRom()
{
const string& result = instance().createConsole(currentNode(), myList->selectedMD5());
const string& result = instance().createConsole(currentNode(), selectedRomMD5());
if(result == EmptyString)
{
instance().settings().setValue("lastrom", myList->getSelectedString());

View File

@ -36,6 +36,8 @@ namespace GUI {
class MessageBox;
}
#include <unordered_map>
#include "bspf.hxx"
#include "Dialog.hxx"
#include "FSNode.hxx"
@ -122,6 +124,7 @@ class LauncherDialog : public Dialog
CheckboxWidget* myAllFiles;
RomInfoWidget* myRomInfoWidget;
std::unordered_map<string,string> myMD5List;
int mySelectedItem;

View File

@ -19,7 +19,6 @@ MODULE_OBJS := \
src/gui/FileListWidget.o \
src/gui/Font.o \
src/gui/GameInfoDialog.o \
src/gui/GameList.o \
src/gui/GlobalPropsDialog.o \
src/gui/HelpDialog.o \
src/gui/InputDialog.o \

View File

@ -19,14 +19,11 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilesystemNodeLIBRETRO::FilesystemNodeLIBRETRO()
: _name("rom"),
_isDirectory(false),
_isFile(true),
_isValid(true)
{
_displayName = "rom";
_path = "";
_isDirectory = false;
_isFile = true;
_isPseudoRoot = false;
_isValid = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -60,7 +57,7 @@ string FilesystemNodeLIBRETRO::getShortPath() const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNodeLIBRETRO::
getChildren(AbstractFSList& myList, ListMode mode, bool hidden) const
getChildren(AbstractFSList& myList, ListMode mode) const
{
return false;
}

View File

@ -20,9 +20,6 @@
#include "FSNode.hxx"
// TODO - fix isFile() functionality so that it actually determines if something
// is a file; for now, it assumes a file if it isn't a directory
/*
* Implementation of the Stella file system API based on LIBRETRO API.
*
@ -37,9 +34,11 @@ class FilesystemNodeLIBRETRO : public AbstractFSNode
explicit FilesystemNodeLIBRETRO(const string& path);
bool exists() const override;
const string& getName() const override { return _displayName; }
const string& getPath() const override { return _path; }
const string& getName() const override { return _name; }
void setName(const string& name) override { _name = name; }
const string& getPath() const override { return _path; }
string getShortPath() const override;
bool hasParent() const override { return false; }
bool isDirectory() const override { return _isDirectory; }
bool isFile() const override { return _isFile; }
bool isReadable() const override;
@ -47,13 +46,13 @@ class FilesystemNodeLIBRETRO : public AbstractFSNode
bool makeDir() override;
bool rename(const string& newfile) override;
bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const override;
bool getChildren(AbstractFSList& list, ListMode mode) const override;
AbstractFSNodePtr getParent() const override;
uInt32 read(ByteBuffer& image) const override;
protected:
string _displayName;
string _name;
string _path;
bool _isDirectory;
bool _isFile;

View File

@ -53,13 +53,13 @@ FilesystemNodePOSIX::FilesystemNodePOSIX()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilesystemNodePOSIX::FilesystemNodePOSIX(const string& p, bool verify)
FilesystemNodePOSIX::FilesystemNodePOSIX(const string& path, bool verify)
: _isValid(true),
_isFile(false),
_isDirectory(true)
{
// Default to home directory
_path = p.length() > 0 ? p : "~";
_path = path.length() > 0 ? path : "~";
// Expand '~' to the HOME environment variable
if(_path[0] == '~')
@ -99,8 +99,13 @@ string FilesystemNodePOSIX::getShortPath() const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNodePOSIX::getChildren(AbstractFSList& myList, ListMode mode,
bool hidden) const
bool FilesystemNodePOSIX::hasParent() const
{
return _path != "" && _path != ROOT_DIR;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNodePOSIX::getChildren(AbstractFSList& myList, ListMode mode) const
{
assert(_isDirectory);
@ -112,12 +117,8 @@ bool FilesystemNodePOSIX::getChildren(AbstractFSList& myList, ListMode mode,
struct dirent* dp;
while ((dp = readdir(dirp)) != nullptr)
{
// Skip 'invisible' files if necessary
if (dp->d_name[0] == '.' && !hidden)
continue;
// Skip '.' and '..' to avoid cycles
if ((dp->d_name[0] == '.' && dp->d_name[1] == 0) || (dp->d_name[0] == '.' && dp->d_name[1] == '.'))
// Ignore all hidden files
if (dp->d_name[0] == '.')
continue;
string newPath(_path);

View File

@ -61,9 +61,11 @@ class FilesystemNodePOSIX : public AbstractFSNode
FilesystemNodePOSIX(const string& path, bool verify = true);
bool exists() const override { return access(_path.c_str(), F_OK) == 0; }
const string& getName() const override { return _displayName; }
const string& getName() const override { return _displayName; }
void setName(const string& name) override { _displayName = name; }
const string& getPath() const override { return _path; }
string getShortPath() const override;
bool hasParent() const override;
bool isDirectory() const override { return _isDirectory; }
bool isFile() const override { return _isFile; }
bool isReadable() const override { return access(_path.c_str(), R_OK) == 0; }
@ -71,7 +73,7 @@ class FilesystemNodePOSIX : public AbstractFSNode
bool makeDir() override;
bool rename(const string& newfile) override;
bool getChildren(AbstractFSList& list, ListMode mode, bool hidden) const override;
bool getChildren(AbstractFSList& list, ListMode mode) const override;
AbstractFSNodePtr getParent() const override;
protected: