Utils: Split off file access interface from IDevice

The file accessing functions form now a class hierarchy by themselves,
the devices return a suitable point.

The previous implementation was mildly confusing by the special handling
of the DesktopDevice, fallbacks and remote cases in the same function
leading to unnecessary boilerplate when adding new functions and
codepaths that sometimes passed the FilePath API twice.

Implemented are a "DesktopDeviceFileAccess" taking care of the
previous !needsDevice() branches and a "UnixDeviceFileAccess"
covering the current docker and RL uses.

As a side-effect this unifies to a large degree the current docker
and RL code paths with were occasionally deviating from each other
while they shouldn't.

Change-Id: I4ff59d4be2a07d13e2ca5e9ace26a84160a87c9d
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
hjk
2022-10-10 17:32:56 +02:00
parent e0832ce7fc
commit 1fa3255242
21 changed files with 1233 additions and 1737 deletions

View File

@@ -646,46 +646,6 @@ FilePaths FileUtils::getOpenFilePaths(QWidget *parent,
#endif // QT_WIDGETS_LIB
// Used on 'ls' output on unix-like systems.
static void iterateLsOutput(const FilePath &base,
const QStringList &entries,
const FileFilter &filter,
const FilePath::IterateDirCallback &callBack)
{
const QList<QRegularExpression> nameRegexps =
transform(filter.nameFilters, [](const QString &filter) {
QRegularExpression re;
re.setPattern(QRegularExpression::wildcardToRegularExpression(filter));
QTC_CHECK(re.isValid());
return re;
});
const auto nameMatches = [&nameRegexps](const QString &fileName) {
for (const QRegularExpression &re : nameRegexps) {
const QRegularExpressionMatch match = re.match(fileName);
if (match.hasMatch())
return true;
}
return nameRegexps.isEmpty();
};
// FIXME: Handle filters. For now bark on unsupported options.
QTC_CHECK(filter.fileFilters == QDir::NoFilter);
for (const QString &entry : entries) {
if (!nameMatches(entry))
continue;
const FilePath current = base.pathAppended(entry);
bool res = false;
if (callBack.index() == 0)
res = std::get<0>(callBack)(current);
else
res = std::get<1>(callBack)(current, current.filePathInfo());
if (!res)
break;
}
}
FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString)
{
bool ok = false;
@@ -741,120 +701,6 @@ FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos)
return {size, flags, dt};
}
static bool iterateWithFindHelper(
const FilePath &filePath,
const FileFilter &filter,
const std::function<RunResult(const CommandLine &)> &runInShell,
const std::function<bool(const QString &)> callBack,
const QString &extraArguments)
{
QTC_CHECK(filePath.isAbsolutePath());
const QStringList arguments = filter.asFindArguments(filePath.path());
CommandLine cmdLine{"find", arguments};
if (!extraArguments.isEmpty())
cmdLine.addArgs(extraArguments, CommandLine::Raw);
const RunResult result = runInShell(cmdLine);
const QString out = QString::fromUtf8(result.stdOut);
if (result.exitCode != 0) {
// Find returns non-zero exit code for any error it encounters, even if it finds some files.
if (!out.startsWith('"' + filePath.path())) {
if (!filePath.exists()) // File does not exist, so no files to find.
return true;
// If the output does not start with the path we are searching in, find has failed.
// Possibly due to unknown options.
return false;
}
}
QStringList entries = out.split("\n", Qt::SkipEmptyParts);
if (entries.isEmpty())
return true;
// Remove the first line, it is always the directory we are searching in.
// as long as we do not specify "mindepth > 0"
if (entries.size() > 0)
entries.pop_front();
for (const QString &entry : entries) {
if (!callBack(entry))
break;
}
return true;
}
// returns whether 'find' could be used.
static bool iterateWithFind(
const FilePath &filePath,
const FileFilter &filter,
const std::function<RunResult(const CommandLine &)> &runInShell,
const FilePath::IterateDirCallback &callBack)
{
const auto toFilePath = [&filePath, &callBack](const QString &entry) {
if (callBack.index() == 0)
return std::get<0>(callBack)(filePath.withNewPath(entry));
const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1);
const QString infos = entry.mid(fileName.length() + 3);
const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos);
if (!fi.fileFlags)
return true;
const FilePath fp = filePath.withNewPath(fileName);
return std::get<1>(callBack)(fp, fi);
};
// TODO: Using stat -L will always return the link target, not the link itself.
// We may wan't to add the information that it is a link at some point.
QString infoArgs;
if (callBack.index() == 1)
infoArgs = R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)";
return iterateWithFindHelper(filePath, filter, runInShell, toFilePath, infoArgs);
}
static void findUsingLs(const QString &current,
const FileFilter &filter,
const std::function<RunResult(const CommandLine &)> &runInShell,
QStringList *found)
{
const RunResult result = runInShell({"ls", {"-1", "-p", "--", current}});
const QStringList entries = QString::fromUtf8(result.stdOut).split('\n', Qt::SkipEmptyParts);
for (QString entry : entries) {
const QChar last = entry.back();
if (last == '/') {
entry.chop(1);
if (filter.iteratorFlags.testFlag(QDirIterator::Subdirectories))
findUsingLs(current + '/' + entry, filter, runInShell, found);
}
found->append(entry);
}
}
void FileUtils::iterateUnixDirectory(const FilePath &filePath,
const FileFilter &filter,
bool *useFind,
const std::function<RunResult (const CommandLine &)> &runInShell,
const FilePath::IterateDirCallback &callBack)
{
// We try to use 'find' first, because that can filter better directly.
// Unfortunately, it's not installed on all devices by default.
if (useFind && *useFind) {
if (iterateWithFind(filePath, filter, runInShell, callBack))
return;
*useFind = false; // remember the failure for the next time and use the 'ls' fallback below.
}
// if we do not have find - use ls as fallback
QStringList entries;
findUsingLs(filePath.path(), filter, runInShell, &entries);
iterateLsOutput(filePath, entries, filter, callBack);
}
/*!
Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain
the target directory, which will be created. Example usage: