1<?php
2
3declare(strict_types=1);
4
5/**
6 * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
7 *
8 * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
9 *
10 * @license GNU AGPL version 3 or any later version
11 *
12 * This program is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU Affero General Public License as
14 * published by the Free Software Foundation, either version 3 of the
15 * License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 * GNU Affero General Public License for more details.
21 *
22 * You should have received a copy of the GNU Affero General Public License
23 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 */
25
26namespace OCA\Mail\Service\Sync;
27
28use OCA\Mail\Account;
29use OCA\Mail\Db\Mailbox;
30use OCA\Mail\Db\MailboxMapper;
31use OCA\Mail\Db\Message;
32use OCA\Mail\Db\MessageMapper;
33use OCA\Mail\Exception\ClientException;
34use OCA\Mail\Exception\MailboxLockedException;
35use OCA\Mail\Exception\MailboxNotCachedException;
36use OCA\Mail\Exception\ServiceException;
37use OCA\Mail\IMAP\MailboxSync;
38use OCA\Mail\IMAP\PreviewEnhancer;
39use OCA\Mail\IMAP\Sync\Response;
40use OCA\Mail\Service\Search\FilterStringParser;
41use OCA\Mail\Service\Search\SearchQuery;
42use Psr\Log\LoggerInterface;
43use function array_diff;
44use function array_map;
45
46class SyncService {
47
48	/** @var ImapToDbSynchronizer */
49	private $synchronizer;
50
51	/** @var FilterStringParser */
52	private $filterStringParser;
53
54	/** @var MailboxMapper */
55	private $mailboxMapper;
56
57	/** @var MessageMapper */
58	private $messageMapper;
59
60	/** @var PreviewEnhancer */
61	private $previewEnhancer;
62
63	/** @var LoggerInterface */
64	private $logger;
65
66	/** @var MailboxSync */
67	private $mailboxSync;
68
69	public function __construct(ImapToDbSynchronizer $synchronizer,
70								FilterStringParser $filterStringParser,
71								MailboxMapper $mailboxMapper,
72								MessageMapper $messageMapper,
73								PreviewEnhancer $previewEnhancer,
74								LoggerInterface $logger,
75								MailboxSync $mailboxSync) {
76		$this->synchronizer = $synchronizer;
77		$this->filterStringParser = $filterStringParser;
78		$this->mailboxMapper = $mailboxMapper;
79		$this->messageMapper = $messageMapper;
80		$this->previewEnhancer = $previewEnhancer;
81		$this->logger = $logger;
82		$this->mailboxSync = $mailboxSync;
83	}
84
85	/**
86	 * @param Account $account
87	 * @param Mailbox $mailbox
88	 *
89	 * @throws MailboxLockedException
90	 * @throws ServiceException
91	 */
92	public function clearCache(Account $account,
93							   Mailbox $mailbox): void {
94		$this->synchronizer->clearCache($account, $mailbox);
95	}
96
97	/**
98	 * @param Account $account
99	 * @param Mailbox $mailbox
100	 * @param int $criteria
101	 * @param int[] $knownIds
102	 * @param bool $partialOnly
103	 *
104	 * @param string|null $filter
105	 *
106	 * @return Response
107	 * @throws ClientException
108	 * @throws MailboxNotCachedException
109	 * @throws ServiceException
110	 */
111	public function syncMailbox(Account $account,
112								Mailbox $mailbox,
113								int $criteria,
114								array $knownIds = null,
115								bool $partialOnly,
116								string $filter = null): Response {
117		if ($partialOnly && !$mailbox->isCached()) {
118			throw MailboxNotCachedException::from($mailbox);
119		}
120
121		$this->synchronizer->sync(
122			$account,
123			$mailbox,
124			$this->logger,
125			$criteria,
126			$knownIds === null ? null : $this->messageMapper->findUidsForIds($mailbox, $knownIds),
127			!$partialOnly
128		);
129
130		$this->mailboxSync->syncStats($account, $mailbox);
131
132		$query = $filter === null ? null : $this->filterStringParser->parse($filter);
133		return $this->getDatabaseSyncChanges(
134			$account,
135			$mailbox,
136			$knownIds ?? [],
137			$query
138		);
139	}
140
141	/**
142	 * @param Account $account
143	 * @param Mailbox $mailbox
144	 * @param int[] $knownIds
145	 * @param SearchQuery $query
146	 *
147	 * @return Response
148	 * @todo does not work with text token search queries
149	 *
150	 */
151	private function getDatabaseSyncChanges(Account $account,
152											Mailbox $mailbox,
153											array $knownIds,
154											?SearchQuery $query): Response {
155		if (empty($knownIds)) {
156			$newIds = $this->messageMapper->findAllIds($mailbox);
157		} else {
158			$newIds = $this->messageMapper->findNewIds($mailbox, $knownIds);
159		}
160
161		if ($query !== null) {
162			// Filter new messages to those that also match the current filter
163			$newUids = $this->messageMapper->findUidsForIds($mailbox, $newIds);
164			$newIds = $this->messageMapper->findIdsByQuery($mailbox, $query, null, $newUids);
165		}
166		$new = $this->messageMapper->findByIds($account->getUserId(), $newIds);
167
168		// TODO: $changed = $this->messageMapper->findChanged($account, $mailbox, $uids);
169		if ($query !== null) {
170			$changedUids = $this->messageMapper->findUidsForIds($mailbox, $knownIds);
171			$changedIds = $this->messageMapper->findIdsByQuery($mailbox, $query, null, $changedUids);
172		} else {
173			$changedIds = $knownIds;
174		}
175		$changed = $this->messageMapper->findByIds($account->getUserId(), $changedIds);
176
177		$stillKnownIds = array_map(static function (Message $msg) {
178			return $msg->getId();
179		}, $changed);
180		$vanished = array_values(array_diff($knownIds, $stillKnownIds));
181
182		return new Response(
183			$this->previewEnhancer->process($account, $mailbox, $new),
184			$changed,
185			$vanished,
186			$mailbox->getStats()
187		);
188	}
189}
190