mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-10-03 18:34:43 +08:00
Resolve #744 (not active!!)
This commit is contained in:
parent
8d1290e2c4
commit
94c3fa464d
12 changed files with 276 additions and 41 deletions
|
@ -22,4 +22,22 @@ export class AccountModel extends AbstractModel {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all mail to main account
|
||||
*//*
|
||||
importAll(account) {
|
||||
rl.app.Remote.streamPerLine(line => {
|
||||
try {
|
||||
line = JSON.parse(line);
|
||||
console.dir(line);
|
||||
} catch (e) {
|
||||
// OOPS
|
||||
}
|
||||
}, 'AccountImport', {
|
||||
Action: 'AccountImport',
|
||||
email: account.email
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
|
@ -287,6 +287,7 @@ trait Messages
|
|||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
* $sStoreAction = \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
|
||||
*/
|
||||
public function MessageStoreFlag(SequenceSet $oRange, array $aInputStoreItems, string $sStoreAction) : ?ResponseCollection
|
||||
{
|
||||
|
|
|
@ -20,20 +20,14 @@ class Folder
|
|||
// RFC5258 Response data STATUS items when using LIST-EXTENDED
|
||||
use Traits\Status;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDelimiter;
|
||||
private ?string $sDelimiter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aFlagsLowerCase;
|
||||
private array $aFlagsLowerCase;
|
||||
|
||||
/**
|
||||
* RFC 5464
|
||||
*/
|
||||
private $aMetadata = array();
|
||||
private array $aMetadata = array();
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
|
@ -57,7 +51,7 @@ class Folder
|
|||
|
||||
public function setFlags(array $aFlags) : void
|
||||
{
|
||||
$this->aFlagsLowerCase = \array_map('strtolower', $aFlags);
|
||||
$this->aFlagsLowerCase = \array_map('mb_strtolower', $aFlags);
|
||||
}
|
||||
|
||||
public function setDelimiter(?string $sDelimiter) : void
|
||||
|
|
|
@ -26,8 +26,8 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
use Commands\Metadata;
|
||||
use Commands\Quota;
|
||||
|
||||
const
|
||||
TAG_PREFIX = 'TAG';
|
||||
public
|
||||
$TAG_PREFIX = 'TAG';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
|
@ -606,7 +606,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
|
||||
protected function getCurrentTag() : string
|
||||
{
|
||||
return self::TAG_PREFIX.$this->iTagCount;
|
||||
return $this->TAG_PREFIX.$this->iTagCount;
|
||||
}
|
||||
|
||||
public function EscapeString(?string $sStringForEscape) : string
|
||||
|
|
|
@ -401,8 +401,7 @@ trait ResponseParser
|
|||
return false;
|
||||
}
|
||||
|
||||
$rImapLiteralStream =
|
||||
\MailSo\Base\StreamWrappers\Literal::CreateStream($this->ConnectionResource(), $iLiteralLen);
|
||||
$rImapLiteralStream = \MailSo\Base\StreamWrappers\Literal::CreateStream($this->ConnectionResource(), $iLiteralLen);
|
||||
|
||||
$this->writeLog('Start Callback for '.$sParent.' / '.$sLiteralAtomUpperCase.
|
||||
' - try to read '.$iLiteralLen.' bytes.', \LOG_INFO);
|
||||
|
@ -411,7 +410,7 @@ trait ResponseParser
|
|||
|
||||
try
|
||||
{
|
||||
$this->aFetchCallbacks[$sFetchKey]($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream);
|
||||
$this->aFetchCallbacks[$sFetchKey]($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream, $iLiteralLen);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
|
@ -445,12 +444,10 @@ trait ResponseParser
|
|||
\fclose($rImapLiteralStream);
|
||||
|
||||
if (0 < $iNotReadLiteralLen) {
|
||||
$this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.',
|
||||
\LOG_WARNING);
|
||||
$this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', \LOG_WARNING);
|
||||
}
|
||||
} else {
|
||||
$this->writeLog('Literal stream is not resource after callback.',
|
||||
\LOG_WARNING);
|
||||
$this->writeLog('Literal stream is not resource after callback.', \LOG_WARNING);
|
||||
}
|
||||
|
||||
$this->bRunningCallback = false;
|
||||
|
|
|
@ -20,20 +20,11 @@ use MailSo\Imap\Enumerations\MetadataKeys;
|
|||
*/
|
||||
class Folder implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bExists;
|
||||
private bool $bExists;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bSubscribed;
|
||||
private bool $bSubscribed;
|
||||
|
||||
/**
|
||||
* @var \MailSo\Imap\Folder
|
||||
*/
|
||||
private $oImapFolder;
|
||||
private \MailSo\Imap\Folder $oImapFolder;
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
|
@ -70,7 +61,7 @@ class Folder implements \JsonSerializable
|
|||
return $this->oImapFolder->NameRaw();
|
||||
}
|
||||
|
||||
public function Delimiter() : string
|
||||
public function Delimiter() : ?string
|
||||
{
|
||||
return $this->oImapFolder->Delimiter();
|
||||
}
|
||||
|
|
|
@ -1096,7 +1096,7 @@ class Actions
|
|||
|
||||
if (!$this->MailClient()->IsLoggined()) {
|
||||
try {
|
||||
$oAccount->ImapConnectAndLoginHelper($this->oPlugins, $this->MailClient(), $this->oConfig);
|
||||
$oAccount->ImapConnectAndLoginHelper($this->oPlugins, $this->MailClient()->ImapClient(), $this->oConfig);
|
||||
} catch (\MailSo\Net\Exceptions\ConnectionException $oException) {
|
||||
throw new Exceptions\ClientException(Notifications::ConnectionError, $oException);
|
||||
} catch (\Throwable $oException) {
|
||||
|
|
|
@ -110,6 +110,47 @@ trait Accounts
|
|||
return $this->TrueResponse(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all mail from AdditionalAccount into MainAccount
|
||||
*/
|
||||
public function DoAccountImport(): array
|
||||
{
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii(\trim($this->GetActionParam('email', '')), true);
|
||||
if (!\strlen($sEmail)) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
|
||||
$oMainAccount = $this->getMainAccountFromToken();
|
||||
$oLogger = $this->Logger();
|
||||
|
||||
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||
if (!isset($aAccounts[$sEmail])) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
$oAccount = AdditionalAccount::NewInstanceFromTokenArray(
|
||||
$this, $aAccounts[$sEmail]
|
||||
);
|
||||
if (!$oAccount) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
|
||||
$oImapTarget = new \MailSo\Imap\ImapClient;
|
||||
$oImapTarget->SetLogger($oLogger);
|
||||
$this->imapConnect($oMainAccount, false, $oImapTarget);
|
||||
|
||||
$oImapSource = new \MailSo\Imap\ImapClient;
|
||||
$oImapSource->SetLogger($oLogger);
|
||||
$this->imapConnect($oAccount, false, $oImapSource);
|
||||
|
||||
$oSync = new \SnappyMail\Imap\Sync;
|
||||
$oSync->oImapSource = $oImapSource;
|
||||
$oSync->oImapTarget = $oImapTarget;
|
||||
|
||||
$rootfolder = $this->GetActionParam('rootfolder', '') ?: $sEmail;
|
||||
$oSync->import($rootfolder);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
*/
|
||||
|
|
|
@ -139,7 +139,7 @@ trait UserAuth
|
|||
}
|
||||
|
||||
try {
|
||||
$this->CheckMailConnection($oAccount, true);
|
||||
$this->imapConnect($oAccount, true);
|
||||
if ($bMainAccount) {
|
||||
$bSignMe && $this->SetSignMeToken($oAccount);
|
||||
$this->StorageProvider()->Put($oAccount, StorageType::SESSION, Utils::GetSessionToken(), 'true');
|
||||
|
@ -188,7 +188,8 @@ trait UserAuth
|
|||
}
|
||||
|
||||
// Test the login
|
||||
$this->CheckMailConnection($oAccount);
|
||||
$oImapClient = new \MailSo\Imap\ImapClient;
|
||||
$this->imapConnect($oAccount, false, $oImapClient);
|
||||
|
||||
$this->SetAdditionalAuthToken($oAccount);
|
||||
return true;
|
||||
|
@ -380,7 +381,7 @@ trait UserAuth
|
|||
if (!$oAccount) {
|
||||
throw new \RuntimeException('token has no account');
|
||||
}
|
||||
$this->CheckMailConnection($oAccount);
|
||||
$this->imapConnect($oAccount);
|
||||
// Update lifetime
|
||||
$this->SetSignMeToken($oAccount);
|
||||
return $oAccount;
|
||||
|
@ -440,10 +441,13 @@ trait UserAuth
|
|||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
protected function CheckMailConnection(Account $oAccount, bool $bAuthLog = false): void
|
||||
protected function imapConnect(Account $oAccount, bool $bAuthLog = false, \MailSo\Imap\ImapClient $oImapClient = null): void
|
||||
{
|
||||
try {
|
||||
$oAccount->ImapConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config());
|
||||
if (!$oImapClient) {
|
||||
$oImapClient = $this->MailClient()->ImapClient();
|
||||
}
|
||||
$oAccount->ImapConnectAndLoginHelper($this->Plugins(), $oImapClient, $this->Config());
|
||||
} catch (ClientException $oException) {
|
||||
throw $oException;
|
||||
} catch (\MailSo\Net\Exceptions\ConnectionException $oException) {
|
||||
|
|
|
@ -216,9 +216,8 @@ abstract class Account implements \JsonSerializable
|
|||
return $oAccount;
|
||||
}
|
||||
|
||||
public function ImapConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Mail\MailClient $oMailClient, \RainLoop\Config\Application $oConfig) : bool
|
||||
public function ImapConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Imap\ImapClient $oImapClient, \RainLoop\Config\Application $oConfig) : bool
|
||||
{
|
||||
$oImapClient = $oMailClient->ImapClient();
|
||||
$oImapClient->__FORCE_SELECT_ON_EXAMINE__ = !!$oConfig->Get('imap', 'use_force_selection');
|
||||
$oImapClient->__DISABLE_METADATA = !!$oConfig->Get('imap', 'disable_metadata');
|
||||
|
||||
|
|
187
snappymail/v/0.0.0/app/libraries/snappymail/imap/sync.php
Normal file
187
snappymail/v/0.0.0/app/libraries/snappymail/imap/sync.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
namespace SnappyMail\Imap;
|
||||
|
||||
use MailSo\Imap\Enumerations\FetchType;
|
||||
use MailSo\Imap\Enumerations\MessageFlag;
|
||||
use MailSo\Imap\FetchResponse;
|
||||
use MailSo\Mime\Enumerations\Header;
|
||||
|
||||
class Sync
|
||||
{
|
||||
public
|
||||
$oImapSource,
|
||||
$oImapTarget;
|
||||
|
||||
function import(string $sTargetRootFolderName = '')
|
||||
{
|
||||
$sParent = '';
|
||||
$sListPattern = '*';
|
||||
|
||||
$this->oImapSource->TAG_PREFIX = 'S';
|
||||
$this->oImapTarget->TAG_PREFIX = 'T';
|
||||
|
||||
// $this->oImapTarget->Logger()->Write('Get oImapTarget->FolderList');
|
||||
\SnappyMail\Log::notice('SYNC', 'Get oImapTarget->FolderList');
|
||||
$aTargetFolders = $this->oImapTarget->FolderList($sParent, $sListPattern);
|
||||
if (!$aTargetFolders) {
|
||||
return null;
|
||||
}
|
||||
$sTargetINBOX = 'INBOX';
|
||||
$sTargetDelimiter = '';
|
||||
foreach ($aTargetFolders as $sFullName => $oImapFolder) {
|
||||
if ($oImapFolder->IsInbox()) {
|
||||
$sTargetINBOX = $sFullName;
|
||||
}
|
||||
if (!$sTargetDelimiter) {
|
||||
$sTargetDelimiter = $oImapFolder->Delimiter();
|
||||
}
|
||||
}
|
||||
|
||||
\SnappyMail\Log::notice('SYNC', 'Get oImapSource->FolderList');
|
||||
$bUseListStatus = $this->oImapSource->IsSupported('LIST-EXTENDED');
|
||||
$aSourceFolders = $this->oImapSource->FolderList($sParent, $sListPattern, false, $bUseListStatus);
|
||||
if (!$aSourceFolders) {
|
||||
return null;
|
||||
}
|
||||
$sSourceINBOX = 'INBOX';
|
||||
|
||||
\SnappyMail\HTTP\Stream::start();
|
||||
\SnappyMail\HTTP\Stream::JSON([
|
||||
'folders' => \count($aSourceFolders)
|
||||
]);
|
||||
|
||||
$fi = 0;
|
||||
foreach ($aSourceFolders as $sSourceFolderName => $oImapFolder) {
|
||||
++$fi;
|
||||
\SnappyMail\HTTP\Stream::JSON([
|
||||
'index' => $fi,
|
||||
'folder' => $sSourceFolderName
|
||||
]);
|
||||
if ($oImapFolder->IsSelectable()) {
|
||||
if ($oImapFolder->IsInbox()) {
|
||||
$sSourceINBOX = $sSourceFolderName;
|
||||
}
|
||||
// Set mailbox delimiter
|
||||
$sTargetFolderName = $sSourceFolderName;
|
||||
if ($sTargetDelimiter) {
|
||||
$sTargetFolderName = \str_replace($oImapFolder->Delimiter(), $sTargetDelimiter, $sTargetFolderName);
|
||||
$sTargetRootFolderName = \str_replace($sTargetDelimiter, '-', $sTargetRootFolderName);
|
||||
}
|
||||
if ($sTargetRootFolderName) {
|
||||
$sTargetFolderName = $sTargetRootFolderName . ($sTargetDelimiter?:'-') . $sTargetFolderName;
|
||||
}
|
||||
// Create mailbox if not exists
|
||||
if (!isset($aTargetFolders[$sTargetFolderName])) {
|
||||
$this->oImapTarget->FolderCreate($sTargetFolderName);
|
||||
if (!$bUseListStatus || \in_array('\\subscribed', $oImapFolder->FlagsLowerCase())) {
|
||||
$this->oImapTarget->FolderSubscribe($sTargetFolderName);
|
||||
}
|
||||
} else if (!$aTargetFolders[$sTargetFolderName]->IsSelectable()) {
|
||||
// Can't copy messages
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set Source metadata on target
|
||||
if ($aMetadata = $oImapFolder->Metadata()) {
|
||||
$this->oImapTarget->FolderSetMetadata($sTargetFolderName, $aMetadata);
|
||||
}
|
||||
|
||||
$oSourceInfo = $this->oImapSource->FolderSelect($sSourceFolderName);
|
||||
if ($oSourceInfo->MESSAGES) {
|
||||
\SnappyMail\HTTP\Stream::JSON([
|
||||
'index' => $fi,
|
||||
'messages' => $oSourceInfo->MESSAGES
|
||||
]);
|
||||
// All id's to skip from source
|
||||
$oTargetInfo = $this->oImapTarget->FolderSelect($sTargetFolderName);
|
||||
if ($oTargetInfo->MESSAGES) {
|
||||
// Get all existing message id's from target to skip
|
||||
$aTargetMessageIDs = [];
|
||||
$this->oImapTarget->SendRequest('FETCH', [
|
||||
'1:*', [FetchType::BuildBodyCustomHeaderRequest([Header::MESSAGE_ID], true)]
|
||||
]);
|
||||
foreach ($this->oImapTarget->yieldUntaggedResponses() as $oResponse) {
|
||||
if ('FETCH' === $oResponse->ResponseList[2]) {
|
||||
// $oResponse->ResponseList[3][0] == 'BODY[HEADER.FIELDS (MESSAGE-ID)]'
|
||||
// 'Message-ID: ...'
|
||||
$aTargetMessageIDs[] = $oResponse->ResponseList[3][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set all existing id's from source to skip and get all flags
|
||||
$aSourceSkipIDs = [];
|
||||
$aSourceFlags = [];
|
||||
$this->oImapSource->SendRequest('FETCH', [
|
||||
'1:*', [FetchType::FLAGS, FetchType::BuildBodyCustomHeaderRequest([Header::MESSAGE_ID], true)]
|
||||
]);
|
||||
foreach ($this->oImapSource->yieldUntaggedResponses() as $oResponse) {
|
||||
if ('FETCH' === $oResponse->ResponseList[2]
|
||||
&& isset($oResponse->ResponseList[3])
|
||||
&& \is_array($oResponse->ResponseList[3])
|
||||
) {
|
||||
$id = $oResponse->ResponseList[1];
|
||||
foreach ($oResponse->ResponseList[3] as $i => $mItem) {
|
||||
if ('FLAGS' === $mItem) {
|
||||
$aSourceFlags[$id] = $oResponse->ResponseList[3][$i+1];
|
||||
} else if ('MESSAGE-ID' === $mItem && \in_array($oResponse->ResponseList[3][$i+1], $aTargetMessageIDs)) {
|
||||
$aSourceSkipIDs[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aTargetMessageIDs = [];
|
||||
// Now copy each message from source to target
|
||||
for ($i = 1; $i <= $oSourceInfo->MESSAGES; ++$i) {
|
||||
if (!\in_array($i, $aSourceSkipIDs)) {
|
||||
$sPeek = $this->oImapSource->IsSupported('BINARY')
|
||||
? FetchType::BINARY_PEEK
|
||||
: FetchType::BODY_PEEK;
|
||||
$iAppendUid = 0;
|
||||
$aFetchResponse = $this->oImapSource->Fetch(array(
|
||||
array(
|
||||
$sPeek.'[]',
|
||||
function ($sParent, $sLiteralAtomUpperCase, $rLiteralStream, $iLiteralLen)
|
||||
use ($sTargetFolderName, &$iAppendUid, $aSourceFlags, $i) {
|
||||
if (\strlen($sLiteralAtomUpperCase) && \is_resource($rLiteralStream) && 'FETCH' === $sParent) {
|
||||
// $sMessage = \stream_get_contents($rLiteralStream);
|
||||
$iAppendUid = $this->oImapTarget->MessageAppendStream(
|
||||
$sTargetFolderName,
|
||||
$rLiteralStream,
|
||||
$iLiteralLen,
|
||||
isset($aSourceFlags[$i]) ? $aSourceFlags[$i] : []
|
||||
);
|
||||
}
|
||||
}
|
||||
)), $i, false);
|
||||
|
||||
/*
|
||||
$aFlags = $aFetchResponse[0]->GetFetchValue('FLAGS');
|
||||
$iAppendUid = $this->oImapTarget->MessageAppendStream(
|
||||
$sTargetFolderName,
|
||||
$rLiteralStream,
|
||||
$iLiteralLen,
|
||||
$aFlags
|
||||
);
|
||||
if ($iAppendUid && $aFlags) {
|
||||
$this->MessageStoreFlag(
|
||||
new SequenceSet([$iAppendUid]),
|
||||
$aFlags,
|
||||
\MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
\SnappyMail\HTTP\Stream::JSON([
|
||||
'index' => $fi,
|
||||
'message' => $i
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,9 @@
|
|||
<td class="e-action">
|
||||
<span class="account-name" data-bind="text: displayName"></span>
|
||||
</td>
|
||||
<!--
|
||||
<td><span class="icon-import" data-bind="click: importAll"></span></td>
|
||||
-->
|
||||
<td>
|
||||
<a class="btn btn-small btn-danger button-confirm-delete" data-bind="css: {'delete-access': askDelete}, click: function(oAccount) { $root.deleteAccount(oAccount); }"
|
||||
data-i18n="GLOBAL/ARE_YOU_SURE"></a>
|
||||
|
|
Loading…
Add table
Reference in a new issue