@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+344 -9
+8
resources/sql/autopatches/20161026.calendar.01.importtriggers.sql
··· 1 + ALTER TABLE {$NAMESPACE}_calendar.calendar_import 2 + ADD triggerPHID VARBINARY(64); 3 + 4 + ALTER TABLE {$NAMESPACE}_calendar.calendar_import 5 + ADD triggerFrequency VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; 6 + 7 + UPDATE {$NAMESPACE}_calendar.calendar_import 8 + SET triggerFrequency = 'once' WHERE triggerFrequency = '';
+6
src/__phutil_library_map__.php
··· 2121 2121 'PhabricatorCalendarImportEpochLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEpochLogType.php', 2122 2122 'PhabricatorCalendarImportFetchLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php', 2123 2123 'PhabricatorCalendarImportFrequencyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php', 2124 + 'PhabricatorCalendarImportFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php', 2124 2125 'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php', 2125 2126 'PhabricatorCalendarImportICSLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php', 2126 2127 'PhabricatorCalendarImportICSURITransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php', ··· 2139 2140 'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php', 2140 2141 'PhabricatorCalendarImportReloadController' => 'applications/calendar/controller/PhabricatorCalendarImportReloadController.php', 2141 2142 'PhabricatorCalendarImportReloadTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportReloadTransaction.php', 2143 + 'PhabricatorCalendarImportReloadWorker' => 'applications/calendar/worker/PhabricatorCalendarImportReloadWorker.php', 2142 2144 'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php', 2143 2145 'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php', 2144 2146 'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php', 2145 2147 'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php', 2148 + 'PhabricatorCalendarImportTriggerLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportTriggerLogType.php', 2146 2149 'PhabricatorCalendarImportUpdateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportUpdateLogType.php', 2147 2150 'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php', 2148 2151 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', ··· 6964 6967 'PhabricatorCalendarImportEpochLogType' => 'PhabricatorCalendarImportLogType', 6965 6968 'PhabricatorCalendarImportFetchLogType' => 'PhabricatorCalendarImportLogType', 6966 6969 'PhabricatorCalendarImportFrequencyLogType' => 'PhabricatorCalendarImportLogType', 6970 + 'PhabricatorCalendarImportFrequencyTransaction' => 'PhabricatorCalendarImportTransactionType', 6967 6971 'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType', 6968 6972 'PhabricatorCalendarImportICSLogType' => 'PhabricatorCalendarImportLogType', 6969 6973 'PhabricatorCalendarImportICSURITransaction' => 'PhabricatorCalendarImportTransactionType', ··· 6986 6990 'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6987 6991 'PhabricatorCalendarImportReloadController' => 'PhabricatorCalendarController', 6988 6992 'PhabricatorCalendarImportReloadTransaction' => 'PhabricatorCalendarImportTransactionType', 6993 + 'PhabricatorCalendarImportReloadWorker' => 'PhabricatorWorker', 6989 6994 'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine', 6990 6995 'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction', 6991 6996 'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 6992 6997 'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType', 6998 + 'PhabricatorCalendarImportTriggerLogType' => 'PhabricatorCalendarImportLogType', 6993 6999 'PhabricatorCalendarImportUpdateLogType' => 'PhabricatorCalendarImportLogType', 6994 7000 'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController', 6995 7001 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule',
+1 -1
src/applications/calendar/controller/PhabricatorCalendarImportDropController.php
··· 33 33 ->addCancelButton($cancel_uri, pht('Done')); 34 34 } 35 35 36 - $engine = new PhabricatorCalendarICSImportEngine(); 36 + $engine = new PhabricatorCalendarICSFileImportEngine(); 37 37 $imports = array(); 38 38 foreach ($files as $file) { 39 39 $import = PhabricatorCalendarImport::initializeNewCalendarImport(
+44
src/applications/calendar/controller/PhabricatorCalendarImportViewController.php
··· 167 167 pht('Source Type'), 168 168 $engine->getImportEngineTypeName()); 169 169 170 + if ($import->getIsDisabled()) { 171 + $auto_updates = phutil_tag('em', array(), pht('Import Disabled')); 172 + $has_trigger = false; 173 + } else { 174 + $frequency = $import->getTriggerFrequency(); 175 + $frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap(); 176 + $frequency_names = ipull($frequency_map, 'name'); 177 + $auto_updates = idx($frequency_names, $frequency, $frequency); 178 + 179 + if ($frequency == PhabricatorCalendarImport::FREQUENCY_ONCE) { 180 + $has_trigger = false; 181 + $auto_updates = phutil_tag('em', array(), $auto_updates); 182 + } else { 183 + $has_trigger = true; 184 + } 185 + } 186 + 187 + $properties->addProperty( 188 + pht('Automatic Updates'), 189 + $auto_updates); 190 + 191 + if ($has_trigger) { 192 + $trigger = id(new PhabricatorWorkerTriggerQuery()) 193 + ->setViewer($viewer) 194 + ->withPHIDs(array($import->getTriggerPHID())) 195 + ->needEvents(true) 196 + ->executeOne(); 197 + 198 + if (!$trigger) { 199 + $next_trigger = phutil_tag('em', array(), pht('Invalid Trigger')); 200 + } else { 201 + $now = PhabricatorTime::getNow(); 202 + $next_epoch = $trigger->getNextEventPrediction(); 203 + $next_trigger = pht( 204 + '%s (%s)', 205 + phabricator_datetime($next_epoch, $viewer), 206 + phutil_format_relative_time($next_epoch - $now)); 207 + } 208 + 209 + $properties->addProperty( 210 + pht('Next Update'), 211 + $next_trigger); 212 + } 213 + 170 214 $engine->appendImportProperties( 171 215 $viewer, 172 216 $import,
+20
src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php
··· 80 80 protected function buildCustomEditFields($object) { 81 81 $viewer = $this->getViewer(); 82 82 83 + $engine = $object->getEngine(); 84 + $can_trigger = $engine->supportsTriggers($object); 85 + 83 86 $fields = array( 84 87 id(new PhabricatorTextEditField()) 85 88 ->setKey('name') ··· 89 92 PhabricatorCalendarImportNameTransaction::TRANSACTIONTYPE) 90 93 ->setConduitDescription(pht('Rename the import.')) 91 94 ->setConduitTypeDescription(pht('New import name.')) 95 + ->setPlaceholder($object->getDisplayName()) 92 96 ->setValue($object->getName()), 93 97 id(new PhabricatorBoolEditField()) 94 98 ->setKey('disabled') ··· 122 126 ->setConduitTypeDescription(pht('True to reload the import.')) 123 127 ->setValue(false), 124 128 ); 129 + 130 + if ($can_trigger) { 131 + $frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap(); 132 + $frequency_options = ipull($frequency_map, 'name'); 133 + 134 + $fields[] = id(new PhabricatorSelectEditField()) 135 + ->setKey('frequency') 136 + ->setLabel(pht('Update Automatically')) 137 + ->setDescription(pht('Configure an automatic update frequncy.')) 138 + ->setTransactionType( 139 + PhabricatorCalendarImportFrequencyTransaction::TRANSACTIONTYPE) 140 + ->setConduitDescription(pht('Set the automatic update frequency.')) 141 + ->setConduitTypeDescription(pht('Update frequency constant.')) 142 + ->setValue($object->getTriggerFrequency()) 143 + ->setOptions($frequency_options); 144 + } 125 145 126 146 $import_engine = $object->getEngine(); 127 147 foreach ($import_engine->newEditEngineFields($this, $object) as $field) {
+93 -7
src/applications/calendar/editor/PhabricatorCalendarImportEditor.php
··· 27 27 protected function applyFinalEffects( 28 28 PhabricatorLiskDAO $object, 29 29 array $xactions) { 30 - 31 - $type_reload = PhabricatorCalendarImportReloadTransaction::TRANSACTIONTYPE; 30 + $actor = $this->getActor(); 32 31 33 32 // We import events when you create a source, or if you later reload it 34 33 // explicitly. 35 34 $should_reload = $this->getIsNewObject(); 35 + 36 + // We adjust the import trigger if you change the import frequency or 37 + // disable the import. 38 + $should_trigger = false; 39 + 36 40 foreach ($xactions as $xaction) { 37 - if ($xaction->getTransactionType() == $type_reload) { 38 - $should_reload = true; 39 - break; 41 + $xaction_type = $xaction->getTransactionType(); 42 + switch ($xaction_type) { 43 + case PhabricatorCalendarImportReloadTransaction::TRANSACTIONTYPE: 44 + $should_reload = true; 45 + break; 46 + case PhabricatorCalendarImportFrequencyTransaction::TRANSACTIONTYPE: 47 + $should_trigger = true; 48 + break; 49 + case PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE: 50 + $should_trigger = true; 51 + break; 40 52 } 41 53 } 42 54 43 55 if ($should_reload) { 44 - $actor = $this->getActor(); 45 - 46 56 $import_engine = $object->getEngine(); 47 57 $import_engine->importEventsFromSource($actor, $object); 58 + } 59 + 60 + if ($should_trigger) { 61 + $trigger_phid = $object->getTriggerPHID(); 62 + if ($trigger_phid) { 63 + $trigger = id(new PhabricatorWorkerTriggerQuery()) 64 + ->setViewer($actor) 65 + ->withPHIDs(array($trigger_phid)) 66 + ->executeOne(); 67 + 68 + if ($trigger) { 69 + $engine = new PhabricatorDestructionEngine(); 70 + $engine->destroyObject($trigger); 71 + } 72 + } 73 + 74 + $frequency = $object->getTriggerFrequency(); 75 + $now = PhabricatorTime::getNow(); 76 + switch ($frequency) { 77 + case PhabricatorCalendarImport::FREQUENCY_ONCE: 78 + $clock = null; 79 + break; 80 + case PhabricatorCalendarImport::FREQUENCY_HOURLY: 81 + $clock = new PhabricatorMetronomicTriggerClock( 82 + array( 83 + 'period' => phutil_units('1 hour in seconds'), 84 + )); 85 + break; 86 + case PhabricatorCalendarImport::FREQUENCY_DAILY: 87 + $clock = new PhabricatorDailyRoutineTriggerClock( 88 + array( 89 + 'start' => $now, 90 + )); 91 + break; 92 + default: 93 + throw new Exception( 94 + pht( 95 + 'Unknown import trigger frequency "%s".', 96 + $frequency)); 97 + } 98 + 99 + // If the object has been disabled, don't write a new trigger. 100 + if ($object->getIsDisabled()) { 101 + $clock = null; 102 + } 103 + 104 + if ($clock) { 105 + $trigger_action = new PhabricatorScheduleTaskTriggerAction( 106 + array( 107 + 'class' => 'PhabricatorCalendarImportReloadWorker', 108 + 'data' => array( 109 + 'importPHID' => $object->getPHID(), 110 + ), 111 + 'options' => array( 112 + 'objectPHID' => $object->getPHID(), 113 + 'priority' => PhabricatorWorker::PRIORITY_BULK, 114 + ), 115 + )); 116 + 117 + $trigger_phid = PhabricatorPHID::generateNewPHID( 118 + PhabricatorWorkerTriggerPHIDType::TYPECONST); 119 + 120 + $object 121 + ->setTriggerPHID($trigger_phid) 122 + ->save(); 123 + 124 + $trigger = id(new PhabricatorWorkerTrigger()) 125 + ->setClock($clock) 126 + ->setAction($trigger_action) 127 + ->setPHID($trigger_phid) 128 + ->save(); 129 + } else { 130 + $object 131 + ->setTriggerPHID(null) 132 + ->save(); 133 + } 48 134 } 49 135 50 136 return $xactions;
+4
src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php
··· 17 17 return pht('Import an event in ".ics" (iCalendar) format.'); 18 18 } 19 19 20 + public function supportsTriggers(PhabricatorCalendarImport $import) { 21 + return false; 22 + } 23 + 20 24 public function appendImportProperties( 21 25 PhabricatorUser $viewer, 22 26 PhabricatorCalendarImport $import,
+4
src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php
··· 17 17 return pht('Import or subscribe to a calendar in .ics format by URI.'); 18 18 } 19 19 20 + public function supportsTriggers(PhabricatorCalendarImport $import) { 21 + return true; 22 + } 23 + 20 24 public function appendImportProperties( 21 25 PhabricatorUser $viewer, 22 26 PhabricatorCalendarImport $import,
+3
src/applications/calendar/import/PhabricatorCalendarImportEngine.php
··· 39 39 throw new PhutilMethodNotImplementedException(); 40 40 } 41 41 42 + abstract public function supportsTriggers( 43 + PhabricatorCalendarImport $import); 44 + 42 45 final public static function getAllImportEngines() { 43 46 return id(new PhutilClassMapQuery()) 44 47 ->setAncestorClass(__CLASS__)
+32
src/applications/calendar/importlog/PhabricatorCalendarImportTriggerLogType.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportTriggerLogType 4 + extends PhabricatorCalendarImportLogType { 5 + 6 + const LOGTYPE = 'trigger'; 7 + 8 + public function getDisplayType( 9 + PhabricatorUser $viewer, 10 + PhabricatorCalendarImportLog $log) { 11 + return pht('Import Triggered'); 12 + } 13 + 14 + public function getDisplayDescription( 15 + PhabricatorUser $viewer, 16 + PhabricatorCalendarImportLog $log) { 17 + return pht('Triggered a periodic update.'); 18 + } 19 + 20 + public function getDisplayIcon( 21 + PhabricatorUser $viewer, 22 + PhabricatorCalendarImportLog $log) { 23 + return 'fa-clock-o'; 24 + } 25 + 26 + public function getDisplayColor( 27 + PhabricatorUser $viewer, 28 + PhabricatorCalendarImportLog $log) { 29 + return 'blue'; 30 + } 31 + 32 + }
+36 -1
src/applications/calendar/storage/PhabricatorCalendarImport.php
··· 14 14 protected $engineType; 15 15 protected $parameters = array(); 16 16 protected $isDisabled = 0; 17 + protected $triggerPHID; 18 + protected $triggerFrequency; 19 + 20 + const FREQUENCY_ONCE = 'once'; 21 + const FREQUENCY_HOURLY = 'hourly'; 22 + const FREQUENCY_DAILY = 'daily'; 17 23 18 24 private $engine = self::ATTACHABLE; 19 25 ··· 27 33 ->setEditPolicy($actor->getPHID()) 28 34 ->setIsDisabled(0) 29 35 ->setEngineType($engine->getImportEngineType()) 30 - ->attachEngine($engine); 36 + ->attachEngine($engine) 37 + ->setTriggerFrequency(self::FREQUENCY_ONCE); 31 38 } 32 39 33 40 protected function getConfiguration() { ··· 40 47 'name' => 'text', 41 48 'engineType' => 'text64', 42 49 'isDisabled' => 'bool', 50 + 'triggerPHID' => 'phid?', 51 + 'triggerFrequency' => 'text64', 43 52 ), 44 53 self::CONFIG_KEY_SCHEMA => array( 45 54 'key_author' => array( ··· 85 94 return $this->getEngine()->getDisplayName($this); 86 95 } 87 96 97 + public static function getTriggerFrequencyMap() { 98 + return array( 99 + self::FREQUENCY_ONCE => array( 100 + 'name' => pht('No Automatic Updates'), 101 + ), 102 + self::FREQUENCY_HOURLY => array( 103 + 'name' => pht('Update Hourly'), 104 + ), 105 + self::FREQUENCY_DAILY => array( 106 + 'name' => pht('Update Daily'), 107 + ), 108 + ); 109 + } 110 + 111 + 88 112 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 89 113 90 114 ··· 154 178 $viewer = $engine->getViewer(); 155 179 156 180 $this->openTransaction(); 181 + 182 + $trigger_phid = $this->getTriggerPHID(); 183 + if ($trigger_phid) { 184 + $trigger = id(new PhabricatorWorkerTriggerQuery()) 185 + ->setViewer($viewer) 186 + ->withPHIDs(array($trigger_phid)) 187 + ->executeOne(); 188 + if ($trigger) { 189 + $engine->destroyObject($trigger); 190 + } 191 + } 157 192 158 193 $events = id(new PhabricatorCalendarEventQuery()) 159 194 ->setViewer($viewer)
+48
src/applications/calendar/worker/PhabricatorCalendarImportReloadWorker.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportReloadWorker extends PhabricatorWorker { 4 + 5 + protected function doWork() { 6 + $import = $this->loadImport(); 7 + $viewer = PhabricatorUser::getOmnipotentUser(); 8 + 9 + if ($import->getIsDisabled()) { 10 + return; 11 + } 12 + 13 + $author = id(new PhabricatorPeopleQuery()) 14 + ->setViewer($viewer) 15 + ->withPHIDs(array($import->getAuthorPHID())) 16 + ->needUserSettings(true) 17 + ->executeOne(); 18 + 19 + $import_engine = $import->getEngine(); 20 + 21 + $import->newLogMessage( 22 + PhabricatorCalendarImportTriggerLogType::LOGTYPE, 23 + array()); 24 + 25 + $import_engine->importEventsFromSource($author, $import); 26 + } 27 + 28 + private function loadImport() { 29 + $viewer = PhabricatorUser::getOmnipotentUser(); 30 + 31 + $data = $this->getTaskData(); 32 + $import_phid = idx($data, 'importPHID'); 33 + 34 + $import = id(new PhabricatorCalendarImportQuery()) 35 + ->setViewer($viewer) 36 + ->withPHIDs(array($import_phid)) 37 + ->executeOne(); 38 + if (!$import) { 39 + throw new PhabricatorWorkerPermanentFailureException( 40 + pht( 41 + 'Failed to load import with PHID "%s".', 42 + $import_phid)); 43 + } 44 + 45 + return $import; 46 + } 47 + 48 + }
+45
src/applications/calendar/xaction/PhabricatorCalendarImportFrequencyTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportFrequencyTransaction 4 + extends PhabricatorCalendarImportTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'calendar.import.frequency'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getTriggerFrequency(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setTriggerFrequency($value); 14 + } 15 + 16 + public function getTitle() { 17 + return pht( 18 + '%s changed the automatic update frequency for this import.', 19 + $this->renderAuthor()); 20 + } 21 + 22 + public function validateTransactions($object, array $xactions) { 23 + $errors = array(); 24 + 25 + $frequency_map = PhabricatorCalendarImport::getTriggerFrequencyMap(); 26 + $valid = array_keys($frequency_map); 27 + $valid = array_fuse($valid); 28 + 29 + foreach ($xactions as $xaction) { 30 + $value = $xaction->getNewValue(); 31 + 32 + if (!isset($valid[$value])) { 33 + $errors[] = $this->newInvalidError( 34 + pht( 35 + 'Import frequency "%s" is not valid. Valid frequences are: %s.', 36 + $value, 37 + implode(', ', $valid)), 38 + $xaction); 39 + } 40 + } 41 + 42 + return $errors; 43 + } 44 + 45 + }