36 #include <QJsonObject>
38 #include <QNetworkCookie>
39 #include <QRegularExpression>
127 if ((idList.size() == 1) && (idList[0] == QLatin1String(
"all")))
134 for (
const QString &idString : idList)
146 int seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, leechesDHT = 0, leechesPeX = 0, leechesLSD = 0;
149 if (peer.isConnecting())
continue;
172 const int disabled = 0;
174 const QString privateMsg {QCoreApplication::translate(
"TrackerListWidget",
"This torrent is private")};
175 const bool isTorrentPrivate = torrent->
isPrivate();
177 const QJsonObject dht
189 const QJsonObject pex
201 const QJsonObject lsd
213 return {dht, pex, lsd};
216 QVector<BitTorrent::TorrentID>
toTorrentIDs(
const QStringList &idStrings)
218 QVector<BitTorrent::TorrentID> idList;
219 idList.reserve(idStrings.size());
220 for (
const QString &hash : idStrings)
258 const QString filter {
params()[
"filter"]};
259 const QString category {
params()[
"category"]};
260 const QString tag {
params()[
"tag"]};
261 const QString sortedColumn {
params()[
"sort"]};
263 int limit {
params()[
"limit"].toInt()};
264 int offset {
params()[
"offset"].toInt()};
265 const QStringList hashes {
params()[
"hashes"].split(
'|', Qt::SkipEmptyParts)};
268 for (
const QString &hash : hashes)
272 QVariantList torrentList;
275 if (torrentFilter.
match(torrent))
279 if (torrentList.isEmpty())
285 if (!sortedColumn.isEmpty())
287 if (!torrentList[0].toMap().contains(sortedColumn))
290 const auto lessThan = [](
const QVariant &left,
const QVariant &right) ->
bool
292 Q_ASSERT(left.type() == right.type());
294 switch (
static_cast<QMetaType::Type
>(left.type()))
296 case QMetaType::Bool:
297 return left.value<
bool>() < right.value<
bool>();
298 case QMetaType::Double:
299 return left.value<
double>() < right.value<
double>();
300 case QMetaType::Float:
301 return left.value<
float>() < right.value<
float>();
303 return left.value<
int>() < right.value<
int>();
304 case QMetaType::LongLong:
305 return left.value<qlonglong>() < right.value<qlonglong>();
306 case QMetaType::QString:
307 return left.value<QString>() < right.value<QString>();
309 qWarning(
"Unhandled QVariant comparison, type: %d, name: %s", left.type()
310 , QMetaType::typeName(left.type()));
316 std::sort(torrentList.begin(), torrentList.end()
317 , [reverse, &sortedColumn, &lessThan](
const QVariant &torrent1,
const QVariant &torrent2)
319 const QVariant value1 {torrent1.toMap().value(sortedColumn)};
320 const QVariant value2 {torrent2.toMap().value(sortedColumn)};
321 return reverse ? lessThan(value2, value1) : lessThan(value1, value2);
325 const int size = torrentList.size();
328 offset = size + offset;
329 if ((offset >= size) || (offset < 0))
335 if ((limit > 0) || (offset > 0))
336 torrentList = torrentList.mid(offset, limit);
338 setResult(QJsonArray::fromVariantList(torrentList));
387 QJsonObject dataDict;
404 const qlonglong ulDuration = torrent->
activeTime();
413 const qreal ratio = torrent->
realRatio();
465 trackerList << QJsonObject
494 QJsonArray webSeedList;
497 webSeedList.append(QJsonObject
527 QVector<int> fileIndexes;
528 const auto idxIt =
params().constFind(QLatin1String(
"indexes"));
529 if (idxIt !=
params().cend())
531 const QStringList indexStrings = idxIt.value().split(
'|');
532 fileIndexes.reserve(indexStrings.size());
533 std::transform(indexStrings.cbegin(), indexStrings.cend(), std::back_inserter(fileIndexes)
534 , [&filesCount](
const QString &indexString) ->
int
537 const int index = indexString.toInt(&ok);
538 if (!ok || (index < 0))
539 throw APIError(APIErrorType::Conflict, tr(
"\"%1\" is not a valid file index.").arg(indexString));
540 if (index >= filesCount)
541 throw APIError(APIErrorType::Conflict, tr(
"Index %1 is out of bounds.").arg(indexString));
547 fileIndexes.reserve(filesCount);
548 for (
int i = 0; i < filesCount; ++i)
549 fileIndexes.append(i);
555 const QVector<BitTorrent::DownloadPriority> priorities = torrent->
filePriorities();
559 for (
const int index :
asConst(fileIndexes))
561 QJsonObject fileDict =
577 fileList.append(fileDict);
595 QJsonArray pieceHashes;
599 for (
const QByteArray &hash : hashes)
600 pieceHashes.append(QString(hash.toHex()));
620 QJsonArray pieceStates;
621 const QBitArray states = torrent->
pieces();
622 for (
int i = 0; i < states.size(); ++i)
623 pieceStates.append(
static_cast<int>(states[i]) * 2);
626 for (
int i = 0; i < states.size(); ++i)
637 const QString urls =
params()[
"urls"];
638 const QString cookie =
params()[
"cookie"];
640 const bool skipChecking =
parseBool(
params()[
"skip_checking"]).value_or(
false);
641 const bool seqDownload =
parseBool(
params()[
"sequentialDownload"]).value_or(
false);
642 const bool firstLastPiece =
parseBool(
params()[
"firstLastPiecePrio"]).value_or(
false);
644 const QString savepath =
params()[
"savepath"].trimmed();
645 const QString downloadPath =
params()[
"downloadPath"].trimmed();
646 const std::optional<bool> useDownloadPath =
parseBool(
params()[
"useDownloadPath"]);
647 const QString category =
params()[
"category"];
648 const QStringList tags =
params()[
"tags"].split(
',', Qt::SkipEmptyParts);
649 const QString torrentName =
params()[
"rename"].trimmed();
656 const QString contentLayoutParam =
params()[
"contentLayout"];
657 const std::optional<BitTorrent::TorrentContentLayout> contentLayout = (!contentLayoutParam.isEmpty()
659 : std::optional<BitTorrent::TorrentContentLayout> {});
661 QList<QNetworkCookie> cookies;
662 if (!cookie.isEmpty())
664 const QStringList cookiesStr = cookie.split(
"; ");
665 for (QString cookieStr : cookiesStr)
667 cookieStr = cookieStr.trimmed();
668 int index = cookieStr.indexOf(
'=');
671 QByteArray name = cookieStr.left(index).toLatin1();
672 QByteArray
value = cookieStr.right(cookieStr.length() - index - 1).toLatin1();
673 cookies += QNetworkCookie(name,
value);
685 addTorrentParams.
savePath = savepath;
688 addTorrentParams.
category = category;
689 addTorrentParams.
tags.insert(tags.cbegin(), tags.cend());
690 addTorrentParams.
name = torrentName;
697 bool partialSuccess =
false;
698 for (QString url :
asConst(urls.split(
'\n')))
708 for (
auto it =
data().constBegin(); it !=
data().constEnd(); ++it)
714 , tr(
"Error: '%1' is not a valid torrent file.").arg(it.key()));
735 QVector<BitTorrent::TrackerEntry> trackers;
736 for (
const QString &urlStr :
asConst(
params()[
"urls"].split(
'\n')))
738 const QUrl url {urlStr.trimmed()};
740 trackers.append({url.toString()});
750 const QString origUrl =
params()[
"origUrl"];
751 const QString newUrl =
params()[
"newUrl"];
757 const QUrl origTrackerUrl(origUrl);
758 const QUrl newTrackerUrl(newUrl);
759 if (origTrackerUrl == newTrackerUrl)
761 if (!newTrackerUrl.isValid())
764 QVector<BitTorrent::TrackerEntry> trackers = torrent->
trackers();
768 const QUrl trackerUrl(tracker.url);
769 if (trackerUrl == newTrackerUrl)
771 if (trackerUrl == origTrackerUrl)
774 tracker.url = newTrackerUrl.toString();
795 const QStringList urls =
params()[
"urls"].split(
'|');
797 const QVector<BitTorrent::TrackerEntry> trackers = torrent->
trackers();
798 QVector<BitTorrent::TrackerEntry> remainingTrackers;
799 remainingTrackers.reserve(trackers.size());
802 if (!urls.contains(entry.url))
803 remainingTrackers.push_back(entry);
806 if (remainingTrackers.size() == trackers.size())
819 const QStringList hashes =
params()[
"hashes"].split(
'|');
820 const QStringList peers =
params()[
"peers"].split(
'|');
822 QVector<BitTorrent::PeerAddress> peerList;
823 peerList.reserve(peers.size());
824 for (
const QString &peer : peers)
827 if (!addr.
ip.isNull())
828 peerList.append(addr);
831 if (peerList.isEmpty())
838 const int peersAdded = std::count_if(peerList.cbegin(), peerList.cend(), [torrent](
const BitTorrent::PeerAddress &peer)
840 return torrent->connectPeer(peer);
843 results[torrent->
id().
toString()] = QJsonObject
845 {
"added", peersAdded},
846 {
"failed", (peers.size() - peersAdded)}
857 const QStringList hashes =
params()[
"hashes"].split(
'|');
865 const QStringList idStrings =
params()[
"hashes"].split(
'|');
889 QVector<BitTorrent::DownloadPriority> priorities = torrent->
filePriorities();
890 bool priorityChanged =
false;
891 for (
const QString &fileID :
params()[
"id"].split(
'|'))
893 const int id = fileID.toInt(&ok);
896 if ((
id < 0) || (
id >= filesCount))
899 if (priorities[
id] != priority)
901 priorities[id] = priority;
902 priorityChanged =
true;
914 const QStringList idList {
params()[
"hashes"].split(
'|')};
916 for (
const QString &
id : idList)
932 const QStringList idList {
params()[
"hashes"].split(
'|')};
934 for (
const QString &
id : idList)
950 qlonglong limit =
params()[
"limit"].toLongLong();
954 const QStringList hashes {
params()[
"hashes"].split(
'|')};
962 qlonglong limit =
params()[
"limit"].toLongLong();
966 const QStringList hashes {
params()[
"hashes"].split(
'|')};
974 const qreal ratioLimit =
params()[
"ratioLimit"].toDouble();
975 const qlonglong seedingTimeLimit =
params()[
"seedingTimeLimit"].toLongLong();
976 const QStringList hashes =
params()[
"hashes"].split(
'|');
989 const QStringList hashes {
params()[
"hashes"].split(
'|')};
997 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1006 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1015 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1018 torrent->
resume(
value ? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged);
1026 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1042 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1053 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1064 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1075 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1083 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1084 const QString newLocation {
params()[
"location"].trimmed()};
1086 if (newLocation.isEmpty())
1090 if (!QDir(newLocation).mkpath(
"."))
1094 if (!QFileInfo(newLocation).isWritable())
1099 LogMsg(tr(
"WebUI Set location: moving \"%1\", from \"%2\" to \"%3\"")
1110 const QStringList ids {
params()[
"id"].split(
'|')};
1111 const QString newPath {
params()[
"path"]};
1113 if (newPath.isEmpty())
1117 if (!QDir(newPath).mkpath(
"."))
1121 if (!QFileInfo(newPath).isWritable())
1135 const QStringList ids {
params()[
"id"].split(
'|')};
1136 const QString newPath {
params()[
"path"]};
1138 if (!newPath.isEmpty())
1141 if (!QDir(newPath).mkpath(
"."))
1145 if (!QFileInfo(newPath).isWritable())
1161 QString name =
params()[
"name"].trimmed();
1170 name.replace(QRegularExpression(
"\r?\n|\r"),
" ");
1178 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1191 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1199 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1207 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1208 const QString category {
params()[
"category"]};
1221 const QString category =
params()[
"category"];
1222 if (category.isEmpty())
1228 const QString savePath =
params()[
"savePath"];
1229 const auto useDownloadPath =
parseBool(
params()[
"downloadPathEnabled"]);
1231 categoryOptions.
savePath = savePath;
1232 if (useDownloadPath.has_value())
1234 const QString downloadPath =
params()[
"downloadPath"];
1235 categoryOptions.
downloadPath = {useDownloadPath.value(), downloadPath};
1246 const QString category =
params()[
"category"];
1247 if (category.isEmpty())
1250 const QString savePath =
params()[
"savePath"];
1251 const auto useDownloadPath =
parseBool(
params()[
"downloadPathEnabled"]);
1253 categoryOptions.
savePath = savePath;
1254 if (useDownloadPath.has_value())
1256 const QString downloadPath =
params()[
"downloadPath"];
1257 categoryOptions.
downloadPath = {useDownloadPath.value(), downloadPath};
1268 const QStringList categories {
params()[
"categories"].split(
'\n')};
1269 for (
const QString &category : categories)
1277 QJsonObject categories;
1278 const QStringList categoriesList = session->categories();
1279 for (
const auto &categoryName : categoriesList)
1282 QJsonObject category = categoryOptions.
toJSON();
1283 category.insert(QLatin1String(
"name"), categoryName);
1284 categories[categoryName] = category;
1294 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1295 const QStringList tags {
params()[
"tags"].split(
',', Qt::SkipEmptyParts)};
1297 for (
const QString &tag : tags)
1299 const QString tagTrimmed {tag.trimmed()};
1302 torrent->
addTag(tagTrimmed);
1311 const QStringList hashes {
params()[
"hashes"].split(
'|')};
1312 const QStringList tags {
params()[
"tags"].split(
',', Qt::SkipEmptyParts)};
1314 for (
const QString &tag : tags)
1316 const QString tagTrimmed {tag.trimmed()};
1336 const QStringList tags {
params()[
"tags"].split(
',', Qt::SkipEmptyParts)};
1338 for (
const QString &tag : tags)
1346 const QStringList tags {
params()[
"tags"].split(
',', Qt::SkipEmptyParts)};
1347 for (
const QString &tag : tags)
1368 const QString oldPath =
params()[
"oldPath"];
1369 const QString newPath =
params()[
"newPath"];
1390 const QString oldPath =
params()[
"oldPath"];
1391 const QString newPath =
params()[
"newPath"];
void requireParams(const QVector< QString > &requiredParams) const
const StringMap & params() const
const DataMap & data() const
void setResult(const QString &result)
void renameFolder(const QString &oldPath, const QString &newPath)
virtual void renameFile(int index, const QString &name)=0
virtual int filesCount() const =0
virtual qlonglong fileSize(int index) const =0
virtual QString filePath(int index) const =0
bool removeTag(const QString &tag)
static Session * instance()
bool isDHTEnabled() const
void decreaseTorrentsQueuePos(const QVector< TorrentID > &ids)
static bool isValidCategoryName(const QString &name)
bool removeCategory(const QString &name)
void bottomTorrentsQueuePos(const QVector< TorrentID > &ids)
bool isPeXEnabled() const
bool addTag(const QString &tag)
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption=DeleteTorrent)
bool isLSDEnabled() const
bool addTorrent(const QString &source, const AddTorrentParams ¶ms=AddTorrentParams())
void topTorrentsQueuePos(const QVector< TorrentID > &ids)
void increaseTorrentsQueuePos(const QVector< TorrentID > &ids)
Torrent * findTorrent(const TorrentID &id) const
virtual qlonglong totalSize() const =0
virtual QVector< PeerInfo > peers() const =0
virtual int uploadPayloadRate() const =0
virtual void forceRecheck()=0
virtual int seedsCount() const =0
virtual int downloadLimit() const =0
virtual void setSuperSeeding(bool enable)=0
virtual qlonglong nextAnnounce() const =0
virtual bool removeTag(const QString &tag)=0
virtual QVector< DownloadPriority > filePriorities() const =0
virtual void setAutoTMMEnabled(bool enabled)=0
virtual void forceReannounce(int index=-1)=0
virtual void setDownloadPath(const QString &downloadPath)=0
static const int USE_GLOBAL_SEEDING_TIME
virtual QVector< qreal > filesProgress() const =0
virtual qlonglong totalUpload() const =0
virtual int piecesHave() const =0
virtual QDateTime creationDate() const =0
void toggleFirstLastPiecePriority()
virtual QString comment() const =0
virtual qlonglong pieceLength() const =0
virtual void setUploadLimit(int limit)=0
virtual qlonglong activeTime() const =0
virtual qlonglong totalPayloadDownload() const =0
virtual void setSeedingTimeLimit(int limit)=0
virtual InfoHash infoHash() const =0
virtual void prioritizeFiles(const QVector< DownloadPriority > &priorities)=0
virtual qlonglong finishedTime() const =0
virtual int piecesCount() const =0
virtual int uploadLimit() const =0
void toggleSequentialDownload()
virtual int connectionsLimit() const =0
virtual int totalSeedsCount() const =0
virtual qlonglong totalDownload() const =0
virtual void resume(TorrentOperatingMode mode=TorrentOperatingMode::AutoManaged)=0
virtual void setSavePath(const QString &savePath)=0
virtual QString creator() const =0
virtual QString savePath() const =0
virtual qreal realRatio() const =0
virtual void removeAllTags()=0
virtual QString downloadPath() const =0
virtual QBitArray downloadingPieces() const =0
virtual bool isPaused() const =0
virtual QVector< TrackerEntry > trackers() const =0
virtual bool setCategory(const QString &category)=0
virtual QDateTime addedTime() const =0
virtual bool isAutoTMMEnabled() const =0
virtual void setName(const QString &name)=0
virtual qlonglong totalPayloadUpload() const =0
virtual void addTrackers(const QVector< TrackerEntry > &trackers)=0
virtual void replaceTrackers(const QVector< TrackerEntry > &trackers)=0
virtual QBitArray pieces() const =0
virtual QVector< qreal > availableFileFractions() const =0
fraction of file pieces that are available at least from one peer
virtual int connectionsCount() const =0
virtual QDateTime completedTime() const =0
virtual bool isPrivate() const =0
virtual QVector< QUrl > urlSeeds() const =0
virtual TorrentInfo info() const =0
static const qreal USE_GLOBAL_RATIO
virtual qlonglong wastedSize() const =0
virtual bool addTag(const QString &tag)=0
virtual QDateTime lastSeenComplete() const =0
virtual void setDownloadLimit(int limit)=0
virtual int leechsCount() const =0
virtual bool isSeed() const =0
virtual qlonglong eta() const =0
static const qreal MAX_RATIO
virtual int downloadPayloadRate() const =0
virtual int totalLeechersCount() const =0
virtual void setRatioLimit(qreal limit)=0
virtual bool hasMetadata() const =0
virtual QString name() const =0
static TorrentID fromString(const QString &hashString)
static nonstd::expected< TorrentInfo, QString > load(const QByteArray &data) noexcept
PieceRange filePieces(const QString &file) const
QVector< QByteArray > pieceHashes() const
QString message() const noexcept
constexpr IndexType last() const
constexpr IndexType first() const
bool setCookiesFromUrl(const QList< QNetworkCookie > &cookieList, const QUrl &url)
static DownloadManager * instance()
static const TorrentIDSet AnyID
bool match(const BitTorrent::Torrent *torrent) const
void toggleFirstLastPiecePrioAction()
void removeCategoriesAction()
void setDownloadLimitAction()
void removeTrackersAction()
void renameFolderAction()
void downloadLimitAction()
void setDownloadPathAction()
void editCategoryAction()
void setAutoManagementAction()
void setForceStartAction()
void increasePrioAction()
void setShareLimitsAction()
void toggleSequentialDownloadAction()
void decreasePrioAction()
void createCategoryAction()
void setSuperSeedingAction()
void setUploadLimitAction()
constexpr std::add_const_t< T > & asConst(T &t) noexcept
void LogMsg(const QString &message, const Log::MsgType &type)
bool isValidDownloadPriority(const DownloadPriority priority)
QString toUniformPath(const QString &path)
QString expandPathAbs(const QString &path)
QString toNativePath(const QString &path)
std::optional< bool > parseBool(const QString &string)
std::optional< int > parseInt(const QString &string)
T toEnum(const QString &serializedValue, const T &defaultValue)
std::optional< double > parseDouble(const QString &string)
T value(const QString &key, const T &defaultValue={})
void applyToTorrents(const QStringList &idList, const std::function< void(BitTorrent::Torrent *torrent)> &func)
QVector< BitTorrent::TorrentID > toTorrentIDs(const QStringList &idStrings)
QJsonArray getStickyTrackers(const BitTorrent::Torrent *const torrent)
QVariantMap serialize(const BitTorrent::Torrent &torrent)
const char KEY_TORRENT_INFOHASHV1[]
const char KEY_TORRENT_INFOHASHV2[]
std::optional< bool > useDownloadPath
bool firstLastPiecePriority
std::optional< bool > useAutoTMM
std::optional< bool > addPaused
std::optional< BitTorrent::TorrentContentLayout > contentLayout
QJsonObject toJSON() const
std::optional< DownloadPathOption > downloadPath
static PeerAddress parse(QStringView address)
QSet< BitTorrent::TorrentID > TorrentIDSet
const char KEY_PROP_DOWNLOADED_SESSION[]
const char KEY_PROP_DL_LIMIT[]
const char KEY_PROP_PEERS_TOTAL[]
const char KEY_PROP_SAVE_PATH[]
const char KEY_FILE_NAME[]
const char KEY_PROP_DL_SPEED_AVG[]
const char KEY_FILE_SIZE[]
const char KEY_PROP_COMPLETION_DATE[]
const char KEY_PROP_UP_SPEED_AVG[]
const char KEY_FILE_IS_SEED[]
const char KEY_PROP_TOTAL_SIZE[]
const char KEY_PROP_PEERS[]
const char KEY_PROP_DL_SPEED[]
const char KEY_TRACKER_DOWNLOADED_COUNT[]
const char KEY_FILE_INDEX[]
const char KEY_PROP_UP_SPEED[]
const char KEY_PROP_PIECE_SIZE[]
const char KEY_TRACKER_LEECHES_COUNT[]
const char KEY_FILE_PIECE_RANGE[]
const char KEY_PROP_CONNECT_COUNT[]
const char KEY_PROP_UPLOADED_SESSION[]
const char KEY_PROP_UP_LIMIT[]
const char KEY_PROP_SEEDS_TOTAL[]
const char KEY_TRACKER_SEEDS_COUNT[]
const char KEY_PROP_PIECES_HAVE[]
const char KEY_FILE_PRIORITY[]
const char KEY_PROP_TIME_ELAPSED[]
const char KEY_PROP_COMMENT[]
const char KEY_TRACKER_PEERS_COUNT[]
const char KEY_TRACKER_MSG[]
const char KEY_PROP_UPLOADED[]
const char KEY_PROP_CREATION_DATE[]
const char KEY_PROP_ETA[]
const char KEY_FILE_AVAILABILITY[]
const char KEY_PROP_CREATED_BY[]
const char KEY_PROP_CONNECT_COUNT_LIMIT[]
const char KEY_FILE_PROGRESS[]
const char KEY_PROP_ADDITION_DATE[]
const char KEY_TRACKER_URL[]
const char KEY_TRACKER_TIER[]
const char KEY_PROP_DOWNLOAD_PATH[]
const char KEY_TRACKER_STATUS[]
const char KEY_PROP_DOWNLOADED[]
const char KEY_PROP_PIECES_NUM[]
const char KEY_PROP_WASTED[]
const char KEY_PROP_LAST_SEEN[]
const char KEY_PROP_SEEDING_TIME[]
const char KEY_WEBSEED_URL[]
const char KEY_PROP_SEEDS[]
const char KEY_PROP_REANNOUNCE[]
const char KEY_PROP_RATIO[]