Changeset View
Changeset View
Standalone View
Standalone View
src/applications/files/storage/PhabricatorFile.php
Show All 20 Lines | final class PhabricatorFile extends PhabricatorFileDAO | ||||
implements | implements | ||||
PhabricatorApplicationTransactionInterface, | PhabricatorApplicationTransactionInterface, | ||||
PhabricatorTokenReceiverInterface, | PhabricatorTokenReceiverInterface, | ||||
PhabricatorSubscribableInterface, | PhabricatorSubscribableInterface, | ||||
PhabricatorFlaggableInterface, | PhabricatorFlaggableInterface, | ||||
PhabricatorPolicyInterface, | PhabricatorPolicyInterface, | ||||
PhabricatorDestructibleInterface { | PhabricatorDestructibleInterface { | ||||
const STORAGE_FORMAT_RAW = 'raw'; | |||||
const METADATA_IMAGE_WIDTH = 'width'; | const METADATA_IMAGE_WIDTH = 'width'; | ||||
const METADATA_IMAGE_HEIGHT = 'height'; | const METADATA_IMAGE_HEIGHT = 'height'; | ||||
const METADATA_CAN_CDN = 'canCDN'; | const METADATA_CAN_CDN = 'canCDN'; | ||||
const METADATA_BUILTIN = 'builtin'; | const METADATA_BUILTIN = 'builtin'; | ||||
const METADATA_PARTIAL = 'partial'; | const METADATA_PARTIAL = 'partial'; | ||||
const METADATA_PROFILE = 'profile'; | const METADATA_PROFILE = 'profile'; | ||||
const METADATA_STORAGE = 'storage'; | |||||
protected $name; | protected $name; | ||||
protected $mimeType; | protected $mimeType; | ||||
protected $byteSize; | protected $byteSize; | ||||
protected $authorPHID; | protected $authorPHID; | ||||
protected $secretKey; | protected $secretKey; | ||||
protected $contentHash; | protected $contentHash; | ||||
protected $metadata = array(); | protected $metadata = array(); | ||||
▲ Show 20 Lines • Show All 183 Lines • ▼ Show 20 Lines | final class PhabricatorFile extends PhabricatorFileDAO | ||||
public static function newFileFromContentHash($hash, array $params) { | public static function newFileFromContentHash($hash, array $params) { | ||||
// Check to see if a file with same contentHash exist | // Check to see if a file with same contentHash exist | ||||
$file = id(new PhabricatorFile())->loadOneWhere( | $file = id(new PhabricatorFile())->loadOneWhere( | ||||
'contentHash = %s LIMIT 1', | 'contentHash = %s LIMIT 1', | ||||
$hash); | $hash); | ||||
if ($file) { | if ($file) { | ||||
// copy storageEngine, storageHandle, storageFormat | |||||
$copy_of_storage_engine = $file->getStorageEngine(); | $copy_of_storage_engine = $file->getStorageEngine(); | ||||
$copy_of_storage_handle = $file->getStorageHandle(); | $copy_of_storage_handle = $file->getStorageHandle(); | ||||
$copy_of_storage_format = $file->getStorageFormat(); | $copy_of_storage_format = $file->getStorageFormat(); | ||||
$copy_of_storage_properties = $file->getStorageProperties(); | |||||
$copy_of_byte_size = $file->getByteSize(); | $copy_of_byte_size = $file->getByteSize(); | ||||
$copy_of_mime_type = $file->getMimeType(); | $copy_of_mime_type = $file->getMimeType(); | ||||
$new_file = self::initializeNewFile(); | $new_file = self::initializeNewFile(); | ||||
$new_file->setByteSize($copy_of_byte_size); | $new_file->setByteSize($copy_of_byte_size); | ||||
$new_file->setContentHash($hash); | $new_file->setContentHash($hash); | ||||
$new_file->setStorageEngine($copy_of_storage_engine); | $new_file->setStorageEngine($copy_of_storage_engine); | ||||
$new_file->setStorageHandle($copy_of_storage_handle); | $new_file->setStorageHandle($copy_of_storage_handle); | ||||
$new_file->setStorageFormat($copy_of_storage_format); | $new_file->setStorageFormat($copy_of_storage_format); | ||||
$new_file->setStorageProperties($copy_of_storage_properties); | |||||
$new_file->setMimeType($copy_of_mime_type); | $new_file->setMimeType($copy_of_mime_type); | ||||
$new_file->copyDimensions($file); | $new_file->copyDimensions($file); | ||||
$new_file->readPropertiesFromParameters($params); | $new_file->readPropertiesFromParameters($params); | ||||
$new_file->save(); | $new_file->save(); | ||||
return $new_file; | return $new_file; | ||||
Show All 26 Lines | if ($chunked_hash) { | ||||
$seed = Filesystem::readRandomBytes(64); | $seed = Filesystem::readRandomBytes(64); | ||||
$hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput( | $hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput( | ||||
$seed); | $seed); | ||||
$file->setContentHash($hash); | $file->setContentHash($hash); | ||||
} | } | ||||
$file->setStorageEngine($engine->getEngineIdentifier()); | $file->setStorageEngine($engine->getEngineIdentifier()); | ||||
$file->setStorageHandle(PhabricatorFileChunk::newChunkHandle()); | $file->setStorageHandle(PhabricatorFileChunk::newChunkHandle()); | ||||
$file->setStorageFormat(self::STORAGE_FORMAT_RAW); | |||||
// Chunked files are always stored raw because they do not actually store | |||||
// data. The chunks do, and can be individually formatted. | |||||
$file->setStorageFormat(PhabricatorFileRawStorageFormat::FORMATKEY); | |||||
$file->setIsPartial(1); | $file->setIsPartial(1); | ||||
$file->readPropertiesFromParameters($params); | $file->readPropertiesFromParameters($params); | ||||
return $file; | return $file; | ||||
} | } | ||||
private static function buildFromFileData($data, array $params = array()) { | private static function buildFromFileData($data, array $params = array()) { | ||||
Show All 15 Lines | private static function buildFromFileData($data, array $params = array()) { | ||||
assert_instances_of($engines, 'PhabricatorFileStorageEngine'); | assert_instances_of($engines, 'PhabricatorFileStorageEngine'); | ||||
if (!$engines) { | if (!$engines) { | ||||
throw new Exception(pht('No valid storage engines are available!')); | throw new Exception(pht('No valid storage engines are available!')); | ||||
} | } | ||||
$file = self::initializeNewFile(); | $file = self::initializeNewFile(); | ||||
$default_key = PhabricatorFileRawStorageFormat::FORMATKEY; | |||||
$format_key = idx($params, 'format', $default_key); | |||||
$format = id(clone PhabricatorFileStorageFormat::requireFormat($format_key)) | |||||
->setFile($file); | |||||
$properties = $format->newStorageProperties(); | |||||
$file->setStorageFormat($format->getStorageFormatKey()); | |||||
$file->setStorageProperties($properties); | |||||
$data_handle = null; | $data_handle = null; | ||||
$engine_identifier = null; | $engine_identifier = null; | ||||
$exceptions = array(); | $exceptions = array(); | ||||
foreach ($engines as $engine) { | foreach ($engines as $engine) { | ||||
$engine_class = get_class($engine); | $engine_class = get_class($engine); | ||||
try { | try { | ||||
list($engine_identifier, $data_handle) = $file->writeToEngine( | list($engine_identifier, $data_handle) = $file->writeToEngine( | ||||
$engine, | $engine, | ||||
Show All 23 Lines | private static function buildFromFileData($data, array $params = array()) { | ||||
} | } | ||||
$file->setByteSize(strlen($data)); | $file->setByteSize(strlen($data)); | ||||
$file->setContentHash(self::hashFileContent($data)); | $file->setContentHash(self::hashFileContent($data)); | ||||
$file->setStorageEngine($engine_identifier); | $file->setStorageEngine($engine_identifier); | ||||
$file->setStorageHandle($data_handle); | $file->setStorageHandle($data_handle); | ||||
// TODO: This is probably YAGNI, but allows for us to do encryption or | |||||
// compression later if we want. | |||||
$file->setStorageFormat(self::STORAGE_FORMAT_RAW); | |||||
$file->readPropertiesFromParameters($params); | $file->readPropertiesFromParameters($params); | ||||
if (!$file->getMimeType()) { | if (!$file->getMimeType()) { | ||||
$tmp = new TempFile(); | $tmp = new TempFile(); | ||||
Filesystem::writeFile($tmp, $data); | Filesystem::writeFile($tmp, $data); | ||||
$file->setMimeType(Filesystem::getMimeType($tmp)); | $file->setMimeType(Filesystem::getMimeType($tmp)); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | final class PhabricatorFile extends PhabricatorFileDAO | ||||
private function writeToEngine( | private function writeToEngine( | ||||
PhabricatorFileStorageEngine $engine, | PhabricatorFileStorageEngine $engine, | ||||
$data, | $data, | ||||
array $params) { | array $params) { | ||||
$engine_class = get_class($engine); | $engine_class = get_class($engine); | ||||
$data_handle = $engine->writeFile($data, $params); | $key = $this->getStorageFormat(); | ||||
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key)) | |||||
->setFile($this); | |||||
$data_iterator = array($data); | |||||
$formatted_iterator = $format->newWriteIterator($data_iterator); | |||||
$formatted_data = $this->loadDataFromIterator($formatted_iterator); | |||||
$data_handle = $engine->writeFile($formatted_data, $params); | |||||
if (!$data_handle || strlen($data_handle) > 255) { | if (!$data_handle || strlen($data_handle) > 255) { | ||||
// This indicates an improperly implemented storage engine. | // This indicates an improperly implemented storage engine. | ||||
throw new PhabricatorFileStorageConfigurationException( | throw new PhabricatorFileStorageConfigurationException( | ||||
pht( | pht( | ||||
"Storage engine '%s' executed %s but did not return a valid ". | "Storage engine '%s' executed %s but did not return a valid ". | ||||
"handle ('%s') to the data: it must be nonempty and no longer ". | "handle ('%s') to the data: it must be nonempty and no longer ". | ||||
"than 255 characters.", | "than 255 characters.", | ||||
▲ Show 20 Lines • Show All 212 Lines • ▼ Show 20 Lines | final class PhabricatorFile extends PhabricatorFileDAO | ||||
} | } | ||||
public static function hashFileContent($data) { | public static function hashFileContent($data) { | ||||
return sha1($data); | return sha1($data); | ||||
} | } | ||||
public function loadFileData() { | public function loadFileData() { | ||||
$iterator = $this->getFileDataIterator(); | |||||
$engine = $this->instantiateStorageEngine(); | return $this->loadDataFromIterator($iterator); | ||||
$data = $engine->readFile($this->getStorageHandle()); | |||||
switch ($this->getStorageFormat()) { | |||||
case self::STORAGE_FORMAT_RAW: | |||||
$data = $data; | |||||
break; | |||||
default: | |||||
throw new Exception(pht('Unknown storage format.')); | |||||
} | |||||
return $data; | |||||
} | } | ||||
/** | /** | ||||
* Return an iterable which emits file content bytes. | * Return an iterable which emits file content bytes. | ||||
* | * | ||||
* @param int Offset for the start of data. | * @param int Offset for the start of data. | ||||
* @param int Offset for the end of data. | * @param int Offset for the end of data. | ||||
* @return Iterable Iterable object which emits requested data. | * @return Iterable Iterable object which emits requested data. | ||||
*/ | */ | ||||
public function getFileDataIterator($begin = null, $end = null) { | public function getFileDataIterator($begin = null, $end = null) { | ||||
$engine = $this->instantiateStorageEngine(); | $engine = $this->instantiateStorageEngine(); | ||||
return $engine->getFileDataIterator($this, $begin, $end); | $raw_iterator = $engine->getRawFileDataIterator($this, $begin, $end); | ||||
$key = $this->getStorageFormat(); | |||||
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key)) | |||||
->setFile($this); | |||||
return $format->newReadIterator($raw_iterator); | |||||
} | } | ||||
public function getViewURI() { | public function getViewURI() { | ||||
if (!$this->getPHID()) { | if (!$this->getPHID()) { | ||||
throw new Exception( | throw new Exception( | ||||
pht('You must save a file before you can generate a view URI.')); | pht('You must save a file before you can generate a view URI.')); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 212 Lines • ▼ Show 20 Lines | final class PhabricatorFile extends PhabricatorFileDAO | ||||
public function validateSecretKey($key) { | public function validateSecretKey($key) { | ||||
return ($key == $this->getSecretKey()); | return ($key == $this->getSecretKey()); | ||||
} | } | ||||
public function generateSecretKey() { | public function generateSecretKey() { | ||||
return Filesystem::readRandomCharacters(20); | return Filesystem::readRandomCharacters(20); | ||||
} | } | ||||
public function setStorageProperties(array $properties) { | |||||
$this->metadata[self::METADATA_STORAGE] = $properties; | |||||
return $this; | |||||
} | |||||
public function getStorageProperties() { | |||||
return idx($this->metadata, self::METADATA_STORAGE, array()); | |||||
} | |||||
public function getStorageProperty($key, $default = null) { | |||||
$properties = $this->getStorageProperties(); | |||||
return idx($properties, $key, $default); | |||||
} | |||||
public function loadDataFromIterator($iterator) { | |||||
$result = ''; | |||||
foreach ($iterator as $chunk) { | |||||
$result .= $chunk; | |||||
} | |||||
return $result; | |||||
} | |||||
public function updateDimensions($save = true) { | public function updateDimensions($save = true) { | ||||
if (!$this->isViewableImage()) { | if (!$this->isViewableImage()) { | ||||
throw new Exception(pht('This file is not a viewable image.')); | throw new Exception(pht('This file is not a viewable image.')); | ||||
} | } | ||||
if (!function_exists('imagecreatefromstring')) { | if (!function_exists('imagecreatefromstring')) { | ||||
throw new Exception(pht('Cannot retrieve image information.')); | throw new Exception(pht('Cannot retrieve image information.')); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 432 Lines • Show Last 20 Lines |