source: trunk/daemon/remote.c @ 3578

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

Add IPC messages to set and retrieve the encryption mode.
Implement encryption mode messages in -daemon and -remote.

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