mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-04 14:42:29 -06:00
@@ -157,10 +157,36 @@ void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QStr
|
||||
emit addTorrentFailed(source, reason);
|
||||
}
|
||||
|
||||
void AddTorrentManager::handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message)
|
||||
void AddTorrentManager::handleDuplicateTorrent(const QString &source
|
||||
, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent)
|
||||
{
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
existingTorrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
|
||||
const bool isPrivate = existingTorrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||
QString message;
|
||||
if (!btSession()->isMergeTrackersEnabled())
|
||||
{
|
||||
message = tr("Merging of trackers is disabled");
|
||||
}
|
||||
else if (isPrivate)
|
||||
{
|
||||
message = tr("Trackers cannot be merged because it is a private torrent");
|
||||
}
|
||||
else
|
||||
{
|
||||
// merge trackers and web seeds
|
||||
existingTorrent->addTrackers(torrentDescr.trackers());
|
||||
existingTorrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
message = tr("Trackers are merged from new source");
|
||||
}
|
||||
|
||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
|
||||
.arg(source, torrent->name(), message));
|
||||
.arg(source, existingTorrent->name(), message));
|
||||
emit addTorrentFailed(source, message);
|
||||
}
|
||||
|
||||
@@ -184,32 +210,7 @@ bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::
|
||||
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||
{
|
||||
// a duplicate torrent is being added
|
||||
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
torrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
|
||||
if (!btSession()->isMergeTrackersEnabled())
|
||||
{
|
||||
handleDuplicateTorrent(source, torrent, tr("Merging of trackers is disabled"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||
if (isPrivate)
|
||||
{
|
||||
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// merge trackers and web seeds
|
||||
torrent->addTrackers(torrentDescr.trackers());
|
||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
|
||||
handleDuplicateTorrent(source, torrent, tr("Trackers are merged from new source"));
|
||||
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ protected:
|
||||
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||
void handleAddTorrentFailed(const QString &source, const QString &reason);
|
||||
void handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message);
|
||||
void handleDuplicateTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent);
|
||||
void setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
|
||||
void releaseTorrentFileGuard(const QString &source);
|
||||
|
||||
|
||||
@@ -92,7 +92,8 @@ namespace BitTorrent
|
||||
{
|
||||
Default = 0,
|
||||
MMap = 1,
|
||||
Posix = 2
|
||||
Posix = 2,
|
||||
SimplePreadPwrite = 3
|
||||
};
|
||||
Q_ENUM_NS(DiskIOType)
|
||||
|
||||
|
||||
@@ -526,7 +526,7 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
||||
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
||||
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
|
||||
, m_torrentContentRemoveOption {BITTORRENT_SESSION_KEY(u"TorrentContentRemoveOption"_s), TorrentContentRemoveOption::MoveToTrash}
|
||||
, m_torrentContentRemoveOption {BITTORRENT_SESSION_KEY(u"TorrentContentRemoveOption"_s), TorrentContentRemoveOption::Delete}
|
||||
, m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
|
||||
, m_seedingLimitTimer {new QTimer(this)}
|
||||
, m_resumeDataTimer {new QTimer(this)}
|
||||
@@ -1638,6 +1638,13 @@ void SessionImpl::initializeNativeSession()
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
// preserve the same behavior as in earlier libtorrent versions
|
||||
pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
|
||||
|
||||
// This is a special case. We use MMap disk IO but tweak it to always fallback to pread/pwrite.
|
||||
if (diskIOType() == DiskIOType::SimplePreadPwrite)
|
||||
{
|
||||
pack.set_int(lt::settings_pack::mmap_file_size_cutoff, std::numeric_limits<int>::max());
|
||||
pack.set_int(lt::settings_pack::disk_write_mode, lt::settings_pack::mmap_write_mode_t::always_pwrite);
|
||||
}
|
||||
#endif
|
||||
|
||||
lt::session_params sessionParams {std::move(pack), {}};
|
||||
@@ -1648,6 +1655,7 @@ void SessionImpl::initializeNativeSession()
|
||||
sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
|
||||
break;
|
||||
case DiskIOType::MMap:
|
||||
case DiskIOType::SimplePreadPwrite:
|
||||
sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
|
||||
break;
|
||||
default:
|
||||
@@ -2807,6 +2815,19 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
||||
loadTorrentParams.name = contentName;
|
||||
}
|
||||
|
||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||
|
||||
Q_ASSERT(p.file_priorities.empty());
|
||||
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
||||
QList<DownloadPriority> filePriorities = addTorrentParams.filePriorities;
|
||||
|
||||
// Filename filter should be applied before `findIncompleteFiles()` is called.
|
||||
if (filePriorities.isEmpty() && isExcludedFileNamesEnabled())
|
||||
{
|
||||
// Check file name blacklist when priorities are not explicitly set
|
||||
applyFilenameFilter(filePaths, filePriorities);
|
||||
}
|
||||
|
||||
if (!loadTorrentParams.hasFinishedStatus)
|
||||
{
|
||||
const Path actualDownloadPath = useAutoTMM
|
||||
@@ -2815,24 +2836,12 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
||||
isFindingIncompleteFiles = true;
|
||||
}
|
||||
|
||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||
if (!isFindingIncompleteFiles)
|
||||
{
|
||||
for (int index = 0; index < filePaths.size(); ++index)
|
||||
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
|
||||
}
|
||||
|
||||
Q_ASSERT(p.file_priorities.empty());
|
||||
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
||||
|
||||
QList<DownloadPriority> filePriorities = addTorrentParams.filePriorities;
|
||||
|
||||
if (filePriorities.isEmpty() && isExcludedFileNamesEnabled())
|
||||
{
|
||||
// Check file name blacklist when priorities are not explicitly set
|
||||
applyFilenameFilter(filePaths, filePriorities);
|
||||
}
|
||||
|
||||
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
||||
@@ -5206,6 +5215,9 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
||||
if (torrent)
|
||||
{
|
||||
torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
|
||||
// The torrent may become "finished" at the end of the move if it was moved
|
||||
// from the "incomplete" location after downloading finished.
|
||||
processPendingFinishedTorrents();
|
||||
}
|
||||
else if (!torrentHasOutstandingJob)
|
||||
{
|
||||
@@ -5217,6 +5229,32 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
||||
}
|
||||
}
|
||||
|
||||
void SessionImpl::processPendingFinishedTorrents()
|
||||
{
|
||||
if (m_pendingFinishedTorrents.isEmpty())
|
||||
return;
|
||||
|
||||
for (TorrentImpl *torrent : asConst(m_pendingFinishedTorrents))
|
||||
{
|
||||
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
||||
emit torrentFinished(torrent);
|
||||
|
||||
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
||||
exportTorrentFile(torrent, exportPath);
|
||||
|
||||
processTorrentShareLimits(torrent);
|
||||
}
|
||||
|
||||
m_pendingFinishedTorrents.clear();
|
||||
|
||||
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
||||
{
|
||||
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
||||
});
|
||||
if (!hasUnfinishedTorrents)
|
||||
emit allTorrentsFinished();
|
||||
}
|
||||
|
||||
void SessionImpl::storeCategories() const
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
@@ -6108,28 +6146,7 @@ void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
|
||||
if (!updatedTorrents.isEmpty())
|
||||
emit torrentsUpdated(updatedTorrents);
|
||||
|
||||
if (!m_pendingFinishedTorrents.isEmpty())
|
||||
{
|
||||
for (TorrentImpl *torrent : m_pendingFinishedTorrents)
|
||||
{
|
||||
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
||||
emit torrentFinished(torrent);
|
||||
|
||||
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
||||
exportTorrentFile(torrent, exportPath);
|
||||
|
||||
processTorrentShareLimits(torrent);
|
||||
}
|
||||
|
||||
m_pendingFinishedTorrents.clear();
|
||||
|
||||
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
||||
{
|
||||
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
||||
});
|
||||
if (!hasUnfinishedTorrents)
|
||||
emit allTorrentsFinished();
|
||||
}
|
||||
processPendingFinishedTorrents();
|
||||
|
||||
if (m_needSaveTorrentsQueue)
|
||||
saveTorrentsQueue();
|
||||
|
||||
@@ -593,6 +593,7 @@ namespace BitTorrent
|
||||
|
||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
||||
void processPendingFinishedTorrents();
|
||||
|
||||
void loadCategories();
|
||||
void storeCategories() const;
|
||||
|
||||
@@ -1819,7 +1819,7 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
||||
m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
|
||||
}
|
||||
|
||||
m_session->applyFilenameFilter(fileNames, m_filePriorities);
|
||||
m_session->applyFilenameFilter(m_filePaths, m_filePriorities);
|
||||
for (int i = 0; i < m_filePriorities.size(); ++i)
|
||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
|
||||
|
||||
|
||||
@@ -429,6 +429,19 @@ void Preferences::setWinStartup(const bool b)
|
||||
settings.remove(profileID);
|
||||
}
|
||||
}
|
||||
|
||||
QString Preferences::getStyle() const
|
||||
{
|
||||
return value<QString>(u"Appearance/Style"_s);
|
||||
}
|
||||
|
||||
void Preferences::setStyle(const QString &styleName)
|
||||
{
|
||||
if (styleName == getStyle())
|
||||
return;
|
||||
|
||||
setValue(u"Appearance/Style"_s, styleName);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
// Downloads
|
||||
|
||||
@@ -130,6 +130,8 @@ public:
|
||||
#ifdef Q_OS_WIN
|
||||
bool WinStartup() const;
|
||||
void setWinStartup(bool b);
|
||||
QString getStyle() const;
|
||||
void setStyle(const QString &styleName);
|
||||
#endif
|
||||
|
||||
// Downloads
|
||||
|
||||
@@ -271,6 +271,11 @@ Path Utils::OS::windowsSystemPath()
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||
bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||
{
|
||||
// Trying to apply this to a non-existent file is unacceptable,
|
||||
// as it may unexpectedly create such a file.
|
||||
if (!file.exists())
|
||||
return false;
|
||||
|
||||
Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:"));
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
|
||||
Reference in New Issue
Block a user