Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15450124
D16784.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
D16784.id.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 29, 2:07 PM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7389740
Default Alt Text
D16784.id.diff (11 KB)
Attached To
Mode
D16784: Automatically send (not-so-great) email notifications for upcoming events
Attached
Detach File
Event Timeline
Log In to Comment