Backport changes to v5.0.x branch

PR #21898.
This commit is contained in:
Vladimir Golovnev 2024-12-09 07:04:06 +03:00 committed by GitHub
commit 53adb7bfa8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 195 additions and 126 deletions

View file

@ -29,7 +29,7 @@ LangString launch_qbt ${LANG_ITALIAN} "Esegui qBittorrent."
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions." ;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_ITALIAN} "Questo installer funziona solo con versioni di Windows a 64bit." LangString inst_requires_64bit ${LANG_ITALIAN} "Questo installer funziona solo con versioni di Windows a 64bit."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019." ;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
LangString inst_requires_win10 ${LANG_ITALIAN} "This installer requires at least Windows 10 (1809) / Windows Server 2019." LangString inst_requires_win10 ${LANG_ITALIAN} "Questo installer richiede almeno Windows 10 (1809) / Windows Server 2019."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent" ;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_ITALIAN} "Disinstalla qBittorrent" LangString inst_uninstall_link_description ${LANG_ITALIAN} "Disinstalla qBittorrent"

View file

@ -67,7 +67,7 @@ namespace
{ {
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s; const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s;
const int DB_VERSION = 7; const int DB_VERSION = 8;
const QString DB_TABLE_META = u"meta"_s; const QString DB_TABLE_META = u"meta"_s;
const QString DB_TABLE_TORRENTS = u"torrents"_s; const QString DB_TABLE_TORRENTS = u"torrents"_s;
@ -628,7 +628,31 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
} }
if (fromVersion <= 6) if (fromVersion <= 6)
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, "TEXTNOT NULL DEFAULT `Default`"); addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, "TEXT NOT NULL DEFAULT `Default`");
if (fromVersion == 7)
{
const QString TEMP_COLUMN_NAME = DB_COLUMN_SHARE_LIMIT_ACTION.name + u"_temp";
auto queryStr = u"ALTER TABLE %1 ADD %2 %3"_s
.arg(quoted(DB_TABLE_TORRENTS), TEMP_COLUMN_NAME, u"TEXT NOT NULL DEFAULT `Default`");
if (!query.exec(queryStr))
throw RuntimeError(query.lastError().text());
queryStr = u"UPDATE %1 SET %2 = %3"_s
.arg(quoted(DB_TABLE_TORRENTS), quoted(TEMP_COLUMN_NAME), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
if (!query.exec(queryStr))
throw RuntimeError(query.lastError().text());
queryStr = u"ALTER TABLE %1 DROP %2"_s.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
if (!query.exec(queryStr))
throw RuntimeError(query.lastError().text());
queryStr = u"ALTER TABLE %1 RENAME %2 TO %3"_s
.arg(quoted(DB_TABLE_TORRENTS), quoted(TEMP_COLUMN_NAME), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
if (!query.exec(queryStr))
throw RuntimeError(query.lastError().text());
}
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE}); const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
if (!query.prepare(updateMetaVersionQuery)) if (!query.prepare(updateMetaVersionQuery))

View file

