Page MenuHomePhabricator

D16784.id40428.diff
No OneTemporary

D16784.id40428.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2067,6 +2067,7 @@
'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php',
'PhabricatorCalendarEventNameHeraldField' => 'applications/calendar/herald/PhabricatorCalendarEventNameHeraldField.php',
'PhabricatorCalendarEventNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventNameTransaction.php',
+ 'PhabricatorCalendarEventNotificationView' => 'applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php',
'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php',
'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php',
'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php',
@@ -6915,6 +6916,7 @@
'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorCalendarEventNameHeraldField' => 'PhabricatorCalendarEventHeraldField',
'PhabricatorCalendarEventNameTransaction' => 'PhabricatorCalendarEventTransactionType',
+ 'PhabricatorCalendarEventNotificationView' => 'Phobject',
'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType',
'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand',
diff --git a/src/applications/calendar/management/PhabricatorCalendarManagementNotifyWorkflow.php b/src/applications/calendar/management/PhabricatorCalendarManagementNotifyWorkflow.php
--- a/src/applications/calendar/management/PhabricatorCalendarManagementNotifyWorkflow.php
+++ b/src/applications/calendar/management/PhabricatorCalendarManagementNotifyWorkflow.php
@@ -10,13 +10,28 @@
->setSynopsis(
pht(
'Test and debug notifications about upcoming events.'))
- ->setArguments(array());
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'minutes',
+ 'param' => 'N',
+ 'help' => pht(
+ 'Notify about events in the next __N__ minutes (default: 15). '.
+ 'Setting this to a larger value makes testing easier.'),
+ ),
+ ));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$engine = new PhabricatorCalendarNotificationEngine();
+
+ $minutes = $args->getArg('minutes');
+ if ($minutes) {
+ $engine->setNotifyWindow(phutil_units("{$minutes} minutes in seconds"));
+ }
+
$engine->publishNotifications();
return 0;
diff --git a/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php b/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/calendar/notifications/PhabricatorCalendarEventNotificationView.php
@@ -0,0 +1,61 @@
+<?php
+
+final class PhabricatorCalendarEventNotificationView
+ extends Phobject {
+
+ private $viewer;
+ private $event;
+ private $epoch;
+ private $dateTime;
+
+ public function setViewer(PhabricatorUser $viewer) {
+ $this->viewer = $viewer;
+ return $this;
+ }
+
+ public function getViewer() {
+ return $this->viewer;
+ }
+
+ public function setEvent(PhabricatorCalendarEvent $event) {
+ $this->event = $event;
+ return $this;
+ }
+
+ public function getEvent() {
+ return $this->event;
+ }
+
+ public function setEpoch($epoch) {
+ $this->epoch = $epoch;
+ return $this;
+ }
+
+ public function getEpoch() {
+ return $this->epoch;
+ }
+
+ public function setDateTime(PhutilCalendarDateTime $date_time) {
+ $this->dateTime = $date_time;
+ return $this;
+ }
+
+ public function getDateTime() {
+ return $this->dateTime;
+ }
+
+ public function getDisplayMinutes() {
+ $epoch = $this->getEpoch();
+ $now = PhabricatorTime::getNow();
+ $minutes = (int)ceil(($epoch - $now) / 60);
+ return new PhutilNumber($minutes);
+ }
+
+ public function getDisplayTime() {
+ $viewer = $this->getViewer();
+
+ $epoch = $this->getEpoch();
+ return phabricator_datetime($epoch, $viewer);
+ }
+
+}
diff --git a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php
--- a/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php
+++ b/src/applications/calendar/notifications/PhabricatorCalendarNotificationEngine.php
@@ -4,19 +4,75 @@
extends Phobject {
private $cursor;
+ private $notifyWindow;
public function getCursor() {
if (!$this->cursor) {
$now = PhabricatorTime::getNow();
- $this->cursor = $now - phutil_units('5 minutes in seconds');
+ $this->cursor = $now - phutil_units('10 minutes in seconds');
}
return $this->cursor;
}
+ public function setCursor($cursor) {
+ $this->cursor = $cursor;
+ return $this;
+ }
+
+ public function setNotifyWindow($notify_window) {
+ $this->notifyWindow = $notify_window;
+ return $this;
+ }
+
+ public function getNotifyWindow() {
+ if (!$this->notifyWindow) {
+ return phutil_units('15 minutes in seconds');
+ }
+
+ return $this->notifyWindow;
+ }
+
public function publishNotifications() {
$cursor = $this->getCursor();
+ $now = PhabricatorTime::getNow();
+ if ($cursor > $now) {
+ return;
+ }
+
+ $calendar_class = 'PhabricatorCalendarApplication';
+ if (!PhabricatorApplication::isClassInstalled($calendar_class)) {
+ return;
+ }
+
+ try {
+ $lock = PhabricatorGlobalLock::newLock('calendar.notify')
+ ->lock(5);
+ } catch (PhutilLockException $ex) {
+ return;
+ }
+
+ $caught = null;
+ try {
+ $this->sendNotifications();
+ } catch (Exception $ex) {
+ $caught = $ex;
+ }
+
+ $lock->unlock();
+
+ // Wait a little while before checking for new notifications to send.
+ $this->setCursor($cursor + phutil_units('1 minute in seconds'));
+
+ if ($caught) {
+ throw $caught;
+ }
+ }
+
+ private function sendNotifications() {
+ $cursor = $this->getCursor();
+
$window_min = $cursor - phutil_units('16 hours in seconds');
$window_max = $cursor + phutil_units('16 hours in seconds');
@@ -100,7 +156,7 @@
}
$notify_min = $cursor;
- $notify_max = $cursor + phutil_units('15 minutes in seconds');
+ $notify_max = $cursor + $this->getNotifyWindow();
$notify_map = array();
foreach ($events as $key => $event) {
$initial_epoch = $event->getUTCInitialEpoch();
@@ -136,11 +192,13 @@
continue;
}
- $notify_map[$user_phid][] = array(
- 'event' => $event,
- 'datetime' => $user_datetime,
- 'epoch' => $user_epoch,
- );
+ $view = id(new PhabricatorCalendarEventNotificationView())
+ ->setViewer($user)
+ ->setEvent($event)
+ ->setDateTime($user_datetime)
+ ->setEpoch($user_epoch);
+
+ $notify_map[$user_phid][] = $view;
}
}
@@ -149,24 +207,23 @@
$now = PhabricatorTime::getNow();
foreach ($notify_map as $user_phid => $events) {
$user = $user_map[$user_phid];
- $events = isort($events, 'epoch');
-
- // TODO: This is just a proof-of-concept that gets dumped to the console;
- // it will be replaced with a nice fancy email and notification.
- $body = array();
- $body[] = pht('%s, these events start soon:', $user->getUsername());
- $body[] = null;
- foreach ($events as $spec) {
- $event = $spec['event'];
- $body[] = $event->getName();
+ $locale = PhabricatorEnv::beginScopedLocale($user->getTranslation());
+ $caught = null;
+ try {
+ $mail_list[] = $this->newMailMessage($user, $events);
+ } catch (Exception $ex) {
+ $caught = $ex;
}
- $body = implode("\n", $body);
- $mail_list[] = $body;
+ unset($locale);
- foreach ($events as $spec) {
- $event = $spec['event'];
+ if ($caught) {
+ throw $ex;
+ }
+
+ foreach ($events as $view) {
+ $event = $view->getEvent();
foreach ($event->getNotificationPHIDs() as $phid) {
$mark_list[] = qsprintf(
$conn,
@@ -192,9 +249,55 @@
}
foreach ($mail_list as $mail) {
- echo $mail;
- echo "\n\n";
+ $mail->saveAndSend();
}
}
+
+ private function newMailMessage(PhabricatorUser $viewer, array $events) {
+ $events = msort($events, 'getEpoch');
+
+ $next_event = head($events);
+
+ $body = new PhabricatorMetaMTAMailBody();
+ foreach ($events as $event) {
+ $body->addTextSection(
+ null,
+ pht(
+ '%s is starting in %s minute(s), at %s.',
+ $event->getEvent()->getName(),
+ $event->getDisplayMinutes(),
+ $event->getDisplayTime()));
+
+ $body->addLinkSection(
+ pht('EVENT DETAIL'),
+ PhabricatorEnv::getProductionURI($event->getEvent()->getURI()));
+ }
+
+ $next_event = head($events)->getEvent();
+ $subject = $next_event->getName();
+ if (count($events) > 1) {
+ $more = pht(
+ '(+%s more...)',
+ new PhutilNumber(count($events) - 1));
+ $subject = "{$subject} {$more}";
+ }
+
+ $calendar_phid = id(new PhabricatorCalendarApplication())
+ ->getPHID();
+
+ return id(new PhabricatorMetaMTAMail())
+ ->setSubject($subject)
+ ->addTos(array($viewer->getPHID()))
+ ->setSensitiveContent(false)
+ ->setFrom($calendar_phid)
+ ->setIsBulk(true)
+ ->setSubjectPrefix(pht('[Calendar]'))
+ ->setVarySubjectPrefix(pht('[Reminder]'))
+ ->setThreadID($next_event->getPHID(), false)
+ ->setRelatedPHID($next_event->getPHID())
+ ->setBody($body->render())
+ ->setHTMLBody($body->renderHTML());
+ }
+
}
diff --git a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php
--- a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php
+++ b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php
@@ -20,6 +20,8 @@
private $nuanceSources;
private $nuanceCursors;
+ private $calendarEngine;
+
protected function run() {
// The trigger daemon is a low-level infrastructure daemon which schedules
@@ -105,6 +107,7 @@
$sleep_duration = $this->getSleepDuration();
$sleep_duration = $this->runNuanceImportCursors($sleep_duration);
$sleep_duration = $this->runGarbageCollection($sleep_duration);
+ $sleep_duration = $this->runCalendarNotifier($sleep_duration);
$this->sleep($sleep_duration);
} while (!$this->shouldExit());
}
@@ -456,4 +459,21 @@
return true;
}
+
+/* -( Calendar Notifier )-------------------------------------------------- */
+
+
+ private function runCalendarNotifier($duration) {
+ $run_until = (PhabricatorTime::getNow() + $duration);
+
+ if (!$this->calendarEngine) {
+ $this->calendarEngine = new PhabricatorCalendarNotificationEngine();
+ }
+
+ $this->calendarEngine->publishNotifications();
+
+ $remaining = max(0, $run_until - PhabricatorTime::getNow());
+ return $remaining;
+ }
+
}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 24, 4:34 PM (2 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7389740
Default Alt Text
D16784.id40428.diff (11 KB)

Event Timeline