Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -32,10 +32,12 @@ #import "OFConnectionFailedException.h" #import "OFHTTPRequestFailedException.h" #import "OFInvalidFormatException.h" #import "OFInvalidServerReplyException.h" #import "OFOpenItemFailedException.h" +#import "OFOutOfRangeException.h" +#import "OFStatItemFailedException.h" #import "OFUnsupportedProtocolException.h" #import "ProgressBar.h" #define GIBIBYTE (1024 * 1024 * 1024) @@ -50,11 +52,11 @@ OFString *_outputPath; bool _continue, _quiet; OFHTTPClient *_HTTPClient; char *_buffer; OFStream *_output; - intmax_t _received, _length; + intmax_t _received, _length, _resumedFrom; ProgressBar *_progressBar; } - (void)downloadNextURL; @end @@ -63,16 +65,17 @@ static void help(OFStream *stream, bool full, int status) { [of_stderr writeFormat: - @"Usage: %@ -[hoq] url1 [url2 ...]\n", + @"Usage: %@ -[choq] url1 [url2 ...]\n", [OFApplication programName]]; if (full) [stream writeString: @"\nOptions:\n" + @" -c Continue download of existing file\n" @" -h Show this help\n" @" -o Output filename\n" @" -q Quiet mode (no output, except errors)\n"]; [OFApplication terminateWithStatus: status]; @@ -97,15 +100,18 @@ } - (void)applicationDidFinishLaunching { OFOptionsParser *optionsParser = - [OFOptionsParser parserWithOptions: @"ho:q"]; + [OFOptionsParser parserWithOptions: @"cho:q"]; of_unichar_t option; while ((option = [optionsParser nextOption]) != '\0') { switch (option) { + case 'c': + _continue = true; + break; case 'h': help(of_stdout, true, 0); break; case 'o': [_outputPath release]; @@ -211,17 +217,18 @@ - (void)downloadNextURL { OFString *URLString = nil; OFURL *URL; + OFMutableDictionary *clientHeaders; OFHTTPRequest *request; OFHTTPResponse *response; OFDictionary *headers; OFString *fileName, *lengthString, *type; _length = -1; - _received = 0; + _received = _resumedFrom = 0; if (_output != of_stdout) [_output release]; _output = nil; @@ -250,12 +257,40 @@ goto next; } if (!_quiet) [of_stdout writeFormat: @"⇣ %@", [URL string]]; + + if (_outputPath != nil) + fileName = _outputPath; + else + fileName = [[URL path] lastPathComponent]; + + clientHeaders = [OFMutableDictionary + dictionaryWithObject: @"OFHTTP" + forKey: @"User-Agent"]; + + if (_continue) { + @try { + off_t size = [OFFile sizeOfFileAtPath: fileName]; + OFString *range; + + if (size > INTMAX_MAX) + @throw [OFOutOfRangeException exception]; + + _resumedFrom = (intmax_t)size; + + range = [OFString stringWithFormat: @"bytes=%jd-", + _resumedFrom]; + [clientHeaders setObject: range + forKey: @"Range"]; + } @catch (OFStatItemFailedException *e) { + } + } request = [OFHTTPRequest requestWithURL: URL]; + [request setHeaders: clientHeaders]; @try { response = [_HTTPClient performRequest: request]; } @catch (OFHTTPRequestFailedException *e) { if (!_quiet) @@ -320,15 +355,10 @@ headers = [response headers]; lengthString = [headers objectForKey: @"Content-Length"]; type = [headers objectForKey: @"Content-Type"]; - if (_outputPath != nil) - fileName = _outputPath; - else - fileName = [[URL path] lastPathComponent]; - if (lengthString != nil) _length = [lengthString decimalValue]; if (!_quiet) { if (type == nil) @@ -335,20 +365,23 @@ type = @"unknown"; if (lengthString != nil) { if (_length >= GIBIBYTE) lengthString = [OFString stringWithFormat: - @"%.2f GiB", (float)_length / GIBIBYTE]; + @"%.2f GiB", + (float)(_resumedFrom + _length) / GIBIBYTE]; else if (_length >= MEBIBYTE) lengthString = [OFString stringWithFormat: - @"%.2f MiB", (float)_length / MEBIBYTE]; + @"%.2f MiB", + (float)(_resumedFrom + _length) / MEBIBYTE]; else if (_length >= KIBIBYTE) lengthString = [OFString stringWithFormat: - @"%.2f KiB", (float)_length / KIBIBYTE]; + @"%.2f KiB", + (float)(_resumedFrom + _length) / KIBIBYTE]; else lengthString = [OFString stringWithFormat: - @"%jd bytes", _length]; + @"%jd bytes", _resumedFrom + _length]; } else lengthString = @"unknown"; [of_stdout writeFormat: @" Name: %@\n", fileName]; [of_stdout writeFormat: @" Type: %@\n", type]; @@ -356,22 +389,24 @@ } if ([_outputPath isEqual: @"-"]) _output = of_stdout; else { - if ([OFFile fileExistsAtPath: fileName]) { + if (!_continue && [OFFile fileExistsAtPath: fileName]) { [of_stderr writeFormat: @"%@: File %@ already exists!\n", [OFApplication programName], fileName]; _errorCode = 1; goto next; } @try { + OFString *mode = + ([response statusCode] == 206 ? @"ab" : @"wb"); _output = [[OFFile alloc] initWithPath: fileName - mode: @"wb"]; + mode: mode]; } @catch (OFOpenItemFailedException *e) { [of_stderr writeFormat: @"%@: Failed to open file %@!\n", [OFApplication programName], fileName]; @@ -379,11 +414,14 @@ goto next; } } if (!_quiet) { - _progressBar = [[ProgressBar alloc] initWithLength: _length]; + _progressBar = [[ProgressBar alloc] + initWithLength: _length + resumedFrom: _resumedFrom]; + [_progressBar setReceived: _received]; [_progressBar draw]; } [response asyncReadIntoBuffer: _buffer length: [OFSystemInfo pageSize] Index: utils/ofhttp/ProgressBar.h ================================================================== --- utils/ofhttp/ProgressBar.h +++ utils/ofhttp/ProgressBar.h @@ -19,17 +19,18 @@ @class OFDate; @class OFTimer; @interface ProgressBar: OFObject { - intmax_t _length, _received, _lastReceived; + intmax_t _received, _lastReceived, _length, _resumedFrom; OFDate *_startDate; double _lastDrawn; OFTimer *_timer; bool _stopped; } -- initWithLength: (intmax_t)length; +- initWithLength: (intmax_t)length + resumedFrom: (intmax_t)resumedFrom; - (void)setReceived: (intmax_t)received; - (void)draw; - (void)stop; @end Index: utils/ofhttp/ProgressBar.m ================================================================== --- utils/ofhttp/ProgressBar.m +++ utils/ofhttp/ProgressBar.m @@ -31,17 +31,19 @@ #define BAR_WIDTH 52 #define UPDATE_INTERVAL 0.1 @implementation ProgressBar - initWithLength: (intmax_t)length + resumedFrom: (intmax_t)resumedFrom { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); _length = length; + _resumedFrom = resumedFrom; _startDate = [[OFDate alloc] init]; _timer = [[OFTimer scheduledTimerWithTimeInterval: UPDATE_INTERVAL target: self selector: @selector(draw) @@ -73,12 +75,14 @@ - (void)_drawProgress { uint_fast8_t i; float bars, percent, bps; - bars = (float)_received / (float)_length * BAR_WIDTH; - percent = (float)_received / (float)_length * 100; + bars = (float)(_resumedFrom + _received) / + (_resumedFrom + _length) * BAR_WIDTH; + percent = (float)(_resumedFrom + _received) / + (_resumedFrom + _length) * 100; [of_stdout writeString: @"\r ▕"]; for (i = 0; i < (uint_fast8_t)bars; i++) [of_stdout writeString: @"█"]; @@ -129,20 +133,24 @@ - (void)_drawReceived { float bps; if (_received >= GIBIBYTE) - [of_stdout writeFormat: @"\r %7.2f GiB (", - (float)_received / GIBIBYTE]; + [of_stdout writeFormat: + @"\r %7.2f GiB ", + (float)(_resumedFrom + _received) / GIBIBYTE]; else if (_received >= MEBIBYTE) - [of_stdout writeFormat: @"\r %7.2f MiB ", - (float)_received / MEBIBYTE]; + [of_stdout writeFormat: + @"\r %7.2f MiB ", + (float)(_resumedFrom + _received) / MEBIBYTE]; else if (_received >= KIBIBYTE) - [of_stdout writeFormat: @"\r %7.2f KiB ", - (float)_received / KIBIBYTE]; + [of_stdout writeFormat: + @"\r %7.2f KiB ", + (float)(_resumedFrom + _received) / KIBIBYTE]; else - [of_stdout writeFormat: @"\r %jd bytes ", _received]; + [of_stdout writeFormat: + @"\r %jd bytes ", _resumedFrom + _received]; if (_stopped) bps = (float)_received / -[_startDate timeIntervalSinceNow]; else bps = (float)(_received - _lastReceived) / UPDATE_INTERVAL;