@ -1606,7 +1606,7 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
reannounceToAllTrackers(); reannounceToAllTrackers();
} }
m_wakeupCheckTimestamp = QDateTime::currentDateTime(); m_wakeupCheckTimestamp = now;
}); });
m_wakeupCheckTimestamp = QDateTime::currentDateTime(); m_wakeupCheckTimestamp = QDateTime::currentDateTime();
m_wakeupCheckTimer->start(30s); m_wakeupCheckTimer->start(30s);
@ -5526,8 +5526,6 @@ void SessionImpl::readAlerts()
// Some torrents may become "finished" after different alerts handling. // Some torrents may become "finished" after different alerts handling.
processPendingFinishedTorrents(); processPendingFinishedTorrents();
processTrackerStatuses();
} }
void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert) void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
@ -6203,7 +6201,10 @@ void SessionImpl::handleTrackerAlert(const lt::tracker_alert *alert)
if (!torrent) if (!torrent)
return; return;
const auto prevSize = m_updatedTrackerStatuses.size();
QMap<int, int> &updateInfo = m_updatedTrackerStatuses[torrent->nativeHandle()][std::string(alert->tracker_url())][alert->local_endpoint]; QMap<int, int> &updateInfo = m_updatedTrackerStatuses[torrent->nativeHandle()][std::string(alert->tracker_url())][alert->local_endpoint];
if (prevSize < m_updatedTrackerStatuses.size())
updateTrackerEntryStatuses(torrent->nativeHandle());
if (alert->type() == lt::tracker_reply_alert::alert_type) if (alert->type() == lt::tracker_reply_alert::alert_type)
{ {
@ -6265,17 +6266,6 @@ void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a
} }
#endif #endif
void SessionImpl::processTrackerStatuses()
{
if (m_updatedTrackerStatuses.isEmpty())
return;
for (auto it = m_updatedTrackerStatuses.cbegin(); it != m_updatedTrackerStatuses.cend(); ++it)
updateTrackerEntryStatuses(it.key(), it.value());
m_updatedTrackerStatuses.clear();
}
void SessionImpl::saveStatistics() const void SessionImpl::saveStatistics() const
{ {
if (!m_isStatisticsDirty) if (!m_isStatisticsDirty)
@ -6300,20 +6290,21 @@ void SessionImpl::loadStatistics()
m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong(); m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong();
} }
void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers) void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
{ {
invokeAsync([this, torrentHandle = std::move(torrentHandle), updatedTrackers = std::move(updatedTrackers)]() mutable invokeAsync([this, torrentHandle = std::move(torrentHandle)]() mutable
{ {
try try
{ {
std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers(); std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers();
invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers) invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers)]
, updatedTrackers = std::move(updatedTrackers)]
{ {
TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash()); TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
if (!torrent || torrent->isStopped()) if (!torrent || torrent->isStopped())
return; return;
QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers = m_updatedTrackerStatuses.take(torrentHandle);
QHash<QString, TrackerEntryStatus> trackers; QHash<QString, TrackerEntryStatus> trackers;
trackers.reserve(updatedTrackers.size()); trackers.reserve(updatedTrackers.size());
for (const lt::announce_entry &announceEntry : nativeTrackers) for (const lt::announce_entry &announceEntry : nativeTrackers)
@ -6363,7 +6354,7 @@ void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString
m_removingTorrents.erase(removingTorrentDataIter); m_removingTorrents.erase(removingTorrentDataIter);
} }
QDateTime SessionImpl::fromLTTimePoint32(const libtorrent::time_point32 &timePoint) const QDateTime SessionImpl::fromLTTimePoint32(const lt::time_point32 &timePoint) const
{ {
const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - m_ltNow + lt::milliseconds(500)).count(); const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - m_ltNow + lt::milliseconds(500)).count();
return m_qNow.addSecs(secsSinceNow); return m_qNow.addSecs(secsSinceNow);

View file

@ -541,7 +541,6 @@ namespace BitTorrent
void populateAdditionalTrackers(); void populateAdditionalTrackers();
void enableIPFilter(); void enableIPFilter();
void disableIPFilter(); void disableIPFilter();
void processTrackerStatuses();
void processTorrentShareLimits(TorrentImpl *torrent); void processTorrentShareLimits(TorrentImpl *torrent);
void populateExcludedFileNamesRegExpList(); void populateExcludedFileNamesRegExpList();
void prepareStartup(); void prepareStartup();
@ -605,7 +604,7 @@ namespace BitTorrent
void saveStatistics() const; void saveStatistics() const;
void loadStatistics(); void loadStatistics();
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers); void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle);
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {}); void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});

View file

