* 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 <type>" -> "<type> const"
Fix small comments
* Lock torrent for setLabels, check for duplicates
* Check for empty labels in daemon
* Stop on first error
"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)
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
| 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
------+---------+-----------+----------------------+-------------------------------
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
{ "isStalled", 9 },
{ "isUTP", 5 },
{ "isUploadingTo", 13 },
+ { "labels", 6 },
{ "lastAnnouncePeerCount", 21 },
{ "lastAnnounceResult", 18 },
{ "lastAnnounceStartTime", 21 },
TR_KEY_isStalled,
TR_KEY_isUTP,
TR_KEY_isUploadingTo,
+ TR_KEY_labels,
TR_KEY_lastAnnouncePeerCount,
TR_KEY_lastAnnounceResult,
TR_KEY_lastAnnounceStartTime,
****
***/
+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;
saveIdleLimits(&top, tor);
saveFilenames(&top, tor);
saveName(&top, tor);
+ saveLabels(&top, tor);
filename = getResumeFilename(tor, TR_METAINFO_BASENAME_HASH);
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... */
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)
};
/**
****
***/
+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;
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;
****
***/
+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;
{
int64_t tmp;
double d;
- tr_variant* files;
- tr_variant* trackers;
+ tr_variant* tmp_variant;
bool boolVal;
tr_torrent* tor;
}
}
- 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))
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))
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);
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);
TR_ASSERT(queueIsSequenced(session));
tr_bandwidthDestruct(&tor->bandwidth);
+ tr_ptrArrayDestruct(&tor->labels, tr_free);
tr_metainfoFree(inf);
memset(tor, ~0, sizeof(tr_torrent));
****
***/
+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));
#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;
/* 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);
uint16_t idleLimitMinutes;
tr_idlelimit idleLimitMode;
bool finishedSeedingByIdle;
+
+ tr_ptrArray labels;
};
static inline tr_torrent* tr_torrentNext(tr_session* session, tr_torrent* current)
{ 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, "<label[,label...]>" },
{ 960, "move", "Move current torrent's data to a new folder", NULL, 1, "<path>" },
{ 961, "find", "Tell Transmission where to find a torrent's data", NULL, 1, "<path>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
case 993: /* no-trash-torrent */
return MODE_SESSION_SET;
+ case 'L': /* labels */
case 712: /* tracker-remove */
case 950: /* seedratio */
case 951: /* seedratio-default */
}
}
+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);
TR_KEY_id,
TR_KEY_isFinished,
TR_KEY_isPrivate,
+ TR_KEY_labels,
TR_KEY_leftUntilDone,
TR_KEY_magnetLink,
TR_KEY_name,
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");
switch (c)
{
+ case 'L':
+ addLabels(args, optarg);
+ break;
+
case 712:
tr_variantListAddInt(tr_variantDictAddList(args, TR_KEY_trackerRemove, 1), atoi(optarg));
break;
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