1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-17 20:32:41 +01:00

Allow Calendar imports to be configured with hourly or daily auto-updates

Summary:
Ref T10747. For URI-based (and, in the future, Google-based) imports, we can automatically refresh them periodically.

(In the general case there's no way to get a push notification for an ICS file, so we just have to do this every-so-often.)

Test Plan:
  - Set an ICS file to update hourly.
  - Used `bin/trigger fire --id ...` to fire it artificially.
  - Saw Calendar update.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16752
This commit is contained in:
epriestley 2016-10-26 11:26:06 -07:00
parent a69ac888b3
commit 2d7f574b9d
13 changed files with 344 additions and 9 deletions

View file

@ -0,0 +1,8 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_import
ADD triggerPHID VARBINARY(64);
ALTER TABLE {$NAMESPACE}_calendar.calendar_import
ADD triggerFrequency VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
UPDATE {$NAMESPACE}_calendar.calendar_import
SET triggerFrequency = 'once' WHERE triggerFrequency = '';

View file

@ -2121,6 +2121,7 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportEpochLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEpochLogType.php',
'PhabricatorCalendarImportFetchLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php',
'PhabricatorCalendarImportFrequencyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php',
'PhabricatorCalendarImportFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php',
'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php',
'PhabricatorCalendarImportICSLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php',
'PhabricatorCalendarImportICSURITransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php',
@ -2139,10 +2140,12 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php',
'PhabricatorCalendarImportReloadController' => 'applications/calendar/controller/PhabricatorCalendarImportReloadController.php',
'PhabricatorCalendarImportReloadTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportReloadTransaction.php',
'PhabricatorCalendarImportReloadWorker' => 'applications/calendar/worker/PhabricatorCalendarImportReloadWorker.php',
'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php',
'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php',
'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php',
'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php',
'PhabricatorCalendarImportTriggerLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportTriggerLogType.php',
'PhabricatorCalendarImportUpdateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php',
'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php',
'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php',
@ -6964,6 +6967,7 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportEpochLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFetchLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFrequencyLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportFrequencyTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportICSLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportICSURITransaction' => 'PhabricatorCalendarImportTransactionType',
@ -6986,10 +6990,12 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarImportReloadController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportReloadTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportReloadWorker' => 'PhabricatorWorker',
'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction',
'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCalendarImportTriggerLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportUpdateLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController',
'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule',

View file

@ -33,7 +33,7 @@ final class PhabricatorCalendarImportDropController
->addCancelButton($cancel_uri, pht('Done'));
}
$engine = new PhabricatorCalendarICSImportEngine();
$engine = new PhabricatorCalendarICSFileImportEngine();
$imports = array();
foreach ($files as $file) {
$import = PhabricatorCalendarImport::initializeNewCalendarImport(

View file

@ -167,6 +167,50 @@ final class PhabricatorCalendarImportViewController
pht('Source Type'),
$engine->getImportEngineTypeName());
if ($import->getIsDisabled()) {
$auto_updates = phutil_tag('em', array(), pht('Import Disabled'));
$has_trigger = false;
} else {
$frequency = $import->getTriggerFrequency();
$frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap();
$frequency_names = ipull($frequency_map, 'name');
$auto_updates = idx($frequency_names, $frequency, $frequency);
if ($frequency == PhabricatorCalendarImport::FREQUENCY_ONCE) {
$has_trigger = false;
$auto_updates = phutil_tag('em', array(), $auto_updates);
} else {
$has_trigger = true;
}
}
$properties->addProperty(
pht('Automatic Updates'),
$auto_updates);
if ($has_trigger) {
$trigger = id(new PhabricatorWorkerTriggerQuery())
->setViewer($viewer)
->withPHIDs(array($import->getTriggerPHID()))
->needEvents(true)
->executeOne();
if (!$trigger) {
$next_trigger = phutil_tag('em', array(), pht('Invalid Trigger'));
} else {
$now = PhabricatorTime::getNow();
$next_epoch = $trigger->getNextEventPrediction();
$next_trigger = pht(
'%s (%s)',
phabricator_datetime($next_epoch, $viewer),
phutil_format_relative_time($next_epoch - $now));
}
$properties->addProperty(
pht('Next Update'),
$next_trigger);
}
$engine->appendImportProperties(
$viewer,
$import,

View file

@ -80,6 +80,9 @@ final class PhabricatorCalendarImportEditEngine
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$engine = $object->getEngine();
$can_trigger = $engine->supportsTriggers($object);
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
@ -89,6 +92,7 @@ final class PhabricatorCalendarImportEditEngine
PhabricatorCalendarImportNameTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Rename the import.'))
->setConduitTypeDescription(pht('New import name.'))
->setPlaceholder($object->getDisplayName())
->setValue($object->getName()),
id(new PhabricatorBoolEditField())
->setKey('disabled')
@ -123,6 +127,22 @@ final class PhabricatorCalendarImportEditEngine
->setValue(false),
);
if ($can_trigger) {
$frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap();
$frequency_options = ipull($frequency_map, 'name');
$fields[] = id(new PhabricatorSelectEditField())
->setKey('frequency')
->setLabel(pht('Update Automatically'))
->setDescription(pht('Configure an automatic update frequncy.'))
->setTransactionType(
PhabricatorCalendarImportFrequencyTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Set the automatic update frequency.'))
->setConduitTypeDescription(pht('Update frequency constant.'))
->setValue($object->getTriggerFrequency())
->setOptions($frequency_options);
}
$import_engine = $object->getEngine();
foreach ($import_engine->newEditEngineFields($this, $object) as $field) {
$fields[] = $field;

View file

@ -27,26 +27,112 @@ final class PhabricatorCalendarImportEditor
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
$type_reload = PhabricatorCalendarImportReloadTransaction::TRANSACTIONTYPE;
$actor = $this->getActor();
// We import events when you create a source, or if you later reload it
// explicitly.
$should_reload = $this->getIsNewObject();
// We adjust the import trigger if you change the import frequency or
// disable the import.
$should_trigger = false;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $type_reload) {
$should_reload = true;
break;
$xaction_type = $xaction->getTransactionType();
switch ($xaction_type) {
case PhabricatorCalendarImportReloadTransaction::TRANSACTIONTYPE:
$should_reload = true;
break;
case PhabricatorCalendarImportFrequencyTransaction::TRANSACTIONTYPE:
$should_trigger = true;
break;
case PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE:
$should_trigger = true;
break;
}
}
if ($should_reload) {
$actor = $this->getActor();
$import_engine = $object->getEngine();
$import_engine->importEventsFromSource($actor, $object);
}
if ($should_trigger) {
$trigger_phid = $object->getTriggerPHID();
if ($trigger_phid) {
$trigger = id(new PhabricatorWorkerTriggerQuery())
->setViewer($actor)
->withPHIDs(array($trigger_phid))
->executeOne();
if ($trigger) {
$engine = new PhabricatorDestructionEngine();
$engine->destroyObject($trigger);
}
}
$frequency = $object->getTriggerFrequency();
$now = PhabricatorTime::getNow();
switch ($frequency) {
case PhabricatorCalendarImport::FREQUENCY_ONCE:
$clock = null;
break;
case PhabricatorCalendarImport::FREQUENCY_HOURLY:
$clock = new PhabricatorMetronomicTriggerClock(
array(
'period' => phutil_units('1 hour in seconds'),
));
break;
case PhabricatorCalendarImport::FREQUENCY_DAILY:
$clock = new PhabricatorDailyRoutineTriggerClock(
array(
'start' => $now,
));
break;
default:
throw new Exception(
pht(
'Unknown import trigger frequency "%s".',
$frequency));
}
// If the object has been disabled, don't write a new trigger.
if ($object->getIsDisabled()) {
$clock = null;
}
if ($clock) {
$trigger_action = new PhabricatorScheduleTaskTriggerAction(
array(
'class' => 'PhabricatorCalendarImportReloadWorker',
'data' => array(
'importPHID' => $object->getPHID(),
),
'options' => array(
'objectPHID' => $object->getPHID(),
'priority' => PhabricatorWorker::PRIORITY_BULK,
),
));
$trigger_phid = PhabricatorPHID::generateNewPHID(
PhabricatorWorkerTriggerPHIDType::TYPECONST);
$object
->setTriggerPHID($trigger_phid)
->save();
$trigger = id(new PhabricatorWorkerTrigger())
->setClock($clock)
->setAction($trigger_action)
->setPHID($trigger_phid)
->save();
} else {
$object
->setTriggerPHID(null)
->save();
}
}
return $xactions;
}

View file

@ -17,6 +17,10 @@ final class PhabricatorCalendarICSFileImportEngine
return pht('Import an event in ".ics" (iCalendar) format.');
}
public function supportsTriggers(PhabricatorCalendarImport $import) {
return false;
}
public function appendImportProperties(
PhabricatorUser $viewer,
PhabricatorCalendarImport $import,

View file

@ -17,6 +17,10 @@ final class PhabricatorCalendarICSURIImportEngine
return pht('Import or subscribe to a calendar in .ics format by URI.');
}
public function supportsTriggers(PhabricatorCalendarImport $import) {
return true;
}
public function appendImportProperties(
PhabricatorUser $viewer,
PhabricatorCalendarImport $import,

View file

@ -39,6 +39,9 @@ abstract class PhabricatorCalendarImportEngine
throw new PhutilMethodNotImplementedException();
}
abstract public function supportsTriggers(
PhabricatorCalendarImport $import);
final public static function getAllImportEngines() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)