@ -215,7 +215,15 @@ namespace BitTorrent
virtual int piecesCount() const = 0; virtual int piecesCount() const = 0;
virtual int piecesHave() const = 0; virtual int piecesHave() const = 0;
virtual qreal progress() const = 0; virtual qreal progress() const = 0;
virtual QDateTime addedTime() const = 0; virtual QDateTime addedTime() const = 0;
virtual QDateTime completedTime() const = 0;
virtual QDateTime lastSeenComplete() const = 0;
virtual qlonglong activeTime() const = 0;
virtual qlonglong finishedTime() const = 0;
virtual qlonglong timeSinceUpload() const = 0;
virtual qlonglong timeSinceDownload() const = 0;
virtual qlonglong timeSinceActivity() const = 0;
// Share limits // Share limits
virtual qreal ratioLimit() const = 0; virtual qreal ratioLimit() const = 0;
@ -254,8 +262,6 @@ namespace BitTorrent
virtual QString error() const = 0; virtual QString error() const = 0;
virtual qlonglong totalDownload() const = 0; virtual qlonglong totalDownload() const = 0;
virtual qlonglong totalUpload() const = 0; virtual qlonglong totalUpload() const = 0;
virtual qlonglong activeTime() const = 0;
virtual qlonglong finishedTime() const = 0;
virtual qlonglong eta() const = 0; virtual qlonglong eta() const = 0;
virtual int seedsCount() const = 0; virtual int seedsCount() const = 0;
virtual int peersCount() const = 0; virtual int peersCount() const = 0;
@ -263,11 +269,6 @@ namespace BitTorrent
virtual int totalSeedsCount() const = 0; virtual int totalSeedsCount() const = 0;
virtual int totalPeersCount() const = 0; virtual int totalPeersCount() const = 0;
virtual int totalLeechersCount() const = 0; virtual int totalLeechersCount() const = 0;
virtual QDateTime lastSeenComplete() const = 0;
virtual QDateTime completedTime() const = 0;
virtual qlonglong timeSinceUpload() const = 0;
virtual qlonglong timeSinceDownload() const = 0;
virtual qlonglong timeSinceActivity() const = 0;
virtual int downloadLimit() const = 0; virtual int downloadLimit() const = 0;
virtual int uploadLimit() const = 0; virtual int uploadLimit() const = 0;
virtual bool superSeeding() const = 0; virtual bool superSeeding() const = 0;

View file

