RangeDownload.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. <?php
  2. namespace Qcloud\Cos;
  3. use Exception;
  4. use Qcloud\Cos\Exception\CosException;
  5. use GuzzleHttp\Pool;
  6. class RangeDownload {
  7. const DEFAULT_PART_SIZE = 52428800;
  8. private $client;
  9. private $options;
  10. private $partSize;
  11. private $parts;
  12. private $progress;
  13. private $totolSize;
  14. private $resumableJson;
  15. public function __construct( $client, $contentLength, $saveAs, $options = array() ) {
  16. $this->client = $client;
  17. $this->options = $options;
  18. $this->partSize = isset( $options['PartSize'] ) ? $options['PartSize'] : self::DEFAULT_PART_SIZE;
  19. $this->concurrency = isset( $options['Concurrency'] ) ? $options['Concurrency'] : 10;
  20. $this->progress = isset( $options['Progress'] ) ? $options['Progress'] : function( $totolSize, $downloadedSize ) {
  21. }
  22. ;
  23. $this->parts = [];
  24. $this->partNumberList = [];
  25. $this->downloadedSize = 0;
  26. $this->totolSize = $contentLength;
  27. $this->saveAs = $saveAs;
  28. $this->resumableJson = [];
  29. $this->resumableJson = isset( $options['ResumableJson'] ) ? $options['ResumableJson'] : [];
  30. unset( $options['ResumableJson'] );
  31. $this->resumableTaskFile = isset( $options['ResumableTaskFile'] ) ? $options['ResumableTaskFile'] : $saveAs . '.cosresumabletask';
  32. $this->resumableDownload = isset( $options['ResumableDownload'] ) ? $options['ResumableDownload'] : false;
  33. }
  34. public function performdownloading() {
  35. if ( $this->resumableDownload ) {
  36. try {
  37. if ( file_exists( $this->resumableTaskFile ) ) {
  38. $origin_content = file_get_contents( $this->resumableTaskFile );
  39. $this->resumableJsonLocal = json_decode( $origin_content, true );
  40. if ( $this->resumableJsonLocal == null ) {
  41. $this->resumableJsonLocal = [];
  42. } else if ( $this->resumableJsonLocal['LastModified'] != $this->resumableJson['LastModified'] ||
  43. $this->resumableJsonLocal['ContentLength'] != $this->resumableJson['ContentLength'] ||
  44. $this->resumableJsonLocal['ETag'] != $this->resumableJson['ETag'] ||
  45. $this->resumableJsonLocal['Crc64ecma'] != $this->resumableJson['Crc64ecma'] ) {
  46. $this->resumableDownload = false;
  47. }
  48. }
  49. } catch ( \Exception $e ) {
  50. $this->resumableDownload = false;
  51. }
  52. }
  53. try {
  54. if ($this->resumableDownload) {
  55. $this->fp = fopen( $this->saveAs, 'r+' );
  56. } else {
  57. $this->fp = fopen( $this->saveAs, 'wb' );
  58. }
  59. $rt = $this->donwloadParts();
  60. $this->resumableJson['DownloadedBlocks'] = [];
  61. if (file_exists( $this->resumableTaskFile )) {
  62. unlink($this->resumableTaskFile);
  63. }
  64. } catch ( \Exception $e ) {
  65. $this->fp_resume = fopen( $this->resumableTaskFile, 'wb' );
  66. fwrite( $this->fp_resume, json_encode( $this->resumableJson ) );
  67. fclose( $this->fp_resume );
  68. throw ( $e );
  69. }
  70. finally {
  71. fclose( $this->fp );
  72. }
  73. return $rt;
  74. }
  75. public function donwloadParts() {
  76. $uploadRequests = function () {
  77. $index = 1;
  78. $partSize = 0;
  79. for ( $offset = 0; $offset < $this->totolSize; ) {
  80. $partSize = $this->partSize;
  81. if ( $offset + $this->partSize >= $this->totolSize ) {
  82. $partSize = $this->totolSize - $offset;
  83. }
  84. $this->parts[$index]['PartSize'] = $partSize;
  85. $this->parts[$index]['Offset'] = $offset;
  86. $begin = $offset;
  87. $end = $offset + $partSize - 1;
  88. if ( !( $this->resumableDownload &&
  89. isset( $this->resumableJsonLocal['DownloadedBlocks'] ) &&
  90. in_array( ['from' => $begin, 'to' => $end], $this->resumableJsonLocal['DownloadedBlocks'] ) ) ) {
  91. $params = array(
  92. 'Bucket' => $this->options['Bucket'],
  93. 'Key' => $this->options['Key'],
  94. 'Range' => sprintf( 'bytes=%d-%d', $begin, $end )
  95. );
  96. $command = $this->client->getCommand( 'getObject', $params );
  97. $request = $this->client->commandToRequestTransformer( $command );
  98. $index += 1;
  99. yield $request;
  100. } else {
  101. $this->resumableJson['DownloadedBlocks'][] = ['from' => $begin, 'to' => $end];
  102. $this->downloadedSize += $partSize;
  103. call_user_func_array( $this->progress, [$this->totolSize, $this->downloadedSize] );
  104. }
  105. $offset += $partSize;
  106. }
  107. }
  108. ;
  109. $pool = new Pool( $this->client->httpClient, $uploadRequests(), [
  110. 'concurrency' => $this->concurrency,
  111. 'fulfilled' => function ( $response, $index ) {
  112. $index = $index + 1;
  113. $stream = $response->getBody();
  114. $offset = $this->parts[$index]['Offset'];
  115. $partsize = 8192;
  116. $begin = $offset;
  117. fseek( $this->fp, $offset );
  118. while ( !$stream->eof() ) {
  119. $output = $stream->read( $partsize );
  120. $writeLen = fwrite( $this->fp, $output );
  121. $offset += $writeLen;
  122. }
  123. $end = $offset - 1;
  124. $this->resumableJson['DownloadedBlocks'][] = ['from' => $begin, 'to' => $end];
  125. $partSize = $this->parts[$index]['PartSize'];
  126. $this->downloadedSize += $partSize;
  127. call_user_func_array( $this->progress, [$this->totolSize, $this->downloadedSize] );
  128. }
  129. ,
  130. 'rejected' => function ( $reason, $index ) {
  131. throw( $reason );
  132. }
  133. ] );
  134. $promise = $pool->promise();
  135. $promise->wait();
  136. }
  137. }