From: qu1ck Date: Sun, 17 Feb 2019 09:33:57 +0000 (-0800) Subject: Add labels feature (#822) X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7aa12a025dcc0379b57ae91dfc874eea0430f838;p=transmission Add labels feature (#822) * applied changes from https://github.com/Elbandi/transmission/tree/elbandi/labels to official transmission repo * Fix compilation errors * Address review comments Changed `tr_ptrArray* labels` to `tr_ptrArray labels`; Removed tr_ptrArrayNew() tr_ptrArrayDup() tr_ptrArrayFree() Use tr_strsep() to split string by delimiters Update transmission-remote.1 Update rpc-spec.txt * Fix warning, address comments * Rebase, fix formatting and address comments Use uncrustify to format changed files Fix "const " -> " const" Fix small comments * Lock torrent for setLabels, check for duplicates * Check for empty labels in daemon * Stop on first error --- diff --git a/extras/rpc-spec.txt b/extras/rpc-spec.txt index 09eac6b8e..6b5a4a0cd 100644 --- a/extras/rpc-spec.txt +++ b/extras/rpc-spec.txt @@ -117,6 +117,7 @@ "files-unwanted" | array indices of file(s) to not download "honorsSessionLimits" | boolean true if session upload limits are honored "ids" | array torrent list, as described in 3.1 + "labels" | array array of string labels "location" | string new location of the torrent's content "peer-limit" | number maximum number of peers "priority-high" | array indices of high-priority file(s) @@ -189,6 +190,7 @@ isFinished | boolean | tr_stat isPrivate | boolean | tr_torrent isStalled | boolean | tr_stat + labels | array (see below) | tr_torrent leftUntilDone | number | tr_stat magnetLink | string | n/a manualAnnounceTime | number | tr_stat @@ -247,6 +249,10 @@ | wanted | boolean | tr_info | priority | number | tr_info -------------------+--------------------------------------+ + labels | an array of strings: | + +-------------------------+------------+ + | label | string | tr_torrent + -------------------+--------------------------------------+ peers | array of objects, each containing: | +-------------------------+------------+ | address | string | tr_peer_stat @@ -784,6 +790,9 @@ ------+---------+-----------+----------------------+------------------------------- 16 | 3.00 | yes | session-get | new request arg "fields" | | yes | session-get | new arg "session-id" + | | yes | torrent-get | new arg "labels" + | | yes | torrent-set | new arg "labels" + 5.1. Upcoming Breakage diff --git a/libtransmission/quark.c b/libtransmission/quark.c index e19ac9a2f..8df91867b 100644 --- a/libtransmission/quark.c +++ b/libtransmission/quark.c @@ -163,6 +163,7 @@ static struct tr_key_struct const my_static[] = { "isStalled", 9 }, { "isUTP", 5 }, { "isUploadingTo", 13 }, + { "labels", 6 }, { "lastAnnouncePeerCount", 21 }, { "lastAnnounceResult", 18 }, { "lastAnnounceStartTime", 21 }, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index 9dc534560..6c44835d0 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -165,6 +165,7 @@ enum TR_KEY_isStalled, TR_KEY_isUTP, TR_KEY_isUploadingTo, + TR_KEY_labels, TR_KEY_lastAnnouncePeerCount, TR_KEY_lastAnnounceResult, TR_KEY_lastAnnounceStartTime, diff --git a/libtransmission/resume.c b/libtransmission/resume.c index 1189c8d82..c438c7eb0 100644 --- a/libtransmission/resume.c +++ b/libtransmission/resume.c @@ -111,6 +111,44 @@ static uint64_t loadPeers(tr_variant* dict, tr_torrent* tor) **** ***/ +static void saveLabels(tr_variant* dict, tr_torrent const* tor) +{ + int const n = tr_ptrArraySize(&tor->labels); + tr_variant* list = tr_variantDictAddList(dict, TR_KEY_labels, n); + char const* const* labels = (char const* const*)tr_ptrArrayBase(&tor->labels); + for (int i = 0; i < n; ++i) + { + tr_variantListAddStr(list, labels[i]); + } +} + +static uint64_t loadLabels(tr_variant* dict, tr_torrent* tor) +{ + uint64_t ret = 0; + tr_variant* list; + if (tr_variantDictFindList(dict, TR_KEY_labels, &list)) + { + int const n = tr_variantListSize(list); + char const* str; + size_t str_len; + for (int i = 0; i < n; ++i) + { + if (tr_variantGetStr(tr_variantListChild(list, i), &str, &str_len) && str != NULL && str_len != 0) + { + tr_ptrArrayAppend(&tor->labels, tr_strndup(str, str_len)); + } + } + + ret = TR_FR_LABELS; + } + + return ret; +} + +/*** +**** +***/ + static void saveDND(tr_variant* dict, tr_torrent const* tor) { tr_variant* list; @@ -735,6 +773,7 @@ void tr_torrentSaveResume(tr_torrent* tor) saveIdleLimits(&top, tor); saveFilenames(&top, tor); saveName(&top, tor); + saveLabels(&top, tor); filename = getResumeFilename(tor, TR_METAINFO_BASENAME_HASH); @@ -943,6 +982,11 @@ static uint64_t loadFromFile(tr_torrent* tor, uint64_t fieldsToLoad, bool* didRe fieldsLoaded |= loadName(&top, tor); } + if ((fieldsToLoad & TR_FR_LABELS) != 0) + { + fieldsLoaded |= loadLabels(&top, tor); + } + /* loading the resume file triggers of a lot of changes, * but none of them needs to trigger a re-saving of the * same resume information... */ diff --git a/libtransmission/resume.h b/libtransmission/resume.h index ea4e48cc5..0ff034fe0 100644 --- a/libtransmission/resume.h +++ b/libtransmission/resume.h @@ -35,7 +35,8 @@ enum TR_FR_TIME_SEEDING = (1 << 18), TR_FR_TIME_DOWNLOADING = (1 << 19), TR_FR_FILENAMES = (1 << 20), - TR_FR_NAME = (1 << 21) + TR_FR_NAME = (1 << 21), + TR_FR_LABELS = (1 << 22) }; /** diff --git a/libtransmission/rpcimpl.c b/libtransmission/rpcimpl.c index e3896f197..92d2fef8a 100644 --- a/libtransmission/rpcimpl.c +++ b/libtransmission/rpcimpl.c @@ -397,6 +397,17 @@ static char const* torrentVerify(tr_session* session, tr_variant* args_in, tr_va **** ***/ +static void addLabels(tr_torrent const* tor, tr_variant* list) +{ + int const labelsCount = tr_ptrArraySize(&tor->labels); + tr_variantInitList(list, labelsCount); + char const* const* labels = (char const* const*)tr_ptrArrayBase(&tor->labels); + for (int i = 0; i < labelsCount; ++i) + { + tr_variantListAddStr(list, labels[i]); + } +} + static void addFileStats(tr_torrent const* tor, tr_variant* list) { tr_file_index_t n; @@ -632,6 +643,10 @@ static void addField(tr_torrent* const tor, tr_info const* const inf, tr_stat co tr_variantDictAddBool(d, key, st->isStalled); break; + case TR_KEY_labels: + addLabels(tor, tr_variantDictAdd(d, key)); + break; + case TR_KEY_leftUntilDone: tr_variantDictAddInt(d, key, st->leftUntilDone); break; @@ -927,6 +942,67 @@ static char const* torrentGet(tr_session* session, tr_variant* args_in, tr_varia **** ***/ +static char const* setLabels(tr_torrent* tor, tr_variant* list) +{ + int const n = tr_variantListSize(list); + char const* errmsg = NULL; + tr_ptrArray labels = TR_PTR_ARRAY_INIT; + int labelcount = 0; + for (int i = 0; i < n; i++) + { + char const* str; + size_t str_len; + if (tr_variantGetStr(tr_variantListChild(list, i), &str, &str_len) && str != NULL) + { + char* label = tr_strndup(str, str_len); + tr_strstrip(label); + if (*label == '\0') + { + errmsg = "labels cannot be empty"; + } + + if (errmsg == NULL && strchr(str, ',') != NULL) + { + errmsg = "labels cannot contain comma (,) character"; + } + + if (errmsg == NULL) + { + bool dup = false; + for (int j = 0; j < labelcount; j++) + { + if (tr_strcmp0(label, (char*)tr_ptrArrayNth(&labels, j)) == 0) + { + dup = true; + break; + } + } + + if (dup) + { + errmsg = "labels cannot contain duplicates"; + } + } + + tr_ptrArrayAppend(&labels, label); + labelcount++; + + if (errmsg != NULL) + { + break; + } + } + } + + if (errmsg == NULL) + { + tr_torrentSetLabels(tor, &labels); + } + + tr_ptrArrayDestruct(&labels, tr_free); + return errmsg; +} + static char const* setFilePriorities(tr_torrent* tor, int priority, tr_variant* list) { int64_t tmp; @@ -1226,8 +1302,7 @@ static char const* torrentSet(tr_session* session, tr_variant* args_in, tr_varia { int64_t tmp; double d; - tr_variant* files; - tr_variant* trackers; + tr_variant* tmp_variant; bool boolVal; tr_torrent* tor; @@ -1241,14 +1316,19 @@ static char const* torrentSet(tr_session* session, tr_variant* args_in, tr_varia } } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_files_unwanted, &files)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_labels, &tmp_variant)) + { + errmsg = setLabels(tor, tmp_variant); + } + + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_files_unwanted, &tmp_variant)) { - errmsg = setFileDLs(tor, false, files); + errmsg = setFileDLs(tor, false, tmp_variant); } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_files_wanted, &files)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_files_wanted, &tmp_variant)) { - errmsg = setFileDLs(tor, true, files); + errmsg = setFileDLs(tor, true, tmp_variant); } if (tr_variantDictFindInt(args_in, TR_KEY_peer_limit, &tmp)) @@ -1256,19 +1336,19 @@ static char const* torrentSet(tr_session* session, tr_variant* args_in, tr_varia tr_torrentSetPeerLimit(tor, tmp); } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_high, &files)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_high, &tmp_variant)) { - errmsg = setFilePriorities(tor, TR_PRI_HIGH, files); + errmsg = setFilePriorities(tor, TR_PRI_HIGH, tmp_variant); } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_low, &files)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_low, &tmp_variant)) { - errmsg = setFilePriorities(tor, TR_PRI_LOW, files); + errmsg = setFilePriorities(tor, TR_PRI_LOW, tmp_variant); } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_normal, &files)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_priority_normal, &tmp_variant)) { - errmsg = setFilePriorities(tor, TR_PRI_NORMAL, files); + errmsg = setFilePriorities(tor, TR_PRI_NORMAL, tmp_variant); } if (tr_variantDictFindInt(args_in, TR_KEY_downloadLimit, &tmp)) @@ -1321,19 +1401,19 @@ static char const* torrentSet(tr_session* session, tr_variant* args_in, tr_varia tr_torrentSetQueuePosition(tor, tmp); } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerAdd, &trackers)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerAdd, &tmp_variant)) { - errmsg = addTrackerUrls(tor, trackers); + errmsg = addTrackerUrls(tor, tmp_variant); } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerRemove, &trackers)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerRemove, &tmp_variant)) { - errmsg = removeTrackers(tor, trackers); + errmsg = removeTrackers(tor, tmp_variant); } - if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerReplace, &trackers)) + if (errmsg == NULL && tr_variantDictFindList(args_in, TR_KEY_trackerReplace, &tmp_variant)) { - errmsg = replaceTrackers(tor, trackers); + errmsg = replaceTrackers(tor, tmp_variant); } notify(session, TR_RPC_TORRENT_CHANGED, tor); diff --git a/libtransmission/torrent.c b/libtransmission/torrent.c index ab16735c4..505a935be 100644 --- a/libtransmission/torrent.c +++ b/libtransmission/torrent.c @@ -937,6 +937,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) tor->uniqueId = nextUniqueId++; tor->magicNumber = TORRENT_MAGIC_NUMBER; tor->queuePosition = session->torrentCount; + tor->labels = TR_PTR_ARRAY_INIT; tr_sha1(tor->obfuscatedHash, "req2", 4, tor->info.hash, SHA_DIGEST_LENGTH, NULL); @@ -1739,6 +1740,7 @@ static void freeTorrent(tr_torrent* tor) TR_ASSERT(queueIsSequenced(session)); tr_bandwidthDestruct(&tor->bandwidth); + tr_ptrArrayDestruct(&tor->labels, tr_free); tr_metainfoFree(inf); memset(tor, ~0, sizeof(tr_torrent)); @@ -2522,6 +2524,30 @@ void tr_torrentSetFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file **** ***/ +void tr_torrentSetLabels(tr_torrent* tor, tr_ptrArray* labels) +{ + TR_ASSERT(tr_isTorrent(tor)); + + tr_torrentLock(tor); + + tr_ptrArrayDestruct(&tor->labels, tr_free); + tor->labels = TR_PTR_ARRAY_INIT; + char** l = (char**)tr_ptrArrayBase(labels); + int const n = tr_ptrArraySize(labels); + for (int i = 0; i < n; i++) + { + tr_ptrArrayAppend(&tor->labels, tr_strdup(l[i])); + } + + tr_torrentSetDirty(tor); + + tr_torrentUnlock(tor); +} + +/*** +**** +***/ + tr_priority_t tr_torrentGetPriority(tr_torrent const* tor) { TR_ASSERT(tr_isTorrent(tor)); diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 522019292..18bc031ef 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -17,6 +17,7 @@ #include "session.h" /* tr_sessionLock(), tr_sessionUnlock() */ #include "tr-assert.h" #include "utils.h" /* TR_GNUC_PRINTF */ +#include "ptrarray.h" struct tr_torrent_tiers; struct tr_magnet_info; @@ -42,6 +43,8 @@ void tr_ctorInitTorrentWanted(tr_ctor const* ctor, tr_torrent* tor); /* just like tr_torrentSetFileDLs but doesn't trigger a fastresume save */ void tr_torrentInitFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file_index_t fileCount, bool do_download); +void tr_torrentSetLabels(tr_torrent* tor, tr_ptrArray* labels); + void tr_torrentRecheckCompleteness(tr_torrent*); void tr_torrentSetHasPiece(tr_torrent* tor, tr_piece_index_t pieceIndex, bool has); @@ -240,6 +243,8 @@ struct tr_torrent uint16_t idleLimitMinutes; tr_idlelimit idleLimitMode; bool finishedSeedingByIdle; + + tr_ptrArray labels; }; static inline tr_torrent* tr_torrentNext(tr_session* session, tr_torrent* current) diff --git a/utils/remote.c b/utils/remote.c index a8ae16c15..9f2b56166 100644 --- a/utils/remote.c +++ b/utils/remote.c @@ -290,6 +290,7 @@ static tr_option opts[] = { 920, "session-info", "Show the session's details", "si", 0, NULL }, { 921, "session-stats", "Show the session's statistics", "st", 0, NULL }, { 'l', "list", "List all torrents", "l", 0, NULL }, + { 'L', "labels", "Set the current torrents' labels", "L", 1, "" }, { 960, "move", "Move current torrent's data to a new folder", NULL, 1, "" }, { 961, "find", "Tell Transmission where to find a torrent's data", NULL, 1, "" }, { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL }, @@ -441,6 +442,7 @@ static int getOptMode(int val) case 993: /* no-trash-torrent */ return MODE_SESSION_SET; + case 'L': /* labels */ case 712: /* tracker-remove */ case 950: /* seedratio */ case 951: /* seedratio-default */ @@ -649,6 +651,29 @@ static void addDays(tr_variant* args, tr_quark const key, char const* arg) } } +static void addLabels(tr_variant* args, char const* arg) +{ + tr_variant* labels; + if (!tr_variantDictFindList(args, TR_KEY_labels, &labels)) + { + labels = tr_variantDictAddList(args, TR_KEY_labels, 10); + } + + char* argcpy = tr_strdup(arg); + char* const tmp = argcpy; /* save copied string start pointer to free later */ + char* token; + while ((token = tr_strsep(&argcpy, ",")) != NULL) + { + tr_strstrip(token); + if (*token != '\0') + { + tr_variantListAddStr(labels, token); + } + } + + tr_free(tmp); +} + static void addFiles(tr_variant* args, tr_quark const key, char const* arg) { tr_variant* files = tr_variantDictAddList(args, key, 100); @@ -706,6 +731,7 @@ static tr_quark const details_keys[] = TR_KEY_id, TR_KEY_isFinished, TR_KEY_isPrivate, + TR_KEY_labels, TR_KEY_leftUntilDone, TR_KEY_magnetLink, TR_KEY_name, @@ -938,6 +964,22 @@ static void printDetails(tr_variant* top) printf(" Magnet: %s\n", str); } + if (tr_variantDictFindList(t, TR_KEY_labels, &l)) + { + int const n = tr_variantListSize(l); + char const* str; + printf(" Labels: "); + for (int i = 0; i < n; i++) + { + if (tr_variantGetStr(tr_variantListChild(l, i), &str, NULL)) + { + printf(i == 0 ? "%s" : ", %s", str); + } + } + + printf("\n"); + } + printf("\n"); printf("TRANSFER\n"); @@ -2651,6 +2693,10 @@ static int processArgs(char const* rpcurl, int argc, char const* const* argv) switch (c) { + case 'L': + addLabels(args, optarg); + break; + case 712: tr_variantListAddInt(tr_variantDictAddList(args, TR_KEY_trackerRemove, 1), atoi(optarg)); break; diff --git a/utils/transmission-remote.1 b/utils/transmission-remote.1 index a6c12eed2..59e4b5e06 100644 --- a/utils/transmission-remote.1 +++ b/utils/transmission-remote.1 @@ -178,6 +178,8 @@ List session information from the server List statistical information from the server .It Fl l Fl -list List all torrents +.It Fl L Fl -labels +Set the specified torrent's labels .It Fl m Fl -portmap Enable portmapping via NAT-PMP or UPnP .It Fl M Fl -no-portmap