]> granicus.if.org Git - transmission/commitdiff
json ipc: implement torrent-add, torrent-set-file, torrent-get-file
authorCharles Kerr <charles@transmissionbt.com>
Mon, 12 May 2008 13:05:06 +0000 (13:05 +0000)
committerCharles Kerr <charles@transmissionbt.com>
Mon, 12 May 2008 13:05:06 +0000 (13:05 +0000)
doc/ipc-json-spec.txt
libtransmission/ipc.c
libtransmission/transmission.h

index ebf09653267118280198c1f9133810272e251008..dcb3515066f1ee3b88772b366994b95000f29d75 100644 (file)
    string             | value type & description
    -------------------+-------------------------------------------------
    "autostart"        | boolean   true means to auto-start torrents
-   "directory"        | string    path to download the torrent to
+   "destination"      | string    path to download the torrent to
    "filename"         | string    location of the .torrent file
    "max-peers"        | int       maximum number of peers
-   "speed-limit-down" | int       maximum download speed (in KiB/s)
-   "speed-limit-up"   | int       maximum upload speed (in KiB/s)
 
    The "filename" argument is required; all others are optional.
 
-   Response arguments: none.
+   Response arguments: on success, a "torrent-added" object in the
+                       form of one of 3.1's tr_info objects.
 
 3.5.  Other torrent settings
 
 
    Request name: "torrent-get"
    Request arguments: none
-   Response arguments: "torrents", a list of torrent objects containing
-                       all of 3.5's arguments plus the id passed in
-                       by "torrent-set".
+   Response arguments: A "torrents" list of objects containing all
+                       of 3.5's arguments plus the torrent's "id" int.
                       
 
 3.6  File Priorities
 
    string             | value type & description
    -------------------+-------------------------------------------------
-   "priority-high"    | array          indices of one or more high-priority files
-   "priority-low"     | array          indices of one or more low-priority files
-   "priority-normal"  | array          indices of one or more normal-priority files
-   "download"         | array          indices of one or more file to download
-   "no-download"      | array          indices of one or more file to not download
+   "priority-high"    | array     indices of one or more high-priority files
+   "priority-low"     | array     indices of one or more low-priority files
+   "priority-normal"  | array     indices of one or more normal-priority files
+   "download"         | array     indices of one or more file to download
+   "no-download"      | array     indices of one or more file to not download
 
 3.6.1.  Mutators
 
 
     Request name: "torrent-get-file"
     Request arguments: none
-   Response arguments: list of objects, one per torrent, containing
-                       all of 3.6's arguments plus the id passed in
-                       by "torrent-set".
+   Response arguments: A "torrents" list of objects containing all
+                       of 3.6's arguments plus the torrent's "id" int.
    
 4.   Session Status Requests
 
    string             | value type & description
    -------------------+-------------------------------------------------
    "autostart"        | boolean   true means to auto-start torrents
-   "directory"        | string    path to download torrents to
+   "destination"      | string    path to download torrents to
    "encryption"       | string    "required", "preferred", or "plaintext"
    "max-peers"        | int       maximum global number of peers
    "port"             | int       port number
index cb2f014bdbc7671d311eb859074fd07deacdc66d..f9bc28fb9735e0b79f18089d069d6419d4ef6764 100644 (file)
  * $Id:$
  */
 
+#include <assert.h>
+#include <string.h> /* strcmp */
+
 #include "transmission.h"
 #include "bencode.h"
 #include "ipc.h"
 #include "torrent.h"
 #include "utils.h"
 
+#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( (ary)[0] ) )
+
 /***
 ****
 ***/
@@ -60,48 +65,43 @@ getTorrents( tr_handle * handle, tr_benc * args, int * setmeCount )
     *setmeCount = torrentCount;
     return torrents;
 }
