Skip to content

Commit

Permalink
Merge pull request #483 from nextcloud/activity-digest
Browse files Browse the repository at this point in the history
add daily activity digest option
  • Loading branch information
nickvergessen authored Aug 21, 2020
2 parents d5c8306 + 71b8197 commit 6460cf8
Show file tree
Hide file tree
Showing 9 changed files with 379 additions and 30 deletions.
3 changes: 2 additions & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
More information is available in the Activity documentation.
</description>

<version>2.13.0</version>
<version>2.13.1</version>
<licence>agpl</licence>
<author>Frank Karlitschek</author>
<author>Joas Schilling</author>
Expand Down Expand Up @@ -42,6 +42,7 @@
<background-jobs>
<job>OCA\Activity\BackgroundJob\EmailNotification</job>
<job>OCA\Activity\BackgroundJob\ExpireActivities</job>
<job>OCA\Activity\BackgroundJob\DigestMail</job>
</background-jobs>

<commands>
Expand Down
49 changes: 49 additions & 0 deletions lib/BackgroundJob/DigestMail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Activity\BackgroundJob;


use OC\BackgroundJob\TimedJob;
use OCA\Activity\DigestSender;
use OCP\AppFramework\Utility\ITimeFactory;

class DigestMail extends TimedJob {

/** @var DigestSender */
protected $digestSender;
/** @var ITimeFactory */
protected $timeFactory;

public function __construct(DigestSender $digestSender, ITimeFactory $timeFactory) {
// run hourly
$this->setInterval(60 * 60);

$this->digestSender = $digestSender;
$this->timeFactory = $timeFactory;
}

protected function run($argument) {
$this->digestSender->sendDigests($this->timeFactory->getTime());
}
}
10 changes: 9 additions & 1 deletion lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,15 @@ public function __construct($appName,
* @param int $notify_setting_batchtime
* @param bool $notify_setting_self
* @param bool $notify_setting_selfemail
* @param bool $activity_digest
* @return DataResponse
*/
public function personal(
$notify_setting_batchtime = UserSettings::EMAIL_SEND_HOURLY,
$notify_setting_self = false,
$notify_setting_selfemail = false) {
$notify_setting_selfemail = false,
$activity_digest = false
) {

$settings = $this->manager->getSettings();
foreach ($settings as $setting) {
Expand Down Expand Up @@ -145,6 +148,11 @@ public function personal(
'notify_setting_selfemail',
(int) $notify_setting_selfemail
);
$this->config->setUserValue(
$this->user, 'activity',
'notify_setting_activity_digest',
(int) $activity_digest
);

return new DataResponse(array(
'data' => array(
Expand Down
101 changes: 74 additions & 27 deletions lib/Data.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ public function send(IEvent $event): int {
'type' => $event->getType(),
'affecteduser' => $event->getAffectedUser(),
'user' => $event->getAuthor(),
'timestamp' => (int) $event->getTimestamp(),
'timestamp' => (int)$event->getTimestamp(),
'subject' => $event->getSubject(),
'subjectparams' => json_encode($event->getSubjectParameters()),
'message' => $event->getMessage(),
'messageparams' => json_encode($event->getMessageParameters()),
'priority' => IExtension::PRIORITY_MEDIUM,
'object_type' => $event->getObjectType(),
'object_id' => (int) $event->getObjectId(),
'object_id' => (int)$event->getObjectId(),
'object_name' => $event->getObjectName(),
'link' => $event->getLink(),
])
Expand All @@ -108,7 +108,7 @@ public function send(IEvent $event): int {
* Send an event as email
*
* @param IEvent $event
* @param int $latestSendTime Activity $timestamp + batch setting of $affectedUser
* @param int $latestSendTime Activity $timestamp + batch setting of $affectedUser
* @return bool
*/
public function storeMail(IEvent $event, int $latestSendTime): bool {
Expand All @@ -124,7 +124,7 @@ public function storeMail(IEvent $event, int $latestSendTime): bool {
'amq_subject' => $query->createNamedParameter($event->getSubject()),
'amq_subjectparams' => $query->createNamedParameter(json_encode($event->getSubjectParameters())),
'amq_affecteduser' => $query->createNamedParameter($affectedUser),
'amq_timestamp' => $query->createNamedParameter((int) $event->getTimestamp()),
'amq_timestamp' => $query->createNamedParameter((int)$event->getTimestamp()),
'amq_type' => $query->createNamedParameter($event->getType()),
'amq_latest_send' => $query->createNamedParameter($latestSendTime),
'object_type' => $query->createNamedParameter($event->getObjectType()),
Expand All @@ -150,12 +150,11 @@ public function storeMail(IEvent $event, int $latestSendTime): bool {
* @param string $objectType Allows to filter the activities to a given object. May only appear together with $objectId
* @param int $objectId Allows to filter the activities to a given object. May only appear together with $objectType
*
* @param bool $returnEvents return only the events
* @return array
*
* @throws \OutOfBoundsException if the user (Code: 1) or the since (Code: 2) is invalid
* @throws \BadMethodCallException if the user has selected to display no types for this filter (Code: 3)
*/
public function get(GroupHelper $groupHelper, UserSettings $userSettings, $user, $since, $limit, $sort, $filter, $objectType = '', $objectId = 0) {
public function get(GroupHelper $groupHelper, UserSettings $userSettings, $user, $since, $limit, $sort, $filter, $objectType = '', $objectId = 0, bool $returnEvents = false) {
// get current user
if ($user === '') {
throw new \OutOfBoundsException('Invalid user', 1);
Expand Down Expand Up @@ -241,13 +240,17 @@ public function get(GroupHelper $groupHelper, UserSettings $userSettings, $user,
$hasMore = true;
break;
}
$headers['X-Activity-Last-Given'] = (int) $row['activity_id'];
$headers['X-Activity-Last-Given'] = (int)$row['activity_id'];
$groupHelper->addActivity($row);
$limit--;
}
$result->closeCursor();

return ['data' => $groupHelper->getActivities(), 'has_more' => $hasMore, 'headers' => $headers];
if ($returnEvents) {
return $groupHelper->getEvents();
} else {
return ['data' => $groupHelper->getActivities(), 'has_more' => $hasMore, 'headers' => $headers];
}
}

/**
Expand All @@ -265,7 +268,7 @@ protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort
$queryBuilder = $this->connection->getQueryBuilder();
$queryBuilder->select(['affecteduser', 'timestamp'])
->from('activity')
->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter((int) $since)));
->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter((int)$since)));
$result = $queryBuilder->execute();
$activity = $result->fetch();
$result->closeCursor();
Expand All @@ -274,7 +277,7 @@ protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort
if ($activity['affecteduser'] !== $user) {
throw new \OutOfBoundsException('Invalid since', 2);
}
$timestamp = (int) $activity['timestamp'];
$timestamp = (int)$activity['timestamp'];

if ($sort === 'DESC') {
$query->andWhere($query->expr()->lte('timestamp', $query->createNamedParameter($timestamp)));
Expand Down Expand Up @@ -302,7 +305,7 @@ protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort

if ($activity !== false) {
return [
'X-Activity-First-Known' => (int) $activity['activity_id'],
'X-Activity-First-Known' => (int)$activity['activity_id'],
];
}

Expand Down Expand Up @@ -342,9 +345,9 @@ public function expire($expireDays = 365) {
$ttl = (60 * 60 * 24 * max(1, $expireDays));

$timelimit = time() - $ttl;
$this->deleteActivities(array(
'timestamp' => array($timelimit, '<'),
));
$this->deleteActivities([
'timestamp' => [$timelimit, '<'],
]);
}

/**
Expand All @@ -356,7 +359,7 @@ public function expire($expireDays = 365) {
*/
public function deleteActivities($conditions) {
$sqlWhere = '';
$sqlParameters = $sqlWhereList = array();
$sqlParameters = $sqlWhereList = [];
foreach ($conditions as $column => $comparison) {
$sqlWhereList[] = " `$column` " . ((is_array($comparison) && isset($comparison[1])) ? $comparison[1] : '=') . ' ? ';
$sqlParameters[] = (is_array($comparison)) ? $comparison[0] : $comparison;
Expand All @@ -368,15 +371,15 @@ public function deleteActivities($conditions) {

// Add galera safe delete chunking if using mysql
// Stops us hitting wsrep_max_ws_rows when large row counts are deleted
if($this->connection->getDatabasePlatform() instanceof MySqlPlatform) {
if ($this->connection->getDatabasePlatform() instanceof MySqlPlatform) {
// Then use chunked delete
$max = 100000;
$query = $this->connection->prepare(
'DELETE FROM `*PREFIX*activity`' . $sqlWhere . " LIMIT " . $max);
do {
$query->execute($sqlParameters);
$deleted = $query->rowCount();
} while($deleted === $max);
} while ($deleted === $max);
} else {
// Dont use chunked delete - let the DB handle the large row count natively
$query = $this->connection->prepare(
Expand All @@ -395,19 +398,63 @@ public function getById(int $activityId): ?IEvent {
$hasMore = false;
if ($row = $result->fetch()) {
$event = $this->activityManager->generateEvent();
$event->setApp((string) $row['app'])
->setType((string) $row['type'])
->setAffectedUser((string) $row['affecteduser'])
->setAuthor((string) $row['user'])
->setTimestamp((int) $row['timestamp'])
->setSubject((string) $row['subject'], (array) json_decode($row['subjectparams'], true))
->setMessage((string) $row['message'], (array) json_decode($row['messageparams'], true))
->setObject((string) $row['object_type'], (int) $row['object_id'], (string) $row['file'])
->setLink((string) $row['link']);
$event->setApp((string)$row['app'])
->setType((string)$row['type'])
->setAffectedUser((string)$row['affecteduser'])
->setAuthor((string)$row['user'])
->setTimestamp((int)$row['timestamp'])
->setSubject((string)$row['subject'], (array)json_decode($row['subjectparams'], true))
->setMessage((string)$row['message'], (array)json_decode($row['messageparams'], true))
->setObject((string)$row['object_type'], (int)$row['object_id'], (string)$row['file'])
->setLink((string)$row['link']);

return $event;
} else {
return null;
}
}

/**
* Get the id of the first activity in the stream since a specified time
*
* @param string $user
* @param int $timestamp
* @return int
*/
public function getFirstActivitySince(string $user, int $timestamp): int {
$query = $this->connection->getQueryBuilder();
$query->select('activity_id')
->from('activity')
->where($query->expr()->eq('affecteduser', $query->createNamedParameter($user)))
->andWhere($query->expr()->gt('timestamp', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT)))
->orderBy('timestamp', 'ASC')
->setMaxResults(1);

$res = $query->execute()->fetch(\PDO::FETCH_COLUMN);
return (int)$res;
}

/**
* Get the number of activity items and the latest activity id since the specified activity
*
* @param string $user
* @param int $since
* @param bool $byOthers
* @return array
*/
public function getActivitySince(string $user, int $since, bool $byOthers) {
$query = $this->connection->getQueryBuilder();
$nameParam = $query->createNamedParameter($user);
$query->select($query->func()->count('activity_id', 'count'))
->selectAlias($query->func()->max('activity_id'), 'max')
->from('activity')
->where($query->expr()->eq('affecteduser', $nameParam))
->andWhere($query->expr()->gt('activity_id', $query->createNamedParameter($since, IQueryBuilder::PARAM_INT)));

if ($byOthers) {
$query->andWhere($query->expr()->neq('user', $nameParam));
}

return $query->execute()->fetch();
}
}
Loading

0 comments on commit 6460cf8

Please sign in to comment.