@ -40,6 +40,7 @@
@@ -40,6 +40,7 @@
# include <QUrl>
# include "base/bittorrent/filepriority.h"
# include "base/bittorrent/peerinfo.h"
# include "base/bittorrent/session.h"
# include "base/bittorrent/torrenthandle.h"
# include "base/bittorrent/torrentinfo.h"
@ -56,8 +57,12 @@
@@ -56,8 +57,12 @@
// Tracker keys
const char KEY_TRACKER_URL [ ] = " url " ;
const char KEY_TRACKER_STATUS [ ] = " status " ;
const char KEY_TRACKER_TIER [ ] = " tier " ;
const char KEY_TRACKER_MSG [ ] = " msg " ;
const char KEY_TRACKER_PEERS [ ] = " num_peers " ;
const char KEY_TRACKER_PEERS_COUNT [ ] = " num_peers " ;
const char KEY_TRACKER_SEEDS_COUNT [ ] = " num_seeds " ;
const char KEY_TRACKER_LEECHES_COUNT [ ] = " num_leeches " ;
const char KEY_TRACKER_DOWNLOADED_COUNT [ ] = " num_downloaded " ;
// Web seed keys
const char KEY_WEBSEED_URL [ ] = " url " ;
@ -114,17 +119,83 @@ namespace
@@ -114,17 +119,83 @@ namespace
void applyToTorrents ( const QStringList & hashes , const std : : function < void ( BitTorrent : : TorrentHandle * torrent ) > & func )
{
if ( ( hashes . size ( ) = = 1 ) & & ( hashes [ 0 ] = = QLatin1String ( " all " ) ) ) {
for ( BitTorrent : : TorrentHandle * torrent : asConst ( BitTorrent : : Session : : instance ( ) - > torrents ( ) ) )
for ( BitTorrent : : TorrentHandle * const torrent : asConst ( BitTorrent : : Session : : instance ( ) - > torrents ( ) ) )
func ( torrent ) ;
}
else {
for ( const QString & hash : hashes ) {
BitTorrent : : TorrentHandle * torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
if ( torrent )
func ( torrent ) ;
}
}
}
QVariantList getStickyTrackers ( const BitTorrent : : TorrentHandle * const torrent )
{
uint seedsDHT = 0 , seedsPeX = 0 , seedsLSD = 0 , leechesDHT = 0 , leechesPeX = 0 , leechesLSD = 0 ;
for ( const BitTorrent : : PeerInfo & peer : torrent - > peers ( ) ) {
if ( peer . isConnecting ( ) ) continue ;
if ( peer . isSeed ( ) ) {
if ( peer . fromDHT ( ) )
+ + seedsDHT ;
if ( peer . fromPeX ( ) )
+ + seedsPeX ;
if ( peer . fromLSD ( ) )
+ + seedsLSD ;
}
else {
if ( peer . fromDHT ( ) )
+ + leechesDHT ;
if ( peer . fromPeX ( ) )
+ + leechesPeX ;
if ( peer . fromLSD ( ) )
+ + leechesLSD ;
}
}
const int working = static_cast < int > ( BitTorrent : : TrackerEntry : : Working ) ;
const int disabled = 0 ;
const QString privateMsg { QCoreApplication : : translate ( " TrackerListWidget " , " This torrent is private " ) } ;
const bool isTorrentPrivate = torrent - > isPrivate ( ) ;
const QVariantMap dht {
{ KEY_TRACKER_URL , " ** [DHT] ** " } ,
{ KEY_TRACKER_TIER , " " } ,
{ KEY_TRACKER_MSG , ( isTorrentPrivate ? privateMsg : " " ) } ,
{ KEY_TRACKER_STATUS , ( ( BitTorrent : : Session : : instance ( ) - > isDHTEnabled ( ) & & ! isTorrentPrivate ) ? working : disabled ) } ,
{ KEY_TRACKER_PEERS_COUNT , 0 } ,
{ KEY_TRACKER_DOWNLOADED_COUNT , 0 } ,
{ KEY_TRACKER_SEEDS_COUNT , seedsDHT } ,
{ KEY_TRACKER_LEECHES_COUNT , leechesDHT }
} ;
const QVariantMap pex {
{ KEY_TRACKER_URL , " ** [PeX] ** " } ,
{ KEY_TRACKER_TIER , " " } ,
{ KEY_TRACKER_MSG , ( isTorrentPrivate ? privateMsg : " " ) } ,
{ KEY_TRACKER_STATUS , ( ( BitTorrent : : Session : : instance ( ) - > isPeXEnabled ( ) & & ! isTorrentPrivate ) ? working : disabled ) } ,
{ KEY_TRACKER_PEERS_COUNT , 0 } ,
{ KEY_TRACKER_DOWNLOADED_COUNT , 0 } ,
{ KEY_TRACKER_SEEDS_COUNT , seedsPeX } ,
{ KEY_TRACKER_LEECHES_COUNT , leechesPeX }
} ;
const QVariantMap lsd {
{ KEY_TRACKER_URL , " ** [LSD] ** " } ,
{ KEY_TRACKER_TIER , " " } ,
{ KEY_TRACKER_MSG , ( isTorrentPrivate ? privateMsg : " " ) } ,
{ KEY_TRACKER_STATUS , ( ( BitTorrent : : Session : : instance ( ) - > isLSDEnabled ( ) & & ! isTorrentPrivate ) ? working : disabled ) } ,
{ KEY_TRACKER_PEERS_COUNT , 0 } ,
{ KEY_TRACKER_DOWNLOADED_COUNT , 0 } ,
{ KEY_TRACKER_SEEDS_COUNT , seedsLSD } ,
{ KEY_TRACKER_LEECHES_COUNT , leechesLSD }
} ;
return QVariantList { dht , pex , lsd } ;
}
}
// Returns all the torrents in JSON format.
@ -295,39 +366,37 @@ void TorrentsController::propertiesAction()
@@ -295,39 +366,37 @@ void TorrentsController::propertiesAction()
// The dictionary keys are:
// - "url": Tracker URL
// - "status": Tracker status
// - "num_peers": Tracker peer count
// - "tier": Tracker tier
// - "num_peers": Number of peers this torrent is currently connected to
// - "num_seeds": Number of peers that have the whole file
// - "num_leeches": Number of peers that are still downloading
// - "num_downloaded": Tracker downloaded count
// - "msg": Tracker message (last)
void TorrentsController : : trackersAction ( )
{
checkParams ( { " hash " } ) ;
const QString hash { params ( ) [ " hash " ] } ;
QVariantList trackerList ;
BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
const BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
if ( ! torrent )
throw APIError ( APIErrorType : : NotFound ) ;
QVariantList trackerList = getStickyTrackers ( torrent ) ;
QHash < QString , BitTorrent : : TrackerInfo > trackersData = torrent - > trackerInfos ( ) ;
for ( const BitTorrent : : TrackerEntry & tracker : asConst ( torrent - > trackers ( ) ) ) {
QVariantMap trackerDict ;
trackerDict [ KEY_TRACKER_URL ] = tracker . url ( ) ;
const BitTorrent : : TrackerInfo data = trackersData . value ( tracker . url ( ) ) ;
QString status ;
switch ( tracker . status ( ) ) {
case BitTorrent : : TrackerEntry : : NotContacted :
status = tr ( " Not contacted yet " ) ; break ;
case BitTorrent : : TrackerEntry : : Updating :
status = tr ( " Updating... " ) ; break ;
case BitTorrent : : TrackerEntry : : Working :
status = tr ( " Working " ) ; break ;
case BitTorrent : : TrackerEntry : : NotWorking :
status = tr ( " Not working " ) ; break ;
}
trackerDict [ KEY_TRACKER_STATUS ] = status ;
trackerDict [ KEY_TRACKER_PEERS ] = data . numPeers ;
trackerDict [ KEY_TRACKER_MSG ] = data . lastMessage . trimmed ( ) ;
trackerList . append ( trackerDict ) ;
trackerList < < QVariantMap {
{ KEY_TRACKER_URL , tracker . url ( ) } ,
{ KEY_TRACKER_TIER , tracker . tier ( ) } ,
{ KEY_TRACKER_STATUS , static_cast < int > ( tracker . status ( ) ) } ,
{ KEY_TRACKER_PEERS_COUNT , data . numPeers } ,
{ KEY_TRACKER_MSG , data . lastMessage . trimmed ( ) } ,
{ KEY_TRACKER_SEEDS_COUNT , tracker . nativeEntry ( ) . scrape_complete } ,
{ KEY_TRACKER_LEECHES_COUNT , tracker . nativeEntry ( ) . scrape_incomplete } ,
{ KEY_TRACKER_DOWNLOADED_COUNT , tracker . nativeEntry ( ) . scrape_downloaded }
} ;
}
setResult ( QJsonArray : : fromVariantList ( trackerList ) ) ;
@ -532,15 +601,82 @@ void TorrentsController::addTrackersAction()
@@ -532,15 +601,82 @@ void TorrentsController::addTrackersAction()
const QString hash = params ( ) [ " hash " ] ;
BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
if ( torrent ) {
QList < BitTorrent : : TrackerEntry > trackers ;
for ( QString url : asConst ( params ( ) [ " urls " ] . split ( ' \n ' ) ) ) {
url = url . trimmed ( ) ;
if ( ! url . isEmpty ( ) )
trackers < < url ;
if ( ! torrent )
throw APIError ( APIErrorType : : NotFound ) ;
QList < BitTorrent : : TrackerEntry > trackers ;
for ( const QString & urlStr : asConst ( params ( ) [ " urls " ] . split ( ' \n ' ) ) ) {
const QUrl url { urlStr . trimmed ( ) } ;
if ( url . isValid ( ) )
trackers < < url . toString ( ) ;
}
torrent - > addTrackers ( trackers ) ;
}
void TorrentsController : : editTrackerAction ( )
{
checkParams ( { " hash " , " origUrl " , " newUrl " } ) ;
const QString hash = params ( ) [ " hash " ] ;
const QString origUrl = params ( ) [ " origUrl " ] ;
const QString newUrl = params ( ) [ " newUrl " ] ;
BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
if ( ! torrent )
throw APIError ( APIErrorType : : NotFound ) ;
const QUrl origTrackerUrl ( origUrl ) ;
const QUrl newTrackerUrl ( newUrl ) ;
if ( origTrackerUrl = = newTrackerUrl )
return ;
if ( ! newTrackerUrl . isValid ( ) )
throw APIError ( APIErrorType : : BadParams , " New tracker URL is invalid " ) ;
QList < BitTorrent : : TrackerEntry > trackers = torrent - > trackers ( ) ;
bool match = false ;
for ( BitTorrent : : TrackerEntry & tracker : trackers ) {
const QUrl trackerUrl ( tracker . url ( ) ) ;
if ( trackerUrl = = newTrackerUrl )
throw APIError ( APIErrorType : : Conflict , " New tracker URL already exists " ) ;
if ( trackerUrl = = origTrackerUrl ) {
match = true ;
BitTorrent : : TrackerEntry newTracker ( newTrackerUrl . toString ( ) ) ;
newTracker . setTier ( tracker . tier ( ) ) ;
tracker = newTracker ;
}
torrent - > addTrackers ( trackers ) ;
}
if ( ! match )
throw APIError ( APIErrorType : : Conflict , " Tracker not found " ) ;
torrent - > replaceTrackers ( trackers ) ;
if ( ! torrent - > isPaused ( ) )
torrent - > forceReannounce ( ) ;
}
void TorrentsController : : removeTrackersAction ( )
{
checkParams ( { " hash " , " urls " } ) ;
const QString hash = params ( ) [ " hash " ] ;
const QStringList urls = params ( ) [ " urls " ] . split ( ' | ' ) ;
BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
if ( ! torrent )
throw APIError ( APIErrorType : : NotFound ) ;
QList < BitTorrent : : TrackerEntry > remainingTrackers ;
const QList < BitTorrent : : TrackerEntry > trackers = torrent - > trackers ( ) ;
for ( const BitTorrent : : TrackerEntry & entry : trackers ) {
if ( ! urls . contains ( entry . url ( ) ) )
remainingTrackers . push_back ( entry ) ;
}
if ( remainingTrackers . size ( ) = = trackers . size ( ) )
throw APIError ( APIErrorType : : Conflict , " No trackers were removed " ) ;
torrent - > replaceTrackers ( remainingTrackers ) ;
if ( ! torrent - > isPaused ( ) )
torrent - > forceReannounce ( ) ;
}
void TorrentsController : : pauseAction ( )
@ -548,7 +684,7 @@ void TorrentsController::pauseAction()
@@ -548,7 +684,7 @@ void TorrentsController::pauseAction()
checkParams ( { " hashes " } ) ;
const QStringList hashes = params ( ) [ " hashes " ] . split ( ' | ' ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > pause ( ) ; } ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > pause ( ) ; } ) ;
}
void TorrentsController : : resumeAction ( )
@ -556,7 +692,7 @@ void TorrentsController::resumeAction()
@@ -556,7 +692,7 @@ void TorrentsController::resumeAction()
checkParams ( { " hashes " } ) ;
const QStringList hashes = params ( ) [ " hashes " ] . split ( ' | ' ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > resume ( ) ; } ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > resume ( ) ; } ) ;
}
void TorrentsController : : filePrioAction ( )
@ -606,7 +742,7 @@ void TorrentsController::uploadLimitAction()
@@ -606,7 +742,7 @@ void TorrentsController::uploadLimitAction()
QVariantMap map ;
for ( const QString & hash : hashes ) {
int limit = - 1 ;
BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
const BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
if ( torrent )
limit = torrent - > uploadLimit ( ) ;
map [ hash ] = limit ;
@ -623,7 +759,7 @@ void TorrentsController::downloadLimitAction()
@@ -623,7 +759,7 @@ void TorrentsController::downloadLimitAction()
QVariantMap map ;
for ( const QString & hash : hashes ) {
int limit = - 1 ;
BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
const BitTorrent : : TorrentHandle * const torrent = BitTorrent : : Session : : instance ( ) - > findTorrent ( hash ) ;
if ( torrent )
limit = torrent - > downloadLimit ( ) ;
map [ hash ] = limit ;
@ -641,7 +777,7 @@ void TorrentsController::setUploadLimitAction()
@@ -641,7 +777,7 @@ void TorrentsController::setUploadLimitAction()
limit = - 1 ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ limit ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > setUploadLimit ( limit ) ; } ) ;
applyToTorrents ( hashes , [ limit ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > setUploadLimit ( limit ) ; } ) ;
}
void TorrentsController : : setDownloadLimitAction ( )
@ -653,7 +789,7 @@ void TorrentsController::setDownloadLimitAction()
@@ -653,7 +789,7 @@ void TorrentsController::setDownloadLimitAction()
limit = - 1 ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ limit ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > setDownloadLimit ( limit ) ; } ) ;
applyToTorrents ( hashes , [ limit ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > setDownloadLimit ( limit ) ; } ) ;
}
void TorrentsController : : setShareLimitsAction ( )
@ -664,7 +800,7 @@ void TorrentsController::setShareLimitsAction()
@@ -664,7 +800,7 @@ void TorrentsController::setShareLimitsAction()
const qlonglong seedingTimeLimit = params ( ) [ " seedingTimeLimit " ] . toLongLong ( ) ;
const QStringList hashes = params ( ) [ " hashes " ] . split ( ' | ' ) ;
applyToTorrents ( hashes , [ ratioLimit , seedingTimeLimit ] ( BitTorrent : : TorrentHandle * torrent )
applyToTorrents ( hashes , [ ratioLimit , seedingTimeLimit ] ( BitTorrent : : TorrentHandle * const torrent )
{
torrent - > setRatioLimit ( ratioLimit ) ;
torrent - > setSeedingTimeLimit ( seedingTimeLimit ) ;
@ -676,7 +812,7 @@ void TorrentsController::toggleSequentialDownloadAction()
@@ -676,7 +812,7 @@ void TorrentsController::toggleSequentialDownloadAction()
checkParams ( { " hashes " } ) ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > toggleSequentialDownload ( ) ; } ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > toggleSequentialDownload ( ) ; } ) ;
}
void TorrentsController : : toggleFirstLastPiecePrioAction ( )
@ -684,7 +820,7 @@ void TorrentsController::toggleFirstLastPiecePrioAction()
@@ -684,7 +820,7 @@ void TorrentsController::toggleFirstLastPiecePrioAction()
checkParams ( { " hashes " } ) ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > toggleFirstLastPiecePriority ( ) ; } ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > toggleFirstLastPiecePriority ( ) ; } ) ;
}
void TorrentsController : : setSuperSeedingAction ( )
@ -693,7 +829,7 @@ void TorrentsController::setSuperSeedingAction()
@@ -693,7 +829,7 @@ void TorrentsController::setSuperSeedingAction()
const bool value { parseBool ( params ( ) [ " value " ] , false ) } ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ value ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > setSuperSeeding ( value ) ; } ) ;
applyToTorrents ( hashes , [ value ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > setSuperSeeding ( value ) ; } ) ;
}
void TorrentsController : : setForceStartAction ( )
@ -702,7 +838,7 @@ void TorrentsController::setForceStartAction()
@@ -702,7 +838,7 @@ void TorrentsController::setForceStartAction()
const bool value { parseBool ( params ( ) [ " value " ] , false ) } ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ value ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > resume ( value ) ; } ) ;
applyToTorrents ( hashes , [ value ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > resume ( value ) ; } ) ;
}
void TorrentsController : : deleteAction ( )
@ -711,7 +847,7 @@ void TorrentsController::deleteAction()
@@ -711,7 +847,7 @@ void TorrentsController::deleteAction()
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
const bool deleteFiles { parseBool ( params ( ) [ " deleteFiles " ] , false ) } ;
applyToTorrents ( hashes , [ deleteFiles ] ( BitTorrent : : TorrentHandle * torrent )
applyToTorrents ( hashes , [ deleteFiles ] ( BitTorrent : : TorrentHandle * const torrent )
{
BitTorrent : : Session : : instance ( ) - > deleteTorrent ( torrent - > hash ( ) , deleteFiles ) ;
} ) ;
@ -779,7 +915,7 @@ void TorrentsController::setLocationAction()
@@ -779,7 +915,7 @@ void TorrentsController::setLocationAction()
if ( ! QFileInfo ( newLocation ) . isWritable ( ) )
throw APIError ( APIErrorType : : AccessDenied , tr ( " Cannot write to directory " ) ) ;
applyToTorrents ( hashes , [ newLocation ] ( BitTorrent : : TorrentHandle * torrent )
applyToTorrents ( hashes , [ newLocation ] ( BitTorrent : : TorrentHandle * const torrent )
{
LogMsg ( tr ( " WebUI Set location: moving \" %1 \" , from \" %2 \" to \" %3 \" " )
. arg ( torrent - > name ( ) , Utils : : Fs : : toNativePath ( torrent - > savePath ( ) ) , Utils : : Fs : : toNativePath ( newLocation ) ) ) ;
@ -812,7 +948,7 @@ void TorrentsController::setAutoManagementAction()
@@ -812,7 +948,7 @@ void TorrentsController::setAutoManagementAction()
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
const bool isEnabled { parseBool ( params ( ) [ " enable " ] , false ) } ;
applyToTorrents ( hashes , [ isEnabled ] ( BitTorrent : : TorrentHandle * torrent )
applyToTorrents ( hashes , [ isEnabled ] ( BitTorrent : : TorrentHandle * const torrent )
{
torrent - > setAutoTMMEnabled ( isEnabled ) ;
} ) ;
@ -823,7 +959,7 @@ void TorrentsController::recheckAction()
@@ -823,7 +959,7 @@ void TorrentsController::recheckAction()
checkParams ( { " hashes " } ) ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > forceRecheck ( ) ; } ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > forceRecheck ( ) ; } ) ;
}
void TorrentsController : : reannounceAction ( )
@ -831,7 +967,7 @@ void TorrentsController::reannounceAction()
@@ -831,7 +967,7 @@ void TorrentsController::reannounceAction()
checkParams ( { " hashes " } ) ;
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * torrent ) { torrent - > forceReannounce ( ) ; } ) ;
applyToTorrents ( hashes , [ ] ( BitTorrent : : TorrentHandle * const torrent ) { torrent - > forceReannounce ( ) ; } ) ;
}
void TorrentsController : : setCategoryAction ( )
@ -841,7 +977,7 @@ void TorrentsController::setCategoryAction()
@@ -841,7 +977,7 @@ void TorrentsController::setCategoryAction()
const QStringList hashes { params ( ) [ " hashes " ] . split ( ' | ' ) } ;
const QString category { params ( ) [ " category " ] . trimmed ( ) } ;
applyToTorrents ( hashes , [ category ] ( BitTorrent : : TorrentHandle * torrent )
applyToTorrents ( hashes , [ category ] ( BitTorrent : : TorrentHandle * const torrent )
{
if ( ! torrent - > setCategory ( category ) )
throw APIError ( APIErrorType : : Conflict , tr ( " Incorrect category name " ) ) ;