/[axel]/branches/3.x-broken/src/axel.c
ViewVC logotype

Contents of /branches/3.x-broken/src/axel.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 79 - (show annotations) (download)
Tue Dec 30 20:13:35 2008 UTC (4 years, 5 months ago) by phihag-guest
File MIME type: text/plain
File size: 19449 byte(s)
Move some stuff around to modularize
1 /********************************************************************\
2 * Axel -- A lighter download accelerator for Linux and other Unices. *
3 * *
4 * Copyright 2001 Wilmer van der Gaast *
5 \********************************************************************/
6
7 /* Main control */
8
9 /*
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License with
21 the Debian GNU/Linux distribution in file /usr/doc/copyright/GPL;
22 if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23 Suite 330, Boston, MA 02111-1307 USA
24 */
25
26 #include "axel.h"
27
28 static void axel_prepare(axel_t* axel);
29 static void axel_teardown(axel_t* axel);
30 static void axel_save_state(axel_t* axel);
31 static void axel_set_state(axel_t* axel, int state);
32 static void axel_update_display(const axel_t* axel);
33 static void axel_startthread(conn_t* conn);
34
35 /**
36 * Add a URL. urlstr is a pointer to a string specified by the user
37 * @param priority The priority of this URL. If not URL_PRIO_NONE, this overwrites any priority in the URL.
38 * @return 1 iff the URL was added
39 */
40 _Bool axel_addurlstr(axel_t* axel, const char* urlstr, int priority) {
41 url_t* url = url_parse_heuristic(urlstr);
42
43 if (url == NULL) {
44 return 0;
45 }
46
47 if (priority != URL_PRIO_NONE) {
48 url->priority = priority;
49 }
50
51 urllist_add(axel->urls, url);
52
53 return 1;
54 }
55
56 void axel_init(axel_t* ax, const conf_t* conf) {
57 ax->message_handler = NULL;
58 ax->display_handler = NULL;
59
60 ax->conf = conf;
61 urllist_init(ax->urls);
62
63 ax->conncount = -1;
64 // conn is set once we know conncount
65
66 ax->filename = NULL;
67 ax->outfd = -1;
68
69 ax->statefilename = NULL;
70
71 ax->size = -1;
72 // start_utime is set once we're ready to start downloading
73
74 ax->state = AXEL_STATE_INIT;
75
76 ax->delay_time = 0;
77
78 pthread_mutex_init(ax->msgmtx);
79 messageq_init(ax->msgs);
80 }
81
82 void axel_destroy(axel_t* axel) {
83 urllist_destroy(axel->urls);
84
85 free(axel->filename);
86 free(axel->statefilename);
87
88 pthread_mutex_destroy(msgmtx);
89 messaq_destroy(ax->msgs);
90 }
91
92 /**
93 * Download a file. If you want to know anything more about the download, register handlers.
94 * While the download is running, the current thread will dispatch the callback functions
95 * Afterwards, a program has still to call axel_destroy() and then exit.
96 * @return 0 on success, an error code otherwise
97 */
98 int axel_download(axel_t* axel) {
99 axel_prepare(axel);
100
101 if (axel->state != AXEL_STATE_DOWNLOADING) {
102 axel_set_state(axel, AXEL_STATE_ERROR);
103
104 return axel->state;
105 }
106
107
108 for (int cid = 0;;cid = (cid < axel->conncount) ? cid + 1 : 0) {
109 // Main loop, visit all connections
110
111
112
113
114 }
115
116
117 // TODO set state to finished if not erred
118
119
120 return axel->state;
121 }
122
123 /**
124 * Prepare a download, start all threads
125 */
126 static void axel_prepare(axel_t* axel) {
127 // TODO Determine file name
128 // TODO determine file size
129
130
131 if (axel->filename == NULL) {
132 axel->filename = safe_strdup(axel->conf->default_filename);
133 }
134
135 // TODO Open outfile
136
137
138 // Determine state file name
139 const size_t SUFFIXLEN = strlen(STATEFILE_SUFFIX);
140 size_t fnlen = strlen(axel->filename);
141 axel->statefilename = malloc(fnlen + SUFFIXLEN + 1);
142 memcpy(axel->statefilename + fnlen, STATEFILE_SUFFIX, SUFFIXLEN);
143 axel->statefilename[fnlen + SUFFIXLEN + SUFFIXLEN] = '\0';
144
145 // Start counting time
146 axel->start_utime = getutime();
147 axel_set_state(axel, AXEL_STATE_DOWNLOADING);
148
149 // TODO set conncount according to conf
150 // TODO start threads
151 }
152
153 /**
154 * Prepare axel to be freed (cancel all connections, close outfile etc.)
155 */
156 static void axel_teardown(axel_t* axel) {
157 // TODO Cancel all remaining connections
158
159
160 // TODO Close outfile
161 }
162
163 /**
164 * Send a message from the main thread
165 * @param message The message to send. Note that this must be freed by the caller
166 */
167 void axel_message(const axel_t* axel, message_t* msg) {
168 message_t* msg = safe_malloc(sizeof(message_t));
169
170 msg->
171
172 axel_message(axel, msg);
173 }
174
175 /**
176 * @param message The message, will be freed by this method
177 */
178 void axel_message_detail(const axel_t* axel, int verbosity, char* message, _Bool msgOnHeap) {
179
180 }
181
182 void axel_message_fmt(const axel_t *axel, int verbosity, const char *format, ...) {
183 const MAX_MSG_SIZE = 1024;
184 char* buf = malloc(MAX_MSG_SIZE);
185
186 va_list params;
187 va_start(params, format);
188 vsnprintf(buf, MAX_MSG_SIZE, format, params );
189 va_end(params);
190
191 axel_message_heap(axel, verbosity, buf);
192 }
193
194 /**
195 * Display a message. Must only be called from the main thread.
196 */
197 void axel_message(const axel_t* axel, const axel_message_t* msg) {
198 if (axel->message_handler != NULL) {
199 axel->message_handler(axel, msg->verbosity, msg->message);
200 }
201 axel_update_display();
202 }
203
204 static void axel_update_display(const axel_t* axel) {
205 if (axel->display_handler != NULL) {
206 axel->display_handler(axel);
207 }
208 }
209
210 static void axel_set_state(axel_t* axel, int state) {
211 axel->state = state;
212 axel_update_display();
213 }
214
215 // Start a new thread that immediately executes afterwards.
216 _Bool axel_startthread(conn_t* c) {
217 if (pthread_create(conn->thread, NULL, conn_threadstart, c) != 0) {
218 axel_message(conn->axel, critical, _("Thread creation failed"));
219 return false;
220 }
221
222 return true;
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238 /* Create a new axel_t structure */
239 axel_t *axel_new( conf_t *conf, int count, void *url )
240 {
241 search_t *res;
242 axel_t *axel;
243 url_t *u;
244 char *s;
245 int i;
246
247 axel = malloc( sizeof( axel_t ) );
248 memset( axel, 0, sizeof( axel_t ) );
249 *axel->conf = *conf;
250 axel->conn = malloc( sizeof( conn_t ) * axel->conf->num_connections );
251 memset( axel->conn, 0, sizeof( conn_t ) * axel->conf->num_connections );
252 if( axel->conf->max_speed > 0 )
253 {
254 if( (float) axel->conf->max_speed / axel->conf->buffer_size < 0.5 )
255 {
256 axel_message( axel, VERBOSITY_NORMAL, _("Buffer resized for this speed."));
257 axel->conf->buffer_size = axel->conf->max_speed;
258 }
259 axel->delay_time = (int) ( (float) 1000000 / axel->conf->max_speed * axel->conf->buffer_size * axel->conf->num_connections );
260 }
261 if( buffer == NULL )
262 buffer = malloc( max( MAX_STRING, axel->conf->buffer_size ) );
263
264 if( count == 0 )
265 {
266 axel->url = malloc( sizeof( url_t ) );
267 axel->url->next = axel->url;
268 strncpy( axel->url->text, (char *) url, MAX_STRING );
269 }
270 else
271 {
272 res = (search_t *) url;
273 u = axel->url = malloc( sizeof( url_t ) );
274 for( i = 0; i < count; i ++ )
275 {
276 strncpy( u->text, res[i].url, MAX_STRING );
277 if( i < count - 1 )
278 {
279 u->next = malloc( sizeof( url_t ) );
280 u = u->next;
281 }
282 else
283 {
284 u->next = axel->url;
285 }
286 }
287 }
288
289 axel->conn[0].conf = axel->conf;
290 if( !conn_set( &axel->conn[0], axel->url->text ) )
291 {
292 axel_message( axel, _("Could not parse URL.\n") );
293 axel->ready = -1;
294 return( axel );
295 }
296
297 axel->conn[0].local_if = axel->conf->interfaces->text;
298 axel->conf->interfaces = axel->conf->interfaces->next;
299
300 strncpy( axel->filename, axel->conn[0].file, MAX_STRING );
301 http_decode( axel->filename );
302 if( *axel->filename == 0 ) /* Index page == no fn */
303 strncpy( axel->filename, axel->conf->default_filename, MAX_STRING );
304 if( ( s = strchr( axel->filename, '?' ) ) != NULL && axel->conf->strip_cgi_parameters )
305 *s = 0; /* Get rid of CGI parameters */
306
307 if( !conn_init( &axel->conn[0] ) )
308 {
309 axel_message( axel, axel->conn[0].message );
310 axel->ready = -1;
311 return( axel );
312 }
313
314 /* This does more than just checking the file size, it all depends
315 on the protocol used. */
316 if( !conn_info( &axel->conn[0] ) )
317 {
318 axel_message( axel, axel->conn[0].message );
319 axel->ready = -1;
320 return( axel );
321 }
322 s = conn_url( axel->conn );
323 strncpy( axel->url->text, s, MAX_STRING );
324 if( ( axel->size = axel->conn[0].size ) != INT_MAX )
325 {
326 if( axel->conf->verbose > 0 )
327 axel_message( axel, _("File size: %lld bytes"), axel->size );
328 }
329
330 /* Wildcards in URL --> Get complete filename */
331 if( strchr( axel->filename, '*' ) || strchr( axel->filename, '?' ) )
332 strncpy( axel->filename, axel->conn[0].file, MAX_STRING );
333
334 return( axel );
335 }
336
337 /* Open a local file to store the downloaded data */
338 int axel_open( axel_t *axel )
339 {
340 int i, fd;
341 long long int j;
342
343 if( axel->conf->verbose > 0 )
344 axel_message( axel, _("Opening output file %s"), axel->filename );
345 snprintf( buffer, MAX_STRING, "%s.st", axel->filename );
346
347 axel->outfd = -1;
348
349 /* Check whether server knows about RESTart and switch back to
350 single connection download if necessary */
351 if( !axel->conn[0].supported )
352 {
353 axel_message( axel, _("Server unsupported, "
354 "starting from scratch with one connection.") );
355 axel->conf->num_connections = 1;
356 axel->conn = realloc( axel->conn, sizeof( conn_t ) );
357 axel_divide( axel );
358 }
359 else if( ( fd = open( buffer, O_RDONLY ) ) != -1 )
360 {
361 read( fd, &axel->conf->num_connections, sizeof( axel->conf->num_connections ) );
362
363 axel->conn = realloc( axel->conn, sizeof( conn_t ) * axel->conf->num_connections );
364 memset( axel->conn + 1, 0, sizeof( conn_t ) * ( axel->conf->num_connections - 1 ) );
365
366 axel_divide( axel );
367
368 read( fd, &axel->bytes_done, sizeof( axel->bytes_done ) );
369 for( i = 0; i < axel->conf->num_connections; i ++ )
370 read( fd, &axel->conn[i].currentbyte, sizeof( axel->conn[i].currentbyte ) );
371
372 axel_message( axel, _("State file found: %lld bytes downloaded, %lld to go."),
373 axel->bytes_done, axel->size - axel->bytes_done );
374
375 close( fd );
376
377 if( ( axel->outfd = open( axel->filename, O_WRONLY, 0666 ) ) == -1 )
378 {
379 axel_message( axel, _("Error opening local file") );
380 return( 0 );
381 }
382 }
383
384 /* If outfd == -1 we have to start from scrath now */
385 if( axel->outfd == -1 )
386 {
387 axel_divide( axel );
388
389 if( ( axel->outfd = open( axel->filename, O_CREAT | O_WRONLY, 0666 ) ) == -1 )
390 {
391 axel_message( axel, _("Error opening local file") );
392 return( 0 );
393 }
394
395 /* And check whether the filesystem can handle seeks to
396 past-EOF areas.. Speeds things up. :) AFAIK this
397 should just not happen: */
398 if( lseek( axel->outfd, axel->size, SEEK_SET ) == -1 && axel->conf->num_connections > 1 )
399 {
400 /* But if the OS/fs does not allow to seek behind
401 EOF, we have to fill the file with zeroes before
402 starting. Slow.. */
403 axel_message( axel, _("Crappy filesystem/OS.. Working around. :-(") );
404 lseek( axel->outfd, 0, SEEK_SET );
405 memset( buffer, 0, axel->conf->buffer_size );
406 j = axel->size;
407 while( j > 0 )
408 {
409 write( axel->outfd, buffer, min( j, axel->conf->buffer_size ) );
410 j -= axel->conf->buffer_size;
411 }
412 }
413 }
414
415 return( 1 );
416 }
417
418 /* Start downloading */
419 void axel_start( axel_t *axel ) {
420 int i;
421
422 /* HTTP might've redirected and FTP handles wildcards, so
423 re-scan the URL for every conn */
424 for( i = 0; i < axel->conf->num_connections; i ++ )
425 {
426 conn_set( &axel->conn[i], axel->url->text );
427 axel->url = axel->url->next;
428 axel->conn[i].local_if = axel->conf->interfaces->text;
429 axel->conf->interfaces = axel->conf->interfaces->next;
430 axel->conn[i].conf = axel->conf;
431 if( i ) axel->conn[i].supported = 1;
432 }
433
434 if( axel->conf->verbose > 0 )
435 axel_message( axel, _("Starting download") );
436
437 for( i = 0; i < axel->conf->num_connections; i ++ )
438 if( axel->conn[i].currentbyte <= axel->conn[i].lastbyte )
439 {
440 if( axel->conf->verbose >= 2 )
441 axel_message( axel, _("Connection %i downloading from %s:%i using interface %s"),
442 i, axel->conn[i].host, axel->conn[i].port, axel->conn[i].local_if );
443 if( pthread_create( axel->conn[i].setup_thread, NULL, setup_thread, &axel->conn[i] ) != 0 )
444 {
445 axel_message( axel, _("pthread error!!!") );
446 axel->ready = -1;
447 }
448 else
449 {
450 axel->conn[i].last_transfer = gettime();
451 axel->conn[i].state = 1;
452 }
453 }
454 }
455
456 /* Main 'loop' */
457 void axel_do( axel_t *axel )
458 {
459 fd_set fds[1];
460 int hifd, i, j;
461 long long int size;
462 struct timeval timeval[1];
463
464 /* Create statefile if necessary */
465 if( gettime() > axel->next_state )
466 {
467 save_state( axel );
468 axel->next_state = gettime() + axel->conf->save_state_interval;
469 }
470
471 /* Wait for data on (one of) the connections */
472 FD_ZERO( fds );
473 hifd = 0;
474 for( i = 0; i < axel->conncount; i ++ )
475 {
476 if( axel->conn[i].enabled )
477 FD_SET( axel->conn[i].fd, fds );
478 hifd = max( hifd, axel->conn[i].fd );
479 }
480 if( hifd == 0 )
481 {
482 /* No connections yet. Wait... */
483 usleep( 100000 );
484 goto conn_check;
485 }
486 else
487 {
488 timeval->tv_sec = 0;
489 timeval->tv_usec = 100000;
490 /* A select() error probably means it was interrupted
491 by a signal, or that something else's very wrong... */
492 if( select( hifd + 1, fds, NULL, NULL, timeval ) == -1 )
493 {
494 axel->ready = -1;
495 return;
496 }
497 }
498
499 /* Handle connections which need attention */
500 for( i = 0; i < axel->conf->num_connections; i ++ )
501 if( axel->conn[i].enabled ) {
502 if( FD_ISSET( axel->conn[i].fd, fds ) )
503 {
504 axel->conn[i].last_transfer = gettime();
505 size = read( axel->conn[i].fd, buffer, axel->conf->buffer_size );
506 if( size == -1 )
507 {
508 if( axel->conf->verbose )
509 {
510 axel_message( axel, _("Error on connection %i! "
511 "Connection closed"), i );
512 }
513 axel->conn[i].enabled = 0;
514 conn_disconnect( &axel->conn[i] );
515 continue;
516 }
517 else if( size == 0 )
518 {
519 if( axel->conf->verbose )
520 {
521 /* Only abnormal behaviour if: */
522 if( axel->conn[i].currentbyte < axel->conn[i].lastbyte && axel->size != INT_MAX )
523 {
524 axel_message( axel, _("Connection %i unexpectedly closed"), i );
525 }
526 else
527 {
528 axel_message( axel, _("Connection %i finished"), i );
529 }
530 }
531 if( !axel->conn[0].supported )
532 {
533 axel->ready = 1;
534 }
535 axel->conn[i].enabled = 0;
536 conn_disconnect( &axel->conn[i] );
537 continue;
538 }
539 /* j == Bytes to go */
540 j = axel->conn[i].lastbyte - axel->conn[i].currentbyte + 1;
541 if( j < size )
542 {
543 if( axel->conf->verbose )
544 {
545 axel_message( axel, _("Connection %i finished"), i );
546 }
547 axel->conn[i].enabled = 0;
548 conn_disconnect( &axel->conn[i] );
549 size = j;
550 /* Don't terminate, still stuff to write! */
551 }
552 /* This should always succeed.. */
553 lseek( axel->outfd, axel->conn[i].currentbyte, SEEK_SET );
554 if( write( axel->outfd, buffer, size ) != size )
555 {
556
557 axel_message( axel, _("Write error!") );
558 axel->ready = -1;
559 return;
560 }
561 axel->conn[i].currentbyte += size;
562 axel->bytes_done += size;
563 }
564 else
565 {
566 if( gettime() > axel->conn[i].last_transfer + axel->conf->connection_timeout )
567 {
568 if( axel->conf->verbose )
569 axel_message( axel, _("Connection %i timed out"), i );
570 conn_disconnect( &axel->conn[i] );
571 axel->conn[i].enabled = 0;
572 }
573 } }
574
575 if( axel->ready )
576 return;
577
578 conn_check:
579 /* Look for aborted connections and attempt to restart them. */
580 for( i = 0; i < axel->conf->num_connections; i ++ )
581 {
582 if( !axel->conn[i].enabled && axel->conn[i].currentbyte < axel->conn[i].lastbyte )
583 {
584 if( axel->conn[i].state == 0 )
585 {
586 conn_set( &axel->conn[i], axel->url->text );
587 axel->url = axel->url->next;
588 /* axel->conn[i].local_if = axel->conf->interfaces->text;
589 axel->conf->interfaces = axel->conf->interfaces->next; */
590 if( axel->conf->verbose >= 2 )
591 axel_message( axel, _("Connection %i downloading from %s:%i using interface %s"),
592 i, axel->conn[i].host, axel->conn[i].port, axel->conn[i].local_if );
593 if( pthread_create( axel->conn[i].setup_thread, NULL, setup_thread, &axel->conn[i] ) == 0 )
594 {
595 axel->conn[i].state = 1;
596 axel->conn[i].last_transfer = gettime();
597 }
598 else
599 {
600 axel_message( axel, _("pthread error!!!") );
601 axel->ready = -1;
602 }
603 }
604 else
605 {
606 if( gettime() > axel->conn[i].last_transfer + axel->conf->reconnect_delay )
607 {
608 pthread_cancel( *axel->conn[i].setup_thread );
609 axel->conn[i].state = 0;
610 }
611 }
612 }
613 }
614
615 /* Calculate current average speed and finish_time */
616 axel->bytes_per_second = (int) ( (double) ( axel->bytes_done - axel->start_byte ) / ( gettime() - axel->start_time ) );
617 axel->finish_time = (int) ( axel->start_time + (double) ( axel->size - axel->start_byte ) / axel->bytes_per_second );
618
619 /* Check speed. If too high, delay for some time to slow things
620 down a bit. I think a 5% deviation should be acceptable. */
621 if( axel->conf->max_speed > 0 )
622 {
623 if( (float) axel->bytes_per_second / axel->conf->max_speed > 1.05 )
624 axel->delay_time += 10000;
625 else if( ( (float) axel->bytes_per_second / axel->conf->max_speed < 0.95 ) && ( axel->delay_time >= 10000 ) )
626 axel->delay_time -= 10000;
627 else if( ( (float) axel->bytes_per_second / axel->conf->max_speed < 0.95 ) )
628 axel->delay_time = 0;
629 usleep( axel->delay_time );
630 }
631
632 /* Ready? */
633 if( axel->bytes_done == axel->size )
634 axel->ready = 1;
635 }
636
637 /* Close an axel connection */
638 void axel_close( axel_t *axel )
639 {
640 int i;
641 message_t *m;
642
643 /* Terminate any thread still running */
644 for( i = 0; i < axel->conf->num_connections; i ++ )
645 /* don't try to kill non existing thread */
646 if ( *axel->conn[i].setup_thread != 0 )
647 pthread_cancel( *axel->conn[i].setup_thread );
648
649 /* Delete state file if necessary */
650 if( axel->ready == 1 )
651 {
652 snprintf( buffer, MAX_STRING, "%s.st", axel->filename );
653 unlink( buffer );
654 }
655 /* Else: Create it.. */
656 else if( axel->bytes_done > 0 )
657 {
658 save_state( axel );
659 }
660
661 /* Delete any message not processed yet */
662 while( axel->message )
663 {
664 m = axel->message;
665 axel->message = axel->message->next;
666 free( m );
667 }
668
669 /* Close all connections and local file */
670 close( axel->outfd );
671 for( i = 0; i < axel->conf->num_connections; i ++ )
672 conn_disconnect( &axel->conn[i] );
673
674 free( axel->conn );
675 free( axel );
676 }
677
678 /* Save the state of the current download */
679 void save_state( axel_t *axel )
680 {
681 int fd, i;
682 char fn[MAX_STRING+4];
683
684 snprintf( fn, MAX_STRING, "%s" STATEFILE_SUFFIX, axel->filename );
685 if( ( fd = open( fn, O_CREAT | O_TRUNC | O_WRONLY, 0600 ) ) == -1 )
686 {
687 return; /* Not 100% fatal.. */
688 }
689 write( fd, &axel->conf->num_connections, sizeof( axel->conf->num_connections ) );
690 write( fd, &axel->bytes_done, sizeof( axel->bytes_done ) );
691 for( i = 0; i < axel->conf->num_connections; i ++ )
692 {
693 write( fd, &axel->conn[i].currentbyte, sizeof( axel->conn[i].currentbyte ) );
694 }
695 close( fd );
696 }
697
698 /* Divide the file and set the locations for each connection */
699 static void axel_divide( axel_t *axel )
700 {
701 int i;
702
703 axel->conn[0].currentbyte = 0;
704 axel->conn[0].lastbyte = axel->size / axel->conf->num_connections - 1;
705 for( i = 1; i < axel->conf->num_connections; i ++ )
706 {
707 #ifdef DEBUG
708 printf( "Downloading %lld-%lld using conn. %i\n", axel->conn[i-1].currentbyte, axel->conn[i-1].lastbyte, i - 1 );
709 #endif
710 axel->conn[i].currentbyte = axel->conn[i-1].lastbyte + 1;
711 axel->conn[i].lastbyte = axel->conn[i].currentbyte + axel->size / axel->conf->num_connections;
712 }
713 axel->conn[axel->conf->num_connections-1].lastbyte = axel->size - 1;
714 #ifdef DEBUG
715 printf( "Downloading %lld-%lld using conn. %i\n", axel->conn[i-1].currentbyte, axel->conn[i-1].lastbyte, i - 1 );
716 #endif
717 }

  ViewVC Help
Powered by ViewVC 1.1.5