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

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

Add an ipc proxy to make remote daemon connections easier.

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