2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2018-2023 Vladimir Golovnev <glassez@yandex.ru>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "torrentscontroller.h"
35 #include <QJsonObject>
37 #include <QNetworkCookie>
38 #include <QRegularExpression>
41 #include "base/addtorrentmanager.h"
42 #include "base/bittorrent/categoryoptions.h"
43 #include "base/bittorrent/downloadpriority.h"
44 #include "base/bittorrent/infohash.h"
45 #include "base/bittorrent/peeraddress.h"
46 #include "base/bittorrent/peerinfo.h"
47 #include "base/bittorrent/session.h"
48 #include "base/bittorrent/torrent.h"
49 #include "base/bittorrent/torrentdescriptor.h"
50 #include "base/bittorrent/trackerentry.h"
51 #include "base/interfaces/iapplication.h"
52 #include "base/global.h"
53 #include "base/logger.h"
54 #include "base/net/downloadmanager.h"
55 #include "base/torrentfilter.h"
56 #include "base/utils/datetime.h"
57 #include "base/utils/fs.h"
58 #include "base/utils/string.h"
60 #include "serialize/serialize_torrent.h"
63 const QString KEY_TRACKER_URL
= u
"url"_s
;
64 const QString KEY_TRACKER_STATUS
= u
"status"_s
;
65 const QString KEY_TRACKER_TIER
= u
"tier"_s
;
66 const QString KEY_TRACKER_MSG
= u
"msg"_s
;
67 const QString KEY_TRACKER_PEERS_COUNT
= u
"num_peers"_s
;
68 const QString KEY_TRACKER_SEEDS_COUNT
= u
"num_seeds"_s
;
69 const QString KEY_TRACKER_LEECHES_COUNT
= u
"num_leeches"_s
;
70 const QString KEY_TRACKER_DOWNLOADED_COUNT
= u
"num_downloaded"_s
;
73 const QString KEY_WEBSEED_URL
= u
"url"_s
;
75 // Torrent keys (Properties)
76 const QString KEY_PROP_TIME_ELAPSED
= u
"time_elapsed"_s
;
77 const QString KEY_PROP_SEEDING_TIME
= u
"seeding_time"_s
;
78 const QString KEY_PROP_ETA
= u
"eta"_s
;
79 const QString KEY_PROP_CONNECT_COUNT
= u
"nb_connections"_s
;
80 const QString KEY_PROP_CONNECT_COUNT_LIMIT
= u
"nb_connections_limit"_s
;
81 const QString KEY_PROP_DOWNLOADED
= u
"total_downloaded"_s
;
82 const QString KEY_PROP_DOWNLOADED_SESSION
= u
"total_downloaded_session"_s
;
83 const QString KEY_PROP_UPLOADED
= u
"total_uploaded"_s
;
84 const QString KEY_PROP_UPLOADED_SESSION
= u
"total_uploaded_session"_s
;
85 const QString KEY_PROP_DL_SPEED
= u
"dl_speed"_s
;
86 const QString KEY_PROP_DL_SPEED_AVG
= u
"dl_speed_avg"_s
;
87 const QString KEY_PROP_UP_SPEED
= u
"up_speed"_s
;
88 const QString KEY_PROP_UP_SPEED_AVG
= u
"up_speed_avg"_s
;
89 const QString KEY_PROP_DL_LIMIT
= u
"dl_limit"_s
;
90 const QString KEY_PROP_UP_LIMIT
= u
"up_limit"_s
;
91 const QString KEY_PROP_WASTED
= u
"total_wasted"_s
;
92 const QString KEY_PROP_SEEDS
= u
"seeds"_s
;
93 const QString KEY_PROP_SEEDS_TOTAL
= u
"seeds_total"_s
;
94 const QString KEY_PROP_PEERS
= u
"peers"_s
;
95 const QString KEY_PROP_PEERS_TOTAL
= u
"peers_total"_s
;
96 const QString KEY_PROP_RATIO
= u
"share_ratio"_s
;
97 const QString KEY_PROP_REANNOUNCE
= u
"reannounce"_s
;
98 const QString KEY_PROP_TOTAL_SIZE
= u
"total_size"_s
;
99 const QString KEY_PROP_PIECES_NUM
= u
"pieces_num"_s
;
100 const QString KEY_PROP_PIECE_SIZE
= u
"piece_size"_s
;
101 const QString KEY_PROP_PIECES_HAVE
= u
"pieces_have"_s
;
102 const QString KEY_PROP_CREATED_BY
= u
"created_by"_s
;
103 const QString KEY_PROP_LAST_SEEN
= u
"last_seen"_s
;
104 const QString KEY_PROP_ADDITION_DATE
= u
"addition_date"_s
;
105 const QString KEY_PROP_COMPLETION_DATE
= u
"completion_date"_s
;
106 const QString KEY_PROP_CREATION_DATE
= u
"creation_date"_s
;
107 const QString KEY_PROP_SAVE_PATH
= u
"save_path"_s
;
108 const QString KEY_PROP_DOWNLOAD_PATH
= u
"download_path"_s
;
109 const QString KEY_PROP_COMMENT
= u
"comment"_s
;
110 const QString KEY_PROP_ISPRIVATE
= u
"is_private"_s
;
113 const QString KEY_FILE_INDEX
= u
"index"_s
;
114 const QString KEY_FILE_NAME
= u
"name"_s
;
115 const QString KEY_FILE_SIZE
= u
"size"_s
;
116 const QString KEY_FILE_PROGRESS
= u
"progress"_s
;
117 const QString KEY_FILE_PRIORITY
= u
"priority"_s
;
118 const QString KEY_FILE_IS_SEED
= u
"is_seed"_s
;
119 const QString KEY_FILE_PIECE_RANGE
= u
"piece_range"_s
;
120 const QString KEY_FILE_AVAILABILITY
= u
"availability"_s
;
124 using Utils::String::parseBool
;
125 using Utils::String::parseInt
;
126 using Utils::String::parseDouble
;
128 void applyToTorrents(const QStringList
&idList
, const std::function
<void (BitTorrent::Torrent
*torrent
)> &func
)
130 if ((idList
.size() == 1) && (idList
[0] == u
"all"))
132 for (BitTorrent::Torrent
*const torrent
: asConst(BitTorrent::Session::instance()->torrents()))
137 for (const QString
&idString
: idList
)
139 const auto hash
= BitTorrent::TorrentID::fromString(idString
);
140 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(hash
);
147 std::optional
<QString
> getOptionalString(const StringMap
¶ms
, const QString
&name
)
149 const auto it
= params
.constFind(name
);
150 if (it
== params
.cend())
156 std::optional
<Tag
> getOptionalTag(const StringMap
¶ms
, const QString
&name
)
158 const auto it
= params
.constFind(name
);
159 if (it
== params
.cend())
162 return Tag(it
.value());
165 QJsonArray
getStickyTrackers(const BitTorrent::Torrent
*const torrent
)
167 int seedsDHT
= 0, seedsPeX
= 0, seedsLSD
= 0, leechesDHT
= 0, leechesPeX
= 0, leechesLSD
= 0;
168 for (const BitTorrent::PeerInfo
&peer
: asConst(torrent
->peers()))
170 if (peer
.isConnecting()) continue;
192 const int working
= static_cast<int>(BitTorrent::TrackerEntryStatus::Working
);
193 const int disabled
= 0;
195 const QString privateMsg
{QCoreApplication::translate("TrackerListWidget", "This torrent is private")};
196 const bool isTorrentPrivate
= torrent
->isPrivate();
198 const QJsonObject dht
200 {KEY_TRACKER_URL
, u
"** [DHT] **"_s
},
201 {KEY_TRACKER_TIER
, -1},
202 {KEY_TRACKER_MSG
, (isTorrentPrivate
? privateMsg
: u
""_s
)},
203 {KEY_TRACKER_STATUS
, ((BitTorrent::Session::instance()->isDHTEnabled() && !isTorrentPrivate
) ? working
: disabled
)},
204 {KEY_TRACKER_PEERS_COUNT
, 0},
205 {KEY_TRACKER_DOWNLOADED_COUNT
, 0},
206 {KEY_TRACKER_SEEDS_COUNT
, seedsDHT
},
207 {KEY_TRACKER_LEECHES_COUNT
, leechesDHT
}
210 const QJsonObject pex
212 {KEY_TRACKER_URL
, u
"** [PeX] **"_s
},
213 {KEY_TRACKER_TIER
, -1},
214 {KEY_TRACKER_MSG
, (isTorrentPrivate
? privateMsg
: u
""_s
)},
215 {KEY_TRACKER_STATUS
, ((BitTorrent::Session::instance()->isPeXEnabled() && !isTorrentPrivate
) ? working
: disabled
)},
216 {KEY_TRACKER_PEERS_COUNT
, 0},
217 {KEY_TRACKER_DOWNLOADED_COUNT
, 0},
218 {KEY_TRACKER_SEEDS_COUNT
, seedsPeX
},
219 {KEY_TRACKER_LEECHES_COUNT
, leechesPeX
}
222 const QJsonObject lsd
224 {KEY_TRACKER_URL
, u
"** [LSD] **"_s
},
225 {KEY_TRACKER_TIER
, -1},
226 {KEY_TRACKER_MSG
, (isTorrentPrivate
? privateMsg
: u
""_s
)},
227 {KEY_TRACKER_STATUS
, ((BitTorrent::Session::instance()->isLSDEnabled() && !isTorrentPrivate
) ? working
: disabled
)},
228 {KEY_TRACKER_PEERS_COUNT
, 0},
229 {KEY_TRACKER_DOWNLOADED_COUNT
, 0},
230 {KEY_TRACKER_SEEDS_COUNT
, seedsLSD
},
231 {KEY_TRACKER_LEECHES_COUNT
, leechesLSD
}
234 return {dht
, pex
, lsd
};
237 QVector
<BitTorrent::TorrentID
> toTorrentIDs(const QStringList
&idStrings
)
239 QVector
<BitTorrent::TorrentID
> idList
;
240 idList
.reserve(idStrings
.size());
241 for (const QString
&hash
: idStrings
)
242 idList
<< BitTorrent::TorrentID::fromString(hash
);
247 void TorrentsController::countAction()
249 setResult(QString::number(BitTorrent::Session::instance()->torrentsCount()));
252 // Returns all the torrents in JSON format.
253 // The return value is a JSON-formatted list of dictionaries.
254 // The dictionary keys are:
255 // - "hash": Torrent hash (ID)
256 // - "name": Torrent name
257 // - "size": Torrent size
258 // - "progress": Torrent progress
259 // - "dlspeed": Torrent download speed
260 // - "upspeed": Torrent upload speed
261 // - "priority": Torrent queue position (-1 if queuing is disabled)
262 // - "num_seeds": Torrent seeds connected to
263 // - "num_complete": Torrent seeds in the swarm
264 // - "num_leechs": Torrent leechers connected to
265 // - "num_incomplete": Torrent leechers in the swarm
266 // - "ratio": Torrent share ratio
267 // - "eta": Torrent ETA
268 // - "state": Torrent state
269 // - "seq_dl": Torrent sequential download state
270 // - "f_l_piece_prio": Torrent first last piece priority state
271 // - "force_start": Torrent force start state
272 // - "category": Torrent category
274 // - filter (string): all, downloading, seeding, completed, paused, resumed, active, inactive, stalled, stalled_uploading, stalled_downloading
275 // - category (string): torrent category for filtering by it (empty string means "uncategorized"; no "category" param presented means "any category")
276 // - tag (string): torrent tag for filtering by it (empty string means "untagged"; no "tag" param presented means "any tag")
277 // - hashes (string): filter by hashes, can contain multiple hashes separated by |
278 // - sort (string): name of column for sorting by its value
279 // - reverse (bool): enable reverse sorting
280 // - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited)
281 // - offset (int): set offset (if less than 0 - offset from end)
282 void TorrentsController::infoAction()
284 const QString filter
{params()[u
"filter"_s
]};
285 const std::optional
<QString
> category
= getOptionalString(params(), u
"category"_s
);
286 const std::optional
<Tag
> tag
= getOptionalTag(params(), u
"tag"_s
);
287 const QString sortedColumn
{params()[u
"sort"_s
]};
288 const bool reverse
{parseBool(params()[u
"reverse"_s
]).value_or(false)};
289 int limit
{params()[u
"limit"_s
].toInt()};
290 int offset
{params()[u
"offset"_s
].toInt()};
291 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|', Qt::SkipEmptyParts
)};
293 std::optional
<TorrentIDSet
> idSet
;
294 if (!hashes
.isEmpty())
296 idSet
= TorrentIDSet();
297 for (const QString
&hash
: hashes
)
298 idSet
->insert(BitTorrent::TorrentID::fromString(hash
));
301 const TorrentFilter torrentFilter
{filter
, idSet
, category
, tag
};
302 QVariantList torrentList
;
303 for (const BitTorrent::Torrent
*torrent
: asConst(BitTorrent::Session::instance()->torrents()))
305 if (torrentFilter
.match(torrent
))
306 torrentList
.append(serialize(*torrent
));
309 if (torrentList
.isEmpty())
311 setResult(QJsonArray
{});
315 if (!sortedColumn
.isEmpty())
317 if (!torrentList
[0].toMap().contains(sortedColumn
))
318 throw APIError(APIErrorType::BadParams
, tr("'sort' parameter is invalid"));
320 const auto lessThan
= [](const QVariant
&left
, const QVariant
&right
) -> bool
322 Q_ASSERT(left
.userType() == right
.userType());
324 switch (left
.userType())
326 case QMetaType::Bool
:
327 return left
.value
<bool>() < right
.value
<bool>();
328 case QMetaType::Double
:
329 return left
.value
<double>() < right
.value
<double>();
330 case QMetaType::Float
:
331 return left
.value
<float>() < right
.value
<float>();
333 return left
.value
<int>() < right
.value
<int>();
334 case QMetaType::LongLong
:
335 return left
.value
<qlonglong
>() < right
.value
<qlonglong
>();
336 case QMetaType::QString
:
337 return left
.value
<QString
>() < right
.value
<QString
>();
339 qWarning("Unhandled QVariant comparison, type: %d, name: %s"
340 , left
.userType(), left
.metaType().name());
346 std::sort(torrentList
.begin(), torrentList
.end()
347 , [reverse
, &sortedColumn
, &lessThan
](const QVariant
&torrent1
, const QVariant
&torrent2
)
349 const QVariant value1
{torrent1
.toMap().value(sortedColumn
)};
350 const QVariant value2
{torrent2
.toMap().value(sortedColumn
)};
351 return reverse
? lessThan(value2
, value1
) : lessThan(value1
, value2
);
355 const int size
= torrentList
.size();
358 offset
= size
+ offset
;
359 if ((offset
>= size
) || (offset
< 0))
363 limit
= -1; // unlimited
365 if ((limit
> 0) || (offset
> 0))
366 torrentList
= torrentList
.mid(offset
, limit
);
368 setResult(QJsonArray::fromVariantList(torrentList
));
371 // Returns the properties for a torrent in JSON format.
372 // The return value is a JSON-formatted dictionary.
373 // The dictionary keys are:
374 // - "time_elapsed": Torrent elapsed time
375 // - "seeding_time": Torrent elapsed time while complete
376 // - "eta": Torrent ETA
377 // - "nb_connections": Torrent connection count
378 // - "nb_connections_limit": Torrent connection count limit
379 // - "total_downloaded": Total data uploaded for torrent
380 // - "total_downloaded_session": Total data downloaded this session
381 // - "total_uploaded": Total data uploaded for torrent
382 // - "total_uploaded_session": Total data uploaded this session
383 // - "dl_speed": Torrent download speed
384 // - "dl_speed_avg": Torrent average download speed
385 // - "up_speed": Torrent upload speed
386 // - "up_speed_avg": Torrent average upload speed
387 // - "dl_limit": Torrent download limit
388 // - "up_limit": Torrent upload limit
389 // - "total_wasted": Total data wasted for torrent
390 // - "seeds": Torrent connected seeds
391 // - "seeds_total": Torrent total number of seeds
392 // - "peers": Torrent connected peers
393 // - "peers_total": Torrent total number of peers
394 // - "share_ratio": Torrent share ratio
395 // - "reannounce": Torrent next reannounce time
396 // - "total_size": Torrent total size
397 // - "pieces_num": Torrent pieces count
398 // - "piece_size": Torrent piece size
399 // - "pieces_have": Torrent pieces have
400 // - "created_by": Torrent creator
401 // - "last_seen": Torrent last seen complete
402 // - "addition_date": Torrent addition date
403 // - "completion_date": Torrent completion date
404 // - "creation_date": Torrent creation date
405 // - "save_path": Torrent save path
406 // - "download_path": Torrent download path
407 // - "comment": Torrent comment
408 // - "infohash_v1": Torrent v1 infohash (or empty string for v2 torrents)
409 // - "infohash_v2": Torrent v2 infohash (or empty string for v1 torrents)
410 // - "hash": Torrent TorrentID (infohashv1 for v1 torrents, truncated infohashv2 for v2/hybrid torrents)
411 // - "name": Torrent name
412 void TorrentsController::propertiesAction()
414 requireParams({u
"hash"_s
});
416 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
417 const BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
419 throw APIError(APIErrorType::NotFound
);
421 const BitTorrent::InfoHash infoHash
= torrent
->infoHash();
422 const qlonglong totalDownload
= torrent
->totalDownload();
423 const qlonglong totalUpload
= torrent
->totalUpload();
424 const qlonglong dlDuration
= torrent
->activeTime() - torrent
->finishedTime();
425 const qlonglong ulDuration
= torrent
->activeTime();
426 const int downloadLimit
= torrent
->downloadLimit();
427 const int uploadLimit
= torrent
->uploadLimit();
428 const qreal ratio
= torrent
->realRatio();
430 const QJsonObject ret
432 {KEY_TORRENT_INFOHASHV1
, infoHash
.v1().toString()},
433 {KEY_TORRENT_INFOHASHV2
, infoHash
.v2().toString()},
434 {KEY_TORRENT_NAME
, torrent
->name()},
435 {KEY_TORRENT_ID
, torrent
->id().toString()},
436 {KEY_PROP_TIME_ELAPSED
, torrent
->activeTime()},
437 {KEY_PROP_SEEDING_TIME
, torrent
->finishedTime()},
438 {KEY_PROP_ETA
, torrent
->eta()},
439 {KEY_PROP_CONNECT_COUNT
, torrent
->connectionsCount()},
440 {KEY_PROP_CONNECT_COUNT_LIMIT
, torrent
->connectionsLimit()},
441 {KEY_PROP_DOWNLOADED
, totalDownload
},
442 {KEY_PROP_DOWNLOADED_SESSION
, torrent
->totalPayloadDownload()},
443 {KEY_PROP_UPLOADED
, totalUpload
},
444 {KEY_PROP_UPLOADED_SESSION
, torrent
->totalPayloadUpload()},
445 {KEY_PROP_DL_SPEED
, torrent
->downloadPayloadRate()},
446 {KEY_PROP_DL_SPEED_AVG
, ((dlDuration
> 0) ? (totalDownload
/ dlDuration
) : -1)},
447 {KEY_PROP_UP_SPEED
, torrent
->uploadPayloadRate()},
448 {KEY_PROP_UP_SPEED_AVG
, ((ulDuration
> 0) ? (totalUpload
/ ulDuration
) : -1)},
449 {KEY_PROP_DL_LIMIT
, ((downloadLimit
> 0) ? downloadLimit
: -1)},
450 {KEY_PROP_UP_LIMIT
, ((uploadLimit
> 0) ? uploadLimit
: -1)},
451 {KEY_PROP_WASTED
, torrent
->wastedSize()},
452 {KEY_PROP_SEEDS
, torrent
->seedsCount()},
453 {KEY_PROP_SEEDS_TOTAL
, torrent
->totalSeedsCount()},
454 {KEY_PROP_PEERS
, torrent
->leechsCount()},
455 {KEY_PROP_PEERS_TOTAL
, torrent
->totalLeechersCount()},
456 {KEY_PROP_RATIO
, ((ratio
> BitTorrent::Torrent::MAX_RATIO
) ? -1 : ratio
)},
457 {KEY_PROP_REANNOUNCE
, torrent
->nextAnnounce()},
458 {KEY_PROP_TOTAL_SIZE
, torrent
->totalSize()},
459 {KEY_PROP_PIECES_NUM
, torrent
->piecesCount()},
460 {KEY_PROP_PIECE_SIZE
, torrent
->pieceLength()},
461 {KEY_PROP_PIECES_HAVE
, torrent
->piecesHave()},
462 {KEY_PROP_CREATED_BY
, torrent
->creator()},
463 {KEY_PROP_ISPRIVATE
, torrent
->isPrivate()},
464 {KEY_PROP_ADDITION_DATE
, Utils::DateTime::toSecsSinceEpoch(torrent
->addedTime())},
465 {KEY_PROP_LAST_SEEN
, Utils::DateTime::toSecsSinceEpoch(torrent
->lastSeenComplete())},
466 {KEY_PROP_COMPLETION_DATE
, Utils::DateTime::toSecsSinceEpoch(torrent
->completedTime())},
467 {KEY_PROP_CREATION_DATE
, Utils::DateTime::toSecsSinceEpoch(torrent
->creationDate())},
468 {KEY_PROP_SAVE_PATH
, torrent
->savePath().toString()},
469 {KEY_PROP_DOWNLOAD_PATH
, torrent
->downloadPath().toString()},
470 {KEY_PROP_COMMENT
, torrent
->comment()}
476 // Returns the trackers for a torrent in JSON format.
477 // The return value is a JSON-formatted list of dictionaries.
478 // The dictionary keys are:
479 // - "url": Tracker URL
480 // - "status": Tracker status
481 // - "tier": Tracker tier
482 // - "num_peers": Number of peers this torrent is currently connected to
483 // - "num_seeds": Number of peers that have the whole file
484 // - "num_leeches": Number of peers that are still downloading
485 // - "num_downloaded": Tracker downloaded count
486 // - "msg": Tracker message (last)
487 void TorrentsController::trackersAction()
489 requireParams({u
"hash"_s
});
491 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
492 const BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
494 throw APIError(APIErrorType::NotFound
);
496 QJsonArray trackerList
= getStickyTrackers(torrent
);
498 for (const BitTorrent::TrackerEntry
&tracker
: asConst(torrent
->trackers()))
500 const bool isNotWorking
= (tracker
.status
== BitTorrent::TrackerEntryStatus::NotWorking
)
501 || (tracker
.status
== BitTorrent::TrackerEntryStatus::TrackerError
)
502 || (tracker
.status
== BitTorrent::TrackerEntryStatus::Unreachable
);
503 trackerList
<< QJsonObject
505 {KEY_TRACKER_URL
, tracker
.url
},
506 {KEY_TRACKER_TIER
, tracker
.tier
},
507 {KEY_TRACKER_STATUS
, static_cast<int>((isNotWorking
? BitTorrent::TrackerEntryStatus::NotWorking
: tracker
.status
))},
508 {KEY_TRACKER_MSG
, tracker
.message
},
509 {KEY_TRACKER_PEERS_COUNT
, tracker
.numPeers
},
510 {KEY_TRACKER_SEEDS_COUNT
, tracker
.numSeeds
},
511 {KEY_TRACKER_LEECHES_COUNT
, tracker
.numLeeches
},
512 {KEY_TRACKER_DOWNLOADED_COUNT
, tracker
.numDownloaded
}
516 setResult(trackerList
);
519 // Returns the web seeds for a torrent in JSON format.
520 // The return value is a JSON-formatted list of dictionaries.
521 // The dictionary keys are:
522 // - "url": Web seed URL
523 void TorrentsController::webseedsAction()
525 requireParams({u
"hash"_s
});
527 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
528 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
530 throw APIError(APIErrorType::NotFound
);
532 QJsonArray webSeedList
;
533 for (const QUrl
&webseed
: asConst(torrent
->urlSeeds()))
535 webSeedList
.append(QJsonObject
537 {KEY_WEBSEED_URL
, webseed
.toString()}
541 setResult(webSeedList
);
544 // Returns the files in a torrent in JSON format.
545 // The return value is a JSON-formatted list of dictionaries.
546 // The dictionary keys are:
547 // - "index": File index
548 // - "name": File name
549 // - "size": File size
550 // - "progress": File progress
551 // - "priority": File priority
552 // - "is_seed": Flag indicating if torrent is seeding/complete
553 // - "piece_range": Piece index range, the first number is the starting piece index
554 // and the second number is the ending piece index (inclusive)
555 void TorrentsController::filesAction()
557 requireParams({u
"hash"_s
});
559 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
560 const BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
562 throw APIError(APIErrorType::NotFound
);
564 const int filesCount
= torrent
->filesCount();
565 QVector
<int> fileIndexes
;
566 const auto idxIt
= params().constFind(u
"indexes"_s
);
567 if (idxIt
!= params().cend())
569 const QStringList indexStrings
= idxIt
.value().split(u
'|');
570 fileIndexes
.reserve(indexStrings
.size());
571 std::transform(indexStrings
.cbegin(), indexStrings
.cend(), std::back_inserter(fileIndexes
)
572 , [&filesCount
](const QString
&indexString
) -> int
575 const int index
= indexString
.toInt(&ok
);
576 if (!ok
|| (index
< 0))
577 throw APIError(APIErrorType::Conflict
, tr("\"%1\" is not a valid file index.").arg(indexString
));
578 if (index
>= filesCount
)
579 throw APIError(APIErrorType::Conflict
, tr("Index %1 is out of bounds.").arg(indexString
));
585 fileIndexes
.reserve(filesCount
);
586 for (int i
= 0; i
< filesCount
; ++i
)
587 fileIndexes
.append(i
);
591 if (torrent
->hasMetadata())
593 const QVector
<BitTorrent::DownloadPriority
> priorities
= torrent
->filePriorities();
594 const QVector
<qreal
> fp
= torrent
->filesProgress();
595 const QVector
<qreal
> fileAvailability
= torrent
->availableFileFractions();
596 const BitTorrent::TorrentInfo info
= torrent
->info();
597 for (const int index
: asConst(fileIndexes
))
599 QJsonObject fileDict
=
601 {KEY_FILE_INDEX
, index
},
602 {KEY_FILE_PROGRESS
, fp
[index
]},
603 {KEY_FILE_PRIORITY
, static_cast<int>(priorities
[index
])},
604 {KEY_FILE_SIZE
, torrent
->fileSize(index
)},
605 {KEY_FILE_AVAILABILITY
, fileAvailability
[index
]},
606 // need to provide paths using a platform-independent separator format
607 {KEY_FILE_NAME
, torrent
->filePath(index
).data()}
610 const BitTorrent::TorrentInfo::PieceRange idx
= info
.filePieces(index
);
611 fileDict
[KEY_FILE_PIECE_RANGE
] = QJsonArray
{idx
.first(), idx
.last()};
614 fileDict
[KEY_FILE_IS_SEED
] = torrent
->isFinished();
616 fileList
.append(fileDict
);
623 // Returns an array of hashes (of each pieces respectively) for a torrent in JSON format.
624 // The return value is a JSON-formatted array of strings (hex strings).
625 void TorrentsController::pieceHashesAction()
627 requireParams({u
"hash"_s
});
629 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
630 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
632 throw APIError(APIErrorType::NotFound
);
634 QJsonArray pieceHashes
;
635 if (torrent
->hasMetadata())
637 const QVector
<QByteArray
> hashes
= torrent
->info().pieceHashes();
638 for (const QByteArray
&hash
: hashes
)
639 pieceHashes
.append(QString::fromLatin1(hash
.toHex()));
642 setResult(pieceHashes
);
645 // Returns an array of states (of each pieces respectively) for a torrent in JSON format.
646 // The return value is a JSON-formatted array of ints.
647 // 0: piece not downloaded
648 // 1: piece requested or downloading
649 // 2: piece already downloaded
650 void TorrentsController::pieceStatesAction()
652 requireParams({u
"hash"_s
});
654 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
655 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
657 throw APIError(APIErrorType::NotFound
);
659 QJsonArray pieceStates
;
660 const QBitArray states
= torrent
->pieces();
661 for (int i
= 0; i
< states
.size(); ++i
)
662 pieceStates
.append(static_cast<int>(states
[i
]) * 2);
664 const QBitArray dlstates
= torrent
->downloadingPieces();
665 for (int i
= 0; i
< states
.size(); ++i
)
671 setResult(pieceStates
);
674 void TorrentsController::addAction()
676 const QString urls
= params()[u
"urls"_s
];
677 const QString cookie
= params()[u
"cookie"_s
];
679 const bool skipChecking
= parseBool(params()[u
"skip_checking"_s
]).value_or(false);
680 const bool seqDownload
= parseBool(params()[u
"sequentialDownload"_s
]).value_or(false);
681 const bool firstLastPiece
= parseBool(params()[u
"firstLastPiecePrio"_s
]).value_or(false);
682 const std::optional
<bool> addToQueueTop
= parseBool(params()[u
"addToTopOfQueue"_s
]);
683 const std::optional
<bool> addPaused
= parseBool(params()[u
"paused"_s
]);
684 const QString savepath
= params()[u
"savepath"_s
].trimmed();
685 const QString downloadPath
= params()[u
"downloadPath"_s
].trimmed();
686 const std::optional
<bool> useDownloadPath
= parseBool(params()[u
"useDownloadPath"_s
]);
687 const QString category
= params()[u
"category"_s
];
688 const QStringList tags
= params()[u
"tags"_s
].split(u
',', Qt::SkipEmptyParts
);
689 const QString torrentName
= params()[u
"rename"_s
].trimmed();
690 const int upLimit
= parseInt(params()[u
"upLimit"_s
]).value_or(-1);
691 const int dlLimit
= parseInt(params()[u
"dlLimit"_s
]).value_or(-1);
692 const double ratioLimit
= parseDouble(params()[u
"ratioLimit"_s
]).value_or(BitTorrent::Torrent::USE_GLOBAL_RATIO
);
693 const int seedingTimeLimit
= parseInt(params()[u
"seedingTimeLimit"_s
]).value_or(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME
);
694 const int inactiveSeedingTimeLimit
= parseInt(params()[u
"inactiveSeedingTimeLimit"_s
]).value_or(BitTorrent::Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME
);
695 const std::optional
<bool> autoTMM
= parseBool(params()[u
"autoTMM"_s
]);
697 const QString stopConditionParam
= params()[u
"stopCondition"_s
];
698 const std::optional
<BitTorrent::Torrent::StopCondition
> stopCondition
= (!stopConditionParam
.isEmpty()
699 ? Utils::String::toEnum(stopConditionParam
, BitTorrent::Torrent::StopCondition::None
)
700 : std::optional
<BitTorrent::Torrent::StopCondition
> {});
702 const QString contentLayoutParam
= params()[u
"contentLayout"_s
];
703 const std::optional
<BitTorrent::TorrentContentLayout
> contentLayout
= (!contentLayoutParam
.isEmpty()
704 ? Utils::String::toEnum(contentLayoutParam
, BitTorrent::TorrentContentLayout::Original
)
705 : std::optional
<BitTorrent::TorrentContentLayout
> {});
707 QList
<QNetworkCookie
> cookies
;
708 if (!cookie
.isEmpty())
710 const QStringList cookiesStr
= cookie
.split(u
"; "_s
);
711 for (QString cookieStr
: cookiesStr
)
713 cookieStr
= cookieStr
.trimmed();
714 int index
= cookieStr
.indexOf(u
'=');
717 QByteArray name
= cookieStr
.left(index
).toLatin1();
718 QByteArray value
= cookieStr
.right(cookieStr
.length() - index
- 1).toLatin1();
719 cookies
+= QNetworkCookie(name
, value
);
724 BitTorrent::AddTorrentParams addTorrentParams
;
725 // TODO: Check if destination actually exists
726 addTorrentParams
.skipChecking
= skipChecking
;
727 addTorrentParams
.sequential
= seqDownload
;
728 addTorrentParams
.firstLastPiecePriority
= firstLastPiece
;
729 addTorrentParams
.addToQueueTop
= addToQueueTop
;
730 addTorrentParams
.addPaused
= addPaused
;
731 addTorrentParams
.stopCondition
= stopCondition
;
732 addTorrentParams
.contentLayout
= contentLayout
;
733 addTorrentParams
.savePath
= Path(savepath
);
734 addTorrentParams
.downloadPath
= Path(downloadPath
);
735 addTorrentParams
.useDownloadPath
= useDownloadPath
;
736 addTorrentParams
.category
= category
;
737 addTorrentParams
.tags
.insert(tags
.cbegin(), tags
.cend());
738 addTorrentParams
.name
= torrentName
;
739 addTorrentParams
.uploadLimit
= upLimit
;
740 addTorrentParams
.downloadLimit
= dlLimit
;
741 addTorrentParams
.seedingTimeLimit
= seedingTimeLimit
;
742 addTorrentParams
.inactiveSeedingTimeLimit
= inactiveSeedingTimeLimit
;
743 addTorrentParams
.ratioLimit
= ratioLimit
;
744 addTorrentParams
.useAutoTMM
= autoTMM
;
746 bool partialSuccess
= false;
747 for (QString url
: asConst(urls
.split(u
'\n')))
752 Net::DownloadManager::instance()->setCookiesFromUrl(cookies
, QUrl::fromEncoded(url
.toUtf8()));
753 partialSuccess
|= app()->addTorrentManager()->addTorrent(url
, addTorrentParams
);
757 const DataMap torrents
= data();
758 for (auto it
= torrents
.constBegin(); it
!= torrents
.constEnd(); ++it
)
760 if (const auto loadResult
= BitTorrent::TorrentDescriptor::load(it
.value()))
762 partialSuccess
|= BitTorrent::Session::instance()->addTorrent(loadResult
.value(), addTorrentParams
);
766 throw APIError(APIErrorType::BadData
, tr("Error: '%1' is not a valid torrent file.").arg(it
.key()));
773 setResult(u
"Fails."_s
);
776 void TorrentsController::addTrackersAction()
778 requireParams({u
"hash"_s
, u
"urls"_s
});
780 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
781 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
783 throw APIError(APIErrorType::NotFound
);
785 const QVector
<BitTorrent::TrackerEntry
> entries
= BitTorrent::parseTrackerEntries(params()[u
"urls"_s
]);
786 torrent
->addTrackers(entries
);
789 void TorrentsController::editTrackerAction()
791 requireParams({u
"hash"_s
, u
"origUrl"_s
, u
"newUrl"_s
});
793 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
794 const QString origUrl
= params()[u
"origUrl"_s
];
795 const QString newUrl
= params()[u
"newUrl"_s
];
797 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
799 throw APIError(APIErrorType::NotFound
);
801 const QUrl origTrackerUrl
{origUrl
};
802 const QUrl newTrackerUrl
{newUrl
};
803 if (origTrackerUrl
== newTrackerUrl
)
805 if (!newTrackerUrl
.isValid())
806 throw APIError(APIErrorType::BadParams
, u
"New tracker URL is invalid"_s
);
808 QVector
<BitTorrent::TrackerEntry
> trackers
= torrent
->trackers();
810 for (BitTorrent::TrackerEntry
&tracker
: trackers
)
812 const QUrl trackerUrl
{tracker
.url
};
813 if (trackerUrl
== newTrackerUrl
)
814 throw APIError(APIErrorType::Conflict
, u
"New tracker URL already exists"_s
);
815 if (trackerUrl
== origTrackerUrl
)
818 tracker
.url
= newTrackerUrl
.toString();
822 throw APIError(APIErrorType::Conflict
, u
"Tracker not found"_s
);
824 torrent
->replaceTrackers(trackers
);
826 if (!torrent
->isPaused())
827 torrent
->forceReannounce();
830 void TorrentsController::removeTrackersAction()
832 requireParams({u
"hash"_s
, u
"urls"_s
});
834 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
835 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
837 throw APIError(APIErrorType::NotFound
);
839 const QStringList urls
= params()[u
"urls"_s
].split(u
'|');
840 torrent
->removeTrackers(urls
);
842 if (!torrent
->isPaused())
843 torrent
->forceReannounce();
846 void TorrentsController::addPeersAction()
848 requireParams({u
"hashes"_s
, u
"peers"_s
});
850 const QStringList hashes
= params()[u
"hashes"_s
].split(u
'|');
851 const QStringList peers
= params()[u
"peers"_s
].split(u
'|');
853 QVector
<BitTorrent::PeerAddress
> peerList
;
854 peerList
.reserve(peers
.size());
855 for (const QString
&peer
: peers
)
857 const BitTorrent::PeerAddress addr
= BitTorrent::PeerAddress::parse(peer
.trimmed());
858 if (!addr
.ip
.isNull())
859 peerList
.append(addr
);
862 if (peerList
.isEmpty())
863 throw APIError(APIErrorType::BadParams
, u
"No valid peers were specified"_s
);
867 applyToTorrents(hashes
, [peers
, peerList
, &results
](BitTorrent::Torrent
*const torrent
)
869 const int peersAdded
= std::count_if(peerList
.cbegin(), peerList
.cend(), [torrent
](const BitTorrent::PeerAddress
&peer
)
871 return torrent
->connectPeer(peer
);
874 results
[torrent
->id().toString()] = QJsonObject
876 {u
"added"_s
, peersAdded
},
877 {u
"failed"_s
, (peers
.size() - peersAdded
)}
884 void TorrentsController::pauseAction()
886 requireParams({u
"hashes"_s
});
888 const QStringList hashes
= params()[u
"hashes"_s
].split(u
'|');
889 applyToTorrents(hashes
, [](BitTorrent::Torrent
*const torrent
) { torrent
->pause(); });
892 void TorrentsController::resumeAction()
894 requireParams({u
"hashes"_s
});
896 const QStringList idStrings
= params()[u
"hashes"_s
].split(u
'|');
897 applyToTorrents(idStrings
, [](BitTorrent::Torrent
*const torrent
) { torrent
->resume(); });
900 void TorrentsController::filePrioAction()
902 requireParams({u
"hash"_s
, u
"id"_s
, u
"priority"_s
});
904 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
906 const auto priority
= static_cast<BitTorrent::DownloadPriority
>(params()[u
"priority"_s
].toInt(&ok
));
908 throw APIError(APIErrorType::BadParams
, tr("Priority must be an integer"));
910 if (!BitTorrent::isValidDownloadPriority(priority
))
911 throw APIError(APIErrorType::BadParams
, tr("Priority is not valid"));
913 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
915 throw APIError(APIErrorType::NotFound
);
916 if (!torrent
->hasMetadata())
917 throw APIError(APIErrorType::Conflict
, tr("Torrent's metadata has not yet downloaded"));
919 const int filesCount
= torrent
->filesCount();
920 QVector
<BitTorrent::DownloadPriority
> priorities
= torrent
->filePriorities();
921 bool priorityChanged
= false;
922 for (const QString
&fileID
: params()[u
"id"_s
].split(u
'|'))
924 const int id
= fileID
.toInt(&ok
);
926 throw APIError(APIErrorType::BadParams
, tr("File IDs must be integers"));
927 if ((id
< 0) || (id
>= filesCount
))
928 throw APIError(APIErrorType::Conflict
, tr("File ID is not valid"));
930 if (priorities
[id
] != priority
)
932 priorities
[id
] = priority
;
933 priorityChanged
= true;
938 torrent
->prioritizeFiles(priorities
);
941 void TorrentsController::uploadLimitAction()
943 requireParams({u
"hashes"_s
});
945 const QStringList idList
{params()[u
"hashes"_s
].split(u
'|')};
947 for (const QString
&id
: idList
)
950 const BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(BitTorrent::TorrentID::fromString(id
));
952 limit
= torrent
->uploadLimit();
959 void TorrentsController::downloadLimitAction()
961 requireParams({u
"hashes"_s
});
963 const QStringList idList
{params()[u
"hashes"_s
].split(u
'|')};
965 for (const QString
&id
: idList
)
968 const BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(BitTorrent::TorrentID::fromString(id
));
970 limit
= torrent
->downloadLimit();
977 void TorrentsController::setUploadLimitAction()
979 requireParams({u
"hashes"_s
, u
"limit"_s
});
981 qlonglong limit
= params()[u
"limit"_s
].toLongLong();
985 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
986 applyToTorrents(hashes
, [limit
](BitTorrent::Torrent
*const torrent
) { torrent
->setUploadLimit(limit
); });
989 void TorrentsController::setDownloadLimitAction()
991 requireParams({u
"hashes"_s
, u
"limit"_s
});
993 qlonglong limit
= params()[u
"limit"_s
].toLongLong();
997 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
998 applyToTorrents(hashes
, [limit
](BitTorrent::Torrent
*const torrent
) { torrent
->setDownloadLimit(limit
); });
1001 void TorrentsController::setShareLimitsAction()
1003 requireParams({u
"hashes"_s
, u
"ratioLimit"_s
, u
"seedingTimeLimit"_s
, u
"inactiveSeedingTimeLimit"_s
});
1005 const qreal ratioLimit
= params()[u
"ratioLimit"_s
].toDouble();
1006 const qlonglong seedingTimeLimit
= params()[u
"seedingTimeLimit"_s
].toLongLong();
1007 const qlonglong inactiveSeedingTimeLimit
= params()[u
"inactiveSeedingTimeLimit"_s
].toLongLong();
1008 const QStringList hashes
= params()[u
"hashes"_s
].split(u
'|');
1010 applyToTorrents(hashes
, [ratioLimit
, seedingTimeLimit
, inactiveSeedingTimeLimit
](BitTorrent::Torrent
*const torrent
)
1012 torrent
->setRatioLimit(ratioLimit
);
1013 torrent
->setSeedingTimeLimit(seedingTimeLimit
);
1014 torrent
->setInactiveSeedingTimeLimit(inactiveSeedingTimeLimit
);
1018 void TorrentsController::toggleSequentialDownloadAction()
1020 requireParams({u
"hashes"_s
});
1022 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1023 applyToTorrents(hashes
, [](BitTorrent::Torrent
*const torrent
) { torrent
->toggleSequentialDownload(); });
1026 void TorrentsController::toggleFirstLastPiecePrioAction()
1028 requireParams({u
"hashes"_s
});
1030 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1031 applyToTorrents(hashes
, [](BitTorrent::Torrent
*const torrent
) { torrent
->toggleFirstLastPiecePriority(); });
1034 void TorrentsController::setSuperSeedingAction()
1036 requireParams({u
"hashes"_s
, u
"value"_s
});
1038 const bool value
{parseBool(params()[u
"value"_s
]).value_or(false)};
1039 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1040 applyToTorrents(hashes
, [value
](BitTorrent::Torrent
*const torrent
) { torrent
->setSuperSeeding(value
); });
1043 void TorrentsController::setForceStartAction()
1045 requireParams({u
"hashes"_s
, u
"value"_s
});
1047 const bool value
{parseBool(params()[u
"value"_s
]).value_or(false)};
1048 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1049 applyToTorrents(hashes
, [value
](BitTorrent::Torrent
*const torrent
)
1051 torrent
->resume(value
? BitTorrent::TorrentOperatingMode::Forced
: BitTorrent::TorrentOperatingMode::AutoManaged
);
1055 void TorrentsController::deleteAction()
1057 requireParams({u
"hashes"_s
, u
"deleteFiles"_s
});
1059 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1060 const DeleteOption deleteOption
= parseBool(params()[u
"deleteFiles"_s
]).value_or(false)
1061 ? DeleteTorrentAndFiles
: DeleteTorrent
;
1062 applyToTorrents(hashes
, [deleteOption
](const BitTorrent::Torrent
*torrent
)
1064 BitTorrent::Session::instance()->deleteTorrent(torrent
->id(), deleteOption
);
1068 void TorrentsController::increasePrioAction()
1070 requireParams({u
"hashes"_s
});
1072 if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
1073 throw APIError(APIErrorType::Conflict
, tr("Torrent queueing must be enabled"));
1075 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1076 BitTorrent::Session::instance()->increaseTorrentsQueuePos(toTorrentIDs(hashes
));
1079 void TorrentsController::decreasePrioAction()
1081 requireParams({u
"hashes"_s
});
1083 if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
1084 throw APIError(APIErrorType::Conflict
, tr("Torrent queueing must be enabled"));
1086 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1087 BitTorrent::Session::instance()->decreaseTorrentsQueuePos(toTorrentIDs(hashes
));
1090 void TorrentsController::topPrioAction()
1092 requireParams({u
"hashes"_s
});
1094 if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
1095 throw APIError(APIErrorType::Conflict
, tr("Torrent queueing must be enabled"));
1097 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1098 BitTorrent::Session::instance()->topTorrentsQueuePos(toTorrentIDs(hashes
));
1101 void TorrentsController::bottomPrioAction()
1103 requireParams({u
"hashes"_s
});
1105 if (!BitTorrent::Session::instance()->isQueueingSystemEnabled())
1106 throw APIError(APIErrorType::Conflict
, tr("Torrent queueing must be enabled"));
1108 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1109 BitTorrent::Session::instance()->bottomTorrentsQueuePos(toTorrentIDs(hashes
));
1112 void TorrentsController::setLocationAction()
1114 requireParams({u
"hashes"_s
, u
"location"_s
});
1116 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1117 const Path newLocation
{params()[u
"location"_s
].trimmed()};
1119 if (newLocation
.isEmpty())
1120 throw APIError(APIErrorType::BadParams
, tr("Save path cannot be empty"));
1122 // try to create the location if it does not exist
1123 if (!Utils::Fs::mkpath(newLocation
))
1124 throw APIError(APIErrorType::Conflict
, tr("Cannot make save path"));
1126 applyToTorrents(hashes
, [newLocation
](BitTorrent::Torrent
*const torrent
)
1128 LogMsg(tr("WebUI Set location: moving \"%1\", from \"%2\" to \"%3\"")
1129 .arg(torrent
->name(), torrent
->savePath().toString(), newLocation
.toString()));
1130 torrent
->setAutoTMMEnabled(false);
1131 torrent
->setSavePath(newLocation
);
1135 void TorrentsController::setSavePathAction()
1137 requireParams({u
"id"_s
, u
"path"_s
});
1139 const QStringList ids
{params()[u
"id"_s
].split(u
'|')};
1140 const Path newPath
{params()[u
"path"_s
]};
1142 if (newPath
.isEmpty())
1143 throw APIError(APIErrorType::BadParams
, tr("Save path cannot be empty"));
1145 // try to create the directory if it does not exist
1146 if (!Utils::Fs::mkpath(newPath
))
1147 throw APIError(APIErrorType::Conflict
, tr("Cannot create target directory"));
1149 // check permissions
1150 if (!Utils::Fs::isWritable(newPath
))
1151 throw APIError(APIErrorType::AccessDenied
, tr("Cannot write to directory"));
1153 applyToTorrents(ids
, [&newPath
](BitTorrent::Torrent
*const torrent
)
1155 if (!torrent
->isAutoTMMEnabled())
1156 torrent
->setSavePath(newPath
);
1160 void TorrentsController::setDownloadPathAction()
1162 requireParams({u
"id"_s
, u
"path"_s
});
1164 const QStringList ids
{params()[u
"id"_s
].split(u
'|')};
1165 const Path newPath
{params()[u
"path"_s
]};
1167 if (!newPath
.isEmpty())
1169 // try to create the directory if it does not exist
1170 if (!Utils::Fs::mkpath(newPath
))
1171 throw APIError(APIErrorType::Conflict
, tr("Cannot create target directory"));
1173 // check permissions
1174 if (!Utils::Fs::isWritable(newPath
))
1175 throw APIError(APIErrorType::AccessDenied
, tr("Cannot write to directory"));
1178 applyToTorrents(ids
, [&newPath
](BitTorrent::Torrent
*const torrent
)
1180 if (!torrent
->isAutoTMMEnabled())
1181 torrent
->setDownloadPath(newPath
);
1185 void TorrentsController::renameAction()
1187 requireParams({u
"hash"_s
, u
"name"_s
});
1189 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
1190 QString name
= params()[u
"name"_s
].trimmed();
1193 throw APIError(APIErrorType::Conflict
, tr("Incorrect torrent name"));
1195 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
1197 throw APIError(APIErrorType::NotFound
);
1199 name
.replace(QRegularExpression(u
"\r?\n|\r"_s
), u
" "_s
);
1200 torrent
->setName(name
);
1203 void TorrentsController::setAutoManagementAction()
1205 requireParams({u
"hashes"_s
, u
"enable"_s
});
1207 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1208 const bool isEnabled
{parseBool(params()[u
"enable"_s
]).value_or(false)};
1210 applyToTorrents(hashes
, [isEnabled
](BitTorrent::Torrent
*const torrent
)
1212 torrent
->setAutoTMMEnabled(isEnabled
);
1216 void TorrentsController::recheckAction()
1218 requireParams({u
"hashes"_s
});
1220 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1221 applyToTorrents(hashes
, [](BitTorrent::Torrent
*const torrent
) { torrent
->forceRecheck(); });
1224 void TorrentsController::reannounceAction()
1226 requireParams({u
"hashes"_s
});
1228 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1229 applyToTorrents(hashes
, [](BitTorrent::Torrent
*const torrent
) { torrent
->forceReannounce(); });
1232 void TorrentsController::setCategoryAction()
1234 requireParams({u
"hashes"_s
, u
"category"_s
});
1236 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1237 const QString category
{params()[u
"category"_s
]};
1239 applyToTorrents(hashes
, [category
](BitTorrent::Torrent
*const torrent
)
1241 if (!torrent
->setCategory(category
))
1242 throw APIError(APIErrorType::Conflict
, tr("Incorrect category name"));
1246 void TorrentsController::createCategoryAction()
1248 requireParams({u
"category"_s
});
1250 const QString category
= params()[u
"category"_s
];
1251 if (category
.isEmpty())
1252 throw APIError(APIErrorType::BadParams
, tr("Category cannot be empty"));
1254 if (!BitTorrent::Session::isValidCategoryName(category
))
1255 throw APIError(APIErrorType::Conflict
, tr("Incorrect category name"));
1257 const Path savePath
{params()[u
"savePath"_s
]};
1258 const auto useDownloadPath
= parseBool(params()[u
"downloadPathEnabled"_s
]);
1259 BitTorrent::CategoryOptions categoryOptions
;
1260 categoryOptions
.savePath
= savePath
;
1261 if (useDownloadPath
.has_value())
1263 const Path downloadPath
{params()[u
"downloadPath"_s
]};
1264 categoryOptions
.downloadPath
= {useDownloadPath
.value(), downloadPath
};
1267 if (!BitTorrent::Session::instance()->addCategory(category
, categoryOptions
))
1268 throw APIError(APIErrorType::Conflict
, tr("Unable to create category"));
1271 void TorrentsController::editCategoryAction()
1273 requireParams({u
"category"_s
, u
"savePath"_s
});
1275 const QString category
= params()[u
"category"_s
];
1276 if (category
.isEmpty())
1277 throw APIError(APIErrorType::BadParams
, tr("Category cannot be empty"));
1279 const Path savePath
{params()[u
"savePath"_s
]};
1280 const auto useDownloadPath
= parseBool(params()[u
"downloadPathEnabled"_s
]);
1281 BitTorrent::CategoryOptions categoryOptions
;
1282 categoryOptions
.savePath
= savePath
;
1283 if (useDownloadPath
.has_value())
1285 const Path downloadPath
{params()[u
"downloadPath"_s
]};
1286 categoryOptions
.downloadPath
= {useDownloadPath
.value(), downloadPath
};
1289 if (!BitTorrent::Session::instance()->editCategory(category
, categoryOptions
))
1290 throw APIError(APIErrorType::Conflict
, tr("Unable to edit category"));
1293 void TorrentsController::removeCategoriesAction()
1295 requireParams({u
"categories"_s
});
1297 const QStringList categories
{params()[u
"categories"_s
].split(u
'\n')};
1298 for (const QString
&category
: categories
)
1299 BitTorrent::Session::instance()->removeCategory(category
);
1302 void TorrentsController::categoriesAction()
1304 const auto *session
= BitTorrent::Session::instance();
1306 QJsonObject categories
;
1307 const QStringList categoriesList
= session
->categories();
1308 for (const auto &categoryName
: categoriesList
)
1310 const BitTorrent::CategoryOptions categoryOptions
= session
->categoryOptions(categoryName
);
1311 QJsonObject category
= categoryOptions
.toJSON();
1312 // adjust it to be compatible with existing WebAPI
1313 category
[u
"savePath"_s
] = category
.take(u
"save_path"_s
);
1314 category
.insert(u
"name"_s
, categoryName
);
1315 categories
[categoryName
] = category
;
1318 setResult(categories
);
1321 void TorrentsController::addTagsAction()
1323 requireParams({u
"hashes"_s
, u
"tags"_s
});
1325 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1326 const QStringList tags
{params()[u
"tags"_s
].split(u
',', Qt::SkipEmptyParts
)};
1328 for (const QString
&tagStr
: tags
)
1330 applyToTorrents(hashes
, [&tagStr
](BitTorrent::Torrent
*const torrent
)
1332 torrent
->addTag(Tag(tagStr
));
1337 void TorrentsController::removeTagsAction()
1339 requireParams({u
"hashes"_s
});
1341 const QStringList hashes
{params()[u
"hashes"_s
].split(u
'|')};
1342 const QStringList tags
{params()[u
"tags"_s
].split(u
',', Qt::SkipEmptyParts
)};
1344 for (const QString
&tagStr
: tags
)
1346 applyToTorrents(hashes
, [&tagStr
](BitTorrent::Torrent
*const torrent
)
1348 torrent
->removeTag(Tag(tagStr
));
1354 applyToTorrents(hashes
, [](BitTorrent::Torrent
*const torrent
)
1356 torrent
->removeAllTags();
1361 void TorrentsController::createTagsAction()
1363 requireParams({u
"tags"_s
});
1365 const QStringList tags
{params()[u
"tags"_s
].split(u
',', Qt::SkipEmptyParts
)};
1367 for (const QString
&tagStr
: tags
)
1368 BitTorrent::Session::instance()->addTag(Tag(tagStr
));
1371 void TorrentsController::deleteTagsAction()
1373 requireParams({u
"tags"_s
});
1375 const QStringList tags
{params()[u
"tags"_s
].split(u
',', Qt::SkipEmptyParts
)};
1376 for (const QString
&tagStr
: tags
)
1377 BitTorrent::Session::instance()->removeTag(Tag(tagStr
));
1380 void TorrentsController::tagsAction()
1383 for (const Tag
&tag
: asConst(BitTorrent::Session::instance()->tags()))
1384 result
<< tag
.toString();
1388 void TorrentsController::renameFileAction()
1390 requireParams({u
"hash"_s
, u
"oldPath"_s
, u
"newPath"_s
});
1392 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
1393 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
1395 throw APIError(APIErrorType::NotFound
);
1397 const Path oldPath
{params()[u
"oldPath"_s
]};
1398 const Path newPath
{params()[u
"newPath"_s
]};
1402 torrent
->renameFile(oldPath
, newPath
);
1404 catch (const RuntimeError
&error
)
1406 throw APIError(APIErrorType::Conflict
, error
.message());
1410 void TorrentsController::renameFolderAction()
1412 requireParams({u
"hash"_s
, u
"oldPath"_s
, u
"newPath"_s
});
1414 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
1415 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
1417 throw APIError(APIErrorType::NotFound
);
1419 const Path oldPath
{params()[u
"oldPath"_s
]};
1420 const Path newPath
{params()[u
"newPath"_s
]};
1424 torrent
->renameFolder(oldPath
, newPath
);
1426 catch (const RuntimeError
&error
)
1428 throw APIError(APIErrorType::Conflict
, error
.message());
1432 void TorrentsController::exportAction()
1434 requireParams({u
"hash"_s
});
1436 const auto id
= BitTorrent::TorrentID::fromString(params()[u
"hash"_s
]);
1437 const BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->getTorrent(id
);
1439 throw APIError(APIErrorType::NotFound
);
1441 const nonstd::expected
<QByteArray
, QString
> result
= torrent
->exportToBuffer();
1443 throw APIError(APIErrorType::Conflict
, tr("Unable to export torrent file. Error: %1").arg(result
.error()));
1445 setResult(result
.value());