source: branches/daemon/daemon/remote.c @ 1696

Last change on this file since 1696 was 1696, checked in by joshe, 15 years ago

Add strlcpy and strlcat from openbsd and use them when not provided by libc.

  • Property svn:keywords set to Date Rev Author Id
File size: 27.1 KB
Line 
1/******************************************************************************
2 * $Id: remote.c 1696 2007-04-10 02:27:46Z joshe $
3 *
4 * Copyright (c) 2007 Joshua Elsasser
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <sys/types.h>
26#include <sys/param.h>
27#include <sys/time.h>
28#include <assert.h>
29#include <ctype.h>
30#include <event.h>
31#include <getopt.h>
32#include <stdlib.h>
33#include <stdio.h>
34#include <string.h>
35#include <unistd.h>
36
37#include "bsdqueue.h"
38#include "bsdtree.h"
39#include "client.h"
40#include "errors.h"
41#include "misc.h"
42#include "transmission.h"
43#include "trcompat.h"
44
45#define BESTDECIMAL(d)          (10.0 > (d) ? 2 : (100.0 > (d) ? 1 : 0))
46
47struct opts
48{
49    int               proxy;
50    char           ** proxycmd;
51    enum confpathtype type;
52    struct strlist    files;
53    int               sendquit;
54    int               port;
55    int               map;
56    int               uplimit;
57    int               up;
58    int               downlimit;
59    int               down;
60    int               listquick;
61    int               listfull;
62    int               startall;
63    struct strlist    start;
64    int               stopall;
65    struct strlist    stop;
66    int               removeall;
67    struct strlist    remove;
68    char              dir[MAXPATHLEN];
69    int               pex;
70};
71
72struct torinfo
73{
74    int     id;
75    char  * name;
76    int64_t size;
77    char  * state;
78    int64_t eta;
79    int64_t done;
80    int64_t down;
81    int64_t up;
82    char  * errorcode;
83    char  * errormsg;
84    RB_ENTRY( torinfo ) idlinks;
85    RB_ENTRY( torinfo ) namelinks;
86};
87
88RB_HEAD( torlist, torinfo );
89RB_HEAD( tornames, torinfo );
90
91struct torhash
92{
93    char                hash[SHA_DIGEST_LENGTH*2+1];
94    int                 id;
95    RB_ENTRY( torhash ) link;
96};
97
98RB_HEAD( torhashes, torhash );
99
100static void   usage        ( const char *, ... );
101static int    readargs     ( int, char **, struct opts * );
102static int    numarg       ( const char * );
103static int    hasharg      ( const char *, struct strlist *, int * );
104static int    fileargs     ( struct strlist *, int, char * const * );
105static void   listmsg      ( int, const char *, const char * );
106static void   infomsg      ( int, const char *, int64_t );
107static void   statmsg      ( int, const char *, int64_t, int64_t, int64_t,
108                             int64_t, const char *, const char * );
109static void   hashmsg      ( int, const char * );
110static float  fmtsize      ( int64_t, const char ** );
111static char * strdup_noctrl( const char * );
112static void   print_eta    ( int64_t );
113static void   printlisting ( void );
114static int    sendidreqs   ( void );
115static int    toridcmp     ( struct torinfo *, struct torinfo * );
116static int    tornamecmp   ( struct torinfo *, struct torinfo * );
117static int    torhashcmp   ( struct torhash *, struct torhash * );
118
119static struct torlist   gl_torinfo      = RB_INITIALIZER( &gl_torinfo );
120static struct strlist * gl_starthashes  = NULL;
121static struct strlist * gl_stophashes   = NULL;
122static struct strlist * gl_removehashes = NULL;
123static struct torhashes gl_hashids      = RB_INITIALIZER( &gl_hashids );
124static int              gl_gotlistinfo  = 0;
125static int              gl_gotliststat  = 0;
126
127RB_GENERATE_STATIC( torlist,   torinfo, idlinks,   toridcmp );
128RB_GENERATE_STATIC( tornames,  torinfo, namelinks, tornamecmp );
129RB_GENERATE_STATIC( torhashes, torhash, link,      torhashcmp );
130
131int
132main( int argc, char ** argv )
133{
134    struct event_base * evbase;
135    struct opts         o;
136    char                sockpath[MAXPATHLEN];
137
138    setmyname( argv[0] );
139    if( 0 > readargs( argc, argv, &o ) )
140    {
141        exit( 1 );
142    }
143
144    evbase = event_init();
145    client_init( evbase );
146
147    if( o.proxy )
148    {
149        client_new_cmd( o.proxycmd );
150    }
151    else
152    {
153        confpath( sockpath, sizeof sockpath, CONF_FILE_SOCKET, o.type );
154        client_new_sock( sockpath );
155    }
156
157    if( ( o.sendquit                &&   0 > client_quit     (           ) ) ||
158        ( '\0' != o.dir[0]          &&   0 > client_dir      ( o.dir     ) ) ||
159        ( !SLIST_EMPTY( &o.files )  &&   0 > client_addfiles ( &o.files  ) ) ||
160        ( o.startall                &&   0 > client_start    ( 0, NULL   ) ) ||
161        ( o.stopall                 &&   0 > client_stop     ( 0, NULL   ) ) ||
162        ( o.removeall               &&   0 > client_remove   ( 0, NULL   ) ) ||
163        ( o.port                    &&   0 > client_port     ( o.port    ) ) ||
164        ( 0 <= o.map                &&   0 > client_automap  ( o.map     ) ) ||
165        ( 0 <= o.pex                &&   0 > client_pex      ( o.pex     ) ) ||
166        ( o.uplimit                 &&   0 > client_uplimit  ( o.up      ) ) ||
167        ( o.downlimit               &&   0 > client_downlimit( o.down    ) ) ||
168        ( o.listquick               &&   0 > client_list     ( listmsg   ) ) ||
169        ( o.listfull                && ( 0 > client_info     ( infomsg     ) ||
170                                         0 > client_status   ( statmsg ) ) ) )
171    {
172        exit( 1 );
173    }
174
175    if( ( !o.startall  && !SLIST_EMPTY( &o.start  ) ) ||
176        ( !o.stopall   && !SLIST_EMPTY( &o.stop   ) ) ||
177        ( !o.removeall && !SLIST_EMPTY( &o.remove ) ) )
178    {
179        if( 0 > client_hashids( hashmsg ) )
180        {
181            exit( 1 );
182        }
183        gl_starthashes  = ( o.startall  ? NULL : &o.start  );
184        gl_stophashes   = ( o.stopall   ? NULL : &o.stop   );
185        gl_removehashes = ( o.removeall ? NULL : &o.remove );
186    }
187
188    event_base_dispatch( evbase );
189
190    return 1;
191}
192
193void
194usage( const char * msg, ... )
195{
196    va_list ap;
197
198    if( NULL != msg )
199    {
200        printf( "%s: ", getmyname() );
201        va_start( ap, msg );
202        vprintf( msg, ap );
203        va_end( ap );
204        printf( "\n" );
205    }
206
207    printf(
208  "usage: %s [options]\n"
209  "\n"
210  "Transmission %s (r%d) http://transmission.m0k.org/\n"
211  "A free, lightweight BitTorrent client with a simple, intuitive interface.\n"
212  "\n"
213  "  -a --add <torrent>        Add a torrent\n"
214  "  -d --download-limit <int> Max download rate in KiB/s\n"
215  "  -D --download-unlimited   No download rate limit\n"
216  "  -e --enable-pex           Enable peer exchange\n"
217  "  -E --disable-pex          Disable peer exchange\n"
218  "  -f --folder <path>        Folder to set for new torrents\n"
219  "  -h --help                 Display this message and exit\n"
220  "  -i --info                 List all torrents with info hashes\n"
221  "  -l --list                 List all torrents with status\n"
222  "  -m --port-mapping         Automatic port mapping via NAT-PMP or UPnP\n"
223  "  -M --no-port-mapping      Disable automatic port mapping\n"
224  "  -p --port <int>           Port to listen for incoming connections on\n"
225  "  -q --quit                 Quit the daemon\n"
226  "  -r --remove <hash>        Remove the torrent with the given hash\n"
227  "  -r --remove all           Remove all torrents\n"
228  "  -s --start <hash>         Start the torrent with the given hash\n"
229  "  -s --start all            Start all stopped torrents\n"
230  "  -S --stop <hash>          Stop the torrent with the given hash\n"
231  "  -S --stop all             Stop all running torrents\n"
232  "  -t --type daemon          Use the daemon frontend, transmission-daemon\n"
233  "  -t --type gtk             Use the GTK+ frontend, transmission-gtk\n"
234  "  -u --upload-limit <int>   Max upload rate in KiB/s\n"
235  "  -U --upload-unlimited     No upload rate limit\n"
236  "  -x --proxy                Use proxy command to connect to frontend\n",
237            getmyname(), VERSION_STRING, VERSION_REVISION );
238    exit( 0 );
239}
240
241int
242readargs( int argc, char ** argv, struct opts * opts )
243{
244    char optstr[] = "a:d:DeEf:hilmMp:qr:s:S:t:u:Ux";
245    struct option longopts[] =
246    {
247        { "add",                required_argument, NULL, 'a' },
248        { "download-limit",     required_argument, NULL, 'd' },
249        { "download-unlimited", no_argument,       NULL, 'D' },
250        { "enable-pex",         no_argument,       NULL, 'e' },
251        { "disable-pex",        no_argument,       NULL, 'E' },
252        { "folder",             required_argument, NULL, 'f' },
253        { "help",               no_argument,       NULL, 'h' },
254        { "info",               no_argument,       NULL, 'i' },
255        { "list",               no_argument,       NULL, 'l' },
256        { "port-mapping",       no_argument,       NULL, 'm' },
257        { "no-port-mapping",    no_argument,       NULL, 'M' },
258        { "port",               required_argument, NULL, 'p' },
259        { "quit",               no_argument,       NULL, 'q' },
260        { "remove",             required_argument, NULL, 'r' },
261        { "start",              required_argument, NULL, 's' },
262        { "stop",               required_argument, NULL, 'S' },
263        { "type",               required_argument, NULL, 't' },
264        { "upload-limit",       required_argument, NULL, 'u' },
265        { "upload-unlimited",   no_argument,       NULL, 'U' },
266        { "proxy",              no_argument,       NULL, 'U' },
267        { NULL, 0, NULL, 0 }
268    };
269    int opt, gotmsg;
270
271    gotmsg = 0;
272    bzero( opts, sizeof *opts );
273    opts->type = CONF_PATH_TYPE_DAEMON;
274    SLIST_INIT( &opts->files );
275    opts->map = -1;
276    opts->pex = -1;
277    SLIST_INIT( &opts->start );
278    SLIST_INIT( &opts->stop );
279    SLIST_INIT( &opts->remove );
280
281    while( 0 <= ( opt = getopt_long( argc, argv, optstr, longopts, NULL ) ) )
282    {
283        switch( opt )
284        {
285            case 'a':
286                if( 0 > fileargs( &opts->files, 1, &optarg ) )
287                {
288                    return -1;
289                }
290                break;
291            case 'd':
292                opts->downlimit = 1;
293                opts->down      = numarg( optarg );
294                break;
295            case 'D':
296                opts->downlimit = 1;
297                opts->down      = -1;
298                break;
299            case 'e':
300                opts->pex       = 1;
301                break;
302            case 'E':
303                opts->pex       = 0;
304                break;
305            case 'f':
306                absolutify( opts->dir, sizeof opts->dir, optarg );
307                break;
308            case 'i':
309                opts->listquick = 1;
310                break;
311            case 'l':
312                opts->listfull  = 1;
313                break;
314            case 'm':
315                opts->map       = 1;
316                break;
317            case 'M':
318                opts->map       = 0;
319                break;
320            case 'p':
321                opts->port      = numarg( optarg );
322                if( 0 >= opts->port || 0xffff <= opts->port )
323                {
324                    usage( "invalid port: %i", opts->port );
325                }
326                break;
327            case 'q':
328                opts->sendquit  = 1;
329                break;
330            case 'r':
331                if( 0 > hasharg( optarg, &opts->remove, &opts->removeall ) )
332                {
333                    return -1;
334                }
335                break;
336            case 's':
337                if( 0 > hasharg( optarg, &opts->start, &opts->startall ) )
338                {
339                    return -1;
340                }
341                break;
342            case 'S':
343                if( 0 > hasharg( optarg, &opts->stop, &opts->stopall ) )
344                {
345                    return -1;
346                }
347                break;
348            case 't':
349                if( 0 == strcasecmp( "daemon", optarg ) )
350                {
351                    opts->type  = CONF_PATH_TYPE_DAEMON;
352                }
353                else if( 0 == strcasecmp( "gtk", optarg ) )
354                {
355                    opts->type  = CONF_PATH_TYPE_GTK;
356                }
357                else
358                {
359                    usage( "invalid type: %s", optarg );
360                }
361                break;
362            case 'u':
363                opts->uplimit   = 1;
364                opts->up        = numarg( optarg );
365                break;
366            case 'U':
367                opts->uplimit   = 1;
368                opts->up        = -1;
369                break;
370            case 'x':
371                opts->proxy     = 1;
372                break;
373            default:
374                usage( NULL );
375                break;
376        }
377        gotmsg = 1;
378    }
379
380    if( !gotmsg && argc == optind )
381    {
382        usage( NULL );
383    }
384
385    if( opts->proxy )
386    {
387        opts->proxycmd = argv + optind;
388    }
389    else if( 0 > fileargs( &opts->files, argc - optind, argv + optind ) )
390    {
391        return -1;
392    }
393
394    return 0;
395}
396
397int
398numarg( const char * arg )
399{
400    char * end;
401    long   num;
402
403    end = NULL;
404    num = strtol( arg, &end, 10 );
405    if( NULL != end && '\0' != *end )
406    {
407        usage( "not a number: %s", arg );
408        return -1;
409    }
410
411    return num;
412}
413
414int
415hasharg( const char * arg, struct strlist * list, int * all )
416{
417    struct stritem * listitem;
418    struct torhash * treeitem, key, * foo;
419    size_t           len, ii;
420
421    /* check for special "all" value */
422    if( 0 == strcasecmp( "all", arg ) )
423    {
424        *all = 1;
425        return 0;
426    }
427
428    /* check hash length */
429    len = strlen( arg );
430    if( SHA_DIGEST_LENGTH * 2 != len )
431    {
432        usage( "torrent info hash length is not %i: %s",
433               SHA_DIGEST_LENGTH * 2, arg );
434        return -1;
435    }
436
437    /* allocate list item */
438    listitem = calloc( 1, sizeof *listitem );
439    if( NULL == listitem )
440    {
441        mallocmsg( sizeof *listitem );
442        return -1;
443    }
444    listitem->str = calloc( len + 1, 1 );
445    if( NULL == listitem->str )
446    {
447        mallocmsg( len + 1 );
448        free( listitem );
449        return -1;
450    }
451
452    /* check that the hash is all hex and copy it in lowercase */
453    for( ii = 0; len > ii; ii++ )
454    {
455        if( !isxdigit( arg[ii] ) )
456        {
457            usage( "torrent info hash is not hex: %s", arg );
458            free( listitem->str );
459            free( listitem );
460            return -1;
461        }
462        listitem->str[ii] = tolower( arg[ii] );
463    }
464
465    /* try to look up the hash in the hash tree */
466    bzero( &key, sizeof key );
467    strlcpy( key.hash, listitem->str, sizeof key.hash );
468    treeitem = RB_FIND( torhashes, &gl_hashids, &key );
469    if( NULL == treeitem )
470    {
471        /* the hash isn't in the tree, allocate a tree item and insert it */
472        treeitem = calloc( 1, sizeof *treeitem );
473        if( NULL == treeitem )
474        {
475            mallocmsg( sizeof *treeitem );
476            free( listitem->str );
477            free( listitem );
478            return -1;
479        }
480        treeitem->id = -1;
481        strlcpy( treeitem->hash, listitem->str, sizeof treeitem->hash );
482        foo = RB_INSERT( torhashes, &gl_hashids, treeitem );
483        assert( NULL == foo );
484    }
485
486    /* finally, add the list item to the list */
487    SLIST_INSERT_HEAD( list, listitem, next );
488
489    return 0;
490}
491
492int
493fileargs( struct strlist * list, int argc, char * const * argv )
494{
495    struct stritem * item;
496    int              ii;
497    char             path[MAXPATHLEN];
498
499    for( ii = 0; argc > ii; ii++ )
500    {
501        item = calloc( 1, sizeof *item );
502        if( NULL == item )
503        {
504            mallocmsg( sizeof *item );
505            return -1;
506        }
507        if( '/' == argv[ii][0] )
508        {
509            item->str = strdup( argv[ii] );
510            if( NULL == item->str )
511            {
512                mallocmsg( strlen( argv[ii] ) + 1 );
513                free( item );
514                return -1;
515            }
516        }
517        else
518        {
519            absolutify( path, sizeof path, argv[ii] );
520            item->str = strdup( path );
521            if( NULL == item->str )
522            {
523                mallocmsg( strlen( path ) + 1 );
524                free( item );
525                return -1;
526            }
527        }
528        SLIST_INSERT_HEAD( list, item, next );
529    }
530
531    return 0;
532}
533
534void
535listmsg( int id, const char * name, const char * hash )
536{
537    char * newname;
538    size_t ii;
539
540    if( !TORRENT_ID_VALID( id ) )
541    {
542        return;
543    }
544
545    if( NULL == name )
546    {
547        errmsg( "missing torrent name from server" );
548        return;
549    }
550    else if( NULL == hash )
551    {
552        errmsg( "missing torrent hash from server" );
553        return;
554    }
555
556    newname = strdup_noctrl( name );
557    if( NULL == newname )
558    {
559        return;
560    }
561
562    for( ii = 0; '\0' != hash[ii]; ii++ )
563    {
564        if( isxdigit( hash[ii] ) )
565        {
566            putchar( tolower( hash[ii] ) );
567        }
568    }
569    putchar( ' ' );
570    printf( "%s\n", newname );
571    free( newname );
572}
573
574void
575infomsg( int id, const char * name, int64_t size )
576{
577    struct torinfo * info, key;
578
579    gl_gotlistinfo = 1;
580
581    if( !TORRENT_ID_VALID( id ) )
582    {
583        if( gl_gotliststat )
584        {
585            printlisting();
586        }
587        return;
588    }
589
590    if( NULL == name || 0 >= size )
591    {
592        errmsg( "missing torrent info from server" );
593        return;
594    }
595
596    bzero( &key, sizeof key );
597    key.id = id;
598    info = RB_FIND( torlist, &gl_torinfo, &key );
599    if( NULL == info )
600    {
601        info = calloc( 1, sizeof *info );
602        if( NULL == info )
603        {
604            mallocmsg( sizeof *info );
605            return;
606        }
607        info->id = id;
608        RB_INSERT( torlist, &gl_torinfo, info );
609    }
610
611    free( info->name );
612    info->name = strdup_noctrl( name );
613    info->size = size;
614}
615
616void
617statmsg( int id, const char * state, int64_t eta, int64_t done,
618         int64_t down, int64_t up, const char * terr, const char * terrmsg )
619{
620    struct torinfo * info, key;
621
622    gl_gotliststat = 1;
623
624    if( !TORRENT_ID_VALID( id ) )
625    {
626        if( gl_gotlistinfo )
627        {
628            printlisting();
629        }
630        return;
631    }
632
633    if( NULL == state || 0 > done || 0 > down || 0 > up )
634    {
635        errmsg( "missing torrent status from server" );
636        return;
637    }
638
639    bzero( &key, sizeof key );
640    key.id = id;
641    info = RB_FIND( torlist, &gl_torinfo, &key );
642    if( NULL == info )
643    {
644        info = calloc( 1, sizeof *info );
645        if( NULL == info )
646        {
647            mallocmsg( sizeof *info );
648            return;
649        }
650        info->id = id;
651        RB_INSERT( torlist, &gl_torinfo, info );
652    }
653
654    free( info->state );
655    info->state = strdup_noctrl( state );
656    info->eta   = eta;
657    info->done  = done;
658    info->down  = down;
659    info->up    = up;
660    if( NULL != terr && '\0' != terr[0] )
661    {
662        info->errorcode = strdup_noctrl( terr );
663    }
664    if( NULL != terrmsg && '\0' != terrmsg[0] )
665    {
666        info->errormsg = strdup_noctrl( terrmsg );
667    }
668}
669
670void
671hashmsg( int id, const char * hash )
672{
673    struct torhash key, * found;
674
675    if( !TORRENT_ID_VALID( id ) )
676    {
677        sendidreqs();
678        return;
679    }
680
681    if( NULL == hash )
682    {
683        errmsg( "missing torrent hash from server" );
684        return;
685    }
686
687    bzero( &key, sizeof key );
688    strlcpy( key.hash, hash, sizeof key.hash );
689    found = RB_FIND( torhashes, &gl_hashids, &key );
690    if( NULL != found )
691    {
692        found->id = id;
693    }
694}
695
696float
697fmtsize( int64_t num, const char ** units )
698{
699    static const char * sizes[] =
700    {
701        "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
702    };
703    float  ret;
704    size_t ii;
705
706    ret = num;
707    for( ii = 0; ARRAYLEN( sizes ) > ii && 1000.0 < ret; ii++ )
708    {
709        ret /= 1024.0;
710    }
711
712    if( NULL != units )
713    {
714        *units = sizes[ii];
715    }
716
717    return ret;
718}
719
720char *
721strdup_noctrl( const char * str )
722{
723    char * ret;
724    size_t ii;
725
726    ii = strlen( str );
727    ret = malloc( ii + 1 );
728    if( NULL == ret )
729    {
730        mallocmsg( ii + 1 );
731        return NULL;
732    }
733
734    for( ii = 0; '\0' != str[ii]; ii++ )
735    {
736        ret[ii] = ( iscntrl( str[ii] ) ? ' ' : str[ii] );
737    }
738    ret[ii] = '\0';
739
740    return ret;
741}
742
743void
744print_eta( int64_t secs )
745{
746    static const struct
747    {
748        const char * label;
749        int64_t      div;
750    }
751    units[] =
752    {
753        { "second", 60 },
754        { "minute", 60 },
755        { "hour",   24 },
756        { "day",    7  },
757        { "week",   1 }
758    };
759    int    readable[ ARRAYLEN( units ) ];
760    int    printed;
761    size_t ii;
762
763    if( 0 > secs )
764    {
765        printf( "stalled" );
766        return;
767    }
768
769    for( ii = 0; ARRAYLEN( units ) > ii; ii++ )
770    {
771        readable[ii] = secs % units[ii].div;
772        secs /= units[ii].div;
773    }
774    readable[ii-1] = MIN( INT_MAX, secs );
775
776    printf( "done in" );
777    for( printed = 0; 0 < ii && 2 > printed; ii-- )
778    {
779        if( 0 != readable[ii-1] ||
780            ( 0 == printed && 1 == ii ) )
781        {
782            printf( " %i %s%s", readable[ii-1], units[ii-1].label,
783                    ( 1 == readable[ii-1] ? "" : "s" ) );
784            printed++;
785        }
786    }
787}
788
789int
790sendidreqs( void )
791{
792    struct
793    {
794        struct strlist * list;
795        int ( * func )( size_t, const int * );
796    }
797    reqs[] =
798    {
799        { gl_starthashes,  client_start  },
800        { gl_stophashes,   client_stop   },
801        { gl_removehashes, client_remove },
802    };
803    struct stritem * jj;
804    size_t           ii;
805    int            * ids, count, ret;
806    struct torhash   key, * found;
807
808    ret = -1;
809    bzero( &key, sizeof key );
810
811    for( ii = 0; ARRAYLEN( reqs ) > ii; ii++)
812    {
813        if( SLIST_EMPTY( reqs[ii].list ) )
814        {
815            continue;
816        }
817
818        count = 0;
819        SLIST_FOREACH( jj, reqs[ii].list, next )
820        {
821            count++;
822        }
823        count++;
824
825        ids = calloc( count, sizeof ids[0] );
826        if( NULL == ids )
827        {
828            mallocmsg( count * sizeof ids[0] );
829            return -1;
830        }
831
832        count = 0;
833        SLIST_FOREACH( jj, reqs[ii].list, next )
834        {
835            strlcpy( key.hash, jj->str, sizeof key.hash );
836            found = RB_FIND( torhashes, &gl_hashids, &key );
837            if( NULL != found && TORRENT_ID_VALID( found->id ) )
838            {
839                ids[count] = found->id;
840                count++;
841            }
842        }
843
844        if( 0 < count )
845        {
846            if( 0 > reqs[ii].func( count, ids ) )
847            {
848                free( ids );
849                return -1;
850            }
851            ret = 0;
852        }
853
854        free( ids );
855    }
856
857    gl_starthashes  = NULL;
858    gl_stophashes   = NULL;
859    gl_removehashes = NULL;
860
861    return ret;
862}
863
864void
865printlisting( void )
866{
867    struct tornames  names;
868    struct torinfo * ii, * next;
869    const char     * units, * name, * upunits, * downunits;
870    float            size, progress, upspeed, downspeed;
871
872    /* sort the torrents by name */
873    RB_INIT( &names );
874    RB_FOREACH( ii, torlist, &gl_torinfo )
875    {
876        RB_INSERT( tornames, &names, ii );
877    }
878
879    /* print the torrent info while freeing the tree */
880    for( ii = RB_MIN( tornames, &names ); NULL != ii; ii = next )
881    {
882        next = RB_NEXT( tornames, &names, ii );
883        RB_REMOVE( tornames, &names, ii );
884        RB_REMOVE( torlist, &gl_torinfo, ii );
885
886        /* massage some numbers into a better format for printing */
887        size      = fmtsize( ii->size, &units );
888        upspeed   = fmtsize( ii->up, &upunits );
889        downspeed = fmtsize( ii->down, &downunits );
890        name      = ( NULL == ii->name ? "???" : ii->name );
891        progress  = ( float )ii->done / ( float )ii->size * 100.0;
892        progress  = MIN( 100.0, progress );
893        progress  = MAX( 0.0, progress );
894
895        /* print name and size */
896        printf( "%s (%.*f %s) - ", name, BESTDECIMAL( size ), size, units );
897
898        /* print hash check progress */
899        if( 0 == strcasecmp( "checking", ii->state ) )
900        {
901            printf( "%.*f%% checking files",
902                    BESTDECIMAL( progress ), progress );
903        }
904        /* print download progress, speeds, and eta */
905        else if( 0 == strcasecmp( "downloading", ii->state ) )
906        {
907            progress = MIN( 99.0, progress );
908            printf( "%.*f%% downloading at %.*f %s/s (UL at %.*f %s/s), ",
909                    BESTDECIMAL( progress ), progress,
910                    BESTDECIMAL( downspeed ), downspeed, downunits,
911                    BESTDECIMAL( upspeed ), upspeed, upunits );
912            print_eta( ii->eta );
913        }
914        /* print seeding speed */
915        else if( 0 == strcasecmp( "seeding", ii->state ) )
916        {
917            printf( "100%% seeding at %.*f %s/s",
918                    BESTDECIMAL( upspeed ), upspeed, upunits );
919        }
920        /* print stopping message */
921        else if( 0 == strcasecmp( "stopping", ii->state ) )
922        {
923            printf( "%.*f%% stopping...", BESTDECIMAL( progress ), progress );
924        }
925        /* print stopped message with progress */
926        else if( 0 == strcasecmp( "paused", ii->state ) )
927        {
928            printf( "%.*f%% stopped", BESTDECIMAL( progress ), progress );
929        }
930        /* unknown status, just print it with progress */
931        else
932        {
933            printf( "%.*f%% %s",
934                    BESTDECIMAL( progress ), progress, ii->state );
935        }
936
937        /* print any error */
938        if( NULL != ii->errorcode || NULL != ii->errormsg )
939        {
940            if( NULL == ii->errorcode )
941            {
942                printf( " [error: %s]", ii->errormsg );
943            }
944            else
945            {
946                printf( " [%s error]", ii->errorcode );
947            }
948        }
949
950        /* don't forget the newline */
951        putchar( '\n' );
952
953        /* free the stuff for this torrent */
954        free( ii->name );
955        free( ii->state );
956        free( ii->errorcode );
957        free( ii->errormsg );
958        free( ii );
959    }
960}
961
962int
963tornamecmp( struct torinfo * left, struct torinfo * right )
964{
965    int ret;
966
967    /* we know they're equal if they're the same struct */
968    if( left == right )
969    {
970        return 0;
971    }
972    /* we might be missing a name, fall back on torrent ID */
973    else if( NULL == left->name && NULL == right->name )
974    {
975        return toridcmp( left, right );
976    }
977    else if( NULL == left->name )
978    {
979        return -1;
980    }
981    else if( NULL == right->name )
982    {
983        return 1;
984    }
985
986    /* if we have two names, compare them */
987    ret = strcasecmp( left->name, right->name );
988    if( 0 != ret )
989    {
990        return ret;
991    }
992    /* if the names are the same then fall back on the torrent ID,
993       this is to handle different torrents with the same name */
994    else
995    {
996        return toridcmp( left, right );
997    }
998}
999
1000int
1001torhashcmp( struct torhash * left, struct torhash * right )
1002{
1003    return strcasecmp( left->hash, right->hash );
1004}
1005
1006INTCMP_FUNC( toridcmp, torinfo, id )
Note: See TracBrowser for help on using the repository browser.