Vincent van Ravesteijn wrote:
Yes, but the problem is that you CAN'T do this just by comparing
strings. In many cases, to be sure, you could tell that the file is
loaded by comparing strings. But you can't be sure it isn't loaded
just by comparing strings. Maybe it's loaded, but under a different
pathname.
I'd think that this could be circumvented pretty easily. When the
InsetInclude needs to load the file with the "user specified file
name" FileName::user_defined, it resolves the "normalized" name,
caches it as FileName::normalized and loads the file. Now the file is
in the BufferList under the normalized name (and we could make sure
that the normalized name is just everywhere internally). loadIfNeeded
would then only have to check whether the FileName::normalized is in
the BufferList (when it is not empty of course). This can be a simple
string comparison.
I pretty much think (and Georg sort of agreed) that this was the
design behind the FileName class, but things got messed up when using
qt-stuff as the real backbone.
OK, so one idea would be something like the attached. I haven't actually
tested this---no time at the moment---but surely something along these
lines works, and works as a general solution. The idea is to cache the
return value of QFileInfo::canonicalFilePath() in FileName itself, and
use it to do the comparisons.
One thing I'm not sure about is whether the weird cases previously
handled in operator== have been correctly transferred to the new
canonicalFilename() method. Btw, at present, we could just put that in
Private, since it's only used internally, but I can imagine wanting it.
That said, QFileInfo already caches this data. So why wasn't that good
enough? Why did Bennett have the problem that required the refresh()
calls that lead to the additional filesystem access? I think the answer
is that the first time the comparison was made, the file didn't exist,
and THAT fact got cached, so that the next time the comparison was made,
it returned false, even though the filenames were the same. (This is the
odd behavior of QFileInfo that Vincent ran into somewhere else.) So we
had to refresh. I think this case is dealt with properly here. If we get
nothing back from canonicalFilename(), then we just compare the paths.
Then again, if there were some other way to avoid the refresh() call,
that would also solve this problem, since then we can let Qt cache for
us. But I don't see one.
Richard
Index: src/support/FileName.cpp
===================================================================
--- src/support/FileName.cpp (revision 28882)
+++ src/support/FileName.cpp (working copy)
@@ -119,9 +119,16 @@
#else
fi = QFileInfo(fi.absoluteFilePath());
#endif
+ canonicalFilename_.clear();
}
+ void clone(Private rhs)
+ {
+ fi = rhs.fi;
+ canonicalFilename_ = rhs.canonicalFilename_;
+ }
+
static
bool isFilesystemEqual(QString const & lhs, QString const & rhs)
{
@@ -131,6 +138,8 @@
///
QFileInfo fi;
+ /// cache this, since it can be expensive to look it up
+ std::string canonicalFilename_;
};
/////////////////////////////////////////////////////////////////////
@@ -159,7 +168,7 @@
FileName::FileName(FileName const & rhs) : d(new Private)
{
- d->fi = rhs.d->fi;
+ d->clone(rhs);
}
@@ -171,7 +180,7 @@
FileName & FileName::operator=(FileName const & rhs)
{
- d->fi = rhs.d->fi;
+ d->clone(rhs);
return *this;
}
@@ -194,9 +203,30 @@
}
+string FileName::canonicalFilename() const
+{
+ if (!d->canonicalFilename_.empty())
+ return d->canonicalFilename_;
+
+ // FIXME: In future use Qt.
+ // Qt 4.4: We need to solve this warning from Qt documentation:
+ // * Long and short file names that refer to the same file on Windows are
+ // treated as if they referred to different files.
+ // This is supposed to be fixed for Qt5.
+ FileName const f(os::internal_path(absFilename()));
+
+ if (f.empty())
+ return d->canonicalFilename_; // still empty
+
+ d->canonicalFilename_ = fromqstr(f.d->fi.canonicalFilePath());
+ return d->canonicalFilename_;
+}
+
+
void FileName::set(string const & name)
{
d->fi.setFile(toqstr(name));
+ d->canonicalFilename_.clear();
}
@@ -206,12 +236,14 @@
d->fi.setFile(rhs.d->fi.filePath() + toqstr(suffix));
else
d->fi.setFile(QDir(rhs.d->fi.absoluteFilePath()), toqstr(suffix));
+ d->canonicalFilename_.clear();
}
void FileName::erase()
{
d->fi = QFileInfo();
+ d->canonicalFilename_.clear();
}
@@ -921,49 +953,13 @@
}
-// Note: According to Qt, QFileInfo::operator== is undefined when
-// both files do not exist (Qt4.5 gives true for all non-existent
-// files, while Qt4.4 compares the filenames).
-// see:
-// http://www.qtsoftware.com/developer/task-tracker/
-// index_html?id=248471&method=entry.
-bool operator==(FileName const & l, FileName const & r)
+bool operator==(FileName const & lhs, FileName const & rhs)
{
- // FIXME: In future use Qt.
- // Qt 4.4: We need to solve this warning from Qt documentation:
- // * Long and short file names that refer to the same file on Windows are
- // treated as if they referred to different files.
- // This is supposed to be fixed for Qt5.
- FileName const lhs(os::internal_path(l.absFilename()));
- FileName const rhs(os::internal_path(r.absFilename()));
-
- if (lhs.empty())
- // QFileInfo::operator==() returns false if the two QFileInfo are empty.
- return rhs.empty();
-
- if (rhs.empty())
- // Avoid unnecessary checks below.
- return false;
-
- lhs.d->refresh();
- rhs.d->refresh();
-
- if (!lhs.d->fi.isSymLink() && !rhs.d->fi.isSymLink()) {
- // Qt already checks if the filesystem is case sensitive or not.
- // see note above why the extra check with fileName is needed.
- return lhs.d->fi == rhs.d->fi
- && lhs.d->fi.fileName() == rhs.d->fi.fileName();
- }
-
- // FIXME: When/if QFileInfo support symlink comparison, remove this code.
- QFileInfo fi1(lhs.d->fi);
- if (fi1.isSymLink())
- fi1 = QFileInfo(fi1.symLinkTarget());
- QFileInfo fi2(rhs.d->fi);
- if (fi2.isSymLink())
- fi2 = QFileInfo(fi2.symLinkTarget());
- // see note above why the extra check with fileName is needed.
- return fi1 == fi2 && fi1.fileName() == fi2.fileName();
+ string lhsName = lhs.canonicalFilename();
+ string rhsName = rhs.canonicalFilename();
+ if (lhsName.empty() && rhsName.empty())
+ return lhs.absFilename() == rhs.absFilename();
+ return lhsName == rhsName;
}
Index: src/support/FileName.h
===================================================================
--- src/support/FileName.h (revision 28882)
+++ src/support/FileName.h (working copy)
@@ -64,6 +64,10 @@
/// get the absolute file name in UTF-8 encoding
std::string absFilename() const;
+ /// get the "normalized" filename, i.e., with symlinks etc resolved
+ /// returns empty if there is an error, or if the file does not exist
+ std::string canonicalFilename() const;
+
/**
* Get the file name in the encoding used by the file system.
* Only use this for accessing the file, e.g. with an fstream.