Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/nuance/command/NuanceCommandImplementation.php b/src/applications/nuance/command/NuanceCommandImplementation.php
index 46220266ff..2b47e40654 100644
--- a/src/applications/nuance/command/NuanceCommandImplementation.php
+++ b/src/applications/nuance/command/NuanceCommandImplementation.php
@@ -1,107 +1,113 @@
<?php
abstract class NuanceCommandImplementation
extends Phobject {
private $actor;
private $transactionQueue = array();
final public function setActor(PhabricatorUser $actor) {
$this->actor = $actor;
return $this;
}
final public function getActor() {
return $this->actor;
}
abstract public function getCommandName();
abstract public function canApplyToItem(NuanceItem $item);
+ public function canApplyImmediately(
+ NuanceItem $item,
+ NuanceItemCommand $command) {
+ return false;
+ }
+
abstract protected function executeCommand(
NuanceItem $item,
NuanceItemCommand $command);
final public function applyCommand(
NuanceItem $item,
NuanceItemCommand $command) {
$command_key = $command->getCommand();
$implementation_key = $this->getCommandKey();
if ($command_key !== $implementation_key) {
throw new Exception(
pht(
'This command implementation("%s") can not apply a command of a '.
'different type ("%s").',
$implementation_key,
$command_key));
}
if (!$this->canApplyToItem($item)) {
throw new Exception(
pht(
'This command implementation ("%s") can not be applied to an '.
'item of type "%s".',
$implementation_key,
$item->getItemType()));
}
$this->transactionQueue = array();
$command_type = NuanceItemCommandTransaction::TRANSACTIONTYPE;
$command_xaction = $this->newTransaction($command_type);
$result = $this->executeCommand($item, $command);
$xactions = $this->transactionQueue;
$this->transactionQueue = array();
$command_xaction->setNewValue(
array(
'command' => $command->getCommand(),
'parameters' => $command->getParameters(),
'result' => $result,
));
// TODO: Maybe preserve the actor's original content source?
$source = PhabricatorContentSource::newForSource(
PhabricatorDaemonContentSource::SOURCECONST);
$actor = $this->getActor();
id(new NuanceItemEditor())
->setActor($actor)
->setActingAsPHID($command->getAuthorPHID())
->setContentSource($source)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->applyTransactions($item, $xactions);
}
final public function getCommandKey() {
return $this->getPhobjectClassConstant('COMMANDKEY');
}
final public static function getAllCommands() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getCommandKey')
->execute();
}
protected function newTransaction($type) {
$xaction = id(new NuanceItemTransaction())
->setTransactionType($type);
$this->transactionQueue[] = $xaction;
return $xaction;
}
protected function newStatusTransaction($status) {
return $this->newTransaction(NuanceItemStatusTransaction::TRANSACTIONTYPE)
->setNewValue($status);
}
}
diff --git a/src/applications/nuance/command/NuanceTrashCommand.php b/src/applications/nuance/command/NuanceTrashCommand.php
index 1f8a729277..b5ac851539 100644
--- a/src/applications/nuance/command/NuanceTrashCommand.php
+++ b/src/applications/nuance/command/NuanceTrashCommand.php
@@ -1,23 +1,29 @@
<?php
final class NuanceTrashCommand
extends NuanceCommandImplementation {
const COMMANDKEY = 'trash';
public function getCommandName() {
return pht('Throw in Trash');
}
public function canApplyToItem(NuanceItem $item) {
$type = $item->getImplementation();
return ($type instanceof NuanceFormItemType);
}
+ public function canApplyImmediately(
+ NuanceItem $item,
+ NuanceItemCommand $command) {
+ return true;
+ }
+
protected function executeCommand(
NuanceItem $item,
NuanceItemCommand $command) {
$this->newStatusTransaction(NuanceItem::STATUS_CLOSED);
}
}
diff --git a/src/applications/nuance/controller/NuanceItemActionController.php b/src/applications/nuance/controller/NuanceItemActionController.php
index c6dc139b11..39ae8fd995 100644
--- a/src/applications/nuance/controller/NuanceItemActionController.php
+++ b/src/applications/nuance/controller/NuanceItemActionController.php
@@ -1,90 +1,121 @@
<?php
final class NuanceItemActionController extends NuanceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
if (!$request->validateCSRF()) {
return new Aphront400Response();
}
// NOTE: This controller can be reached from an individual item (usually
// by a user) or while working through a queue (usually by staff). When
// a command originates from a queue, the URI will have a queue ID.
$item = id(new NuanceItemQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$item) {
return new Aphront404Response();
}
$cancel_uri = $item->getURI();
$queue_id = $request->getURIData('queueID');
$queue = null;
if ($queue_id) {
$queue = id(new NuanceQueueQuery())
->setViewer($viewer)
->withIDs(array($queue_id))
->executeOne();
if (!$queue) {
return new Aphront404Response();
}
$item_queue = $item->getQueue();
if (!$item_queue || ($item_queue->getPHID() != $queue->getPHID())) {
return $this->newDialog()
->setTitle(pht('Wrong Queue'))
->appendParagraph(
pht(
'You are trying to act on this item from the wrong queue: it '.
'is currently in a different queue.'))
->addCancelButton($cancel_uri);
}
}
$action = $request->getURIData('action');
$impl = $item->getImplementation();
$impl->setViewer($viewer);
$impl->setController($this);
+ $executors = NuanceCommandImplementation::getAllCommands();
+ $executor = idx($executors, $action);
+ if (!$executor) {
+ return new Aphront404Response();
+ }
+
+ $executor = id(clone $executor)
+ ->setActor($viewer);
+
+ if (!$executor->canApplyToItem($item)) {
+ return $this->newDialog()
+ ->setTitle(pht('Command Not Supported'))
+ ->appendParagraph(
+ pht(
+ 'This item does not support the specified command ("%s").',
+ $action))
+ ->addCancelButton($cancel_uri);
+ }
+
$command = NuanceItemCommand::initializeNewCommand()
->setItemPHID($item->getPHID())
->setAuthorPHID($viewer->getPHID())
->setCommand($action);
if ($queue) {
$command->setQueuePHID($queue->getPHID());
}
$command->save();
- // TODO: Here, we should check if the command should be tried immediately,
- // and just defer it to the daemons if not. If we're going to try to apply
- // the command directly, we should first acquire the worker lock. If we
- // can not, we should defer the command even if it's an immediate command.
- // For the moment, skip all this stuff by deferring unconditionally.
+ // If this command can be applied immediately, try to apply it now.
- $should_defer = true;
- if ($should_defer) {
+ // In most cases, local commands (like closing an item) can be applied
+ // immediately.
+
+ // Commands that require making a call to a remote system (for example,
+ // to reply to a tweet or close a remote object) are usually done in the
+ // background so the user doesn't have to wait for the operation to
+ // complete before they can continue work.
+
+ $did_apply = false;
+ $immediate = $executor->canApplyImmediately($item, $command);
+ if ($immediate) {
+ // TODO: Move this stuff to a new Engine, and have the controller and
+ // worker both call into the Engine.
+ $worker = new NuanceItemUpdateWorker(array());
+ $did_apply = $worker->executeCommands($item, array($command));
+ }
+
+ // If this can't be applied immediately or we were unable to get a lock
+ // fast enough, do the update in the background instead.
+ if (!$did_apply) {
$item->scheduleUpdate();
- } else {
- // ...
}
if ($queue) {
$done_uri = $queue->getWorkURI();
} else {
$done_uri = $item->getURI();
}
return id(new AphrontRedirectResponse())
->setURI($done_uri);
}
}
diff --git a/src/applications/nuance/worker/NuanceItemUpdateWorker.php b/src/applications/nuance/worker/NuanceItemUpdateWorker.php
index 30d5621872..cd6637187c 100644
--- a/src/applications/nuance/worker/NuanceItemUpdateWorker.php
+++ b/src/applications/nuance/worker/NuanceItemUpdateWorker.php
@@ -1,108 +1,161 @@
<?php
final class NuanceItemUpdateWorker
extends NuanceWorker {
protected function doWork() {
$item_phid = $this->getTaskDataValue('itemPHID');
- $hash = PhabricatorHash::digestForIndex($item_phid);
- $lock_key = "nuance.item.{$hash}";
- $lock = PhabricatorGlobalLock::newLock($lock_key);
+ $lock = $this->newLock($item_phid);
$lock->lock(1);
try {
$item = $this->loadItem($item_phid);
$this->updateItem($item);
$this->routeItem($item);
$this->applyCommands($item);
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
}
$lock->unlock();
}
private function updateItem(NuanceItem $item) {
$impl = $item->getImplementation();
if (!$impl->canUpdateItems()) {
return null;
}
$viewer = $this->getViewer();
$impl->setViewer($viewer);
$impl->updateItem($item);
}
private function routeItem(NuanceItem $item) {
$status = $item->getStatus();
if ($status != NuanceItem::STATUS_ROUTING) {
return;
}
$source = $item->getSource();
// For now, always route items into the source's default queue.
$item
->setQueuePHID($source->getDefaultQueuePHID())
->setStatus(NuanceItem::STATUS_OPEN)
->save();
}
private function applyCommands(NuanceItem $item) {
$viewer = $this->getViewer();
- $impl = $item->getImplementation();
- $impl->setViewer($viewer);
-
$commands = id(new NuanceItemCommandQuery())
->setViewer($viewer)
->withItemPHIDs(array($item->getPHID()))
->withStatuses(
array(
NuanceItemCommand::STATUS_ISSUED,
))
->execute();
$commands = msort($commands, 'getID');
+ $this->executeCommandList($item, $commands);
+ }
+
+ public function executeCommands(NuanceItem $item, array $commands) {
+ if (!$commands) {
+ return true;
+ }
+
+ $item_phid = $item->getPHID();
+ $viewer = $this->getViewer();
+
+ $lock = $this->newLock($item_phid);
+ try {
+ $lock->lock(1);
+ } catch (PhutilLockException $ex) {
+ return false;
+ }
+
+ try {
+ $item = $this->loadItem($item_phid);
+
+ // Reload commands now that we have a lock, to make sure we don't
+ // execute any commands twice by mistake.
+ $commands = id(new NuanceItemCommandQuery())
+ ->setViewer($viewer)
+ ->withIDs(mpull($commands, 'getID'))
+ ->execute();
+
+ $this->executeCommandList($item, $commands);
+ } catch (Exception $ex) {
+ $lock->unlock();
+ throw $ex;
+ }
+
+ $lock->unlock();
+
+ return true;
+ }
+
+ private function executeCommandList(NuanceItem $item, array $commands) {
+ $viewer = $this->getViewer();
+
$executors = NuanceCommandImplementation::getAllCommands();
foreach ($commands as $command) {
+ if ($command->getItemPHID() !== $item->getPHID()) {
+ throw new Exception(
+ pht('Trying to apply a command to the wrong item!'));
+ }
+
+ if ($command->getStatus() !== NuanceItemCommand::STATUS_ISSUED) {
+ // Never execute commands which have already been issued.
+ continue;
+ }
+
$command
->setStatus(NuanceItemCommand::STATUS_EXECUTING)
->save();
try {
$command_key = $command->getCommand();
$executor = idx($executors, $command_key);
if (!$executor) {
throw new Exception(
pht(
'Unable to execute command "%s": this command does not have '.
'a recognized command implementation.',
$command_key));
}
$executor = clone $executor;
$executor
->setActor($viewer)
->applyCommand($item, $command);
$command
->setStatus(NuanceItemCommand::STATUS_DONE)
->save();
} catch (Exception $ex) {
$command
->setStatus(NuanceItemCommand::STATUS_FAILED)
->save();
throw $ex;
}
}
}
+ private function newLock($item_phid) {
+ $hash = PhabricatorHash::digestForIndex($item_phid);
+ $lock_key = "nuance.item.{$hash}";
+ return PhabricatorGlobalLock::newLock($lock_key);
+ }
+
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Mar 14, 6:52 AM (3 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
71649
Default Alt Text
(13 KB)

Event Timeline