@ -36,6 +36,7 @@
#include <libtorrent/file_storage.hpp> #include <libtorrent/file_storage.hpp>
#include <libtorrent/torrent_info.hpp> #include <libtorrent/torrent_info.hpp>
#include <QtSystemDetection>
#include <QDirIterator> #include <QDirIterator>
#include <QFileInfo> #include <QFileInfo>
#include <QHash> #include <QHash>
@ -123,7 +124,14 @@ void TorrentCreator::run()
// need to sort the file names by natural sort order // need to sort the file names by natural sort order
QStringList dirs = {m_params.sourcePath.data()}; QStringList dirs = {m_params.sourcePath.data()};
QDirIterator dirIter {m_params.sourcePath.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories}; #ifdef Q_OS_WIN
// libtorrent couldn't handle .lnk files on Windows
// Also, Windows users do not expect torrent creator to traverse into .lnk files so skip over them
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks};
#else
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot};
#endif
QDirIterator dirIter {m_params.sourcePath.data(), dirFilters, QDirIterator::Subdirectories};
while (dirIter.hasNext()) while (dirIter.hasNext())
{ {
const QString filePath = dirIter.next(); const QString filePath = dirIter.next();
@ -138,7 +146,12 @@ void TorrentCreator::run()
{ {
QStringList tmpNames; // natural sort files within each dir QStringList tmpNames; // natural sort files within each dir
QDirIterator fileIter {dir, QDir::Files}; #ifdef Q_OS_WIN
const QDir::Filters fileFilters {QDir::Files | QDir::NoSymLinks};
#else
const QDir::Filters fileFilters {QDir::Files};
#endif
QDirIterator fileIter {dir, fileFilters};
while (fileIter.hasNext()) while (fileIter.hasNext())
{ {
const QFileInfo fileInfo = fileIter.nextFileInfo(); const QFileInfo fileInfo = fileIter.nextFileInfo();

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -35,9 +35,7 @@
#include <libtorrent/write_resume_data.hpp> #include <libtorrent/write_resume_data.hpp>
#include <QByteArray> #include <QByteArray>
#include <QDateTime>
#include <QRegularExpression> #include <QRegularExpression>
#include <QString>
#include <QUrl> #include <QUrl>
#include "base/global.h" #include "base/global.h"
@ -147,7 +145,13 @@ BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTor
: m_ltAddTorrentParams {std::move(ltAddTorrentParams)} : m_ltAddTorrentParams {std::move(ltAddTorrentParams)}
{ {
if (m_ltAddTorrentParams.ti && m_ltAddTorrentParams.ti->is_valid()) if (m_ltAddTorrentParams.ti && m_ltAddTorrentParams.ti->is_valid())
{
m_info.emplace(*m_ltAddTorrentParams.ti); m_info.emplace(*m_ltAddTorrentParams.ti);
if (m_ltAddTorrentParams.ti->creation_date() > 0)
m_creationDate = QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date());
m_creator = QString::fromStdString(m_ltAddTorrentParams.ti->creator());
m_comment = QString::fromStdString(m_ltAddTorrentParams.ti->comment());
}
} }
BitTorrent::InfoHash BitTorrent::TorrentDescriptor::infoHash() const BitTorrent::InfoHash BitTorrent::TorrentDescriptor::infoHash() const
@ -166,18 +170,17 @@ QString BitTorrent::TorrentDescriptor::name() const
QDateTime BitTorrent::TorrentDescriptor::creationDate() const QDateTime BitTorrent::TorrentDescriptor::creationDate() const
{ {
return ((m_ltAddTorrentParams.ti->creation_date() != 0) return m_creationDate;
? QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date()) : QDateTime());
} }
QString BitTorrent::TorrentDescriptor::creator() const QString BitTorrent::TorrentDescriptor::creator() const
{ {
return QString::fromStdString(m_ltAddTorrentParams.ti->creator()); return m_creator;
} }
QString BitTorrent::TorrentDescriptor::comment() const QString BitTorrent::TorrentDescriptor::comment() const
{ {
return QString::fromStdString(m_ltAddTorrentParams.ti->comment()); return m_comment;
} }
const std::optional<BitTorrent::TorrentInfo> &BitTorrent::TorrentDescriptor::info() const const std::optional<BitTorrent::TorrentInfo> &BitTorrent::TorrentDescriptor::info() const

View file

@ -33,7 +33,9 @@
#include <libtorrent/add_torrent_params.hpp> #include <libtorrent/add_torrent_params.hpp>
#include <QtContainerFwd> #include <QtContainerFwd>
#include <QDateTime>
#include <QMetaType> #include <QMetaType>
#include <QString>
#include "base/3rdparty/expected.hpp" #include "base/3rdparty/expected.hpp"
#include "base/path.h" #include "base/path.h"
@ -41,8 +43,6 @@
#include "torrentinfo.h" #include "torrentinfo.h"
class QByteArray; class QByteArray;
class QDateTime;
class QString;
class QUrl; class QUrl;
namespace BitTorrent namespace BitTorrent
@ -78,6 +78,9 @@ namespace BitTorrent
lt::add_torrent_params m_ltAddTorrentParams; lt::add_torrent_params m_ltAddTorrentParams;
std::optional<TorrentInfo> m_info; std::optional<TorrentInfo> m_info;
QDateTime m_creationDate;
QString m_creator;
QString m_comment;
}; };
} }

View file

