From e329c41ef2952b7cc9db1a19bd69dd69356ca17d Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Tue, 4 Jun 2019 20:46:41 +0800 Subject: [PATCH] Properly remove empty leftover folders after rename TorrentInfo::origFilePath will return the very original path from .torrent file, not the most recent file path before the rename operation and thus the code would not be working as we expected. --- src/base/bittorrent/torrenthandle.cpp | 64 +++++++++++++++++---------- src/base/bittorrent/torrenthandle.h | 10 +++++ src/base/utils/string.cpp | 11 +++++ src/base/utils/string.h | 3 ++ src/gui/torrentcontenttreeview.cpp | 9 ---- 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/base/bittorrent/torrenthandle.cpp b/src/base/bittorrent/torrenthandle.cpp index 93aa75d60..2da728bcf 100644 --- a/src/base/bittorrent/torrenthandle.cpp +++ b/src/base/bittorrent/torrenthandle.cpp @@ -1402,6 +1402,7 @@ void TorrentHandle::setTrackerLogin(const QString &username, const QString &pass void TorrentHandle::renameFile(int index, const QString &name) { + m_oldPath[LTFileIndex {index}].push_back(filePath(index)); ++m_renameCount; qDebug() << Q_FUNC_INFO << index << name; m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString()); @@ -1685,33 +1686,42 @@ void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_r void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert *p) { -#if LIBTORRENT_VERSION_NUM < 10100 - QString newName = Utils::Fs::fromNativePath(QString::fromStdString(p->name)); -#else - QString newName = Utils::Fs::fromNativePath(p->new_name()); -#endif - - // TODO: Check this! - if (filesCount() > 1) { - // Check if folders were renamed - QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split('/'); - oldPathParts.removeLast(); - QString oldPath = oldPathParts.join('/'); - QStringList newPathParts = newName.split('/'); - newPathParts.removeLast(); - QString newPath = newPathParts.join('/'); - if (!newPathParts.isEmpty() && (oldPath != newPath)) { - qDebug("oldPath(%s) != newPath(%s)", qUtf8Printable(oldPath), qUtf8Printable(newPath)); - oldPath = QString("%1/%2").arg(savePath(true), oldPath); - qDebug("Detected folder renaming, attempt to delete old folder: %s", qUtf8Printable(oldPath)); - QDir().rmpath(oldPath); - } - } - // We don't really need to call updateStatus() in this place. // All we need to do is make sure we have a valid instance of the TorrentInfo object. m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; + // remove empty leftover folders + // for example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will + // be removed if they are empty + const QString oldFilePath = m_oldPath[LTFileIndex {p->index}].takeFirst(); + const QString newFilePath = Utils::Fs::fromNativePath(p->new_name()); + + if (m_oldPath[LTFileIndex {p->index}].isEmpty()) + m_oldPath.remove(LTFileIndex {p->index}); + + QVector oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts); + oldPathParts.removeLast(); // drop file name part + QVector newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts); + newPathParts.removeLast(); // drop file name part + +#if defined(Q_OS_WIN) + const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; +#else + const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; +#endif + + int pathIdx = 0; + while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) { + if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0) + break; + ++pathIdx; + } + + for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) { + QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/"))); + oldPathParts.removeLast(); + } + --m_renameCount; while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) m_moveFinishedTriggers.takeFirst()(); @@ -1719,7 +1729,13 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p) { - Q_UNUSED(p); + LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"") + .arg(name(), filePath(p->index) + , QString::fromStdString(p->error.message())), Log::WARNING); + + m_oldPath[LTFileIndex {p->index}].removeFirst(); + if (m_oldPath[LTFileIndex {p->index}].isEmpty()) + m_oldPath.remove(LTFileIndex {p->index}); --m_renameCount; while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) diff --git a/src/base/bittorrent/torrenthandle.h b/src/base/bittorrent/torrenthandle.h index 425e688d4..c9d9d9a67 100644 --- a/src/base/bittorrent/torrenthandle.h +++ b/src/base/bittorrent/torrenthandle.h @@ -385,6 +385,12 @@ namespace BitTorrent private: typedef boost::function EventTrigger; +#if (LIBTORRENT_VERSION_NUM < 10200) + using LTFileIndex = int; +#else + using LTFileIndex = lt::file_index_t; +#endif + void updateStatus(); void updateStatus(const libtorrent::torrent_status &nativeStatus); void updateState(); @@ -446,6 +452,10 @@ namespace BitTorrent QQueue m_moveFinishedTriggers; int m_renameCount; + // Until libtorrent provide an "old_name" field in `file_renamed_alert` + // we will rely on this workaround to remove empty leftover folders + QHash> m_oldPath; + bool m_useAutoTMM; // Persistent data diff --git a/src/base/utils/string.cpp b/src/base/utils/string.cpp index 85b5b9191..59c07031b 100644 --- a/src/base/utils/string.cpp +++ b/src/base/utils/string.cpp @@ -213,3 +213,14 @@ TriStateBool Utils::String::parseTriStateBool(const QString &string) return TriStateBool::False; return TriStateBool::Undefined; } + +QString Utils::String::join(const QVector &strings, const QString &separator) +{ + if (strings.empty()) + return {}; + + QString ret = strings[0].toString(); + for (int i = 1; i < strings.count(); ++i) + ret += (separator + strings[i]); + return ret; +} diff --git a/src/base/utils/string.h b/src/base/utils/string.h index 5a1088622..3f15322da 100644 --- a/src/base/utils/string.h +++ b/src/base/utils/string.h @@ -31,6 +31,7 @@ #define UTILS_STRING_H #include +#include class QByteArray; class QLatin1String; @@ -70,6 +71,8 @@ namespace Utils bool parseBool(const QString &string, const bool defaultValue); TriStateBool parseTriStateBool(const QString &string); + + QString join(const QVector &strings, const QString &separator); } } diff --git a/src/gui/torrentcontenttreeview.cpp b/src/gui/torrentcontenttreeview.cpp index 3e65b70fd..285d3f568 100644 --- a/src/gui/torrentcontenttreeview.cpp +++ b/src/gui/torrentcontenttreeview.cpp @@ -205,15 +205,6 @@ void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentHandle *torre if (needForceRecheck) torrent->forceRecheck(); - // Remove old folder - const QString oldFullPath = torrent->savePath(true) + oldPath; - int timeout = 10; - while (!QDir().rmpath(oldFullPath) && (timeout > 0)) { - // FIXME: We should not sleep here (freezes the UI for 1 second) - QThread::msleep(100); - --timeout; - } - model->setData(modelIndex, newName); } }