diff --git a/resources/sql/patches/20130103.filemetadata.sql b/resources/sql/patches/20130103.filemetadata.sql new file mode 100644 index 0000000000..80cc85d690 --- /dev/null +++ b/resources/sql/patches/20130103.filemetadata.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_file.file + ADD metadata LONGTEXT COLLATE utf8_bin NOT NULL; + +UPDATE {$NAMESPACE}_file.file + SET metadata = '{}' WHERE metadata = ''; diff --git a/scripts/files/manage_files.php b/scripts/files/manage_files.php index 02893a3e0f..f1507f7623 100755 --- a/scripts/files/manage_files.php +++ b/scripts/files/manage_files.php @@ -18,6 +18,7 @@ $workflows = array( new PhabricatorFilesManagementEnginesWorkflow(), new PhabricatorFilesManagementMigrateWorkflow(), new PhutilHelpArgumentWorkflow(), + new PhabricatorFilesManagementMetadataWorkflow(), ); $args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 395914be34..bed56eebcc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -830,6 +830,7 @@ phutil_register_library_map(array( 'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php', 'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php', 'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php', + 'PhabricatorFilesManagementMetadataWorkflow' => 'applications/files/management/PhabricatorFilesManagementMetadataWorkflow.php', 'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php', 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', 'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php', @@ -2177,6 +2178,7 @@ phutil_register_library_map(array( 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileUploadException' => 'Exception', 'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow', + 'PhabricatorFilesManagementMetadataWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementWorkflow' => 'PhutilArgumentWorkflow', 'PhabricatorFlag' => 'PhabricatorFlagDAO', diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 11fe9238d9..293ee7b320 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -125,6 +125,17 @@ final class PhabricatorFileInfoController extends PhabricatorFileController { pht('Handle'), phutil_escape_html($file->getStorageHandle())); + $metadata = $file->getMetadata(); + if (!empty($metadata)) { + $view->addSectionHeader(pht('Metadata')); + + foreach ($metadata as $key => $value) { + $view->addProperty( + PhabricatorFile::getMetadataName($key), + phutil_escape_html($value)); + } + } + if ($file->isViewableInBrowser()) { // TODO: Clean this up after Pholio (dark backgrounds, standardization, diff --git a/src/applications/files/management/PhabricatorFilesManagementMetadataWorkflow.php b/src/applications/files/management/PhabricatorFilesManagementMetadataWorkflow.php new file mode 100644 index 0000000000..deb8a02f78 --- /dev/null +++ b/src/applications/files/management/PhabricatorFilesManagementMetadataWorkflow.php @@ -0,0 +1,131 @@ +setName('metadata') + ->setSynopsis('Update metadata of old files.') + ->setArguments( + array( + array( + 'name' => 'all', + 'help' => 'Update all files.', + ), + array( + 'name' => 'names', + 'wildcard' => true, + 'help' => 'Update the given files.', + ), + array( + 'name' => 'dry-run', + 'help' => 'Show what would be updated.', + ), + ) + ); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + if ($args->getArg('all')) { + if ($args->getArg('names')) { + throw new PhutilArgumentUsageException( + "Specify either a list of files or `--all`, but not both."); + } + $iterator = new LiskMigrationIterator(new PhabricatorFile()); + } else if ($args->getArg('names')) { + $iterator = array(); + + foreach ($args->getArg('names') as $name) { + $name = trim($name); + + $id = preg_replace('/^F/i', '', $name); + if (ctype_digit($id)) { + $file = id(new PhabricatorFile())->loadOneWhere( + 'id = %d', + $id); + if (!$file) { + throw new PhutilArgumentUsageException( + "No file exists with id '{$name}'."); + } + } else { + $file = id(new PhabricatorFile())->loadOneWhere( + 'phid = %d', + $name); + if (!$file) { + throw new PhutilArgumentUsageException( + "No file exists with PHID '{$name}'."); + } + } + $iterator[] = $file; + } + } else { + throw new PhutilArgumentUsageException( + "Either specify a list of files to update, or use `--all` ". + "to update all files."); + } + + $is_dry_run = $args->getArg('dry-run'); + + $failed = array(); + + foreach ($iterator as $file) { + $fid = 'F'.$file->getID(); + + if (!$file->isViewableImage()) { + $console->writeOut( + "%s: Not an image file.\n", + $fid); + continue; + } + + $metadata = $file->getMetadata(); + $image_width = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH); + $image_height = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT); + if ($image_width && $image_height) { + $console->writeOut( + "%s: Already updated\n", + $fid); + continue; + } + + if ($is_dry_run) { + $console->writeOut( + "%s: Would update file (dry run)\n", + $fid); + continue; + } + + $console->writeOut( + "%s: Updating metadata... ", + $fid); + + try { + $file->updateDimensions(); + $console->writeOut("done.\n"); + } catch (Exception $ex) { + $console->writeOut("failed!\n"); + $console->writeErr("%s\n", (string)$ex); + $failed[] = $file; + } + } + + if ($failed) { + $console->writeOut("**Failures!**\n"); + $ids = array(); + foreach ($failed as $file) { + $ids[] = 'F'.$file->getID(); + } + $console->writeOut("%s\n", implode(', ', $ids)); + + return 1; + } else { + $console->writeOut("**Success!**\n"); + return 0; + } + + return 0; + } +} diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 48fdb8c445..56fdbbf38b 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -5,6 +5,9 @@ final class PhabricatorFile extends PhabricatorFileDAO const STORAGE_FORMAT_RAW = 'raw'; + const METADATA_IMAGE_WIDTH = 'width'; + const METADATA_IMAGE_HEIGHT = 'height'; + protected $phid; protected $name; protected $mimeType; @@ -12,6 +15,7 @@ final class PhabricatorFile extends PhabricatorFileDAO protected $authorPHID; protected $secretKey; protected $contentHash; + protected $metadata = array(); protected $storageEngine; protected $storageFormat; @@ -20,6 +24,9 @@ final class PhabricatorFile extends PhabricatorFileDAO public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'metadata' => self::SERIALIZATION_JSON, + ), ) + parent::getConfiguration(); } @@ -196,6 +203,12 @@ final class PhabricatorFile extends PhabricatorFileDAO $file->setMimeType(Filesystem::getMimeType($tmp)); } + try { + $file->updateDimensions(false); + } catch (Exception $ex) { + // Do nothing + } + $file->save(); return $file; @@ -485,6 +498,51 @@ final class PhabricatorFile extends PhabricatorFileDAO return Filesystem::readRandomCharacters(20); } + public function updateDimensions($save = true) { + if (!$this->isViewableImage()) { + throw new Exception( + "This file is not a viewable image."); + } + + if (!function_exists("imagecreatefromstring")) { + throw new Exception( + "Cannot retrieve image information."); + } + + $data = $this->loadFileData(); + + $img = imagecreatefromstring($data); + if ($img === false) { + throw new Exception( + "Error when decoding image."); + } + + $this->metadata[self::METADATA_IMAGE_WIDTH] = imagesx($img); + $this->metadata[self::METADATA_IMAGE_HEIGHT] = imagesy($img); + + if ($save) { + $this->save(); + } + + return $this; + } + + public static function getMetadataName($metadata) { + switch ($metadata) { + case self::METADATA_IMAGE_WIDTH: + $name = pht('Width'); + break; + case self::METADATA_IMAGE_HEIGHT: + $name = pht('Height'); + break; + default: + $name = ucfirst($metadata); + break; + } + + return $name; + } + /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 98f4b9b2cf..8e78e1c462 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1081,6 +1081,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'name' => $this->getPatchPath('20130102.metamtareceivedmailmessageidhash.sql'), ), + '20130103.filemetadata.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130103.filemetadata.sql'), + ), ); }