Browse Source

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.
adaptive-webui-19844
Chocobo1 6 years ago
parent
commit
440860c4a9
No known key found for this signature in database
GPG Key ID: 210D9C873253A68C
  1. 59
      src/base/bittorrent/torrenthandle.cpp
  2. 10
      src/base/bittorrent/torrenthandle.h
  3. 11
      src/base/utils/string.cpp
  4. 4
      src/base/utils/string.h
  5. 9
      src/gui/torrentcontenttreeview.cpp

59
src/base/bittorrent/torrenthandle.cpp

@ -59,6 +59,7 @@
#include "base/profile.h" #include "base/profile.h"
#include "base/tristatebool.h" #include "base/tristatebool.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/string.h"
#include "downloadpriority.h" #include "downloadpriority.h"
#include "peerinfo.h" #include "peerinfo.h"
#include "session.h" #include "session.h"
@ -1481,6 +1482,7 @@ void TorrentHandle::moveStorage(const QString &newPath, bool overwrite)
void TorrentHandle::renameFile(const int index, const QString &name) void TorrentHandle::renameFile(const int index, const QString &name)
{ {
m_oldPath[LTFileIndex {index}].push_back(filePath(index));
++m_renameCount; ++m_renameCount;
m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString()); m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString());
} }
@ -1744,29 +1746,42 @@ void TorrentHandle::handleFastResumeRejectedAlert(const lt::fastresume_rejected_
void TorrentHandle::handleFileRenamedAlert(const lt::file_renamed_alert *p) void TorrentHandle::handleFileRenamedAlert(const lt::file_renamed_alert *p)
{ {
const QString newName = Utils::Fs::fromNativePath(p->new_name());
// 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();
const 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. // 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. // All we need to do is make sure we have a valid instance of the TorrentInfo object.
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; 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<QStringRef> oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts);
oldPathParts.removeLast(); // drop file name part
QVector<QStringRef> 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; --m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();
@ -1774,12 +1789,14 @@ void TorrentHandle::handleFileRenamedAlert(const lt::file_renamed_alert *p)
void TorrentHandle::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p) void TorrentHandle::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
{ {
Q_UNUSED(p);
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"") LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
.arg(name(), filePath(p->index) .arg(name(), filePath(p->index)
, QString::fromStdString(p->error.message())), Log::WARNING); , 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; --m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();

10
src/base/bittorrent/torrenthandle.h

@ -355,6 +355,12 @@ namespace BitTorrent
private: private:
typedef std::function<void ()> EventTrigger; typedef std::function<void ()> EventTrigger;
#if (LIBTORRENT_VERSION_NUM < 10200)
using LTFileIndex = int;
#else
using LTFileIndex = lt::file_index_t;
#endif
void updateStatus(); void updateStatus();
void updateStatus(const lt::torrent_status &nativeStatus); void updateStatus(const lt::torrent_status &nativeStatus);
void updateState(); void updateState();
@ -417,6 +423,10 @@ namespace BitTorrent
QQueue<EventTrigger> m_moveFinishedTriggers; QQueue<EventTrigger> m_moveFinishedTriggers;
int m_renameCount; 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<LTFileIndex, QVector<QString>> m_oldPath;
bool m_useAutoTMM; bool m_useAutoTMM;
// Persistent data // Persistent data

11
src/base/utils/string.cpp

@ -198,3 +198,14 @@ TriStateBool Utils::String::parseTriStateBool(const QString &string)
return TriStateBool::False; return TriStateBool::False;
return TriStateBool::Undefined; return TriStateBool::Undefined;
} }
QString Utils::String::join(const QVector<QStringRef> &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;
}

4
src/base/utils/string.h

@ -31,8 +31,10 @@
#define UTILS_STRING_H #define UTILS_STRING_H
#include <QLatin1String> #include <QLatin1String>
#include <QVector>
class QString; class QString;
class QStringRef;
class TriStateBool; class TriStateBool;
@ -66,6 +68,8 @@ namespace Utils
bool parseBool(const QString &string, bool defaultValue); bool parseBool(const QString &string, bool defaultValue);
TriStateBool parseTriStateBool(const QString &string); TriStateBool parseTriStateBool(const QString &string);
QString join(const QVector<QStringRef> &strings, const QString &separator);
} }
} }

9
src/gui/torrentcontenttreeview.cpp

@ -204,15 +204,6 @@ void TorrentContentTreeView::renameSelectedFile(BitTorrent::TorrentHandle *torre
if (needForceRecheck) if (needForceRecheck)
torrent->forceRecheck(); 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); model->setData(modelIndex, newName);
} }
} }

Loading…
Cancel
Save