-            
-static const char*
-torrentStart( tr_handle * handle, tr_benc * args_in, tr_benc * args_out UNUSED )
+
+typedef void( *tor_func )( tr_torrent * tor );
+
+static void callTorrentFunc( tr_handle * h, tr_benc * args_in, tor_func func )
 {
     int i, torrentCount;
-    tr_torrent ** torrents = getTorrents( handle, args_in, &torrentCount );
+    tr_torrent ** torrents = getTorrents( h, args_in, &torrentCount );
     for( i=0; i<torrentCount; ++i )
-        tr_torrentStart( torrents[i] );
+        ( *func )( torrents[i] );
     tr_free( torrents );
+}
+
+static const char*
+torrentStart( tr_handle * h, tr_benc * args_in, tr_benc * args_out UNUSED )
+{
+    callTorrentFunc( h, args_in, tr_torrentStart );
     return NULL;
 }
 
 static const char*
-torrentStop( tr_handle * handle, tr_benc * args_in, tr_benc * args_out UNUSED )
+torrentStop( tr_handle * h, tr_benc * args_in, tr_benc * args_out UNUSED )
 {
-    int i, torrentCount;
-    tr_torrent ** torrents = getTorrents( handle, args_in, &torrentCount );
-    for( i=0; i<torrentCount; ++i )
-        tr_torrentStop( torrents[i] );
-    tr_free( torrents );
+    callTorrentFunc( h, args_in, tr_torrentStop );
     return NULL;
 }
 
 static const char*
-torrentClose( tr_handle * handle, tr_benc * args_in, tr_benc * args_out UNUSED )
+torrentClose( tr_handle * h, tr_benc * args_in, tr_benc * args_out UNUSED )
 {
-    int i, torrentCount;
-    tr_torrent ** torrents = getTorrents( handle, args_in, &torrentCount );
-    for( i=0; i<torrentCount; ++i )
-        tr_torrentClose( torrents[i] );
-    tr_free( torrents );
+    callTorrentFunc( h, args_in, tr_torrentClose );
     return NULL;
 }
 
 static const char*
-torrentVerify( tr_handle * handle, tr_benc * args_in, tr_benc * args_out UNUSED )
+torrentVerify( tr_handle * h, tr_benc * args_in, tr_benc * args_out UNUSED )
 {
-    int i, torrentCount;
-    tr_torrent ** torrents = getTorrents( handle, args_in, &torrentCount );
-    for( i=0; i<torrentCount; ++i )
-        tr_torrentVerify( torrents[i] );
-    tr_free( torrents );
+    callTorrentFunc( h, args_in, tr_torrentVerify );
     return NULL;
 }
 
@@ -118,6 +118,8 @@ torrentStatus( tr_handle * handle, tr_benc * args_in, tr_benc * args_out )
         const tr_stat * st = tr_torrentStat( tor );
         tr_benc * d = tr_bencListAddDict( list, 32 );
         tr_benc * t;
+        const int * f = st->peersFrom;
+        const struct tr_tracker_stat * s = &st->tracker_stat;
 
         tr_bencDictAddInt( d, "id", tor->uniqueId );
         tr_bencDictAddInt( d, "status", st->status );
@@ -150,18 +152,18 @@ torrentStatus( tr_handle * handle, tr_benc * args_in, tr_benc * args_out )
         tr_bencDictAddInt( d, "startDate", st->startDate );
         tr_bencDictAddInt( d, "activityDate", st->activityDate );
         t = tr_bencDictAddDict( d, "peersFrom", 4 );
-            tr_bencDictAddInt( t, "cache",    st->peersFrom[TR_PEER_FROM_CACHE] );
-            tr_bencDictAddInt( t, "incoming", st->peersFrom[TR_PEER_FROM_INCOMING] );
-            tr_bencDictAddInt( t, "pex",      st->peersFrom[TR_PEER_FROM_PEX] );
-            tr_bencDictAddInt( t, "tracker",  st->peersFrom[TR_PEER_FROM_TRACKER] );
+            tr_bencDictAddInt( t, "cache",    f[TR_PEER_FROM_CACHE] );
+            tr_bencDictAddInt( t, "incoming", f[TR_PEER_FROM_INCOMING] );
+            tr_bencDictAddInt( t, "pex",      f[TR_PEER_FROM_PEX] );
+            tr_bencDictAddInt( t, "tracker",  f[TR_PEER_FROM_TRACKER] );
         t = tr_bencDictAddDict( d, "tracker_stat", 7 );
