diff --git a/sync/db/003_folders.sql b/sync/db/003_folders.sql index 1a99f423..1006b222 100644 --- a/sync/db/003_folders.sql +++ b/sync/db/003_folders.sql @@ -4,6 +4,10 @@ CREATE TABLE IF NOT EXISTS `folders` ( `name` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `count` int(10) unsigned DEFAULT '0', `synced` int(10) unsigned DEFAULT '0', + `sync_status` ENUM('not_synced','syncing', 'syncing_need_resync','synced_need_resync', 'synced','error') NOT NULL DEFAULT 'not_synced', + `sync_host` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `sync_pid` int(11) DEFAULT NULL, + `synced_at` timestamp NULL DEFAULT NULL, `uid_validity` int(10) unsigned DEFAULT '0', `deleted` tinyint(1) unsigned DEFAULT '0', `ignored` tinyint(1) unsigned DEFAULT '0', diff --git a/sync/src/Enum/FolderSyncStatus.php b/sync/src/Enum/FolderSyncStatus.php new file mode 100644 index 00000000..c92a22d4 --- /dev/null +++ b/sync/src/Enum/FolderSyncStatus.php @@ -0,0 +1,57 @@ + 'Not synced', + self::SYNCING => 'Syncing', + self::SYNCING_NEED_RESYNC => 'Syncing but need resync', + self::SYNCED_NEED_RESYNC => 'Synced but need resync', + self::SYNCED => 'Synced', + self::ERROR => 'Error' + ]; + + + public static function getValues(): array + { + return \array_keys(static::$choices); + } +} \ No newline at end of file diff --git a/sync/src/Model/Folder.php b/sync/src/Model/Folder.php index c4df2886..5e4cb4ab 100644 --- a/sync/src/Model/Folder.php +++ b/sync/src/Model/Folder.php @@ -3,6 +3,7 @@ namespace App\Model; use Fn; +use App\Enum\FolderSyncStatus; use PDO; use DateTime; use App\Model; @@ -26,6 +27,10 @@ class Folder extends Model public $account_id; public $created_at; public $uid_validity; + public $sync_status; + public $sync_host; + public $sync_pid; + public $synced_at; const DRAFTS = [ '[Gmail]/Drafts', @@ -339,4 +344,51 @@ public function getSentByAccount(int $accountId) throw new NotFoundException('sent mail folder'); } + + /** + * @param string $status + * @param string $host + * @param int $pid + * @return int + * @throws DatabaseUpdateException + */ + public function updateSyncData(string $status, string $host, int $pid) + { + if (!in_array($status, FolderSyncStatus::getValues())) { + throw new \LogicException("Unsupported folder sync status '{$status}'"); + } + $data = [ + 'sync_status' => $status, + 'sync_host' => $host, + 'sync_pid' => $pid + ]; + if ($status == FolderSyncStatus::SYNCED) { + // In UTC + $now = new \DateTime(); + $date = date('Y-m-d H:i:s', $now->getTimestamp()); + $data['synced_at'] = $date; + } + $updated = $this->db() + ->update($data) + ->table('folders') + ->where('id', '=', $this->getId()) + ->execute(); + $this->errorHandle($updated); + return $updated; + + } + + /** + * @return string + */ + public function getActualSyncStatus() + { + $row = $this->db() + ->select(['sync_status']) + ->from('folders') + ->where('id', '=', $this->getId()) + ->execute() + ->fetch(); + return $row ? $row['sync_status'] : FolderSyncStatus::__default; + } } diff --git a/sync/src/Model/Message.php b/sync/src/Model/Message.php index 75f25823..7f762844 100644 --- a/sync/src/Model/Message.php +++ b/sync/src/Model/Message.php @@ -1051,19 +1051,4 @@ private function formatAttachments(array $attachments) return json_encode($formatted, JSON_UNESCAPED_SLASHES); } - - /** - * @param bool | int $updated Response from update operation - * - * @throws DatabaseUpdateException - */ - private function errorHandle($updated) - { - if (! Belt::isNumber($updated)) { - throw new DatabaseUpdateException( - MESSAGE, - $this->getError() - ); - } - } } diff --git a/sync/src/Sync.php b/sync/src/Sync.php index ae227cfb..e61c5239 100644 --- a/sync/src/Sync.php +++ b/sync/src/Sync.php @@ -7,6 +7,7 @@ namespace App; use Fn; +use App\Enum\FolderSyncStatus; use DateTime; use Exception; use PDOException; @@ -616,6 +617,7 @@ private function syncMessages(AccountModel $account, array $folders, array $opti $this->log->debug('Syncing messages in each folder'); } + /** @var FolderModel $folder */ foreach ($folders as $folder) { $this->retriesMessages[$account->email] = 1; $this->stats->setActiveFolder($folder->name); @@ -708,15 +710,56 @@ private function syncFolderMessages( // Select the folder's mailbox, this is sent to the // messages sync library to perform operations on $selectStats = $this->mailbox->select($folder->name); + if (! Fn\get($options, Sync::OPT_SKIP_DOWNLOAD)) { + $folder->updateSyncData( + FolderSyncStatus::SYNCING, + gethostname(), + getmypid() + ); + } $messageSync->run($account, $folder, $selectStats, $options); + /** + * If watcher received imap event and mark this folder to resync + * after sync is started => make sync one more time. + */ + if ($folder->getActualSyncStatus() == FolderSyncStatus::SYNCING_NEED_RESYNC) { + $messageSync->run($account, $folder, $selectStats, $options); + } + if (! Fn\get($options, Sync::OPT_SKIP_DOWNLOAD)) { + $folder->updateSyncData( + FolderSyncStatus::SYNCED, + gethostname(), + getmypid() + ); + } $this->checkForHalt(); } catch (PDOException $e) { + $folder->updateSyncData( + FolderSyncStatus::ERROR, + gethostname(), + getmypid() + ); throw $e; } catch (StopException $e) { + $folder->updateSyncData( + FolderSyncStatus::ERROR, + gethostname(), + getmypid() + ); throw $e; } catch (TerminateException $e) { + $folder->updateSyncData( + FolderSyncStatus::ERROR, + gethostname(), + getmypid() + ); throw $e; } catch (Exception $e) { + $folder->updateSyncData( + FolderSyncStatus::ERROR, + gethostname(), + getmypid() + ); $this->stats->unsetActiveFolder(); $this->log->error(substr($e->getMessage(), 0, 500)); $this->checkForClosedConnection($e); diff --git a/sync/src/Sync/Folders.php b/sync/src/Sync/Folders.php index 6e17bc43..55924a4e 100644 --- a/sync/src/Sync/Folders.php +++ b/sync/src/Sync/Folders.php @@ -7,6 +7,7 @@ namespace App\Sync; use Fn; +use App\Enum\FolderSyncStatus; use App\Sync; use Monolog\Logger; use League\CLImate\CLImate; @@ -96,7 +97,11 @@ private function addNewFolders( $folder = new FolderModel([ 'name' => $folderName, 'account_id' => $account->getId(), - 'ignored' => $this->getIgnored($folderName) + 'ignored' => $this->getIgnored($folderName), + 'sync_status' => FolderSyncStatus::NOT_SYNCED, + 'sync_host' => NULL, + 'sync_pid' => NULL, + 'synced_at' => NULL ]); $folder->save(); diff --git a/sync/src/Traits/Model.php b/sync/src/Traits/Model.php index 7a93e842..beebf2f9 100644 --- a/sync/src/Traits/Model.php +++ b/sync/src/Traits/Model.php @@ -2,6 +2,7 @@ namespace App\Traits; +use App\Exceptions\DatabaseUpdate as DatabaseUpdateException; use Belt\Belt; use App\Exceptions\NotFound as NotFoundException; use App\Exceptions\Validation as ValidationException; @@ -150,4 +151,19 @@ private function updateUtf8Values(array &$data, array $keys) $data[$key] = @iconv('UTF-8', 'UTF-8//IGNORE', $data[$key]); } } + + /** + * @param bool | int $updated Response from update operation + * + * @throws DatabaseUpdateException + */ + protected function errorHandle($updated) + { + if (! Belt::isNumber($updated)) { + throw new DatabaseUpdateException( + MESSAGE, + $this->getError() + ); + } + } }