3 abstract class PhabricatorFileUploadSource
16 private $totalBytesWritten = 0;
17 private $totalBytesRead = 0;
18 private $byteLimit = 0;
20 public function setName($name) {
25 public function getName() {
29 public function setRelativeTTL($relative_ttl) {
30 $this->relativeTTL
= $relative_ttl;
34 public function getRelativeTTL() {
35 return $this->relativeTTL
;
38 public function setViewPolicy($view_policy) {
39 $this->viewPolicy
= $view_policy;
43 public function getViewPolicy() {
44 return $this->viewPolicy
;
47 public function setByteLimit($byte_limit) {
48 $this->byteLimit
= $byte_limit;
52 public function getByteLimit() {
53 return $this->byteLimit
;
56 public function setMIMEType($mime_type) {
57 $this->mimeType
= $mime_type;
61 public function getMIMEType() {
62 return $this->mimeType
;
65 public function setAuthorPHID($author_phid) {
66 $this->authorPHID
= $author_phid;
70 public function getAuthorPHID() {
71 return $this->authorPHID
;
74 public function uploadFile() {
75 if (!$this->shouldChunkFile()) {
76 return $this->writeSingleFile();
78 return $this->writeChunkedFile();
82 private function getDataIterator() {
84 $this->data
= $this->newDataIterator();
89 private function getRope() {
91 $this->rope
= new PhutilRope();
96 abstract protected function newDataIterator();
97 abstract protected function getDataLength();
99 private function readFileData() {
100 $data = $this->getDataIterator();
102 if (!$this->didRewind
) {
104 $this->didRewind
= true;
106 if ($data->valid()) {
111 if (!$data->valid()) {
115 $read_bytes = $data->current();
116 $this->totalBytesRead +
= strlen($read_bytes);
118 if ($this->byteLimit
&& ($this->totalBytesRead
> $this->byteLimit
)) {
119 throw new PhabricatorFileUploadSourceByteLimitException();
122 $rope = $this->getRope();
123 $rope->append($read_bytes);
128 private function shouldChunkFile() {
129 if ($this->shouldChunk
!== null) {
130 return $this->shouldChunk
;
133 $threshold = PhabricatorFileStorageEngine
::getChunkThreshold();
135 if ($threshold === null) {
136 // If there are no chunk engines available, we clearly can't chunk the
138 $this->shouldChunk
= false;
140 // If we don't know how large the file is, we're going to read some data
141 // from it until we know whether it's a small file or not. This will give
142 // us enough information to make a decision about chunking.
143 $length = $this->getDataLength();
144 if ($length === null) {
145 $rope = $this->getRope();
146 while ($this->readFileData()) {
147 $length = $rope->getByteLength();
148 if ($length > $threshold) {
154 $this->shouldChunk
= ($length > $threshold);
157 return $this->shouldChunk
;
160 private function writeSingleFile() {
161 while ($this->readFileData()) {
162 // Read the entire file.
165 $rope = $this->getRope();
166 $data = $rope->getAsString();
168 $parameters = $this->getNewFileParameters();
170 return PhabricatorFile
::newFromFileData($data, $parameters);
173 private function writeChunkedFile() {
174 $engine = $this->getChunkEngine();
176 $parameters = $this->getNewFileParameters();
178 $data_length = $this->getDataLength();
179 if ($data_length !== null) {
180 $length = $data_length;
185 $file = PhabricatorFile
::newChunkedFile($engine, $length, $parameters);
186 $file->saveAndIndex();
188 $rope = $this->getRope();
190 // Read the source, writing chunks as we get enough data.
191 while ($this->readFileData()) {
193 $rope_length = $rope->getByteLength();
194 if ($rope_length < $engine->getChunkSize()) {
197 $this->writeChunk($file, $engine);
201 // If we have extra bytes at the end, write them. Note that it's possible
202 // that we have more than one chunk of bytes left if the read was very
204 while ($rope->getByteLength()) {
205 $this->writeChunk($file, $engine);
208 $file->setIsPartial(0);
209 if ($data_length === null) {
210 $file->setByteSize($this->getTotalBytesWritten());
217 private function writeChunk(
218 PhabricatorFile
$file,
219 PhabricatorFileStorageEngine
$engine) {
221 $offset = $this->getTotalBytesWritten();
222 $max_length = $engine->getChunkSize();
223 $rope = $this->getRope();
225 $data = $rope->getPrefixBytes($max_length);
226 $actual_length = strlen($data);
227 $rope->removeBytesFromHead($actual_length);
230 'name' => $file->getMonogram().'.chunk-'.$offset,
231 'viewPolicy' => PhabricatorPolicies
::POLICY_NOONE
,
235 // If this isn't the initial chunk, provide a dummy MIME type so we do not
236 // try to detect it. See T12857.
238 $params['mime-type'] = 'application/octet-stream';
241 $chunk_data = PhabricatorFile
::newFromFileData($data, $params);
243 $chunk = PhabricatorFileChunk
::initializeNewChunk(
244 $file->getStorageHandle(),
246 $offset +
$actual_length);
249 ->setDataFilePHID($chunk_data->getPHID())
252 $this->setTotalBytesWritten($offset +
$actual_length);
257 private function getNewFileParameters() {
259 'name' => $this->getName(),
260 'viewPolicy' => $this->getViewPolicy(),
263 $ttl = $this->getRelativeTTL();
265 $parameters['ttl.relative'] = $ttl;
268 $mime_type = $this->getMimeType();
269 if ($mime_type !== null) {
270 $parameters['mime-type'] = $mime_type;
273 $author_phid = $this->getAuthorPHID();
274 if ($author_phid !== null) {
275 $parameters['authorPHID'] = $author_phid;
281 private function getChunkEngine() {
282 $chunk_engines = PhabricatorFileStorageEngine
::loadWritableChunkEngines();
283 if (!$chunk_engines) {
286 'Unable to upload file: this server is not configured with any '.
287 'storage engine which can store large files.'));
290 return head($chunk_engines);
293 private function setTotalBytesWritten($total_bytes_written) {
294 $this->totalBytesWritten
= $total_bytes_written;
298 private function getTotalBytesWritten() {
299 return $this->totalBytesWritten
;