@ -322,6 +322,11 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
{ {
if (m_ltAddTorrentParams.ti) if (m_ltAddTorrentParams.ti)
{ {
if (const std::time_t creationDate = m_ltAddTorrentParams.ti->creation_date(); creationDate > 0)
m_creationDate = QDateTime::fromSecsSinceEpoch(creationDate);
m_creator = QString::fromStdString(m_ltAddTorrentParams.ti->creator());
m_comment = QString::fromStdString(m_ltAddTorrentParams.ti->comment());
// Initialize it only if torrent is added with metadata. // Initialize it only if torrent is added with metadata.
// Otherwise it should be initialized in "Metadata received" handler. // Otherwise it should be initialized in "Metadata received" handler.
m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti); m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
@ -365,6 +370,12 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
m_urlSeeds.append(QString::fromStdString(urlSeed)); m_urlSeeds.append(QString::fromStdString(urlSeed));
m_nativeStatus = extensionData->status; m_nativeStatus = extensionData->status;
m_addedTime = QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
if (m_nativeStatus.completed_time > 0)
m_completedTime = QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
if (m_nativeStatus.last_seen_complete > 0)
m_lastSeenComplete = QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
if (hasMetadata()) if (hasMetadata())
updateProgress(); updateProgress();
@ -408,17 +419,17 @@ QString TorrentImpl::name() const
QDateTime TorrentImpl::creationDate() const QDateTime TorrentImpl::creationDate() const
{ {
return m_torrentInfo.creationDate(); return m_creationDate;
} }
QString TorrentImpl::creator() const QString TorrentImpl::creator() const
{ {
return m_torrentInfo.creator(); return m_creator;
} }
QString TorrentImpl::comment() const QString TorrentImpl::comment() const
{ {
return m_torrentInfo.comment(); return m_comment;
} }
bool TorrentImpl::isPrivate() const bool TorrentImpl::isPrivate() const
@ -947,7 +958,52 @@ void TorrentImpl::removeAllTags()
QDateTime TorrentImpl::addedTime() const QDateTime TorrentImpl::addedTime() const
{ {
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time); return m_addedTime;
}
QDateTime TorrentImpl::completedTime() const
{
return m_completedTime;
}
QDateTime TorrentImpl::lastSeenComplete() const
{
return m_lastSeenComplete;
}
qlonglong TorrentImpl::activeTime() const
{
return lt::total_seconds(m_nativeStatus.active_duration);
}
qlonglong TorrentImpl::finishedTime() const
{
return lt::total_seconds(m_nativeStatus.finished_duration);
}
qlonglong TorrentImpl::timeSinceUpload() const
{
if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
return -1;
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
}
qlonglong TorrentImpl::timeSinceDownload() const
{
if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
return -1;
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
}
qlonglong TorrentImpl::timeSinceActivity() const
{
const qlonglong upTime = timeSinceUpload();
const qlonglong downTime = timeSinceDownload();
return ((upTime < 0) != (downTime < 0))
? std::max(upTime, downTime)
: std::min(upTime, downTime);
} }
qreal TorrentImpl::ratioLimit() const qreal TorrentImpl::ratioLimit() const
@ -1266,16 +1322,6 @@ qlonglong TorrentImpl::totalUpload() const
return m_nativeStatus.all_time_upload; return m_nativeStatus.all_time_upload;
} }
qlonglong TorrentImpl::activeTime() const
{
return lt::total_seconds(m_nativeStatus.active_duration);
}
qlonglong TorrentImpl::finishedTime() const
{
return lt::total_seconds(m_nativeStatus.finished_duration);
}
qlonglong TorrentImpl::eta() const qlonglong TorrentImpl::eta() const
{ {
if (isStopped()) return MAX_ETA; if (isStopped()) return MAX_ETA;
@ -1385,45 +1431,6 @@ int TorrentImpl::totalLeechersCount() const
return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds); return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
} }
QDateTime TorrentImpl::lastSeenComplete() const
{
if (m_nativeStatus.last_seen_complete > 0)
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
else
return {};
}
QDateTime TorrentImpl::completedTime() const
{
if (m_nativeStatus.completed_time > 0)
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
else
return {};
}
qlonglong TorrentImpl::timeSinceUpload() const
{
if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
return -1;
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
}
qlonglong TorrentImpl::timeSinceDownload() const
{
if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
return -1;
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
}
qlonglong TorrentImpl::timeSinceActivity() const
{
const qlonglong upTime = timeSinceUpload();
const qlonglong downTime = timeSinceDownload();
return ((upTime < 0) != (downTime < 0))
? std::max(upTime, downTime)
: std::min(upTime, downTime);
}
int TorrentImpl::downloadLimit() const int TorrentImpl::downloadLimit() const
{ {
return m_downloadLimit;; return m_downloadLimit;;
@ -2625,11 +2632,23 @@ bool TorrentImpl::isMoveInProgress() const
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus) void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
{ {
// Since libtorrent alerts are handled asynchronously there can be obsolete
// "state update" event reached here after torrent was reloaded in libtorrent.
// Just discard such events.
if (nativeStatus.handle != m_nativeHandle) [[unlikely]]
return;
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus); const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
if (m_nativeStatus.num_pieces != oldStatus.num_pieces) if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
updateProgress(); updateProgress();
if (m_nativeStatus.completed_time != oldStatus.completed_time)
m_completedTime = (m_nativeStatus.completed_time > 0) ? QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time) : QDateTime();
if (m_nativeStatus.last_seen_complete != oldStatus.last_seen_complete)
m_lastSeenComplete = QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
updateState(); updateState();
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate

View file

@ -138,7 +138,15 @@ namespace BitTorrent
int piecesCount() const override; int piecesCount() const override;
int piecesHave() const override; int piecesHave() const override;
qreal progress() const override; qreal progress() const override;
QDateTime addedTime() const override; QDateTime addedTime() const override;
QDateTime completedTime() const override;
QDateTime lastSeenComplete() const override;
qlonglong activeTime() const override;
qlonglong finishedTime() const override;
qlonglong timeSinceUpload() const override;
qlonglong timeSinceDownload() const override;
qlonglong timeSinceActivity() const override;
qreal ratioLimit() const override; qreal ratioLimit() const override;
void setRatioLimit(qreal limit) override; void setRatioLimit(qreal limit) override;
@ -181,8 +189,6 @@ namespace BitTorrent
QString error() const override; QString error() const override;
qlonglong totalDownload() const override; qlonglong totalDownload() const override;
qlonglong totalUpload() const override; qlonglong totalUpload() const override;
qlonglong activeTime() const override;
qlonglong finishedTime() const override;
qlonglong eta() const override; qlonglong eta() const override;
QVector<qreal> filesProgress() const override; QVector<qreal> filesProgress() const override;
int seedsCount() const override; int seedsCount() const override;
@ -191,11 +197,6 @@ namespace BitTorrent
int totalSeedsCount() const override; int totalSeedsCount() const override;
int totalPeersCount() const override; int totalPeersCount() const override;
int totalLeechersCount() const override; int totalLeechersCount() const override;
QDateTime lastSeenComplete() const override;
QDateTime completedTime() const override;
qlonglong timeSinceUpload() const override;
qlonglong timeSinceDownload() const override;
qlonglong timeSinceActivity() const override;
int downloadLimit() const override; int downloadLimit() const override;
int uploadLimit() const override; int uploadLimit() const override;
bool superSeeding() const override; bool superSeeding() const override;
@ -342,6 +343,14 @@ namespace BitTorrent
InfoHash m_infoHash; InfoHash m_infoHash;
QDateTime m_creationDate;
QString m_creator;
QString m_comment;
QDateTime m_addedTime;
QDateTime m_completedTime;
QDateTime m_lastSeenComplete;
// m_moveFinishedTriggers is activated only when the following conditions are met: // m_moveFinishedTriggers is activated only when the following conditions are met:
// all file rename jobs complete, all file move jobs complete // all file rename jobs complete, all file move jobs complete
QQueue<EventTrigger> m_moveFinishedTriggers; QQueue<EventTrigger> m_moveFinishedTriggers;

View file

@ -396,6 +396,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-REPACK"); m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-REPACK");
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-PROPER"); m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-PROPER");
} }
return true;
} }
m_dataPtr->lastComputedEpisodes.append(episodeStr); m_dataPtr->lastComputedEpisodes.append(episodeStr);

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -29,11 +29,8 @@
#include "rss_parser.h" #include "rss_parser.h"
#include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QGlobalStatic>
#include <QHash> #include <QHash>
#include <QMetaObject>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStringList> #include <QStringList>
#include <QVariant> #include <QVariant>
@ -359,7 +356,7 @@ namespace
}; };
// Ported to Qt from KDElibs4 // Ported to Qt from KDElibs4
QDateTime parseDate(const QString &string) QDateTime parseDate(const QString &string, const QDateTime &fallbackDate)
{ {
const char16_t shortDay[][4] = const char16_t shortDay[][4] =
{ {
@ -382,7 +379,7 @@ namespace
const QString str = string.trimmed(); const QString str = string.trimmed();
if (str.isEmpty()) if (str.isEmpty())
return QDateTime::currentDateTime(); return fallbackDate;
int nyear = 6; // indexes within string to values int nyear = 6; // indexes within string to values
int nmonth = 4; int nmonth = 4;
@ -402,14 +399,14 @@ namespace
const bool h1 = (parts[3] == u"-"); const bool h1 = (parts[3] == u"-");
const bool h2 = (parts[5] == u"-"); const bool h2 = (parts[5] == u"-");
if (h1 != h2) if (h1 != h2)
return QDateTime::currentDateTime(); return fallbackDate;
} }
else else
{ {
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY" // Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
rx = QRegularExpression {u"^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"_s}; rx = QRegularExpression {u"^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"_s};
if (str.indexOf(rx, 0, &rxMatch) != 0) if (str.indexOf(rx, 0, &rxMatch) != 0)
return QDateTime::currentDateTime(); return fallbackDate;
nyear = 7; nyear = 7;
nmonth = 2; nmonth = 2;
@ -427,14 +424,14 @@ namespace
const int hour = parts[nhour].toInt(&ok[2]); const int hour = parts[nhour].toInt(&ok[2]);
const int minute = parts[nmin].toInt(&ok[3]); const int minute = parts[nmin].toInt(&ok[3]);
if (!ok[0] || !ok[1] || !ok[2] || !ok[3]) if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
return QDateTime::currentDateTime(); return fallbackDate;
int second = 0; int second = 0;
if (!parts[nsec].isEmpty()) if (!parts[nsec].isEmpty())
{ {
second = parts[nsec].toInt(&ok[0]); second = parts[nsec].toInt(&ok[0]);
if (!ok[0]) if (!ok[0])
return QDateTime::currentDateTime(); return fallbackDate;
} }
const bool leapSecond = (second == 60); const bool leapSecond = (second == 60);
@ -518,21 +515,21 @@ namespace
const QDate qDate(year, month + 1, day); // convert date, and check for out-of-range const QDate qDate(year, month + 1, day); // convert date, and check for out-of-range
if (!qDate.isValid()) if (!qDate.isValid())
return QDateTime::currentDateTime(); return fallbackDate;
const QTime qTime(hour, minute, second); const QTime qTime(hour, minute, second);
QDateTime result(qDate, qTime, Qt::UTC); QDateTime result(qDate, qTime, Qt::UTC);
if (offset) if (offset)
result = result.addSecs(-offset); result = result.addSecs(-offset);
if (!result.isValid()) if (!result.isValid())
return QDateTime::currentDateTime(); // invalid date/time return fallbackDate; // invalid date/time
if (leapSecond) if (leapSecond)
{ {
// Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC. // Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
// Convert the time to UTC and check that it is 00:00:00. // Convert the time to UTC and check that it is 00:00:00.
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours) if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
return QDateTime::currentDateTime(); // the time isn't the last second of the day return fallbackDate; // the time isn't the last second of the day
} }
return result; return result;
@ -550,6 +547,7 @@ RSS::Private::Parser::Parser(const QString &lastBuildDate)
void RSS::Private::Parser::parse(const QByteArray &feedData) void RSS::Private::Parser::parse(const QByteArray &feedData)
{ {
QXmlStreamReader xml {feedData}; QXmlStreamReader xml {feedData};
m_fallbackDate = QDateTime::currentDateTime();
XmlStreamEntityResolver resolver; XmlStreamEntityResolver resolver;
xml.setEntityResolver(&resolver); xml.setEntityResolver(&resolver);
bool foundChannel = false; bool foundChannel = false;
@ -641,7 +639,7 @@ void RSS::Private::Parser::parseRssArticle(QXmlStreamReader &xml)
} }
else if (name == u"pubDate") else if (name == u"pubDate")
{ {
article[Article::KeyDate] = parseDate(xml.readElementText().trimmed()); article[Article::KeyDate] = parseDate(xml.readElementText().trimmed(), m_fallbackDate);
} }
else if (name == u"author") else if (name == u"author")
{ {
@ -755,7 +753,7 @@ void RSS::Private::Parser::parseAtomArticle(QXmlStreamReader &xml)
{ {
// ATOM uses standard compliant date, don't do fancy stuff // ATOM uses standard compliant date, don't do fancy stuff
const QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate); const QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
article[Article::KeyDate] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime()); article[Article::KeyDate] = (articleDate.isValid() ? articleDate : m_fallbackDate);
} }
else if (name == u"author") else if (name == u"author")
{ {

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -29,6 +29,7 @@
#pragma once #pragma once
#include <QDateTime>
#include <QList> #include <QList>
#include <QObject> #include <QObject>
#include <QSet> #include <QSet>
@ -66,6 +67,7 @@ namespace RSS::Private
void parseAtomChannel(QXmlStreamReader &xml); void parseAtomChannel(QXmlStreamReader &xml);
void addArticle(QVariantHash article); void addArticle(QVariantHash article);
QDateTime m_fallbackDate;
QString m_baseUrl; QString m_baseUrl;
ParsingResult m_result; ParsingResult m_result;
QSet<QString> m_articleIDs; QSet<QString> m_articleIDs;

View file

@ -106,10 +106,12 @@ void HtmlBrowser::resourceLoaded(QNetworkReply *reply)
atts[QNetworkRequest::HttpStatusCodeAttribute] = 200; atts[QNetworkRequest::HttpStatusCodeAttribute] = 200;
atts[QNetworkRequest::HttpReasonPhraseAttribute] = u"Ok"_s; atts[QNetworkRequest::HttpReasonPhraseAttribute] = u"Ok"_s;
metaData.setAttributes(atts); metaData.setAttributes(atts);
metaData.setLastModified(QDateTime::currentDateTime()); const auto currentDateTime = QDateTime::currentDateTime();
metaData.setExpirationDate(QDateTime::currentDateTime().addDays(1)); metaData.setLastModified(currentDateTime);
metaData.setExpirationDate(currentDateTime.addDays(1));
QIODevice *dev = m_diskCache->prepare(metaData); QIODevice *dev = m_diskCache->prepare(metaData);
if (!dev) return; if (!dev)
return;
QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(32, 32).save(dev, "PNG"); QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(32, 32).save(dev, "PNG");
m_diskCache->insert(dev); m_diskCache->insert(dev);

View file

@ -104,7 +104,7 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
const qlonglong timeSinceActivity = torrent.timeSinceActivity(); const qlonglong timeSinceActivity = torrent.timeSinceActivity();
return (timeSinceActivity < 0) return (timeSinceActivity < 0)
? Utils::DateTime::toSecsSinceEpoch(torrent.addedTime()) ? Utils::DateTime::toSecsSinceEpoch(torrent.addedTime())
: (QDateTime::currentDateTime().toSecsSinceEpoch() - timeSinceActivity); : (QDateTime::currentSecsSinceEpoch() - timeSinceActivity);
}; };
return { return {

View file

@ -129,7 +129,7 @@ void TorrentCreatorController::addTaskAction()
.sourcePath = Path(params()[KEY_SOURCE_PATH]), .sourcePath = Path(params()[KEY_SOURCE_PATH]),
.torrentFilePath = Path(params()[KEY_TORRENT_FILE_PATH]), .torrentFilePath = Path(params()[KEY_TORRENT_FILE_PATH]),
.comment = params()[KEY_COMMENT], .comment = params()[KEY_COMMENT],
.source = params()[KEY_COMMENT], .source = params()[KEY_SOURCE],
.trackers = params()[KEY_TRACKERS].split(u'|'), .trackers = params()[KEY_TRACKERS].split(u'|'),
.urlSeeds = params()[KEY_URL_SEEDS].split(u'|') .urlSeeds = params()[KEY_URL_SEEDS].split(u'|')
}; };

View file

@ -219,7 +219,7 @@ window.qBittorrent.PropTrackers = (function() {
method: "post", method: "post",
data: { data: {
hash: current_hash, hash: current_hash,
urls: selectedTrackers.join("|") urls: selectedTrackers.map(encodeURIComponent).join("|")
}, },
onSuccess: function() { onSuccess: function() {
updateData(); updateData();

View file

@ -37,11 +37,14 @@ function submitLoginForm(event) {
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8"); xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.addEventListener("readystatechange", () => { xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === 4) { // DONE state if (xhr.readyState === 4) { // DONE state
if ((xhr.status === 200) && (xhr.responseText === "Ok.")) if ((xhr.status === 200) && (xhr.responseText === "Ok.")) {
location.replace(location); location.replace(location);
else location.reload(true);
}
else {
errorMsgElement.textContent = "QBT_TR(Invalid Username or Password.)QBT_TR[CONTEXT=HttpServer]"; errorMsgElement.textContent = "QBT_TR(Invalid Username or Password.)QBT_TR[CONTEXT=HttpServer]";
} }
}
}); });
xhr.addEventListener("error", () => { xhr.addEventListener("error", () => {
errorMsgElement.textContent = (xhr.responseText !== "") errorMsgElement.textContent = (xhr.responseText !== "")