Remove product literal strings in "pht()", part 5
[phabricator.git] / src / applications / files / engine / PhabricatorChunkedFileStorageEngine.php
blob30de63b9045a89a19629eb0ebf6aad8daf4e8b71
1 <?php
3 final class PhabricatorChunkedFileStorageEngine
4 extends PhabricatorFileStorageEngine {
6 public function getEngineIdentifier() {
7 return 'chunks';
10 public function getEnginePriority() {
11 return 60000;
14 /**
15 * We can write chunks if we have at least one valid storage engine
16 * underneath us.
18 public function canWriteFiles() {
19 return (bool)$this->getWritableEngine();
22 public function hasFilesizeLimit() {
23 return false;
26 public function isChunkEngine() {
27 return true;
30 public function writeFile($data, array $params) {
31 // The chunk engine does not support direct writes.
32 throw new PhutilMethodNotImplementedException();
35 public function readFile($handle) {
36 // This is inefficient, but makes the API work as expected.
37 $chunks = $this->loadAllChunks($handle, true);
39 $buffer = '';
40 foreach ($chunks as $chunk) {
41 $data_file = $chunk->getDataFile();
42 if (!$data_file) {
43 throw new Exception(pht('This file data is incomplete!'));
46 $buffer .= $chunk->getDataFile()->loadFileData();
49 return $buffer;
52 public function deleteFile($handle) {
53 $engine = new PhabricatorDestructionEngine();
54 $chunks = $this->loadAllChunks($handle, true);
55 foreach ($chunks as $chunk) {
56 $engine->destroyObject($chunk);
60 private function loadAllChunks($handle, $need_files) {
61 $chunks = id(new PhabricatorFileChunkQuery())
62 ->setViewer(PhabricatorUser::getOmnipotentUser())
63 ->withChunkHandles(array($handle))
64 ->needDataFiles($need_files)
65 ->execute();
67 $chunks = msort($chunks, 'getByteStart');
69 return $chunks;
72 /**
73 * Compute a chunked file hash for the viewer.
75 * We can not currently compute a real hash for chunked file uploads (because
76 * no process sees all of the file data).
78 * We also can not trust the hash that the user claims to have computed. If
79 * we trust the user, they can upload some `evil.exe` and claim it has the
80 * same file hash as `good.exe`. When another user later uploads the real
81 * `good.exe`, we'll just create a reference to the existing `evil.exe`. Users
82 * who download `good.exe` will then receive `evil.exe`.
84 * Instead, we rehash the user's claimed hash with account secrets. This
85 * allows users to resume file uploads, but not collide with other users.
87 * Ideally, we'd like to be able to verify hashes, but this is complicated
88 * and time consuming and gives us a fairly small benefit.
90 * @param PhabricatorUser Viewing user.
91 * @param string Claimed file hash.
92 * @return string Rehashed file hash.
94 public static function getChunkedHash(PhabricatorUser $viewer, $hash) {
95 if (!$viewer->getPHID()) {
96 throw new Exception(
97 pht('Unable to compute chunked hash without real viewer!'));
100 $input = $viewer->getAccountSecret().':'.$hash.':'.$viewer->getPHID();
101 return self::getChunkedHashForInput($input);
104 public static function getChunkedHashForInput($input) {
105 $rehash = PhabricatorHash::weakDigest($input);
107 // Add a suffix to identify this as a chunk hash.
108 $rehash = substr($rehash, 0, -2).'-C';
110 return $rehash;
113 public function allocateChunks($length, array $properties) {
114 $file = PhabricatorFile::newChunkedFile($this, $length, $properties);
116 $chunk_size = $this->getChunkSize();
118 $handle = $file->getStorageHandle();
120 $chunks = array();
121 for ($ii = 0; $ii < $length; $ii += $chunk_size) {
122 $chunks[] = PhabricatorFileChunk::initializeNewChunk(
123 $handle,
124 $ii,
125 min($ii + $chunk_size, $length));
128 $file->openTransaction();
129 foreach ($chunks as $chunk) {
130 $chunk->save();
132 $file->saveAndIndex();
133 $file->saveTransaction();
135 return $file;
139 * Find a storage engine which is suitable for storing chunks.
141 * This engine must be a writable engine, have a filesize limit larger than
142 * the chunk limit, and must not be a chunk engine itself.
144 private function getWritableEngine() {
145 // NOTE: We can't just load writable engines or we'll loop forever.
146 $engines = parent::loadAllEngines();
148 foreach ($engines as $engine) {
149 if ($engine->isChunkEngine()) {
150 continue;
153 if ($engine->isTestEngine()) {
154 continue;
157 if (!$engine->canWriteFiles()) {
158 continue;
161 if ($engine->hasFilesizeLimit()) {
162 if ($engine->getFilesizeLimit() < $this->getChunkSize()) {
163 continue;
167 return true;
170 return false;
173 public function getChunkSize() {
174 return (4 * 1024 * 1024);
177 public function getRawFileDataIterator(
178 PhabricatorFile $file,
179 $begin,
180 $end,
181 PhabricatorFileStorageFormat $format) {
183 // NOTE: It is currently impossible for files stored with the chunk
184 // engine to have their own formatting (instead, the individual chunks
185 // are formatted), so we ignore the format object.
187 $chunks = id(new PhabricatorFileChunkQuery())
188 ->setViewer(PhabricatorUser::getOmnipotentUser())
189 ->withChunkHandles(array($file->getStorageHandle()))
190 ->withByteRange($begin, $end)
191 ->needDataFiles(true)
192 ->execute();
194 return new PhabricatorFileChunkIterator($chunks, $begin, $end);