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