source: trunk/daemon/remote.c @ 2344

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

Don't segfault if an 'all' hash argument is combined with specific hashes.

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