Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bugs and add support for UNC paths on Windows. #901

Merged
merged 2 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 147 additions & 66 deletions Engine/FileSystemModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,50 +56,123 @@ CLANG_DIAG_ON(uninitialized)
#include "Global/FloatingPointExceptions.h"
#endif

#ifdef __NATRON_WIN32__
#include "AppManager.h" // appPTR
#endif
#include "AppManager.h" // appPTR & StrUtils


NATRON_NAMESPACE_ENTER

static QStringList
getSplitPath(const QString& path)
static int
getDriveNameSize(const QString& name)
{
if ( path.isEmpty() ) {
return QStringList();
#ifdef __NATRON_WIN32__
if (name.size() < 3) {
return 0;
}
QString pathCpy = path;
bool isdriveOrRoot;

if (name.at(0).isLetter() && name.at(1) == QLatin1Char(':') && ( name.at(2) == QLatin1Char('/') || name.at(2) == QLatin1Char('\\') ) ) {
// Drive path. (e.g. C:/ or C:\)
return 3;
}

if (((name.at(0) == QLatin1Char('/') && name.at(1) == QLatin1Char('/')) ||
(name.at(0) == QLatin1Char('\\') && name.at(1) == QLatin1Char('\\'))) && name.at(2).isLetterOrNumber()) {
// Possible Network share. (e.g. //SomeHost/ShareName)

// Search for end of hostname.
int idx = name.indexOf(QLatin1Char('/'), 2);
if (idx == -1) {
idx = name.indexOf(QLatin1Char('\\'), 2);
}

if (idx == -1) {
// Hostname only without a trailing slash. (e.g. //SomeHost).
// Do not consider this valid, just like "C:" is not considered a valid drive.
return 0;
}

++idx; // Move beyond slash.
return idx;
}
#else
if (name.startsWith(QDir::rootPath())) {
return QDir::rootPath().size();
}
#endif

return 0;
}

#ifdef __NATRON_WIN32__
QString startPath = pathCpy.mid(0, 3);
isdriveOrRoot = FileSystemModel::isDriveName(startPath);
if (isdriveOrRoot) {
pathCpy = pathCpy.remove(0, 3);
// Ensures that drive names and network share hostnames are always upper case.
static QString
ensureCanonicalDriveAndShareNames(const QString& path)
{
if (path.isEmpty() || !FileSystemModel::startsWithDriveName(path)) {
return path;
}
if ( (pathCpy.size() > 0) && ( pathCpy[pathCpy.size() - 1] == QChar::fromLatin1('/') ) ) {
pathCpy = pathCpy.mid(0, pathCpy.size() - 1);

if (path.size() < 3) {
return path;
}
QStringList splitPath = pathCpy.split( QChar::fromLatin1('/') );
if (isdriveOrRoot) {
splitPath.prepend( startPath.mid(0, 3) );

// Assume that any path that reaches this point has already had backslashes converted
// to forward slashes.
assert(path.indexOf(QLatin1Char('\\')) == -1);

QString ret = path;
if (path[0] == QLatin1Char('/') && path[1] == QLatin1Char('/') && path[2].isLetterOrNumber()) {
// Network share name.

int idx = path.indexOf(QLatin1Char('/'), 2);
ret.truncate(2); // keep starting slashes.
if (idx == -1) {
// No trailing slash. Hostname only case.
// Change the hostname to upper case and add the trailing slash.
ret.append(path.mid(2).toUpper());
ret.append(QLatin1Char('/')); // Add a trailing slash.
} else {
// Hostname with slash and possible path components.
const QString hostname = path.mid(2, idx - 2);

// Change hostname to upper case and append any path components that may remain.
ret.append(hostname.toUpper());
ret.append(path.mid(idx));
}
} else if (path[0].isLetter() && path[1] == QLatin1Char(':') && path[2] == QLatin1Char('/')) {
// Drive name.
// Force drive letter to be upper case.
ret[0] = ret[0].toUpper();
}

#else
isdriveOrRoot = pathCpy.startsWith( QChar::fromLatin1('/') );
if (isdriveOrRoot) {
pathCpy = pathCpy.remove(0, 1);
return ret;
}
#endif // __NATRON_WIN32__

QStringList
FileSystemModel::getSplitPath(const QString& path)
{
if ( path.isEmpty() ) {
return QStringList();
}
QString pathCpy = path;

int driveNameSize = getDriveNameSize(path);
if (driveNameSize > 0) {
// Remove the drive name from pathCpy.
pathCpy = pathCpy.remove(0, driveNameSize);
}

// Remove trailing slash.
if ( (pathCpy.size() > 0) && ( pathCpy[pathCpy.size() - 1] == QChar::fromLatin1('/') ) ) {
pathCpy = pathCpy.mid(0, pathCpy.size() - 1);
pathCpy = pathCpy.mid(0, pathCpy.size() - 1);
}

QStringList splitPath = pathCpy.split( QChar::fromLatin1('/') );
if (isdriveOrRoot) {
splitPath.prepend( QChar::fromLatin1('/') );
if (driveNameSize > 0) {
// Put the drive name at the beginning of the list.
splitPath.prepend(path.mid(0, driveNameSize));
}

#endif

return splitPath;
}

Expand Down Expand Up @@ -169,9 +242,7 @@ generateChildAbsoluteName(FileSystemItem* parent,
{
QString childName = parent->absoluteFilePath();

if ( !childName.endsWith( QChar::fromLatin1('/') ) ) {
childName.append( QChar::fromLatin1('/') );
}
StrUtils::ensureLastPathSeparator(childName);
childName.append(name);

return childName;
Expand Down Expand Up @@ -617,25 +688,57 @@ FileSystemModel::getSharedItemPtr(FileSystemItem* item) const
bool
FileSystemModel::isDriveName(const QString& name)
{
#ifdef __NATRON_WIN32__

return name.size() == 3 && name.at(0).isLetter() && name.at(1) == QLatin1Char(':') && ( name.at(2) == QLatin1Char('/') || name.at(2) == QLatin1Char('\\') );
#else

return name == QDir::rootPath();
#endif
return !name.isEmpty() && getDriveNameSize(name) == name.size();
}

bool
FileSystemModel::startsWithDriveName(const QString& name)
{
return getDriveNameSize(name) > 0;
}

QString FileSystemModel::cleanPath(const QString& path) {
if (path.isEmpty()) {
return path;
}

QString newPath = path;

#ifdef __NATRON_WIN32__
// Replace backslashes with slashes.
newPath.replace( QLatin1Char('\\'), QLatin1Char('/') );
#endif

return name.size() >= 3 && name.at(0).isLetter() && name.at(1) == QLatin1Char(':') && ( name.at(2) == QLatin1Char('/') || name.at(2) == QLatin1Char('\\') );
#else
bool startedWithDriveName = startsWithDriveName(newPath);

// Resolve and remove '.' and "..".
newPath = QDir::cleanPath(newPath);

if (newPath.isEmpty()) {
return newPath;
}

return name.startsWith( QDir::rootPath() );
// QDir::cleanPath() strips the trailing slash for network shares
// (e.g. //SomeHost/) , but not for drives (e.g. C:/). The trailing
// slash is required for network shares to behave the same way as
// drives elsewhere in the code so we need to put back the trailing
// slash.
if (startedWithDriveName && !startsWithDriveName(newPath)) {
// The path no longer starts with a drive name after cleaning.
// See if appending a trailing slash fixes it.
const QString newPathWithTrailingSlash = newPath + QLatin1Char('/');
if (startsWithDriveName(newPathWithTrailingSlash)) {
newPath = newPathWithTrailingSlash;
}
}

#ifdef __NATRON_WIN32__
// Make sure drive letters and share hostnames are upper case so they get properly
// collapsed together in the UI and other path matching.
newPath = ensureCanonicalDriveAndShareNames(newPath);
#endif

return newPath;
}

QVariant
Expand Down Expand Up @@ -900,24 +1003,6 @@ FileSystemModel::rootPath() const
return _imp->currentRootPath;
}

QVariant
FileSystemModel::myComputer(int /*role*/) const
{
// switch (role) {
// case Qt::DisplayRole:
#ifdef Q_OS_WIN

return tr("Computer");
#else

return tr("Computer");
#endif
// case Qt::DecorationRole:
// return d->fileInfoGatherer.iconProvider()->icon(QFileIconProvider::Computer);
// }
// return QVariant();
}

void
FileSystemModel::setFilter(const QDir::Filters& filters)
{
Expand Down Expand Up @@ -1197,7 +1282,7 @@ FileSystemModelPrivate::getItemFromPath(const QString &path) const
if ( path.isEmpty() || !rootItem ) {
return rootItem;
}
QStringList splitPath = getSplitPath(path);
QStringList splitPath = FileSystemModel::getSplitPath(path);

return rootItem->matchPath( splitPath, 0 );
}
Expand All @@ -1209,11 +1294,8 @@ FileSystemModel::setRootPath(const QString& path)


///Check if the path exists
if ( !path.isEmpty() ) {
QDir dir(path);
if ( !dir.exists() ) {
return false;
}
if ( !path.isEmpty() && !QDir(path).exists()) {
return false;
}

_imp->currentRootPath = path;
Expand Down Expand Up @@ -1314,8 +1396,7 @@ FileSystemModel::onWatchedDirectoryChanged(const QString& directory)
}
}

QDir dir(_imp->currentRootPath);
if ( !dir.exists() ) {
if ( !QDir(_imp->currentRootPath).exists() ) {
///The current directory has changed its name or was deleted.. just fallback the filesystem to the root-path
setRootPath( QDir::rootPath() );
} else {
Expand Down
4 changes: 2 additions & 2 deletions Engine/FileSystemModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON

static bool isDriveName(const QString& name);
static bool startsWithDriveName(const QString& name);
static QStringList getSplitPath(const QString& path);
static QString cleanPath(const QString& path);
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const OVERRIDE FINAL WARN_UNUSED_RETURN;
virtual Qt::ItemFlags flags(const QModelIndex &index) const OVERRIDE FINAL WARN_UNUSED_RETURN;
virtual int columnCount(const QModelIndex & parent) const OVERRIDE FINAL WARN_UNUSED_RETURN;
Expand Down Expand Up @@ -271,8 +273,6 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON
**/
bool setRootPath(const QString& path);

QVariant myComputer(int role = Qt::DisplayRole) const WARN_UNUSED_RETURN;

void setFilter(const QDir::Filters& filters);

const QDir::Filters filter() const WARN_UNUSED_RETURN;
Expand Down
Loading