-            tr_bencDictAddStr( t, "scrapeResponse", st->tracker_stat.scrapeResponse );
-            tr_bencDictAddStr( t, "announceResponse", st->tracker_stat.announceResponse );
-            tr_bencDictAddInt( t, "lastScrapeTime", st->tracker_stat.lastScrapeTime );
-            tr_bencDictAddInt( t, "nextScrapeTime", st->tracker_stat.nextScrapeTime );
-            tr_bencDictAddInt( t, "lastAnnounceTime", st->tracker_stat.lastAnnounceTime );
-            tr_bencDictAddInt( t, "nextAnnounceTime", st->tracker_stat.nextAnnounceTime );
-            tr_bencDictAddInt( t, "nextManualAnnounceTime", st->tracker_stat.nextManualAnnounceTime );
+            tr_bencDictAddStr( t, "scrapeResponse", s->scrapeResponse );
+            tr_bencDictAddStr( t, "announceResponse", s->announceResponse );
+            tr_bencDictAddInt( t, "lastScrapeTime", s->lastScrapeTime );
+            tr_bencDictAddInt( t, "nextScrapeTime", s->nextScrapeTime );
+            tr_bencDictAddInt( t, "lastAnnounceTime", s->lastAnnounceTime );
+            tr_bencDictAddInt( t, "nextAnnounceTime", s->nextAnnounceTime );
+            tr_bencDictAddInt( t, "nextManualAnnounceTime", s->nextManualAnnounceTime );
     }
 
     /* cleanup */
@@ -198,6 +200,27 @@ addTrackers( const tr_info * info, tr_benc * trackers )
     }
 }
 
+static void
+serializeInfo( const tr_torrent * tor, tr_benc * d )
+{
+    const tr_info * inf = tr_torrentInfo( tor );
+    tr_bencInitDict( d, 14 ); 
+    tr_bencDictAddInt( d, "id", tor->uniqueId );
+    tr_bencDictAddStr( d, "torrent", inf->torrent );
+    tr_bencDictAddStr( d, "hashString", inf->hashString );
+    tr_bencDictAddStr( d, "name", inf->name );
+    tr_bencDictAddStr( d, "comment", inf->comment ? inf->comment : "" );
+    tr_bencDictAddStr( d, "creator", inf->creator ? inf->creator : "" );
+    tr_bencDictAddInt( d, "isPrivate", inf->isPrivate );
+    tr_bencDictAddInt( d, "isMultifile", inf->isMultifile );
+    tr_bencDictAddInt( d, "dateCreated", inf->dateCreated );
+    tr_bencDictAddInt( d, "pieceSize", inf->pieceSize );
+    tr_bencDictAddInt( d, "pieceCount", inf->pieceCount );
+    tr_bencDictAddInt( d, "totalSize", inf->totalSize );
+    addFiles   ( inf, tr_bencDictAddList( d, "files", inf->fileCount ) );
+    addTrackers( inf, tr_bencDictAddList( d, "files", inf->trackerCount ) );
+}
+
 static const char*
 torrentInfo( tr_handle * handle, tr_benc * args_in, tr_benc * args_out )
 {
@@ -206,26 +229,8 @@ torrentInfo( tr_handle * handle, tr_benc * args_in, tr_benc * args_out )
     tr_benc * list = tr_bencDictAddList( args_out, "status", torrentCount );
 
     for( i=0; i<torrentCount; ++i )
-    {
-        tr_torrent * tor = torrents[i];
-        const tr_info * inf = tr_torrentInfo( tor );
-        tr_benc * d = tr_bencListAddDict( list, 13 );
-        tr_bencDictAddStr( d, "torrent", inf->torrent );
-        tr_bencDictAddStr( d, "hashString", inf->hashString );
-        tr_bencDictAddStr( d, "name", inf->name );
-        tr_bencDictAddStr( d, "comment", inf->comment ? inf->comment : "" );
-        tr_bencDictAddStr( d, "creator", inf->creator ? inf->creator : "" );
-        tr_bencDictAddInt( d, "isPrivate", inf->isPrivate );
-        tr_bencDictAddInt( d, "isMultifile", inf->isMultifile );
-        tr_bencDictAddInt( d, "dateCreated", inf->dateCreated );
-        tr_bencDictAddInt( d, "pieceSize", inf->pieceSize );
-        tr_bencDictAddInt( d, "pieceCount", inf->pieceCount );
-        tr_bencDictAddInt( d, "totalSize", inf->totalSize );
-        addFiles   ( inf, tr_bencDictAddList( d, "files", inf->fileCount ) );
-        addTrackers( inf, tr_bencDictAddList( d, "files", inf->trackerCount ) );
-    }
+        serializeInfo( torrents[i], tr_bencListAdd( list ) );
 
-    /* cleanup */
     tr_free( torrents );
     return NULL;
 }
