From 8b6fb4416961e2b660ea972eebaf3ee6a76dc4da Mon Sep 17 00:00:00 2001 From: APTX Date: Sun, 5 May 2013 02:53:19 +0200 Subject: [PATCH] Improve renaming on windows. Files can now be renamed, even if they differ in case only. Case changes are more difficult to handle on windows, because a case change does not change the path, requiring checking and/or renaming of every path element. The path stored in file location is now the path reported by the system. --- localmylist/filelocationchecktask.cpp | 19 ++++- localmylist/renamehandler.cpp | 23 ++++-- localmylist/renameutils.cpp | 115 ++++++++++++++++++++++++++ localmylist/renameutils.h | 4 + 4 files changed, 152 insertions(+), 9 deletions(-) diff --git a/localmylist/filelocationchecktask.cpp b/localmylist/filelocationchecktask.cpp index 1e8b584..bba8286 100644 --- a/localmylist/filelocationchecktask.cpp +++ b/localmylist/filelocationchecktask.cpp @@ -1,6 +1,8 @@ #include "filelocationchecktask.h" #include "database.h" +#include "renameutils.h" + #include #include @@ -28,7 +30,7 @@ void FileLocationCheckTask::workUnit() db->transaction(); QList fileLocations = db->getFileLocationBatch(lastLocationId, OPERATIONS_PER_UNIT); - for (const FileLocation &fl : fileLocations) + for (FileLocation &fl : fileLocations) { if (!QFileInfo(fl.path).exists()) { @@ -37,6 +39,21 @@ void FileLocationCheckTask::workUnit() .arg(fl.path)); db->removeFileLocation(fl.locationId); } + else + { + QString canonicalPath = exactPath(QFileInfo(fl.path).canonicalFilePath()); + + if (!canonicalPath.isEmpty() && canonicalPath != fl.path) + { + qDebug() << QObject::tr("Fixing File location %1: %2") + .arg(fl.locationId) + .arg(fl.path); + fl.path = canonicalPath; + db->setFileLocation(fl); + } + + } + lastLocationId = fl.locationId; } db->commit(); diff --git a/localmylist/renamehandler.cpp b/localmylist/renamehandler.cpp index 52c160c..fd59717 100644 --- a/localmylist/renamehandler.cpp +++ b/localmylist/renamehandler.cpp @@ -103,11 +103,8 @@ void RenameHandler::handleRename() QString newFileString = newFilePath + "/" + newFileName; QFileInfo newFile(newFileString); -#ifdef Q_OS_WIN - bool isSamePath = oldFile.canonicalFilePath().compare(newFileString, Qt::CaseInsensitive) == 0; -#else bool isSamePath = oldFile.canonicalFilePath() == newFileString; -#endif + bool differenceInCaseOnly = !isSamePath && oldFile.canonicalFilePath().compare(newFileString, Qt::CaseInsensitive) == 0; if (isSamePath) { @@ -116,7 +113,7 @@ void RenameHandler::handleRename() continue; } - if (newFile.exists()) + if (!differenceInCaseOnly && newFile.exists()) { fl.failedRename = true; fl.renameError = tr("Rename: Failed to rename file <%1>. Destination <%2> exists.").arg(oldFile.filePath()).arg(newFileString); @@ -166,7 +163,8 @@ void RenameHandler::handleRename() } #endif - if (!QFile::rename(oldFile.canonicalFilePath(), newFileString)) + QString exactFileName; + if (!renameFile(oldFile.canonicalFilePath(), newFileString, &exactFileName)) { fl.failedRename = true; fl.renameError = tr("Rename: Failed to rename file <%1>. Failed to rename file to <%2>").arg(oldFile.canonicalFilePath()).arg(newFileString); @@ -175,12 +173,21 @@ void RenameHandler::handleRename() continue; } - fl.path = newFileString; + if (exactFileName.isEmpty()) + { + qWarning() << "exactFileName is empty"; + fl.path = newFileString; + } + else + { + fl.path = exactFileName; + } + fl.failedRename = false; fl.renameError = ""; db->setFileLocation(fl); - db->log(tr("Rename: File <%1> was renamed to <%2>").arg(oldFile.canonicalFilePath()).arg(newFileString)); + db->log(tr("Rename: File <%1> was renamed to <%2>").arg(oldFile.canonicalFilePath()).arg(fl.path)); if (settings->get("renameRemoveEmptyDirectories", false)) { diff --git a/localmylist/renameutils.cpp b/localmylist/renameutils.cpp index a1e115f..e6f4bdb 100644 --- a/localmylist/renameutils.cpp +++ b/localmylist/renameutils.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace LocalMyList { void setupRenameEnv(const QSqlRecord &record, RenameParser::Environment &env) @@ -65,4 +67,117 @@ void setupRenameEnv(const QSqlRecord &record, RenameParser::Environment &env) env["MaxRelatedEpNo"] = record.value("max_related_epno").toString(); } +#ifdef Q_OS_WIN +#include +#include +#endif + + +QString toWindowsPath(const QString &path) +{ + return QDir::toNativeSeparators(path).prepend("\\\\?\\"); +} + +QString exactPath(const QString &path) +{ +#ifndef Q_OS_WIN + return QFileInfo(path).canonicalFilePath(); +#else + HANDLE handle = INVALID_HANDLE_VALUE; + static const DWORD BUFFERSIZE = 10000; + wchar_t finalName[BUFFERSIZE]; + + QString pathWindows = toWindowsPath(path); + const wchar_t *pathW = reinterpret_cast(pathWindows.utf16()); + + handle = CreateFileW(pathW, + 0, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if (handle == INVALID_HANDLE_VALUE) + { + DWORD lastError = GetLastError(); + qDebug() << "exactPath invalid handle" << lastError << path; + return QString(); + } + + DWORD length = GetFinalPathNameByHandleW(handle, + finalName, + BUFFERSIZE, + VOLUME_NAME_DOS); + + if (length > BUFFERSIZE) + return QString(); + + QString ret = QString::fromWCharArray(finalName, length); + CloseHandle(handle); + + ret = ret.mid(4); + ret = QDir::fromNativeSeparators(ret); + + return ret; +#endif +} + +bool renameFile(const QString &oldName, const QString &newName, QString *actualPath) +{ +#ifndef Q_OS_WIN + if (!QFile::rename(oldName, newName)) + return false; + + if (actualPath) *actualPath = exactPath(oldName); + return true; + +#else + if (oldName == newName) + { + if (actualPath) *actualPath = exactPath(oldName); + return true; + } + + const QString oldNameWindows = toWindowsPath(oldName); + const QString newNameWindows = toWindowsPath(newName); + const wchar_t *oldNameW = reinterpret_cast(oldNameWindows.utf16()); + const wchar_t *newNameW = reinterpret_cast(newNameWindows.utf16()); + + bool ret = MoveFileW(oldNameW, newNameW); + + if (!ret) + { + DWORD lastError = GetLastError(); + qDebug() << "Move Failed" << lastError; + return false; + } + + const QString newNameExact = exactPath(newName); + + if (newNameExact.isEmpty()) + { + qDebug() << "Failed to get exact path"; + return false; + } + + const QString newNameDir = QFileInfo(newName).absolutePath(); + const QString newNameExactDir = QFileInfo(newNameExact).absolutePath(); + + // Paths differ in case only + if (newNameDir != newNameExactDir && newNameDir.compare(newNameExactDir, Qt::CaseInsensitive) == 0) + { + bool ret = renameFile(newNameExactDir, newNameDir); + if (actualPath) *actualPath = exactPath(newName); + return ret; + } + + // Either paths are identical or the new path differs by more than case. + if (actualPath) *actualPath = newNameExact; + + return true; +#endif +} + + } // namespace LocalMyList diff --git a/localmylist/renameutils.h b/localmylist/renameutils.h index c5628a0..874942d 100644 --- a/localmylist/renameutils.h +++ b/localmylist/renameutils.h @@ -4,11 +4,15 @@ #include "localmylist_global.h" #include #include +#include namespace LocalMyList { void LOCALMYLISTSHARED_EXPORT setupRenameEnv(const QSqlRecord &record, RenameParser::Environment &env); +QString exactPath(const QString &path); +bool LOCALMYLISTSHARED_EXPORT renameFile(const QString &oldName, const QString &newName, QString *actualPath = 0); + } // namespace LocalMyList #endif // RENAMEUTILS_H -- 2.52.0