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

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

Minor cleanup of client code.
Display ratio in transmission-remote -l output.

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