mirror of https://github.com/buggins/dlangui.git
Merge pull request #362 from FreeSlave/files_and_filedialog
Fix crash when listDirectory can't access parent path. Add listDirect…
This commit is contained in:
commit
420118d885
|
@ -131,29 +131,36 @@ version(OSX) {} else version(Posix)
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private string getDeviceLabelFallback(in char[] type, in char[] fsName)
|
||||
|
||||
private string getDeviceLabelFallback(in char[] type, in char[] fsName, in char[] mountDir)
|
||||
{
|
||||
import std.format : format;
|
||||
import std.string : startsWith;
|
||||
if (type == "vboxsf") {
|
||||
return "VirtualBox shared folder";
|
||||
}
|
||||
if (fsName.startsWith("gvfsd")) {
|
||||
return "GNOME virtual file system";
|
||||
if (type == "fuse.gvfsd-fuse") {
|
||||
return "GNOME Virtual file system";
|
||||
}
|
||||
return format("%s volume", type);
|
||||
return format("%s (%s)", mountDir.baseName, type);
|
||||
}
|
||||
|
||||
|
||||
private RootEntryType getDeviceRootEntryType(in char[] type)
|
||||
{
|
||||
if (type == "iso9660") {
|
||||
return RootEntryType.CDROM;
|
||||
}
|
||||
if (type == "vfat") {
|
||||
return RootEntryType.REMOVABLE;
|
||||
}
|
||||
return RootEntryType.FIXED;
|
||||
switch(type)
|
||||
{
|
||||
case "iso9660":
|
||||
return RootEntryType.CDROM;
|
||||
case "vfat":
|
||||
return RootEntryType.REMOVABLE;
|
||||
case "cifs":
|
||||
case "davfs":
|
||||
case "fuse.sshfs":
|
||||
case "nfs":
|
||||
case "nfs4":
|
||||
return RootEntryType.NETWORK;
|
||||
default:
|
||||
return RootEntryType.FIXED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,6 +254,7 @@ private:
|
|||
// do nothing
|
||||
} else version(linux) {
|
||||
import std.string : fromStringz;
|
||||
import std.exception : collectException;
|
||||
|
||||
mntent ent;
|
||||
char[1024] buf;
|
||||
|
@ -267,22 +275,25 @@ private:
|
|||
|
||||
string label;
|
||||
enum byLabel = "/dev/disk/by-label";
|
||||
try {
|
||||
foreach(entry; dirEntries(byLabel, SpanMode.shallow))
|
||||
{
|
||||
if (entry.isSymlink) {
|
||||
auto normalized = buildNormalizedPath(byLabel, entry.readLink);
|
||||
if (normalized == fsName) {
|
||||
label = entry.name.baseName.unescapeLabel();
|
||||
if (fsName.isAbsolute) {
|
||||
try {
|
||||
foreach(entry; dirEntries(byLabel, SpanMode.shallow))
|
||||
{
|
||||
string resolvedLink;
|
||||
if (entry.isSymlink && collectException(entry.readLink, resolvedLink) is null) {
|
||||
auto normalized = buildNormalizedPath(byLabel, resolvedLink);
|
||||
if (normalized == fsName) {
|
||||
label = entry.name.baseName.unescapeLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
} catch(Exception e) {
|
||||
|
||||
}
|
||||
|
||||
if (!label.length) {
|
||||
label = getDeviceLabelFallback(type, fsName);
|
||||
label = getDeviceLabelFallback(type, fsName, mountDir);
|
||||
}
|
||||
auto entryType = getDeviceRootEntryType(type);
|
||||
res ~= RootEntry(entryType, mountDir.idup, label.toUTF32);
|
||||
|
@ -307,7 +318,7 @@ private:
|
|||
continue;
|
||||
}
|
||||
|
||||
string label = getDeviceLabelFallback(type, fsName);
|
||||
string label = getDeviceLabelFallback(type, fsName, mountDir);
|
||||
res ~= RootEntry(getDeviceRootEntryType(type), mountDir.idup, label.toUTF32);
|
||||
}
|
||||
}
|
||||
|
@ -515,6 +526,7 @@ bool isHidden(in string path) nothrow {
|
|||
return false;
|
||||
}
|
||||
} else version(Posix) {
|
||||
//TODO: check for hidden attribute on macOS
|
||||
return path.baseName.startsWith(".");
|
||||
} else {
|
||||
return false;
|
||||
|
@ -530,6 +542,49 @@ unittest
|
|||
}
|
||||
}
|
||||
|
||||
private bool isReadable(in string filePath) nothrow
|
||||
{
|
||||
version(Posix) {
|
||||
import core.sys.posix.unistd : access, R_OK;
|
||||
import std.string : toStringz;
|
||||
return access(toStringz(filePath), R_OK) == 0;
|
||||
} else {
|
||||
// TODO: Windows version
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isWritable(in string filePath) nothrow
|
||||
{
|
||||
version(Posix) {
|
||||
import core.sys.posix.unistd : access, W_OK;
|
||||
import std.string : toStringz;
|
||||
return access(toStringz(filePath), W_OK) == 0;
|
||||
} else {
|
||||
// TODO: Windows version
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isExecutable(in string filePath) nothrow
|
||||
{
|
||||
version(Windows) {
|
||||
//TODO: Use GetEffectiveRightsFromAclW? For now just check extension
|
||||
string extension = filePath.extension;
|
||||
foreach(ext; [".exe", ".com", ".bat", ".cmd"]) {
|
||||
if (filenameCmp(extension, ext) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else version(Posix) {
|
||||
import core.sys.posix.unistd : access, X_OK;
|
||||
import std.string : toStringz;
|
||||
return access(toStringz(filePath), X_OK) == 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// returns parent directory for specified path
|
||||
string parentDir(in string path) pure nothrow {
|
||||
return buildNormalizedPath(path, "..");
|
||||
|
@ -550,71 +605,92 @@ bool filterFilename(in string filename, in string[] filters) pure nothrow {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** List directory content
|
||||
|
||||
Optionally filters file names by filter.
|
||||
enum AttrFilter
|
||||
{
|
||||
none = 0,
|
||||
files = 1 << 0, /// Include regular files that match the filters.
|
||||
dirs = 1 << 1, /// Include directories.
|
||||
hidden = 1 << 2, /// Include hidden files and directoroies.
|
||||
parent = 1 << 3, /// Include parent directory (..). Takes effect only with includeDirs.
|
||||
thisDir = 1 << 4, /// Include this directory (.). Takes effect only with includeDirs.
|
||||
special = 1 << 5, /// Include special files (On Unix: socket and device files, FIFO) that match the filters.
|
||||
readable = 1 << 6, /// Listing only readable files and directories.
|
||||
writable = 1 << 7, /// Listing only writable files and directories.
|
||||
executable = 1 << 8, /// Include only executable files. This filter does not affect directories.
|
||||
allVisible = AttrFilter.files | AttrFilter.dirs, /// Include all non-hidden files and directories without parent directory, this directory and special files.
|
||||
all = AttrFilter.allVisible | AttrFilter.hidden /// Include all files and directories including hidden ones but without parent directory, this directory and special files.
|
||||
}
|
||||
|
||||
/** List directory content
|
||||
|
||||
Optionally filters file names by filter (not applied to directories).
|
||||
|
||||
Returns true if directory exists and listed successfully, false otherwise.
|
||||
Throws: Exception if $(D dir) is not directory or some error occured during directory listing.
|
||||
*/
|
||||
DirEntry[] listDirectory(in string dir, AttrFilter attrFilter = AttrFilter.all, in string[] filters = [])
|
||||
{
|
||||
DirEntry[] entries;
|
||||
|
||||
DirEntry[] dirs;
|
||||
DirEntry[] files;
|
||||
foreach (DirEntry e; dirEntries(dir, SpanMode.shallow)) {
|
||||
if (!(attrFilter & AttrFilter.hidden) && e.name.isHidden())
|
||||
continue;
|
||||
if ((attrFilter & AttrFilter.readable) && !e.name.isReadable())
|
||||
continue;
|
||||
if ((attrFilter & AttrFilter.writable) && !e.name.isWritable())
|
||||
continue;
|
||||
if (!e.isDir && (attrFilter & AttrFilter.executable) && !e.name.isExecutable())
|
||||
continue;
|
||||
if (e.isDir && (attrFilter & AttrFilter.dirs)) {
|
||||
dirs ~= e;
|
||||
} else if ((attrFilter & AttrFilter.files) && filterFilename(e.name, filters)) {
|
||||
if (e.isFile) {
|
||||
files ~= e;
|
||||
} else if (attrFilter & AttrFilter.special) {
|
||||
files ~= e;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((attrFilter & AttrFilter.dirs) && (attrFilter & AttrFilter.thisDir) ) {
|
||||
entries ~= DirEntry(appendPath(dir, ".")) ~ entries;
|
||||
}
|
||||
if (!isRoot(dir) && (attrFilter & AttrFilter.dirs) && (attrFilter & AttrFilter.parent)) {
|
||||
entries ~= DirEntry(appendPath(dir, ".."));
|
||||
}
|
||||
dirs.sort!((a,b) => filenameCmp!(std.path.CaseSensitive.no)(a.name,b.name) < 0);
|
||||
files.sort!((a,b) => filenameCmp!(std.path.CaseSensitive.no)(a.name,b.name) < 0);
|
||||
entries ~= dirs;
|
||||
entries ~= files;
|
||||
return entries;
|
||||
}
|
||||
|
||||
/** List directory content
|
||||
|
||||
Optionally filters file names by filter (not applied to directories).
|
||||
|
||||
Result will be placed into entries array.
|
||||
|
||||
Returns true if directory exists and listed successfully, false otherwise.
|
||||
*/
|
||||
bool listDirectory(in string dir, in bool includeDirs, in bool includeFiles, in bool showHiddenFiles, in string[] filters, ref DirEntry[] entries, in bool showExecutables = false) {
|
||||
deprecated bool listDirectory(in string dir, in bool includeDirs, in bool includeFiles, in bool showHiddenFiles, in string[] filters, ref DirEntry[] entries, in bool showExecutables = false) {
|
||||
entries.length = 0;
|
||||
|
||||
|
||||
AttrFilter attrFilter;
|
||||
if (includeDirs) {
|
||||
attrFilter |= AttrFilter.dirs;
|
||||
attrFilter |= AttrFilter.parent;
|
||||
}
|
||||
if (includeFiles)
|
||||
attrFilter |= AttrFilter.files;
|
||||
if (showHiddenFiles)
|
||||
attrFilter |= AttrFilter.hidden;
|
||||
if (showExecutables)
|
||||
attrFilter |= AttrFilter.executable;
|
||||
|
||||
import std.exception : collectException;
|
||||
bool dirExists;
|
||||
collectException(dir.isDir, dirExists);
|
||||
if (!dirExists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isRoot(dir) && includeDirs) {
|
||||
entries ~= DirEntry(appendPath(dir, ".."));
|
||||
}
|
||||
|
||||
try {
|
||||
DirEntry[] dirs;
|
||||
DirEntry[] files;
|
||||
foreach (DirEntry e; dirEntries(dir, SpanMode.shallow)) {
|
||||
if (!showHiddenFiles && e.name.isHidden())
|
||||
continue;
|
||||
if (e.isDir) {
|
||||
dirs ~= e;
|
||||
} else if (e.isFile) {
|
||||
files ~= e;
|
||||
}
|
||||
}
|
||||
dirs.sort!((a,b) => filenameCmp!(std.path.CaseSensitive.no)(a.name,b.name) < 0);
|
||||
files.sort!((a,b) => filenameCmp!(std.path.CaseSensitive.no)(a.name,b.name) < 0);
|
||||
if (includeDirs)
|
||||
foreach(DirEntry e; dirs)
|
||||
entries ~= e;
|
||||
if (includeFiles)
|
||||
foreach(DirEntry e; files) {
|
||||
bool passed = false;
|
||||
if (showExecutables) {
|
||||
uint attr_mask = (1 << 0) | (1 << 3) | (1 << 6);
|
||||
version(Windows) {
|
||||
passed = e.name.endsWith(".exe") || e.name.endsWith(".EXE")
|
||||
|| e.name.endsWith(".cmd") || e.name.endsWith(".CMD")
|
||||
|| e.name.endsWith(".bat") || e.name.endsWith(".BAT");
|
||||
} else version (Posix) {
|
||||
// execute permission for others
|
||||
passed = (e.attributes & attr_mask) != 0;
|
||||
} else version(OSX) {
|
||||
passed = (e.attributes & attr_mask) != 0;
|
||||
}
|
||||
} else {
|
||||
passed = filterFilename(e.name, filters);
|
||||
}
|
||||
if (passed)
|
||||
entries ~= e;
|
||||
}
|
||||
return true;
|
||||
} catch (FileException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return collectException(listDirectory(dir, attrFilter, filters), entries) is null;
|
||||
}
|
||||
|
||||
/// Returns true if char ch is / or \ slash
|
||||
|
|
|
@ -256,9 +256,21 @@ class FileDialog : Dialog, CustomGridCellAdapter {
|
|||
protected bool openDirectory(string dir, string selectedItemPath) {
|
||||
dir = buildNormalizedPath(dir);
|
||||
Log.d("FileDialog.openDirectory(", dir, ")");
|
||||
_fileList.rows = 0;
|
||||
if (!listDirectory(dir, true, true, _showHiddenFiles, selectedFilter, _entries, executableFilterSelected))
|
||||
DirEntry[] entries;
|
||||
|
||||
auto attrFilter = (showHiddenFiles ? AttrFilter.all : AttrFilter.allVisible) | AttrFilter.special | AttrFilter.parent;
|
||||
if (executableFilterSelected()) {
|
||||
attrFilter |= AttrFilter.executable;
|
||||
}
|
||||
try {
|
||||
_entries = listDirectory(dir, attrFilter, selectedFilter());
|
||||
} catch(Exception e) {
|
||||
import dlangui.dialogs.msgbox;
|
||||
auto msgBox = new MessageBox(UIString("Error"d), UIString(e.msg.toUTF32), window());
|
||||
msgBox.show();
|
||||
return false;
|
||||
}
|
||||
_fileList.rows = 0;
|
||||
_path = dir;
|
||||
_isRoot = isRoot(dir);
|
||||
_edPath.path = _path; //toUTF32(_path);
|
||||
|
@ -689,8 +701,12 @@ class FilePathPanelItem : HorizontalLayout {
|
|||
// show popup menu with subdirs
|
||||
string[] filters;
|
||||
DirEntry[] entries;
|
||||
if (!listDirectory(_path, true, false, false, filters, entries))
|
||||
try {
|
||||
AttrFilter attrFilter = AttrFilter.dirs | AttrFilter.parent;
|
||||
entries = listDirectory(_path, attrFilter);
|
||||
} catch(Exception e) {
|
||||
return false;
|
||||
}
|
||||
if (entries.length == 0)
|
||||
return false;
|
||||
MenuItem dirs = new MenuItem();
|
||||
|
@ -699,10 +715,11 @@ class FilePathPanelItem : HorizontalLayout {
|
|||
string fullPath = e.name;
|
||||
string d = baseName(fullPath);
|
||||
Action a = new Action(itemId++, toUTF32(d));
|
||||
a.stringParam = fullPath;
|
||||
MenuItem item = new MenuItem(a);
|
||||
item.menuItemClick = delegate(MenuItem item) {
|
||||
item.menuItemAction = delegate(const Action action) {
|
||||
if (onPathSelectionListener.assigned)
|
||||
return onPathSelectionListener(fullPath);
|
||||
return onPathSelectionListener(action.stringParam);
|
||||
return false;
|
||||
};
|
||||
dirs.add(item);
|
||||
|
|
|
@ -113,8 +113,11 @@ version (Windows) {
|
|||
import dlangui.core.files;
|
||||
import std.file : DirEntry;
|
||||
DirEntry[] entries;
|
||||
if (!listDirectory(dir, false, true, true, ["*.ttf"], entries))
|
||||
return null;
|
||||
try {
|
||||
entries = listDirectory(dir, AttrFilter.files, ["*.ttf"]);
|
||||
} catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] res;
|
||||
foreach(entry; entries) {
|
||||
|
|
Loading…
Reference in New Issue