OLD | NEW |
1 library googleapis_beta.common_internal; | 1 library googleapis_beta.common_internal; |
2 | 2 |
3 import "dart:async"; | 3 import "dart:async"; |
4 import "dart:convert"; | 4 import "dart:convert"; |
5 import "dart:collection" as collection; | 5 import "dart:collection" as collection; |
6 | 6 |
7 import "package:crypto/crypto.dart" as crypto; | 7 import "package:crypto/crypto.dart" as crypto; |
8 import "../common/common.dart" as common_external; | 8 import "../common/common.dart" as common_external; |
9 import "package:http/http.dart" as http; | 9 import "package:http/http.dart" as http; |
10 | 10 |
11 const String USER_AGENT_STRING = | 11 const String USER_AGENT_STRING = |
12 'google-api-dart-client googleapis_beta/0.1.1'; | 12 'google-api-dart-client googleapis_beta/0.2.0'; |
13 | 13 |
14 const CONTENT_TYPE_JSON_UTF8 = 'application/json; charset=utf-8'; | 14 const CONTENT_TYPE_JSON_UTF8 = 'application/json; charset=utf-8'; |
15 | 15 |
16 /** | 16 /** |
17 * Base class for all API clients, offering generic methods for | 17 * Base class for all API clients, offering generic methods for |
18 * HTTP Requests to the API | 18 * HTTP Requests to the API |
19 */ | 19 */ |
20 class ApiRequester { | 20 class ApiRequester { |
21 final http.Client _httpClient; | 21 final http.Client _httpClient; |
22 final String _rootUrl; | 22 final String _rootUrl; |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
145 if (downloadAsMedia) { | 145 if (downloadAsMedia) { |
146 queryParams['alt'] = const ['media']; | 146 queryParams['alt'] = const ['media']; |
147 } else if (downloadOptions != null) { | 147 } else if (downloadOptions != null) { |
148 queryParams['alt'] = const ['json']; | 148 queryParams['alt'] = const ['json']; |
149 } | 149 } |
150 | 150 |
151 var path; | 151 var path; |
152 if (requestUrl.startsWith('/')) { | 152 if (requestUrl.startsWith('/')) { |
153 path ="$_rootUrl${requestUrl.substring(1)}"; | 153 path ="$_rootUrl${requestUrl.substring(1)}"; |
154 } else { | 154 } else { |
155 path ="$_rootUrl${_basePath.substring(1)}$requestUrl"; | 155 path ="$_rootUrl${_basePath}$requestUrl"; |
156 } | 156 } |
157 | 157 |
158 bool containsQueryParameter = path.contains('?'); | 158 bool containsQueryParameter = path.contains('?'); |
159 addQueryParameter(String name, String value) { | 159 addQueryParameter(String name, String value) { |
160 name = Escaper.escapeQueryComponent(name); | 160 name = Escaper.escapeQueryComponent(name); |
161 value = Escaper.escapeQueryComponent(value); | 161 value = Escaper.escapeQueryComponent(value); |
162 if (containsQueryParameter) { | 162 if (containsQueryParameter) { |
163 path = '$path&$name=$value'; | 163 path = '$path&$name=$value'; |
164 } else { | 164 } else { |
165 path = '$path?$name=$value'; | 165 path = '$path?$name=$value'; |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
423 | 423 |
424 var completer = new Completer<http.StreamedResponse>(); | 424 var completer = new Completer<http.StreamedResponse>(); |
425 bool completed = false; | 425 bool completed = false; |
426 | 426 |
427 var chunkStack = new ChunkStack(_options.chunkSize); | 427 var chunkStack = new ChunkStack(_options.chunkSize); |
428 subscription = _uploadMedia.stream.listen((List<int> bytes) { | 428 subscription = _uploadMedia.stream.listen((List<int> bytes) { |
429 chunkStack.addBytes(bytes); | 429 chunkStack.addBytes(bytes); |
430 | 430 |
431 // Upload all but the last chunk. | 431 // Upload all but the last chunk. |
432 // The final send will be done in the [onDone] handler. | 432 // The final send will be done in the [onDone] handler. |
433 if (chunkStack.length > 1) { | 433 bool hasPartialChunk = chunkStack.hasPartialChunk; |
| 434 if (chunkStack.length > 1 || |
| 435 (chunkStack.length == 1 && hasPartialChunk)) { |
434 // Pause the input stream. | 436 // Pause the input stream. |
435 subscription.pause(); | 437 subscription.pause(); |
436 | 438 |
437 // Upload all chunks except the last one. | 439 // Upload all chunks except the last one. |
438 var fullChunks = chunkStack.removeSublist(0, chunkStack.length - 1); | 440 var fullChunks; |
| 441 if (hasPartialChunk) { |
| 442 fullChunks = chunkStack.removeSublist(0, chunkStack.length); |
| 443 } else { |
| 444 fullChunks = chunkStack.removeSublist(0, chunkStack.length - 1); |
| 445 } |
439 Future.forEach(fullChunks, | 446 Future.forEach(fullChunks, |
440 (c) => _uploadChunkDrained(uploadUri, c)).then((_) { | 447 (c) => _uploadChunkDrained(uploadUri, c)).then((_) { |
441 // All chunks uploaded, we can continue consuming data. | 448 // All chunks uploaded, we can continue consuming data. |
442 subscription.resume(); | 449 subscription.resume(); |
443 }).catchError((error, stack) { | 450 }).catchError((error, stack) { |
444 subscription.cancel(); | 451 subscription.cancel(); |
445 completed = true; | 452 completed = true; |
446 completer.completeError(error, stack); | 453 completer.completeError(error, stack); |
447 }); | 454 }); |
448 } | 455 } |
449 }, onError: (error, stack) { | 456 }, onError: (error, stack) { |
450 subscription.cancel(); | 457 subscription.cancel(); |
451 if (!completed) { | 458 if (!completed) { |
452 completed = true; | 459 completed = true; |
453 completer.completeError(error, stack); | 460 completer.completeError(error, stack); |
454 } | 461 } |
455 }, onDone: () { | 462 }, onDone: () { |
456 if (!completed) { | 463 if (!completed) { |
457 chunkStack.finalize(); | 464 chunkStack.finalize(); |
458 | 465 |
459 var lastChunk; | 466 var lastChunk; |
460 if (chunkStack.totalByteLength > 0) { | 467 if (chunkStack.length == 1) { |
461 assert(chunkStack.length == 1); | |
462 lastChunk = chunkStack.removeSublist(0, chunkStack.length).first; | 468 lastChunk = chunkStack.removeSublist(0, chunkStack.length).first; |
463 } else { | 469 } else { |
464 lastChunk = new ResumableChunk([], 0, 0); | 470 completer.completeError(new StateError( |
| 471 'Resumable uploads need to result in at least one non-empty ' |
| 472 'chunk at the end.')); |
| 473 return; |
465 } | 474 } |
466 var end = lastChunk.endOfChunk; | 475 var end = lastChunk.endOfChunk; |
467 | 476 |
468 // Validate that we have the correct number of bytes if length was | 477 // Validate that we have the correct number of bytes if length was |
469 // specified. | 478 // specified. |
470 if (_uploadMedia.length != null) { | 479 if (_uploadMedia.length != null) { |
471 if (end < _uploadMedia.length) { | 480 if (end < _uploadMedia.length) { |
472 completer.completeError(new common_external.ApiRequestError( | 481 completer.completeError(new common_external.ApiRequestError( |
473 'Received less bytes than indicated by [Media.length].')); | 482 'Received less bytes than indicated by [Media.length].')); |
474 return; | 483 return; |
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
647 /** | 656 /** |
648 * Represents a stack of [ResumableChunk]s. | 657 * Represents a stack of [ResumableChunk]s. |
649 */ | 658 */ |
650 class ChunkStack { | 659 class ChunkStack { |
651 final int _chunkSize; | 660 final int _chunkSize; |
652 final List<ResumableChunk> _chunkStack = []; | 661 final List<ResumableChunk> _chunkStack = []; |
653 | 662 |
654 // Currently accumulated data. | 663 // Currently accumulated data. |
655 List<List<int>> _byteArrays = []; | 664 List<List<int>> _byteArrays = []; |
656 int _length = 0; | 665 int _length = 0; |
657 int _totalLength = 0; | |
658 int _offset = 0; | 666 int _offset = 0; |
659 | 667 |
660 bool _finalized = false; | 668 bool _finalized = false; |
661 | 669 |
662 ChunkStack(this._chunkSize); | 670 ChunkStack(this._chunkSize); |
663 | 671 |
| 672 /** |
| 673 * Whether data for a not-yet-finished [ResumableChunk] is present. A call to |
| 674 * `finalize` will create a [ResumableChunk] of this data. |
| 675 */ |
| 676 bool get hasPartialChunk => _length > 0; |
| 677 |
| 678 /** |
| 679 * The number of chunks in this [ChunkStack]. |
| 680 */ |
664 int get length => _chunkStack.length; | 681 int get length => _chunkStack.length; |
665 | 682 |
666 int get totalByteLength => _offset; | 683 /** |
| 684 * The total number of bytes which have been converted to [ResumableChunk]s. |
| 685 * Can only be called once this [ChunkStack] has been finalized. |
| 686 */ |
| 687 int get totalByteLength { |
| 688 if (!_finalized) { |
| 689 throw new StateError('ChunkStack has not been finalized yet.'); |
| 690 } |
| 691 |
| 692 return _offset; |
| 693 } |
667 | 694 |
668 /** | 695 /** |
669 * Returns the chunks [from] ... [to] and deletes it from the stack. | 696 * Returns the chunks [from] ... [to] and deletes it from the stack. |
670 */ | 697 */ |
671 List<ResumableChunk> removeSublist(int from, int to) { | 698 List<ResumableChunk> removeSublist(int from, int to) { |
672 var sublist = _chunkStack.sublist(from, to); | 699 var sublist = _chunkStack.sublist(from, to); |
673 _chunkStack.removeRange(from, to); | 700 _chunkStack.removeRange(from, to); |
674 return sublist; | 701 return sublist; |
675 } | 702 } |
676 | 703 |
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
885 assert(key != null); | 912 assert(key != null); |
886 if(convert == null) { | 913 if(convert == null) { |
887 result[key] = value; | 914 result[key] = value; |
888 } else { | 915 } else { |
889 result[key] = convert(value); | 916 result[key] = convert(value); |
890 } | 917 } |
891 }); | 918 }); |
892 return result; | 919 return result; |
893 } | 920 } |
894 | 921 |
OLD | NEW |