@@ -240,10 +245,15 @@ torrentGet( tr_handle * handle, tr_benc * args_in, tr_benc * args_out )
     for( i=0; i<torrentCount; ++i )
     {
         tr_torrent * tor = torrents[i];
-        tr_benc * d = tr_bencListAddDict( list, 3 );
-        tr_bencDictAddInt( d, "max-peers", tr_torrentGetMaxConnectedPeers( tor ) );
-        tr_bencDictAddInt( d, "speed-limit-down", tr_torrentGetSpeedLimit( tor, TR_DOWN ) );
-        tr_bencDictAddInt( d, "speed-limit-up", tr_torrentGetSpeedLimit( tor, TR_UP ) );
+        tr_benc * d = tr_bencListAddDict( list, 4 );
+        tr_bencDictAddInt( d, "id",
+                           tor->uniqueId );
+        tr_bencDictAddInt( d, "max-peers",
+                           tr_torrentGetMaxConnectedPeers( tor ) );
+        tr_bencDictAddInt( d, "speed-limit-down",
+                           tr_torrentGetSpeedLimit( tor, TR_DOWN ) );
+        tr_bencDictAddInt( d, "speed-limit-up",
+                           tr_torrentGetSpeedLimit( tor, TR_UP ) );
     }
 
     tr_free( torrents );
@@ -272,33 +282,198 @@ torrentSet( tr_handle * handle, tr_benc * args_in, tr_benc * args_out UNUSED )
     return NULL;
 }
 
+typedef int( *fileTestFunc )( const tr_torrent * tor, int i );
+
+static int
+testFileHigh( const tr_torrent * tor, int i )
+{
+    return tor->info.files[i].priority == TR_PRI_HIGH;
+}
+static int
+testFileLow( const tr_torrent * tor, int i )
+{
+    return tor->info.files[i].priority == TR_PRI_LOW;
+}
+static int
+testFileNormal( const tr_torrent * tor, int i )
+{
+    return tor->info.files[i].priority == TR_PRI_NORMAL;
+}
+static int
+testFileDND( const tr_torrent * tor, int i )
+{
+    return tor->info.files[i].dnd != 0;
+}
+static int
+testFileDownload( const tr_torrent * tor, int i )
+{
+    return tor->info.files[i].dnd == 0;
+}
+
+static void
+buildFileList( const tr_torrent * tor, tr_benc * dict,
+               const char * key, fileTestFunc func )
+{
+    int i;
+    const int n = tor->info.fileCount;
+    tr_benc * list;
+    int * files = tr_new0( int, n );
+    int fileCount = 0;
+    
+    for( i=0; i<n; ++i )
+        if( func( tor, i ) )
+            files[fileCount++] = i;
+
+    list = tr_bencDictAddList( dict, key, fileCount );
+
+    for( i=0; i<fileCount; ++i )
+        tr_bencListAddInt( list, files[i] );
+
+    tr_free( files );
+}
+
 static const char*