View file

@ -0,0 +1,32 @@
<?php
final class PhabricatorCalendarImportTriggerLogType
extends PhabricatorCalendarImportLogType {
const LOGTYPE = 'trigger';
public function getDisplayType(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Import Triggered');
}
public function getDisplayDescription(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return pht('Triggered a periodic update.');
}
public function getDisplayIcon(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'fa-clock-o';
}
public function getDisplayColor(
PhabricatorUser $viewer,
PhabricatorCalendarImportLog $log) {
return 'blue';
}
}

View file

@ -14,6 +14,12 @@ final class PhabricatorCalendarImport
protected $engineType;
protected $parameters = array();
protected $isDisabled = 0;
protected $triggerPHID;
protected $triggerFrequency;
const FREQUENCY_ONCE = 'once';
const FREQUENCY_HOURLY = 'hourly';
const FREQUENCY_DAILY = 'daily';
private $engine = self::ATTACHABLE;
@ -27,7 +33,8 @@ final class PhabricatorCalendarImport
->setEditPolicy($actor->getPHID())
->setIsDisabled(0)
->setEngineType($engine->getImportEngineType())
->attachEngine($engine);
->attachEngine($engine)
->setTriggerFrequency(self::FREQUENCY_ONCE);
}
protected function getConfiguration() {
@ -40,6 +47,8 @@ final class PhabricatorCalendarImport
'name' => 'text',
'engineType' => 'text64',
'isDisabled' => 'bool',
'triggerPHID' => 'phid?',
'triggerFrequency' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_author' => array(
@ -85,6 +94,21 @@ final class PhabricatorCalendarImport
return $this->getEngine()->getDisplayName($this);
}
public static function getTriggerFrequencyMap() {
return array(
self::FREQUENCY_ONCE => array(
'name' => pht('No Automatic Updates'),
),
self::FREQUENCY_HOURLY => array(
'name' => pht('Update Hourly'),
),
self::FREQUENCY_DAILY => array(
'name' => pht('Update Daily'),
),
);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -155,6 +179,17 @@ final class PhabricatorCalendarImport
$this->openTransaction();
$trigger_phid = $this->getTriggerPHID();
if ($trigger_phid) {
$trigger = id(new PhabricatorWorkerTriggerQuery())
->setViewer($viewer)
->withPHIDs(array($trigger_phid))
->executeOne();
if ($trigger) {
$engine->destroyObject($trigger);
}
}
$events = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withImportSourcePHIDs(array($this->getPHID()))

View file

@ -0,0 +1,48 @@
<?php
final class PhabricatorCalendarImportReloadWorker extends PhabricatorWorker {
protected function doWork() {
$import = $this->loadImport();
$viewer = PhabricatorUser::getOmnipotentUser();
if ($import->getIsDisabled()) {
return;
}
$author = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($import->getAuthorPHID()))
->needUserSettings(true)
->executeOne();
$import_engine = $import->getEngine();
$import->newLogMessage(
PhabricatorCalendarImportTriggerLogType::LOGTYPE,
array());
$import_engine->importEventsFromSource($author, $import);
}
private function loadImport() {
$viewer = PhabricatorUser::getOmnipotentUser();
$data = $this->getTaskData();
$import_phid = idx($data, 'importPHID');
$import = id(new PhabricatorCalendarImportQuery())
->setViewer($viewer)
->withPHIDs(array($import_phid))
->executeOne();
if (!$import) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Failed to load import with PHID "%s".',
$import_phid));
}
return $import;
}
}

View file

@ -0,0 +1,45 @@
<?php
final class PhabricatorCalendarImportFrequencyTransaction
extends PhabricatorCalendarImportTransactionType {
const TRANSACTIONTYPE = 'calendar.import.frequency';
public function generateOldValue($object) {
return $object->getTriggerFrequency();
}
public function applyInternalEffects($object, $value) {
$object->setTriggerFrequency($value);
}
public function getTitle() {
return pht(
'%s changed the automatic update frequency for this import.',
$this->renderAuthor());
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap();
$valid = array_keys($frequency_map);
$valid = array_fuse($valid);
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (!isset($valid[$value])) {
$errors[] = $this->newInvalidError(
pht(
'Import frequency "%s" is not valid. Valid frequences are: %s.',
$value,
implode(', ', $valid)),
$xaction);
}
}
return $errors;
}
}