Skip to content

Commit febaba1

Browse files
SebastianKrupinskist3iny
authored andcommitted
feat: adjust background sync on user activity
Signed-off-by: SebastianKrupinski <[email protected]> Signed-off-by: Richard Steinmetz <[email protected]>
1 parent 28c86a0 commit febaba1

File tree

6 files changed

+110
-24
lines changed

6 files changed

+110
-24
lines changed

lib/BackgroundJob/SyncJob.php

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace OCA\Mail\BackgroundJob;
1010

1111
use Horde_Imap_Client_Exception;
12+
use OCA\Mail\AppInfo\Application;
1213
use OCA\Mail\Exception\IncompleteSyncException;
1314
use OCA\Mail\Exception\ServiceException;
1415
use OCA\Mail\IMAP\MailboxSync;
@@ -26,21 +27,26 @@
2627
use function sprintf;
2728

2829
class SyncJob extends TimedJob {
30+
private const DEFAULT_SYNC_INTERVAL = 3600;
31+
2932
private IUserManager $userManager;
3033
private AccountService $accountService;
3134
private ImapToDbSynchronizer $syncService;
3235
private MailboxSync $mailboxSync;
3336
private LoggerInterface $logger;
3437
private IJobList $jobList;
38+
private readonly bool $forcedSyncInterval;
3539

36-
public function __construct(ITimeFactory $time,
40+
public function __construct(
41+
ITimeFactory $time,
3742
IUserManager $userManager,
3843
AccountService $accountService,
3944
MailboxSync $mailboxSync,
4045
ImapToDbSynchronizer $syncService,
4146
LoggerInterface $logger,
4247
IJobList $jobList,
43-
IConfig $config) {
48+
private readonly IConfig $config,
49+
) {
4450
parent::__construct($time);
4551

4652
$this->userManager = $userManager;
@@ -50,12 +56,15 @@ public function __construct(ITimeFactory $time,
5056
$this->logger = $logger;
5157
$this->jobList = $jobList;
5258

53-
$this->setInterval(
54-
max(
55-
5 * 60,
56-
$config->getSystemValueInt('app.mail.background-sync-interval', 3600)
57-
),
58-
);
59+
$configuredSyncInterval = $config->getSystemValueInt('app.mail.background-sync-interval');
60+
if ($configuredSyncInterval > 0) {
61+
$this->forcedSyncInterval = true;
62+
} else {
63+
$this->forcedSyncInterval = false;
64+
$configuredSyncInterval = self::DEFAULT_SYNC_INTERVAL;
65+
}
66+
67+
$this->setInterval(max(5 * 60, $configuredSyncInterval));
5968
$this->setTimeSensitivity(self::TIME_SENSITIVE);
6069
}
6170

@@ -79,6 +88,31 @@ protected function run($argument) {
7988
return;
8089
}
8190

91+
// If an admin configured a custom sync interval, always abide by it
92+
if (!$this->forcedSyncInterval) {
93+
$now = $this->time->getTime();
94+
$heartbeat = (int)$this->config->getUserValue(
95+
$account->getUserId(),
96+
Application::APP_ID,
97+
'ui-heartbeat',
98+
$now + 1, // Force negative value for $lastUsed in case of no heartbeat
99+
);
100+
$lastUsed = $now - $heartbeat;
101+
if ($lastUsed > 3 * 24 * 3600) {
102+
// User did not open the app in more than three days -> defer sync
103+
$this->setInterval(6 * 3600);
104+
} elseif ($lastUsed > 24 * 3600) {
105+
// User opened the app at least once within the last three days -> default sync
106+
$this->setInterval(self::DEFAULT_SYNC_INTERVAL);
107+
} elseif ($lastUsed > 0) {
108+
// User opened the app at least once within the last 24 hours -> sync more often
109+
$this->setInterval(15 * 60);
110+
} else {
111+
// Default to the hourly interval in case there is no heartbeat
112+
$this->setInterval(self::DEFAULT_SYNC_INTERVAL);
113+
}
114+
}
115+
82116
$user = $this->userManager->get($account->getUserId());
83117
if ($user === null || !$user->isEnabled()) {
84118
$this->logger->debug(sprintf(

lib/Controller/MailboxesController.php

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
namespace OCA\Mail\Controller;
1212

1313
use Horde_Imap_Client;
14+
use OCA\Mail\AppInfo\Application;
1415
use OCA\Mail\Contracts\IMailManager;
1516
use OCA\Mail\Contracts\IMailSearch;
1617
use OCA\Mail\Exception\ClientException;
@@ -27,29 +28,27 @@
2728
use OCP\AppFramework\Http\Attribute\OpenAPI;
2829
use OCP\AppFramework\Http\Attribute\UserRateLimit;
2930
use OCP\AppFramework\Http\JSONResponse;
31+
use OCP\AppFramework\Utility\ITimeFactory;
32+
use OCP\IConfig;
3033
use OCP\IRequest;
3134

3235
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
3336
class MailboxesController extends Controller {
3437
private AccountService $accountService;
35-
private ?string $currentUserId;
3638
private IMailManager $mailManager;
3739
private SyncService $syncService;
40+
private ?string $currentUserId;
3841

39-
/**
40-
* @param string $appName
41-
* @param IRequest $request
42-
* @param AccountService $accountService
43-
* @param string|null $UserId
44-
* @param IMailManager $mailManager
45-
* @param SyncService $syncService
46-
*/
47-
public function __construct(string $appName,
42+
public function __construct(
43+
string $appName,
4844
IRequest $request,
4945
AccountService $accountService,
5046
?string $UserId,
5147
IMailManager $mailManager,
52-
SyncService $syncService) {
48+
SyncService $syncService,
49+
private readonly IConfig $config,
50+
private readonly ITimeFactory $timeFactory,
51+
) {
5352
parent::__construct($appName, $request);
5453

5554
$this->accountService = $accountService;
@@ -140,6 +139,13 @@ public function sync(int $id, array $ids = [], ?int $lastMessageTimestamp = null
140139
$account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
141140
$order = $sortOrder === 'newest' ? IMailSearch::ORDER_NEWEST_FIRST: IMailSearch::ORDER_OLDEST_FIRST;
142141

142+
$this->config->setUserValue(
143+
$this->currentUserId,
144+
Application::APP_ID,
145+
'ui-heartbeat',
146+
(string)$this->timeFactory->getTime(),
147+
);
148+
143149
try {
144150
$syncResponse = $this->syncService->syncMailbox(
145151
$account,

lib/Service/AccountService.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
namespace OCA\Mail\Service;
1212

1313
use OCA\Mail\Account;
14+
use OCA\Mail\AppInfo\Application;
1415
use OCA\Mail\BackgroundJob\PreviewEnhancementProcessingJob;
1516
use OCA\Mail\BackgroundJob\QuotaJob;
1617
use OCA\Mail\BackgroundJob\SyncJob;
@@ -21,7 +22,9 @@
2122
use OCA\Mail\Exception\ServiceException;
2223
use OCA\Mail\IMAP\IMAPClientFactory;
2324
use OCP\AppFramework\Db\DoesNotExistException;
25+
use OCP\AppFramework\Utility\ITimeFactory;
2426
use OCP\BackgroundJob\IJobList;
27+
use OCP\IConfig;
2528
use function array_map;
2629

2730
class AccountService {
@@ -44,10 +47,14 @@ class AccountService {
4447
/** @var IMAPClientFactory */
4548
private $imapClientFactory;
4649

47-
public function __construct(MailAccountMapper $mapper,
50+
public function __construct(
51+
MailAccountMapper $mapper,
4852
AliasesService $aliasesService,
4953
IJobList $jobList,
50-
IMAPClientFactory $imapClientFactory) {
54+
IMAPClientFactory $imapClientFactory,
55+
private readonly IConfig $config,
56+
private readonly ITimeFactory $time,
57+
) {
5158
$this->mapper = $mapper;
5259
$this->aliasesService = $aliasesService;
5360
$this->jobList = $jobList;
@@ -174,6 +181,14 @@ public function save(MailAccount $newAccount): MailAccount {
174181
$this->jobList->add(PreviewEnhancementProcessingJob::class, ['accountId' => $newAccount->getId()]);
175182
$this->jobList->add(QuotaJob::class, ['accountId' => $newAccount->getId()]);
176183

184+
// Set initial heartbeat
185+
$this->config->setUserValue(
186+
$newAccount->getUserId(),
187+
Application::APP_ID,
188+
'ui-heartbeat',
189+
(string)$this->time->getTime(),
190+
);
191+
177192
return $newAccount;
178193
}
179194

tests/Integration/MailboxSynchronizationTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
use OCA\Mail\Service\Sync\SyncService;
2121
use OCA\Mail\Tests\Integration\Framework\ImapTest;
2222
use OCA\Mail\Tests\Integration\Framework\ImapTestAccount;
23+
use OCP\AppFramework\Utility\ITimeFactory;
24+
use OCP\IConfig;
2325
use OCP\IRequest;
2426
use OCP\Server;
2527
use Psr\Log\LoggerInterface;
@@ -46,7 +48,9 @@ protected function setUp(): void {
4648
Server::get(AccountService::class),
4749
$this->getTestAccountUserId(),
4850
Server::get(IMailManager::class),
49-
Server::get(SyncService::class)
51+
Server::get(SyncService::class),
52+
Server::get(IConfig::class),
53+
Server::get(ITimeFactory::class),
5054
);
5155

5256
$this->account = $this->createTestAccount('user12345');

tests/Unit/Controller/MailboxesControllerTest.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
use OCA\Mail\Service\AccountService;
2121
use OCA\Mail\Service\Sync\SyncService;
2222
use OCP\AppFramework\Http\JSONResponse;
23+
use OCP\AppFramework\Utility\ITimeFactory;
24+
use OCP\IConfig;
2325
use OCP\IRequest;
2426
use PHPUnit\Framework\MockObject\MockObject;
2527

@@ -45,20 +47,28 @@ class MailboxesControllerTest extends TestCase {
4547
/** @var SyncService|MockObject */
4648
private $syncService;
4749

50+
private IConfig|MockObject $config;
51+
private ITimeFactory|MockObject $timeFactory;
52+
4853
public function setUp(): void {
4954
parent::setUp();
5055

5156
$this->request = $this->createMock(IRequest::class);
5257
$this->accountService = $this->createMock(AccountService::class);
5358
$this->mailManager = $this->createMock(IMailManager::class);
5459
$this->syncService = $this->createMock(SyncService::class);
60+
$this->config = $this->createMock(IConfig::class);
61+
$this->timeFactory = $this->createMock(ITimeFactory::class);
62+
5563
$this->controller = new MailboxesController(
5664
$this->appName,
5765
$this->request,
5866
$this->accountService,
5967
$this->userId,
6068
$this->mailManager,
61-
$this->syncService
69+
$this->syncService,
70+
$this->config,
71+
$this->timeFactory
6272
);
6373
}
6474

tests/Unit/Service/AccountServiceTest.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
use OCA\Mail\IMAP\IMAPClientFactory;
1818
use OCA\Mail\Service\AccountService;
1919
use OCA\Mail\Service\AliasesService;
20+
use OCP\AppFramework\Utility\ITimeFactory;
2021
use OCP\BackgroundJob\IJobList;
22+
use OCP\IConfig;
2123
use OCP\IL10N;
2224
use PHPUnit\Framework\MockObject\MockObject;
2325

@@ -55,6 +57,9 @@ class AccountServiceTest extends TestCase {
5557
/** @var Horde_Imap_Client_Socket|MockObject */
5658
private $client;
5759

60+
private IConfig&MockObject $config;
61+
private ITimeFactory&MockObject $time;
62+
5863
protected function setUp(): void {
5964
parent::setUp();
6065

@@ -63,11 +68,15 @@ protected function setUp(): void {
6368
$this->aliasesService = $this->createMock(AliasesService::class);
6469
$this->jobList = $this->createMock(IJobList::class);
6570
$this->imapClientFactory = $this->createMock(IMAPClientFactory::class);
71+
$this->config = $this->createMock(IConfig::class);
72+
$this->time = $this->createMock(ITimeFactory::class);
6673
$this->accountService = new AccountService(
6774
$this->mapper,
6875
$this->aliasesService,
6976
$this->jobList,
70-
$this->imapClientFactory
77+
$this->imapClientFactory,
78+
$this->config,
79+
$this->time,
7180
);
7281

7382
$this->account1 = new MailAccount();
@@ -155,12 +164,20 @@ public function testDeleteByAccountId() {
155164

156165
public function testSave() {
157166
$account = new MailAccount();
167+
$account->setUserId('user1');
158168

159169
$this->mapper->expects($this->once())
160170
->method('save')
161171
->with($account)
162172
->will($this->returnArgument(0));
163173

174+
$this->time->expects(self::once())
175+
->method('getTime')
176+
->willReturn(1755850409);
177+
$this->config->expects(self::once())
178+
->method('setUserValue')
179+
->with('user1', 'mail', 'ui-heartbeat', 1755850409);
180+
164181
$actual = $this->accountService->save($account);
165182

166183
$this->assertEquals($account, $actual);

0 commit comments

Comments
 (0)