@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Stream chunks when sending chunked files

Summary: Ref T7149. Return a real iterator from the Chunk engine, which processes chunks sequentially.

Test Plan:
This is a bit hard to read, but shows the underlying chunks being accessed one at a time and only some being accessed when requesting a range of a file:

```
$ ./bin/files cat F878 --trace --begin 100 --end 256
...
>>> [10] <query> SELECT * FROM `file_storageblob` WHERE `id` = 85
<<< [10] <query> 240 us
better software.

Phabricat>>> [11] <query> SELECT * FROM `file_storageblob` WHERE `id` = 84
<<< [11] <query> 205 us
or includes applications for:

>>> [12] <query> SELECT * FROM `file_storageblob` WHERE `id` = 83
<<< [12] <query> 226 us
- reviewing and auditing source>>> [13] <query> SELECT * FROM `file_storageblob` WHERE `id` = 82
<<< [13] <query> 203 us
code;
- hosting and browsing >>> [14] <query> SELECT * FROM `file_storageblob` WHERE `id` = 81
<<< [14] <query> 231 us
repositories;
- tracking bugs;
```

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: joshuaspence, epriestley

Maniphest Tasks: T7149

Differential Revision: https://secure.phabricator.com/D12073

+105 -13
+5
src/__phutil_library_map__.php
··· 1795 1795 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', 1796 1796 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', 1797 1797 'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php', 1798 + 'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php', 1798 1799 'PhabricatorFileChunkQuery' => 'applications/files/query/PhabricatorFileChunkQuery.php', 1799 1800 'PhabricatorFileCommentController' => 'applications/files/controller/PhabricatorFileCommentController.php', 1800 1801 'PhabricatorFileComposeController' => 'applications/files/controller/PhabricatorFileComposeController.php', ··· 5095 5096 'PhabricatorFileDAO', 5096 5097 'PhabricatorPolicyInterface', 5097 5098 'PhabricatorDestructibleInterface', 5099 + ), 5100 + 'PhabricatorFileChunkIterator' => array( 5101 + 'Phobject', 5102 + 'Iterator', 5098 5103 ), 5099 5104 'PhabricatorFileChunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5100 5105 'PhabricatorFileCommentController' => 'PhabricatorFileController',
+11
src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php
··· 177 177 return 32; 178 178 } 179 179 180 + public function getFileDataIterator(PhabricatorFile $file, $begin, $end) { 181 + $chunks = id(new PhabricatorFileChunkQuery()) 182 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 183 + ->withChunkHandles(array($file->getStorageHandle())) 184 + ->withByteRange($begin, $end) 185 + ->needDataFiles(true) 186 + ->execute(); 187 + 188 + return new PhabricatorFileChunkIterator($chunks, $begin, $end); 189 + } 190 + 180 191 }
+72
src/applications/files/engine/PhabricatorFileChunkIterator.php
··· 1 + <?php 2 + 3 + final class PhabricatorFileChunkIterator 4 + extends Phobject 5 + implements Iterator { 6 + 7 + private $chunks; 8 + private $cursor; 9 + private $begin; 10 + private $end; 11 + private $data; 12 + 13 + public function __construct(array $chunks, $begin = null, $end = null) { 14 + $chunks = msort($chunks, 'getByteStart'); 15 + $this->chunks = $chunks; 16 + 17 + if ($begin !== null) { 18 + foreach ($chunks as $key => $chunk) { 19 + if ($chunk->getByteEnd() >= $begin) { 20 + unset($chunks[$key]); 21 + } 22 + break; 23 + } 24 + $this->begin = $begin; 25 + } 26 + 27 + if ($end !== null) { 28 + foreach ($chunks as $key => $chunk) { 29 + if ($chunk->getByteStart() <= $end) { 30 + unset($chunks[$key]); 31 + } 32 + } 33 + $this->end = $end; 34 + } 35 + } 36 + 37 + public function current() { 38 + $chunk = head($this->chunks); 39 + $data = $chunk->getDataFile()->loadFileData(); 40 + 41 + if ($this->end !== null) { 42 + if ($chunk->getByteEnd() > $this->end) { 43 + $data = substr($data, 0, ($this->end - $chunk->getByteStart())); 44 + } 45 + } 46 + 47 + if ($this->begin !== null) { 48 + if ($chunk->getByteStart() < $this->begin) { 49 + $data = substr($data, ($this->begin - $chunk->getByteStart())); 50 + } 51 + } 52 + 53 + return $data; 54 + } 55 + 56 + public function key() { 57 + return head_key($this->chunks); 58 + } 59 + 60 + public function next() { 61 + unset($this->chunks[$this->key()]); 62 + } 63 + 64 + public function rewind() { 65 + return; 66 + } 67 + 68 + public function valid() { 69 + return (count($this->chunks) > 0); 70 + } 71 + 72 + }
+15
src/applications/files/engine/PhabricatorFileStorageEngine.php
··· 290 290 return $engine->getChunkSize(); 291 291 } 292 292 293 + public function getFileDataIterator(PhabricatorFile $file, $begin, $end) { 294 + // The default implementation is trivial and just loads the entire file 295 + // upfront. 296 + $data = $file->loadFileData(); 297 + 298 + if ($begin !== null && $end !== null) { 299 + $data = substr($data, $begin, ($end - $begin)); 300 + } else if ($begin !== null) { 301 + $data = substr($data, $begin); 302 + } else if ($end !== null) { 303 + $data = substr($data, 0, $end); 304 + } 305 + 306 + return array($data); 307 + } 293 308 294 309 }
+2 -13
src/applications/files/storage/PhabricatorFile.php
··· 612 612 * @return Iterable Iterable object which emits requested data. 613 613 */ 614 614 public function getFileDataIterator($begin = null, $end = null) { 615 - // The default implementation is trivial and just loads the entire file 616 - // upfront. 617 - $data = $this->loadFileData(); 618 - 619 - if ($begin !== null && $end !== null) { 620 - $data = substr($data, $begin, ($end - $begin)); 621 - } else if ($begin !== null) { 622 - $data = substr($data, $begin); 623 - } else if ($end !== null) { 624 - $data = substr($data, 0, $end); 625 - } 626 - 627 - return array($data); 615 + $engine = $this->instantiateStorageEngine(); 616 + return $engine->getFileDataIterator($this, $begin, $end); 628 617 } 629 618 630 619