-torrentGetFile( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_out UNUSED )
+torrentGetFile( tr_handle * handle, tr_benc * args_in, tr_benc * args_out )
 {
+    int i, torrentCount;
+    tr_torrent ** torrents = getTorrents( handle, args_in, &torrentCount );
+    tr_benc * list = tr_bencDictAddList( args_out, "torrents", torrentCount );
+
+    for( i=0; i<torrentCount; ++i )
+    {
+        const tr_torrent * tor = torrents[i];
+        tr_benc * d = tr_bencListAddDict( list, 6 );
+        tr_bencDictAddInt( d, "id", tor->uniqueId );
+        buildFileList( tor, d, "priority-high", testFileHigh );
+        buildFileList( tor, d, "priority-low", testFileLow );
+        buildFileList( tor, d, "priority-normal", testFileNormal );
+        buildFileList( tor, d, "download", testFileDownload );
+        buildFileList( tor, d, "no-download", testFileDND );
+    }
+
+    tr_free( torrents );
     return NULL;
 }
 
+static void
+setFilePriorities( tr_torrent * tor, int priority, tr_benc * list )
+{
+    const int n = tr_bencListSize( list );
+    int i;
+    int64_t tmp;
+    int fileCount = 0;
+    tr_file_index_t * files = tr_new0( tr_file_index_t, n );
+
+    for( i=0; i<n; ++i )
+        if( tr_bencGetInt( tr_bencListChild(list,i), &tmp ) )
+            files[fileCount++] = tmp;
+
+    if( fileCount )
+        tr_torrentSetFilePriorities( tor, files, fileCount, priority );
+
+    tr_free( files );
+}
+
+static void
+setFileDLs( tr_torrent * tor, int do_download, tr_benc * list )
+{
+    const int n = tr_bencListSize( list );
+    int i;
+    int64_t tmp;
+    int fileCount = 0;
+    tr_file_index_t * files = tr_new0( tr_file_index_t, n );
+
+    for( i=0; i<n; ++i )
+        if( tr_bencGetInt( tr_bencListChild(list,i), &tmp ) )
+            files[fileCount++] = tmp;
+
+    if( fileCount )
+        tr_torrentSetFileDLs( tor, files, fileCount, do_download );
+
+    tr_free( files );
+}
+
 static const char*
-torrentSetFile( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_out UNUSED )
+torrentSetFile( tr_handle * h, tr_benc * args_in, tr_benc * args_out UNUSED )
 {
+    int i, torrentCount;
+    tr_torrent ** torrents = getTorrents( h, args_in, &torrentCount );
+
+    for( i=0; i<torrentCount; ++i )
+    {
+        tr_benc * files;
+        tr_torrent * tor = torrents[i];
+
+        if( tr_bencDictFindList( args_in, "priority-high", &files ) )
+            setFilePriorities( tor, TR_PRI_HIGH, files );
+        if( tr_bencDictFindList( args_in, "priority-low", &files ) )
+            setFilePriorities( tor, TR_PRI_LOW, files );
+        if( tr_bencDictFindList( args_in, "priority-normal", &files ) )
+            setFilePriorities( tor, TR_PRI_NORMAL, files );
+        if( tr_bencDictFindList( args_in, "download", &files ) )
+            setFileDLs( tor, TRUE, files );
+        if( tr_bencDictFindList( args_in, "no-download", &files ) )
+            setFileDLs( tor, FALSE, files );
+    }
+
+    tr_free( torrents );
     return NULL;
 }
 
+/***
+****
+***/
+
 static const char*
-sessionSet( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_out UNUSED )
+torrentAdd( tr_handle * h, tr_benc * args_in, tr_benc * args_out )
 {
+    const char * filename;
+    if( !tr_bencDictFindStr( args_in, "filename", &filename ) )
+        return "no filename specified";
+    else {
+        int64_t i;
+        int err = 0;
+        const char * str;
+        tr_ctor * ctor;
+        tr_torrent * tor;
+
+        ctor = tr_ctorNew( h );
+        tr_ctorSetMetainfoFromFile( ctor, filename );
+        if( tr_bencDictFindInt( args_in, "autostart", &i ) )
+            tr_ctorSetPaused( ctor, TR_FORCE, !i );
+        if( tr_bencDictFindInt( args_in, "max-peers", &i ) )
+            tr_ctorSetMaxConnectedPeers( ctor, TR_FORCE, i );
+        if( tr_bencDictFindStr( args_in, "destination", &str ) )
+            tr_ctorSetDestination( ctor, TR_FORCE, str );
+        tor = tr_torrentNew( h, ctor, &err );
+        tr_ctorFree( ctor );
+
+        if( tor )
+            serializeInfo( tor, tr_bencDictAdd( args_out, "torrent-added" ) );
+        else if( err == TR_EDUPLICATE )
+            return "duplicate torrent";
+        else if( err == TR_EINVALID )
+            return "invalid or corrupt torrent file";
+    }
+
     return NULL;
 }
 
