source: trunk/daemon/remote.c @ 3401

Last change on this file since 3401 was 3401, checked in by charles, 15 years ago

janitorial work for the freeze: (1) finish replacing "transmission-gtk" with "transmission". (2) add Charles to authors lists in man pages. (3) standardize the summary as "A fast and easy BitTorrent? client" in the rpm spec, gtk about dialog, and man pages. (4) fold together similar translation strings in the gtk client. (5) use g_strerror instead of strerror in the gtk client.

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