diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 616239eeb..9df072d88 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -1,78 +1,79 @@ <?php final class DrydockManagementLeaseWorkflow extends DrydockManagementWorkflow { public function didConstruct() { $this ->setName('lease') ->setSynopsis('Lease a resource.') ->setArguments( array( array( 'name' => 'type', 'param' => 'resource_type', 'help' => 'Resource type.', ), array( 'name' => 'attributes', 'param' => 'name=value,...', 'help' => 'Resource specficiation.', ), )); } public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $resource_type = $args->getArg('type'); if (!$resource_type) { throw new PhutilArgumentUsageException( "Specify a resource type with `--type`."); } $attributes = $args->getArg('attributes'); if ($attributes) { $options = new PhutilSimpleOptions(); + $options->setCaseSensitive(true); $attributes = $options->parse($attributes); } $lease = new DrydockLease(); $lease->setResourceType($resource_type); if ($attributes) { $lease->setAttributes($attributes); } $lease->queueForActivation(); $root = dirname(phutil_get_library_root('phabricator')); $wait = new ExecFuture( 'php -f %s wait-for-lease --id %s', $root.'/scripts/drydock/drydock_control.php', $lease->getID()); $cursor = 0; foreach (Futures(array($wait))->setUpdateInterval(1) as $key => $future) { if ($future) { $future->resolvex(); break; } $logs = id(new DrydockLogQuery()) ->withLeaseIDs(array($lease->getID())) ->withAfterID($cursor) ->setOrder(DrydockLogQuery::ORDER_ID) ->execute(); if ($logs) { foreach ($logs as $log) { $console->writeErr("%s\n", $log->getMessage()); } $cursor = max(mpull($logs, 'getID')); } } $console->writeOut("Acquired Lease %s\n", $lease->getID()); return 0; } } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index de0ebf9bf..fd657f631 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -1,161 +1,161 @@ <?php final class DrydockLease extends DrydockDAO { protected $resourceID; protected $resourceType; protected $until; protected $ownerPHID; protected $attributes = array(); protected $status = DrydockLeaseStatus::STATUS_PENDING; protected $taskID; private $resource; public function getLeaseName() { return pht('Lease %d', $this->getID()); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'attributes' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function setAttribute($key, $value) { - $this->attributes[strtolower($key)] = $value; + $this->attributes[$key] = $value; return $this; } public function getAttribute($key, $default = null) { - return idx($this->attributes, strtolower($key), $default); + return idx($this->attributes, $key, $default); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPHIDConstants::PHID_TYPE_DRYL); } public function getInterface($type) { return $this->getResource()->getInterface($this, $type); } public function getResource() { $this->assertActive(); if ($this->resource === null) { throw new Exception("Resource is not yet loaded."); } return $this->resource; } public function attachResource(DrydockResource $resource) { $this->assertActive(); $this->resource = $resource; return $this; } public function loadResource() { $this->assertActive(); return id(new DrydockResource())->loadOneWhere( 'id = %d', $this->getResourceID()); } public function queueForActivation() { if ($this->getID()) { throw new Exception( "Only new leases may be queued for activation!"); } $this->setStatus(DrydockLeaseStatus::STATUS_PENDING); $this->save(); // NOTE: Prevent a race where some eager worker quickly grabs the task // before we can save the Task ID. $this->openTransaction(); $this->beginReadLocking(); $this->reload(); $task = PhabricatorWorker::scheduleTask( 'DrydockAllocatorWorker', $this->getID()); $this->setTaskID($task->getID()); $this->save(); $this->endReadLocking(); $this->saveTransaction(); return $this; } public function release() { $this->setStatus(DrydockLeaseStatus::STATUS_RELEASED); $this->save(); $this->resource = null; return $this; } private function assertActive() { if ($this->status != DrydockLeaseStatus::STATUS_ACTIVE) { throw new Exception( "Lease is not active! You can not interact with resources through ". "an inactive lease."); } } public static function waitForLeases(array $leases) { assert_instances_of($leases, 'DrydockLease'); $task_ids = array_filter(mpull($leases, 'getTaskID')); PhabricatorWorker::waitForTasks($task_ids); $unresolved = $leases; while (true) { foreach ($unresolved as $key => $lease) { $lease->reload(); switch ($lease->getStatus()) { case DrydockLeaseStatus::STATUS_ACTIVE: unset($unresolved[$key]); break; case DrydockLeaseStatus::STATUS_RELEASED: throw new Exception("Lease has already been released!"); case DrydockLeaseStatus::STATUS_EXPIRED: throw new Exception("Lease has already expired!"); case DrydockLeaseStatus::STATUS_BROKEN: throw new Exception("Lease has been broken!"); case DrydockLeaseStatus::STATUS_PENDING: break; } } if ($unresolved) { sleep(1); } else { break; } } foreach ($leases as $lease) { $lease->attachResource($lease->loadResource()); } } public function waitUntilActive() { if (!$this->getID()) { $this->queueForActivation(); } self::waitForLeases(array($this)); return $this; } } diff --git a/src/infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php b/src/infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php index 6dd3d772e..883e41554 100644 --- a/src/infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php +++ b/src/infrastructure/celerity/__tests__/CelerityResourceTransformerTestCase.php @@ -1,38 +1,39 @@ <?php /** * @group celerity */ final class CelerityResourceTransformerTestCase extends PhabricatorTestCase { public function testTransformation() { $files = dirname(__FILE__).'/transformer/'; foreach (Filesystem::listDirectory($files) as $file) { $name = basename($file); $data = Filesystem::readFile($files.'/'.$file); $parts = preg_split('/^~~~+\n/m', $data); $parts = array_merge($parts, array(null)); list($options, $in, $expect) = $parts; - $options = PhutilSimpleOptions::parse($options) + array( + $parser = new PhutilSimpleOptions(); + $options = $parser->parse($options) + array( 'minify' => false, 'name' => $name, ); $xformer = new CelerityResourceTransformer(); $xformer->setRawResourceMap( array( '/rsrc/example.png' => array( 'uri' => '/res/hash/example.png', ), )); $xformer->setMinify($options['minify']); $result = $xformer->transformResource($options['name'], $in); $this->assertEqual(rtrim($expect), rtrim($result), $file); } } } diff --git a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php index 0f5ba3cbb..7a92eada9 100644 --- a/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php +++ b/src/infrastructure/markup/rule/PhabricatorRemarkupRuleEmbedFile.php @@ -1,165 +1,166 @@ <?php /** * @group markup */ final class PhabricatorRemarkupRuleEmbedFile extends PhutilRemarkupRule { const KEY_RULE_EMBED_FILE = 'rule.embed.file'; public function apply($text) { return preg_replace_callback( "@{F(\d+)([^}]+?)?}@", array($this, 'markupEmbedFile'), $text); } public function markupEmbedFile($matches) { $file = null; if ($matches[1]) { // TODO: This is pretty inefficient if there are a bunch of files. $file = id(new PhabricatorFile())->load($matches[1]); } if (!$file) { return $matches[0]; } $phid = $file->getPHID(); $engine = $this->getEngine(); $token = $engine->storeText(''); $metadata_key = self::KEY_RULE_EMBED_FILE; $metadata = $engine->getTextMetadata($metadata_key, array()); $bundle = array('token' => $token); $options = array( 'size' => 'thumb', 'layout' => 'left', 'float' => false, 'name' => null, ); if (!empty($matches[2])) { $matches[2] = trim($matches[2], ', '); - $options = PhutilSimpleOptions::parse($matches[2]) + $options; + $parser = new PhutilSimpleOptions(); + $options = $parser->parse($matches[2]) + $options; } $file_name = coalesce($options['name'], $file->getName()); $options['name'] = $file_name; $attrs = array(); switch ($options['size']) { case 'full': $attrs['src'] = $file->getBestURI(); $options['image_class'] = null; break; case 'thumb': default: $attrs['src'] = $file->getPreview220URI(); $options['image_class'] = 'phabricator-remarkup-embed-image'; break; } $bundle['attrs'] = $attrs; $bundle['options'] = $options; $bundle['meta'] = array( 'phid' => $file->getPHID(), 'viewable' => $file->isViewableImage(), 'uri' => $file->getBestURI(), 'dUri' => $file->getDownloadURI(), 'name' => $options['name'], ); $metadata[$phid][] = $bundle; $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_EMBED_FILE; $metadata = $engine->getTextMetadata($metadata_key, array()); if (!$metadata) { return; } foreach ($metadata as $phid => $bundles) { foreach ($bundles as $data) { $options = $data['options']; $meta = $data['meta']; if (!$meta['viewable'] || $options['layout'] == 'link') { $link = id(new PhabricatorFileLinkView()) ->setFilePHID($meta['phid']) ->setFileName($meta['name']) ->setFileDownloadURI($meta['dUri']) ->setFileViewURI($meta['uri']) ->setFileViewable($meta['viewable']); $embed = $link->render(); $engine->overwriteStoredText($data['token'], $embed); continue; } require_celerity_resource('lightbox-attachment-css'); $img = phutil_render_tag('img', $data['attrs']); $embed = javelin_render_tag( 'a', array( 'href' => $meta['uri'], 'class' => $options['image_class'], 'sigil' => 'lightboxable', 'mustcapture' => true, 'meta' => $meta, ), $img); $layout_class = null; switch ($options['layout']) { case 'right': case 'center': case 'inline': case 'left': $layout_class = 'phabricator-remarkup-embed-layout-'. $options['layout']; break; default: $layout_class = 'phabricator-remarkup-embed-layout-left'; break; } if ($options['float']) { switch ($options['layout']) { case 'center': case 'inline': break; case 'right': $layout_class .= ' phabricator-remarkup-embed-float-right'; break; case 'left': default: $layout_class .= ' phabricator-remarkup-embed-float-left'; break; } } if ($layout_class) { $embed = phutil_render_tag( 'div', array( 'class' => $layout_class, ), $embed); } $engine->overwriteStoredText($data['token'], $embed); } } $engine->setTextMetadata($metadata_key, array()); } }