+/***
+****
+***/
+
 static const char*
-sessionGet( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_out UNUSED )
+sessionSet( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_out UNUSED )
 {
+    /* TODO */
     return NULL;
 }
 
 static const char*
-torrentAdd( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_out UNUSED )
+sessionGet( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_out UNUSED )
 {
+    /* TODO */
     return NULL;
 }
 
@@ -306,6 +481,28 @@ torrentAdd( tr_handle * handle UNUSED, tr_benc * args_in UNUSED, tr_benc * args_
 ****
 ***/
 
+typedef const char* (handler)( tr_handle*, tr_benc*, tr_benc* );
+
+struct request_handler
+{
+    const char * name;
+    handler * func;
+} request_handlers[] = { 
+    { "torrent-start", torrentStart },
+    { "torrent-stop", torrentStop },
+    { "torrent-close", torrentClose },
+    { "torrent-verify", torrentVerify },
+    { "torrent-status", torrentStatus },
+    { "torrent-info", torrentInfo },
+    { "torrent-add", torrentAdd },
+    { "torrent-set", torrentSet },
+    { "torrent-get", torrentGet },
+    { "torrent-set-file", torrentSetFile },
+    { "torrent-get-file", torrentGetFile },
+    { "session-set", sessionSet },
+    { "session-get", sessionGet }
+};
+
 static char*
 request_exec( struct tr_handle * handle,
               tr_benc          * request,
@@ -340,23 +537,16 @@ request_exec( struct tr_handle * handle,
     if( !tr_bencDictFindStr( body_in, "name", &str ) )
         result = "no request name given";
     else {
-             if( !strcmp( str, "torrent-start" ) )    result = torrentStart  ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-stop" ) )     result = torrentStop   ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-close" ) )    result = torrentClose  ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-verify" ) )   result = torrentVerify ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-status" ) )   result = torrentStatus ( handle, args_in, args_out );
-
-        else if( !strcmp( str, "torrent-info" ) )     result = torrentInfo ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-add" ) )      result = torrentAdd    ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-set" ) )      result = torrentSet    ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-get" ) )      result = torrentGet    ( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-set-file" ) ) result = torrentSetFile( handle, args_in, args_out );
-        else if( !strcmp( str, "torrent-get-file" ) ) result = torrentGetFile( handle, args_in, args_out );
-        else if( !strcmp( str, "session-set" ) )      result = sessionSet    ( handle, args_in, args_out );
-        else if( !strcmp( str, "session-get" ) )      result = sessionGet    ( handle, args_in, args_out );
-        else                                          result = "request name not recognized";
+        const int n = TR_N_ELEMENTS( request_handlers );
+        for( i=0; i<n; ++i )
+            if( !strcmp( str, request_handlers[i].name ) )
+                break;
+        result = i==n
+            ? "request name not recognized"
+            : (*request_handlers[i].func)( handle, args_in, args_out );
     }
 
+    /* serialize & return the response */
     if( !result )
         result = "success";
     tr_bencDictAddStr( body_out, "result", result );
index 2b33ed253b76edf9ed988755009e0b01d552a4c4..78a689955e55a7e69c11bdec881c1141ab6a3c8c 100644 (file)
@@ -510,9 +510,7 @@ int tr_torrentParse( const tr_handle  * handle,
  * Instantiate a single torrent.
  */
 #define TR_EINVALID     1
-#define TR_EUNSUPPORTED 2
-#define TR_EDUPLICATE   3
-#define TR_EOTHER       666
+#define TR_EDUPLICATE   2
 tr_torrent * tr_torrentNew( tr_handle      * handle,
                             const tr_ctor  * ctor,
                             int            * setmeError );