]> granicus.if.org Git - transmission/commitdiff
Add labels feature (#822)
authorqu1ck <anlutsenko@gmail.com>
Sun, 17 Feb 2019 09:33:57 +0000 (01:33 -0800)
committerCharles Kerr <ckerr@github.com>
Sun, 17 Feb 2019 09:33:57 +0000 (04:33 -0500)
* 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

extras/rpc-spec.txt
libtransmission/quark.c
libtransmission/quark.h
libtransmission/resume.c
libtransmission/resume.h
libtransmission/rpcimpl.c
libtransmission/torrent.c
libtransmission/torrent.h
utils/remote.c
utils/transmission-remote.1

index 09eac6b8e28be669e03a65e55667ca54bc337867..6b5a4a0cd29d749ec7ffcebca74d5bc746206932 100644 (file)
    "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
 
index e19ac9a2f4204dc7ebc7659137556ce43d174c3e..8df91867b79865e0f7d2e26be14796e8e527c9b8 100644 (file)
@@ -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 },
index 9dc5345601915e305640a09f197e835f71c4e71e..6c44835d06459d2e8ad6beeb1b22787084e8f134 100644 (file)
@@ -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,
index 1189c8d822ce4ee7f8a6ea7081d808fd45735079..c438c7eb029a9202a725f1827d7614bcfbaf18be 100644 (file)
@@ -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... */
index ea4e48cc59b3631af647e2d19855f0ae019a6da2..0ff034fe0146d7631db25fd50ecc2b58930bd664 100644 (file)
@@ -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)
 };
 
 /**
index e3896f197f53fa56fc30a5c532ff75eef3d032fe..92d2fef8ae05797c506353e3a818337bc37ac8e3 100644 (file)
@@ -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);
index ab16735c49b4f678ded0829ad596c896b80259bf..505a935be249a13b5c64088ea03beb6319e4adca 100644 (file)
@@ -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));
index 5220192920d5f13c9e5371336d429231bad6e013..18bc031efec9e62696cd9482029a3aa6dbbf816c 100644 (file)
@@ -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)
index a8ae16c158b72916b62d2ff9684db64061883dd5..9f2b561663f3beaa8209c6b302241e8fb0a0d3a0 100644 (file)
@@ -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, "<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 },
@@ -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;
index a6c12eed2a66ca190699222981bb60829062b6f9..59e4b5e069fca7f09184c040a972921ee445aab0 100644 (file)
@@ -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