Path: blob/master/src/applications/files/uploadsource/PhabricatorFileUploadSource.php
12242 views
<?php12abstract class PhabricatorFileUploadSource3extends Phobject {45private $name;6private $relativeTTL;7private $viewPolicy;8private $mimeType;9private $authorPHID;1011private $rope;12private $data;13private $shouldChunk;14private $didRewind;15private $totalBytesWritten = 0;16private $totalBytesRead = 0;17private $byteLimit = 0;1819public function setName($name) {20$this->name = $name;21return $this;22}2324public function getName() {25return $this->name;26}2728public function setRelativeTTL($relative_ttl) {29$this->relativeTTL = $relative_ttl;30return $this;31}3233public function getRelativeTTL() {34return $this->relativeTTL;35}3637public function setViewPolicy($view_policy) {38$this->viewPolicy = $view_policy;39return $this;40}4142public function getViewPolicy() {43return $this->viewPolicy;44}4546public function setByteLimit($byte_limit) {47$this->byteLimit = $byte_limit;48return $this;49}5051public function getByteLimit() {52return $this->byteLimit;53}5455public function setMIMEType($mime_type) {56$this->mimeType = $mime_type;57return $this;58}5960public function getMIMEType() {61return $this->mimeType;62}6364public function setAuthorPHID($author_phid) {65$this->authorPHID = $author_phid;66return $this;67}6869public function getAuthorPHID() {70return $this->authorPHID;71}7273public function uploadFile() {74if (!$this->shouldChunkFile()) {75return $this->writeSingleFile();76} else {77return $this->writeChunkedFile();78}79}8081private function getDataIterator() {82if (!$this->data) {83$this->data = $this->newDataIterator();84}85return $this->data;86}8788private function getRope() {89if (!$this->rope) {90$this->rope = new PhutilRope();91}92return $this->rope;93}9495abstract protected function newDataIterator();96abstract protected function getDataLength();9798private function readFileData() {99$data = $this->getDataIterator();100101if (!$this->didRewind) {102$data->rewind();103$this->didRewind = true;104} else {105if ($data->valid()) {106$data->next();107}108}109110if (!$data->valid()) {111return false;112}113114$read_bytes = $data->current();115$this->totalBytesRead += strlen($read_bytes);116117if ($this->byteLimit && ($this->totalBytesRead > $this->byteLimit)) {118throw new PhabricatorFileUploadSourceByteLimitException();119}120121$rope = $this->getRope();122$rope->append($read_bytes);123124return true;125}126127private function shouldChunkFile() {128if ($this->shouldChunk !== null) {129return $this->shouldChunk;130}131132$threshold = PhabricatorFileStorageEngine::getChunkThreshold();133134if ($threshold === null) {135// If there are no chunk engines available, we clearly can't chunk the136// file.137$this->shouldChunk = false;138} else {139// If we don't know how large the file is, we're going to read some data140// from it until we know whether it's a small file or not. This will give141// us enough information to make a decision about chunking.142$length = $this->getDataLength();143if ($length === null) {144$rope = $this->getRope();145while ($this->readFileData()) {146$length = $rope->getByteLength();147if ($length > $threshold) {148break;149}150}151}152153$this->shouldChunk = ($length > $threshold);154}155156return $this->shouldChunk;157}158159private function writeSingleFile() {160while ($this->readFileData()) {161// Read the entire file.162}163164$rope = $this->getRope();165$data = $rope->getAsString();166167$parameters = $this->getNewFileParameters();168169return PhabricatorFile::newFromFileData($data, $parameters);170}171172private function writeChunkedFile() {173$engine = $this->getChunkEngine();174175$parameters = $this->getNewFileParameters();176177$data_length = $this->getDataLength();178if ($data_length !== null) {179$length = $data_length;180} else {181$length = 0;182}183184$file = PhabricatorFile::newChunkedFile($engine, $length, $parameters);185$file->saveAndIndex();186187$rope = $this->getRope();188189// Read the source, writing chunks as we get enough data.190while ($this->readFileData()) {191while (true) {192$rope_length = $rope->getByteLength();193if ($rope_length < $engine->getChunkSize()) {194break;195}196$this->writeChunk($file, $engine);197}198}199200// If we have extra bytes at the end, write them. Note that it's possible201// that we have more than one chunk of bytes left if the read was very202// fast.203while ($rope->getByteLength()) {204$this->writeChunk($file, $engine);205}206207$file->setIsPartial(0);208if ($data_length === null) {209$file->setByteSize($this->getTotalBytesWritten());210}211$file->save();212213return $file;214}215216private function writeChunk(217PhabricatorFile $file,218PhabricatorFileStorageEngine $engine) {219220$offset = $this->getTotalBytesWritten();221$max_length = $engine->getChunkSize();222$rope = $this->getRope();223224$data = $rope->getPrefixBytes($max_length);225$actual_length = strlen($data);226$rope->removeBytesFromHead($actual_length);227228$params = array(229'name' => $file->getMonogram().'.chunk-'.$offset,230'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,231'chunk' => true,232);233234// If this isn't the initial chunk, provide a dummy MIME type so we do not235// try to detect it. See T12857.236if ($offset > 0) {237$params['mime-type'] = 'application/octet-stream';238}239240$chunk_data = PhabricatorFile::newFromFileData($data, $params);241242$chunk = PhabricatorFileChunk::initializeNewChunk(243$file->getStorageHandle(),244$offset,245$offset + $actual_length);246247$chunk248->setDataFilePHID($chunk_data->getPHID())249->save();250251$this->setTotalBytesWritten($offset + $actual_length);252253return $chunk;254}255256private function getNewFileParameters() {257$parameters = array(258'name' => $this->getName(),259'viewPolicy' => $this->getViewPolicy(),260);261262$ttl = $this->getRelativeTTL();263if ($ttl !== null) {264$parameters['ttl.relative'] = $ttl;265}266267$mime_type = $this->getMimeType();268if ($mime_type !== null) {269$parameters['mime-type'] = $mime_type;270}271272$author_phid = $this->getAuthorPHID();273if ($author_phid !== null) {274$parameters['authorPHID'] = $author_phid;275}276277return $parameters;278}279280private function getChunkEngine() {281$chunk_engines = PhabricatorFileStorageEngine::loadWritableChunkEngines();282if (!$chunk_engines) {283throw new Exception(284pht(285'Unable to upload file: this server is not configured with any '.286'storage engine which can store large files.'));287}288289return head($chunk_engines);290}291292private function setTotalBytesWritten($total_bytes_written) {293$this->totalBytesWritten = $total_bytes_written;294return $this;295}296297private function getTotalBytesWritten() {298return $this->totalBytesWritten;299}300301}302303304