mirror of
https://github.com/chatmail/core.git
synced 2026-05-02 04:46:29 +03:00
feat: remove MvboxMove and OnlyFetchMvbox
This commit is contained in:
@@ -430,14 +430,6 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* 1=send a copy of outgoing messages to self (default).
|
* 1=send a copy of outgoing messages to self (default).
|
||||||
* Sending messages to self is needed for a proper multi-account setup,
|
* Sending messages to self is needed for a proper multi-account setup,
|
||||||
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
|
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
|
||||||
* - `mvbox_move` = 1=detect chat messages,
|
|
||||||
* move them to the `DeltaChat` folder,
|
|
||||||
* and watch the `DeltaChat` folder for updates (default),
|
|
||||||
* 0=do not move chat-messages
|
|
||||||
* - `only_fetch_mvbox` = 1=Do not fetch messages from folders other than the
|
|
||||||
* `DeltaChat` folder. Messages will still be fetched from the
|
|
||||||
* spam folder.
|
|
||||||
* 0=watch all folders normally (default)
|
|
||||||
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
|
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
|
||||||
* show direct replies to chats only,
|
* show direct replies to chats only,
|
||||||
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
|
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ pub struct EnteredLoginParam {
|
|||||||
/// Imap server port.
|
/// Imap server port.
|
||||||
pub imap_port: Option<u16>,
|
pub imap_port: Option<u16>,
|
||||||
|
|
||||||
|
/// IMAP server folder.
|
||||||
|
///
|
||||||
|
/// Defaults to "INBOX" if not set.
|
||||||
|
/// Should not be an empty string.
|
||||||
|
pub imap_folder: Option<String>,
|
||||||
|
|
||||||
/// Imap socket security.
|
/// Imap socket security.
|
||||||
pub imap_security: Option<Socket>,
|
pub imap_security: Option<Socket>,
|
||||||
|
|
||||||
@@ -66,6 +72,7 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
|
|||||||
password: param.imap.password,
|
password: param.imap.password,
|
||||||
imap_server: param.imap.server.into_option(),
|
imap_server: param.imap.server.into_option(),
|
||||||
imap_port: param.imap.port.into_option(),
|
imap_port: param.imap.port.into_option(),
|
||||||
|
imap_folder: param.imap.folder.into_option(),
|
||||||
imap_security: imap_security.into_option(),
|
imap_security: imap_security.into_option(),
|
||||||
imap_user: param.imap.user.into_option(),
|
imap_user: param.imap.user.into_option(),
|
||||||
smtp_server: param.smtp.server.into_option(),
|
smtp_server: param.smtp.server.into_option(),
|
||||||
@@ -85,14 +92,15 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
|
|||||||
fn try_from(param: EnteredLoginParam) -> Result<Self> {
|
fn try_from(param: EnteredLoginParam) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
addr: param.addr,
|
addr: param.addr,
|
||||||
imap: dc::EnteredServerLoginParam {
|
imap: dc::EnteredImapLoginParam {
|
||||||
server: param.imap_server.unwrap_or_default(),
|
server: param.imap_server.unwrap_or_default(),
|
||||||
port: param.imap_port.unwrap_or_default(),
|
port: param.imap_port.unwrap_or_default(),
|
||||||
|
folder: param.imap_folder.unwrap_or_default(),
|
||||||
security: param.imap_security.unwrap_or_default().into(),
|
security: param.imap_security.unwrap_or_default().into(),
|
||||||
user: param.imap_user.unwrap_or_default(),
|
user: param.imap_user.unwrap_or_default(),
|
||||||
password: param.password,
|
password: param.password,
|
||||||
},
|
},
|
||||||
smtp: dc::EnteredServerLoginParam {
|
smtp: dc::EnteredSmtpLoginParam {
|
||||||
server: param.smtp_server.unwrap_or_default(),
|
server: param.smtp_server.unwrap_or_default(),
|
||||||
port: param.smtp_port.unwrap_or_default(),
|
port: param.smtp_port.unwrap_or_default(),
|
||||||
security: param.smtp_security.unwrap_or_default().into(),
|
security: param.smtp_security.unwrap_or_default().into(),
|
||||||
|
|||||||
@@ -2,32 +2,13 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import pytest
|
|
||||||
from imap_tools import AND, U
|
from imap_tools import AND, U
|
||||||
|
|
||||||
from deltachat_rpc_client import Contact, EventType, Message
|
from deltachat_rpc_client import Contact, EventType, Message
|
||||||
|
|
||||||
|
|
||||||
def test_move_works(acfactory, direct_imap):
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
ac2_direct_imap = direct_imap(ac2)
|
|
||||||
ac2_direct_imap.create_folder("DeltaChat")
|
|
||||||
ac2.set_config("mvbox_move", "1")
|
|
||||||
ac2.bring_online()
|
|
||||||
|
|
||||||
chat = ac1.create_chat(ac2)
|
|
||||||
chat.send_text("message1")
|
|
||||||
|
|
||||||
# Message is moved to the movebox
|
|
||||||
ac2.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
||||||
|
|
||||||
# Message is downloaded
|
|
||||||
msg = ac2.wait_for_incoming_msg().get_snapshot()
|
|
||||||
assert msg.text == "message1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
||||||
"""When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
|
"""When a batch of messages is moved from Inbox to another folder with a single MOVE command,
|
||||||
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
|
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
|
||||||
processed by receive_imf in the wrong order, and, particularly, reactions were processed before
|
processed by receive_imf in the wrong order, and, particularly, reactions were processed before
|
||||||
messages they refer to and thus dropped.
|
messages they refer to and thus dropped.
|
||||||
@@ -37,9 +18,6 @@ def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
|||||||
addr, password = acfactory.get_credentials()
|
addr, password = acfactory.get_credentials()
|
||||||
ac2 = acfactory.get_unconfigured_account()
|
ac2 = acfactory.get_unconfigured_account()
|
||||||
ac2.add_or_update_transport({"addr": addr, "password": password})
|
ac2.add_or_update_transport({"addr": addr, "password": password})
|
||||||
ac2_direct_imap = direct_imap(ac2)
|
|
||||||
ac2_direct_imap.create_folder("DeltaChat")
|
|
||||||
ac2.set_config("mvbox_move", "1")
|
|
||||||
assert ac2.is_configured()
|
assert ac2.is_configured()
|
||||||
|
|
||||||
ac2.bring_online()
|
ac2.bring_online()
|
||||||
@@ -55,11 +33,17 @@ def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
|||||||
react_str = "\N{THUMBS UP SIGN}"
|
react_str = "\N{THUMBS UP SIGN}"
|
||||||
msg1.send_reaction(react_str).wait_until_delivered()
|
msg1.send_reaction(react_str).wait_until_delivered()
|
||||||
|
|
||||||
logging.info("moving messages to ac2's DeltaChat folder in the reverse order")
|
logging.info("moving messages to ac2's movebox folder in the reverse order")
|
||||||
ac2_direct_imap = direct_imap(ac2)
|
ac2_direct_imap = direct_imap(ac2)
|
||||||
|
ac2_direct_imap.create_folder("Movebox")
|
||||||
ac2_direct_imap.connect()
|
ac2_direct_imap.connect()
|
||||||
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
|
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
|
||||||
ac2_direct_imap.conn.move(uid, "DeltaChat")
|
ac2_direct_imap.conn.move(uid, "Movebox")
|
||||||
|
|
||||||
|
logging.info("moving messages back")
|
||||||
|
ac2_direct_imap.select_folder("Movebox")
|
||||||
|
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()]):
|
||||||
|
ac2_direct_imap.conn.move(uid, "INBOX")
|
||||||
|
|
||||||
logging.info("receiving messages by ac2")
|
logging.info("receiving messages by ac2")
|
||||||
ac2.start_io()
|
ac2.start_io()
|
||||||
@@ -72,33 +56,22 @@ def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
|||||||
assert list(reactions.reactions_by_contact.values())[0] == [react_str]
|
assert list(reactions.reactions_by_contact.values())[0] == [react_str]
|
||||||
|
|
||||||
|
|
||||||
def test_move_works_on_self_sent(acfactory, direct_imap):
|
def test_moved_markseen(acfactory, direct_imap, log):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
||||||
|
|
||||||
# Create and enable movebox.
|
|
||||||
ac1_direct_imap = direct_imap(ac1)
|
|
||||||
ac1_direct_imap.create_folder("DeltaChat")
|
|
||||||
ac1.set_config("mvbox_move", "1")
|
|
||||||
ac1.set_config("bcc_self", "1")
|
|
||||||
ac1.bring_online()
|
|
||||||
|
|
||||||
chat = ac1.create_chat(ac2)
|
|
||||||
chat.send_text("message1")
|
|
||||||
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
||||||
chat.send_text("message2")
|
|
||||||
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
||||||
chat.send_text("message3")
|
|
||||||
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
||||||
|
|
||||||
|
|
||||||
def test_moved_markseen(acfactory, direct_imap):
|
|
||||||
"""Test that message already moved to DeltaChat folder is marked as seen."""
|
"""Test that message already moved to DeltaChat folder is marked as seen."""
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1 = acfactory.get_online_account()
|
||||||
|
|
||||||
|
addr, password = acfactory.get_credentials()
|
||||||
|
ac2 = acfactory.get_unconfigured_account()
|
||||||
|
ac2.add_or_update_transport({"addr": addr, "password": password})
|
||||||
|
ac2.bring_online()
|
||||||
|
|
||||||
|
log.section("ac2: creating DeltaChat folder")
|
||||||
ac2_direct_imap = direct_imap(ac2)
|
ac2_direct_imap = direct_imap(ac2)
|
||||||
ac2_direct_imap.create_folder("DeltaChat")
|
ac2_direct_imap.create_folder("DeltaChat")
|
||||||
ac2.set_config("mvbox_move", "1")
|
|
||||||
ac2.set_config("delete_server_after", "0")
|
ac2.set_config("delete_server_after", "0")
|
||||||
ac2.set_config("sync_msgs", "0") # Do not send a sync message when accepting a contact request.
|
ac2.set_config("sync_msgs", "0") # Do not send a sync message when accepting a contact request.
|
||||||
|
|
||||||
|
ac2.add_or_update_transport({"addr": addr, "password": password, "imapFolder": "DeltaChat"})
|
||||||
ac2.bring_online()
|
ac2.bring_online()
|
||||||
|
|
||||||
ac2.stop_io()
|
ac2.stop_io()
|
||||||
@@ -108,6 +81,7 @@ def test_moved_markseen(acfactory, direct_imap):
|
|||||||
idle2.wait_for_new_message()
|
idle2.wait_for_new_message()
|
||||||
|
|
||||||
# Emulate moving of the message to DeltaChat folder by Sieve rule.
|
# Emulate moving of the message to DeltaChat folder by Sieve rule.
|
||||||
|
log.section("ac2: moving message into DeltaChat folder")
|
||||||
ac2_direct_imap.conn.move(["*"], "DeltaChat")
|
ac2_direct_imap.conn.move(["*"], "DeltaChat")
|
||||||
ac2_direct_imap.select_folder("DeltaChat")
|
ac2_direct_imap.select_folder("DeltaChat")
|
||||||
assert len(list(ac2_direct_imap.conn.fetch("*", mark_seen=False))) == 1
|
assert len(list(ac2_direct_imap.conn.fetch("*", mark_seen=False))) == 1
|
||||||
@@ -131,17 +105,11 @@ def test_moved_markseen(acfactory, direct_imap):
|
|||||||
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True, uid=U(1, "*")), mark_seen=False))) == 1
|
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True, uid=U(1, "*")), mark_seen=False))) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mvbox_move", [True, False])
|
def test_markseen_message_and_mdn(acfactory, direct_imap):
|
||||||
def test_markseen_message_and_mdn(acfactory, direct_imap, mvbox_move):
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
for ac in ac1, ac2:
|
for ac in ac1, ac2:
|
||||||
ac.set_config("delete_server_after", "0")
|
ac.set_config("delete_server_after", "0")
|
||||||
if mvbox_move:
|
|
||||||
ac_direct_imap = direct_imap(ac)
|
|
||||||
ac_direct_imap.create_folder("DeltaChat")
|
|
||||||
ac.set_config("mvbox_move", "1")
|
|
||||||
ac.bring_online()
|
|
||||||
|
|
||||||
# Do not send BCC to self, we only want to test MDN on ac1.
|
# Do not send BCC to self, we only want to test MDN on ac1.
|
||||||
ac1.set_config("bcc_self", "0")
|
ac1.set_config("bcc_self", "0")
|
||||||
@@ -150,10 +118,7 @@ def test_markseen_message_and_mdn(acfactory, direct_imap, mvbox_move):
|
|||||||
msg = ac2.wait_for_incoming_msg()
|
msg = ac2.wait_for_incoming_msg()
|
||||||
msg.mark_seen()
|
msg.mark_seen()
|
||||||
|
|
||||||
if mvbox_move:
|
rex = re.compile("Marked messages [0-9]+ in folder INBOX as seen.")
|
||||||
rex = re.compile("Marked messages [0-9]+ in folder DeltaChat as seen.")
|
|
||||||
else:
|
|
||||||
rex = re.compile("Marked messages [0-9]+ in folder INBOX as seen.")
|
|
||||||
|
|
||||||
for ac in ac1, ac2:
|
for ac in ac1, ac2:
|
||||||
while True:
|
while True:
|
||||||
@@ -161,12 +126,11 @@ def test_markseen_message_and_mdn(acfactory, direct_imap, mvbox_move):
|
|||||||
if event.kind == EventType.INFO and rex.search(event.msg):
|
if event.kind == EventType.INFO and rex.search(event.msg):
|
||||||
break
|
break
|
||||||
|
|
||||||
folder = "mvbox" if mvbox_move else "inbox"
|
|
||||||
ac1_direct_imap = direct_imap(ac1)
|
ac1_direct_imap = direct_imap(ac1)
|
||||||
ac2_direct_imap = direct_imap(ac2)
|
ac2_direct_imap = direct_imap(ac2)
|
||||||
|
|
||||||
ac1_direct_imap.select_config_folder(folder)
|
ac1_direct_imap.select_folder("INBOX")
|
||||||
ac2_direct_imap.select_config_folder(folder)
|
ac2_direct_imap.select_folder("INBOX")
|
||||||
|
|
||||||
# Check that the mdn is marked as seen
|
# Check that the mdn is marked as seen
|
||||||
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ def test_add_second_address(acfactory) -> None:
|
|||||||
account = acfactory.new_configured_account()
|
account = acfactory.new_configured_account()
|
||||||
assert len(account.list_transports()) == 1
|
assert len(account.list_transports()) == 1
|
||||||
|
|
||||||
# When the first transport is created,
|
|
||||||
# mvbox_move and only_fetch_mvbox should be disabled.
|
|
||||||
assert account.get_config("mvbox_move") == "0"
|
|
||||||
assert account.get_config("only_fetch_mvbox") == "0"
|
|
||||||
assert account.get_config("show_emails") == "2"
|
assert account.get_config("show_emails") == "2"
|
||||||
|
|
||||||
qr = acfactory.get_account_qr()
|
qr = acfactory.get_account_qr()
|
||||||
@@ -32,32 +28,10 @@ def test_add_second_address(acfactory) -> None:
|
|||||||
account.delete_transport(second_addr)
|
account.delete_transport(second_addr)
|
||||||
assert len(account.list_transports()) == 2
|
assert len(account.list_transports()) == 2
|
||||||
|
|
||||||
# Enabling mvbox_move or only_fetch_mvbox
|
|
||||||
# is not allowed when multi-transport is enabled.
|
|
||||||
for option in ["mvbox_move", "only_fetch_mvbox"]:
|
|
||||||
with pytest.raises(JsonRpcError):
|
|
||||||
account.set_config(option, "1")
|
|
||||||
|
|
||||||
# show_emails does not matter for multi-relay, can be set to anything
|
# show_emails does not matter for multi-relay, can be set to anything
|
||||||
account.set_config("show_emails", "0")
|
account.set_config("show_emails", "0")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("key", ["mvbox_move", "only_fetch_mvbox"])
|
|
||||||
def test_no_second_transport_with_mvbox(acfactory, key) -> None:
|
|
||||||
"""Test that second transport cannot be configured if mvbox is used."""
|
|
||||||
account = acfactory.new_configured_account()
|
|
||||||
assert len(account.list_transports()) == 1
|
|
||||||
|
|
||||||
assert account.get_config("mvbox_move") == "0"
|
|
||||||
assert account.get_config("only_fetch_mvbox") == "0"
|
|
||||||
|
|
||||||
qr = acfactory.get_account_qr()
|
|
||||||
account.set_config(key, "1")
|
|
||||||
|
|
||||||
with pytest.raises(JsonRpcError):
|
|
||||||
account.add_transport_from_qr(qr)
|
|
||||||
|
|
||||||
|
|
||||||
def test_second_transport_without_classic_emails(acfactory) -> None:
|
def test_second_transport_without_classic_emails(acfactory) -> None:
|
||||||
"""Test that second transport can be configured if classic emails are not fetched."""
|
"""Test that second transport can be configured if classic emails are not fetched."""
|
||||||
account = acfactory.new_configured_account()
|
account = acfactory.new_configured_account()
|
||||||
@@ -147,44 +121,13 @@ def test_download_on_demand(acfactory) -> None:
|
|||||||
assert msg.get_snapshot().download_state == dstate
|
assert msg.get_snapshot().download_state == dstate
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("is_chatmail", ["0", "1"])
|
|
||||||
def test_mvbox_move_first_transport(acfactory, is_chatmail) -> None:
|
|
||||||
"""Test that mvbox_move is disabled by default even for non-chatmail accounts.
|
|
||||||
Disabling mvbox_move is required to be able to setup a second transport.
|
|
||||||
"""
|
|
||||||
account = acfactory.get_unconfigured_account()
|
|
||||||
|
|
||||||
account.set_config("fix_is_chatmail", "1")
|
|
||||||
account.set_config("is_chatmail", is_chatmail)
|
|
||||||
|
|
||||||
# The default value when the setting is unset is "1".
|
|
||||||
# This is not changed for compatibility with old databases
|
|
||||||
# imported from backups.
|
|
||||||
assert account.get_config("mvbox_move") == "1"
|
|
||||||
|
|
||||||
qr = acfactory.get_account_qr()
|
|
||||||
account.add_transport_from_qr(qr)
|
|
||||||
|
|
||||||
# Once the first transport is set up,
|
|
||||||
# mvbox_move is disabled.
|
|
||||||
assert account.get_config("mvbox_move") == "0"
|
|
||||||
assert account.get_config("is_chatmail") == is_chatmail
|
|
||||||
|
|
||||||
|
|
||||||
def test_reconfigure_transport(acfactory) -> None:
|
def test_reconfigure_transport(acfactory) -> None:
|
||||||
"""Test that reconfiguring the transport works
|
"""Test that reconfiguring the transport works."""
|
||||||
even if settings not supported for multi-transport
|
|
||||||
like mvbox_move are enabled."""
|
|
||||||
account = acfactory.get_online_account()
|
account = acfactory.get_online_account()
|
||||||
account.set_config("mvbox_move", "1")
|
|
||||||
|
|
||||||
[transport] = account.list_transports()
|
[transport] = account.list_transports()
|
||||||
account.add_or_update_transport(transport)
|
account.add_or_update_transport(transport)
|
||||||
|
|
||||||
# Reconfiguring the transport should not reset
|
|
||||||
# the settings as if when configuring the first transport.
|
|
||||||
assert account.get_config("mvbox_move") == "1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_transport_synchronization(acfactory, log) -> None:
|
def test_transport_synchronization(acfactory, log) -> None:
|
||||||
"""Test synchronization of transports between devices."""
|
"""Test synchronization of transports between devices."""
|
||||||
|
|||||||
@@ -522,7 +522,6 @@ class ACFactory:
|
|||||||
ac = self.get_unconfigured_account()
|
ac = self.get_unconfigured_account()
|
||||||
assert "addr" in configdict and "mail_pw" in configdict, configdict
|
assert "addr" in configdict and "mail_pw" in configdict, configdict
|
||||||
configdict.setdefault("bcc_self", False)
|
configdict.setdefault("bcc_self", False)
|
||||||
configdict.setdefault("mvbox_move", False)
|
|
||||||
configdict.setdefault("sync_msgs", False)
|
configdict.setdefault("sync_msgs", False)
|
||||||
configdict.setdefault("delete_server_after", 0)
|
configdict.setdefault("delete_server_after", 0)
|
||||||
ac.update_config(configdict)
|
ac.update_config(configdict)
|
||||||
|
|||||||
@@ -52,19 +52,19 @@ class TestOfflineAccountBasic:
|
|||||||
|
|
||||||
def test_set_config_int_conversion(self, acfactory):
|
def test_set_config_int_conversion(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
ac1.set_config("mvbox_move", False)
|
ac1.set_config("bcc_self", False)
|
||||||
assert ac1.get_config("mvbox_move") == "0"
|
assert ac1.get_config("bcc_self") == "0"
|
||||||
ac1.set_config("mvbox_move", True)
|
ac1.set_config("bcc_self", True)
|
||||||
assert ac1.get_config("mvbox_move") == "1"
|
assert ac1.get_config("bcc_self") == "1"
|
||||||
ac1.set_config("mvbox_move", 0)
|
ac1.set_config("bcc_self", 0)
|
||||||
assert ac1.get_config("mvbox_move") == "0"
|
assert ac1.get_config("bcc_self") == "0"
|
||||||
ac1.set_config("mvbox_move", 1)
|
ac1.set_config("bcc_self", 1)
|
||||||
assert ac1.get_config("mvbox_move") == "1"
|
assert ac1.get_config("bcc_self") == "1"
|
||||||
|
|
||||||
def test_update_config(self, acfactory):
|
def test_update_config(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
ac1.update_config({"mvbox_move": False})
|
ac1.update_config({"bcc_self": True})
|
||||||
assert ac1.get_config("mvbox_move") == "0"
|
assert ac1.get_config("bcc_self") == "1"
|
||||||
|
|
||||||
def test_has_bccself(self, acfactory):
|
def test_has_bccself(self, acfactory):
|
||||||
ac1 = acfactory.get_unconfigured_account()
|
ac1 = acfactory.get_unconfigured_account()
|
||||||
|
|||||||
@@ -155,18 +155,6 @@ pub enum Config {
|
|||||||
#[strum(props(default = "1"))]
|
#[strum(props(default = "1"))]
|
||||||
MdnsEnabled,
|
MdnsEnabled,
|
||||||
|
|
||||||
/// True if chat messages should be moved to a separate folder. Auto-sent messages like sync
|
|
||||||
/// ones are moved there anyway.
|
|
||||||
#[strum(props(default = "1"))]
|
|
||||||
MvboxMove,
|
|
||||||
|
|
||||||
/// Watch for new messages in the "Mvbox" (aka DeltaChat folder) only.
|
|
||||||
///
|
|
||||||
/// This will not entirely disable other folders, e.g. the spam folder will also still
|
|
||||||
/// be watched for new messages.
|
|
||||||
#[strum(props(default = "0"))]
|
|
||||||
OnlyFetchMvbox,
|
|
||||||
|
|
||||||
/// Whether to show classic emails or only chat messages.
|
/// Whether to show classic emails or only chat messages.
|
||||||
#[strum(props(default = "2"))] // also change ShowEmails.default() on changes
|
#[strum(props(default = "2"))] // also change ShowEmails.default() on changes
|
||||||
ShowEmails,
|
ShowEmails,
|
||||||
@@ -268,9 +256,6 @@ pub enum Config {
|
|||||||
/// Configured folder for incoming messages.
|
/// Configured folder for incoming messages.
|
||||||
ConfiguredInboxFolder,
|
ConfiguredInboxFolder,
|
||||||
|
|
||||||
/// Configured folder for chat messages.
|
|
||||||
ConfiguredMvboxFolder,
|
|
||||||
|
|
||||||
/// Unix timestamp of the last successful configuration.
|
/// Unix timestamp of the last successful configuration.
|
||||||
ConfiguredTimestamp,
|
ConfiguredTimestamp,
|
||||||
|
|
||||||
@@ -467,7 +452,6 @@ impl Config {
|
|||||||
self,
|
self,
|
||||||
Self::Displayname
|
Self::Displayname
|
||||||
| Self::MdnsEnabled
|
| Self::MdnsEnabled
|
||||||
| Self::MvboxMove
|
|
||||||
| Self::ShowEmails
|
| Self::ShowEmails
|
||||||
| Self::Selfavatar
|
| Self::Selfavatar
|
||||||
| Self::Selfstatus,
|
| Self::Selfstatus,
|
||||||
@@ -476,10 +460,7 @@ impl Config {
|
|||||||
|
|
||||||
/// Whether the config option needs an IO scheduler restart to take effect.
|
/// Whether the config option needs an IO scheduler restart to take effect.
|
||||||
pub(crate) fn needs_io_restart(&self) -> bool {
|
pub(crate) fn needs_io_restart(&self) -> bool {
|
||||||
matches!(
|
matches!(self, Config::ConfiguredAddr)
|
||||||
self,
|
|
||||||
Config::MvboxMove | Config::OnlyFetchMvbox | Config::ConfiguredAddr
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,12 +575,6 @@ impl Context {
|
|||||||
.is_some_and(|x| x != 0))
|
.is_some_and(|x| x != 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if movebox ("DeltaChat" folder) should be watched.
|
|
||||||
pub(crate) async fn should_watch_mvbox(&self) -> Result<bool> {
|
|
||||||
Ok(self.get_config_bool(Config::MvboxMove).await?
|
|
||||||
|| self.get_config_bool(Config::OnlyFetchMvbox).await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if sync messages should be sent.
|
/// Returns true if sync messages should be sent.
|
||||||
pub(crate) async fn should_send_sync_msgs(&self) -> Result<bool> {
|
pub(crate) async fn should_send_sync_msgs(&self) -> Result<bool> {
|
||||||
Ok(self.get_config_bool(Config::SyncMsgs).await?
|
Ok(self.get_config_bool(Config::SyncMsgs).await?
|
||||||
@@ -681,8 +656,6 @@ impl Context {
|
|||||||
| Config::ProxyEnabled
|
| Config::ProxyEnabled
|
||||||
| Config::BccSelf
|
| Config::BccSelf
|
||||||
| Config::MdnsEnabled
|
| Config::MdnsEnabled
|
||||||
| Config::MvboxMove
|
|
||||||
| Config::OnlyFetchMvbox
|
|
||||||
| Config::Configured
|
| Config::Configured
|
||||||
| Config::Bot
|
| Config::Bot
|
||||||
| Config::NotifyAboutWrongPw
|
| Config::NotifyAboutWrongPw
|
||||||
@@ -705,11 +678,6 @@ impl Context {
|
|||||||
pub async fn set_config(&self, key: Config, value: Option<&str>) -> Result<()> {
|
pub async fn set_config(&self, key: Config, value: Option<&str>) -> Result<()> {
|
||||||
Self::check_config(key, value)?;
|
Self::check_config(key, value)?;
|
||||||
|
|
||||||
let n_transports = self.count_transports().await?;
|
|
||||||
if n_transports > 1 && matches!(key, Config::MvboxMove | Config::OnlyFetchMvbox) {
|
|
||||||
bail!("Cannot reconfigure {key} when multiple transports are configured");
|
|
||||||
}
|
|
||||||
|
|
||||||
let _pause = match key.needs_io_restart() {
|
let _pause = match key.needs_io_restart() {
|
||||||
true => self.scheduler.pause(self).await?,
|
true => self.scheduler.pause(self).await?,
|
||||||
_ => Default::default(),
|
_ => Default::default(),
|
||||||
@@ -788,12 +756,6 @@ impl Context {
|
|||||||
.set_raw_config(key.as_ref(), value.map(|s| s.to_lowercase()).as_deref())
|
.set_raw_config(key.as_ref(), value.map(|s| s.to_lowercase()).as_deref())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Config::MvboxMove => {
|
|
||||||
self.sql.set_raw_config(key.as_ref(), value).await?;
|
|
||||||
self.sql
|
|
||||||
.set_raw_config(constants::DC_FOLDERS_CONFIGURED_KEY, None)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Config::ConfiguredAddr => {
|
Config::ConfiguredAddr => {
|
||||||
let Some(addr) = value else {
|
let Some(addr) = value else {
|
||||||
bail!("Cannot unset configured_addr");
|
bail!("Cannot unset configured_addr");
|
||||||
|
|||||||
@@ -196,11 +196,11 @@ async fn test_sync() -> Result<()> {
|
|||||||
sync(&alice0, &alice1).await;
|
sync(&alice0, &alice1).await;
|
||||||
assert_eq!(alice1.get_config_bool(Config::MdnsEnabled).await?, false);
|
assert_eq!(alice1.get_config_bool(Config::MdnsEnabled).await?, false);
|
||||||
|
|
||||||
for key in [Config::ShowEmails, Config::MvboxMove] {
|
{
|
||||||
let val = alice0.get_config_bool(key).await?;
|
let val = alice0.get_config_bool(Config::ShowEmails).await?;
|
||||||
alice0.set_config_bool(key, !val).await?;
|
alice0.set_config_bool(Config::ShowEmails, !val).await?;
|
||||||
sync(&alice0, &alice1).await;
|
sync(&alice0, &alice1).await;
|
||||||
assert_eq!(alice1.get_config_bool(key).await?, !val);
|
assert_eq!(alice1.get_config_bool(Config::ShowEmails).await?, !val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Config::SyncMsgs` mustn't be synced.
|
// `Config::SyncMsgs` mustn't be synced.
|
||||||
|
|||||||
@@ -273,31 +273,16 @@ impl Context {
|
|||||||
(¶m.addr,),
|
(¶m.addr,),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
&& self
|
||||||
// Should be checked before `MvboxMove` because the latter makes no sense in presense of
|
|
||||||
// `OnlyFetchMvbox` and even grayed out in the UIs in this case.
|
|
||||||
if self.get_config(Config::OnlyFetchMvbox).await?.as_deref() != Some("0") {
|
|
||||||
bail!(
|
|
||||||
"To use additional relays, disable the legacy option \"Settings / Advanced / Only Fetch from DeltaChat Folder\"."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if self.get_config(Config::MvboxMove).await?.as_deref() != Some("0") {
|
|
||||||
bail!(
|
|
||||||
"To use additional relays, disable the legacy option \"Settings / Advanced / Move automatically to DeltaChat Folder\"."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self
|
|
||||||
.sql
|
.sql
|
||||||
.count("SELECT COUNT(*) FROM transports", ())
|
.count("SELECT COUNT(*) FROM transports", ())
|
||||||
.await?
|
.await?
|
||||||
>= MAX_TRANSPORT_RELAYS
|
>= MAX_TRANSPORT_RELAYS
|
||||||
{
|
{
|
||||||
bail!(
|
bail!(
|
||||||
"You have reached the maximum number of relays ({}).",
|
"You have reached the maximum number of relays ({}).",
|
||||||
MAX_TRANSPORT_RELAYS
|
MAX_TRANSPORT_RELAYS
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let provider = match configure(self, param).await {
|
let provider = match configure(self, param).await {
|
||||||
@@ -510,6 +495,7 @@ async fn get_configured_param(
|
|||||||
.collect(),
|
.collect(),
|
||||||
imap_user: param.imap.user.clone(),
|
imap_user: param.imap.user.clone(),
|
||||||
imap_password: param.imap.password.clone(),
|
imap_password: param.imap.password.clone(),
|
||||||
|
imap_folder: Some(param.imap.folder.clone()).filter(|folder| !folder.is_empty()),
|
||||||
smtp: servers
|
smtp: servers
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|params| {
|
.filter_map(|params| {
|
||||||
@@ -605,10 +591,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
|
|||||||
progress!(ctx, 900);
|
progress!(ctx, 900);
|
||||||
|
|
||||||
let is_configured = ctx.is_configured().await?;
|
let is_configured = ctx.is_configured().await?;
|
||||||
if !is_configured {
|
|
||||||
ctx.sql.set_raw_config("mvbox_move", Some("0")).await?;
|
|
||||||
ctx.sql.set_raw_config("only_fetch_mvbox", None).await?;
|
|
||||||
}
|
|
||||||
if !ctx.get_config_bool(Config::FixIsChatmail).await? {
|
if !ctx.get_config_bool(Config::FixIsChatmail).await? {
|
||||||
if imap_session.is_chatmail() {
|
if imap_session.is_chatmail() {
|
||||||
ctx.sql.set_raw_config("is_chatmail", Some("1")).await?;
|
ctx.sql.set_raw_config("is_chatmail", Some("1")).await?;
|
||||||
@@ -772,7 +754,7 @@ pub enum Error {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::login_param::EnteredServerLoginParam;
|
use crate::login_param::EnteredImapLoginParam;
|
||||||
use crate::test_utils::TestContext;
|
use crate::test_utils::TestContext;
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
@@ -791,7 +773,7 @@ mod tests {
|
|||||||
let entered_param = EnteredLoginParam {
|
let entered_param = EnteredLoginParam {
|
||||||
addr: "alice@example.org".to_string(),
|
addr: "alice@example.org".to_string(),
|
||||||
|
|
||||||
imap: EnteredServerLoginParam {
|
imap: EnteredImapLoginParam {
|
||||||
user: "alice@example.net".to_string(),
|
user: "alice@example.net".to_string(),
|
||||||
password: "foobar".to_string(),
|
password: "foobar".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -210,11 +210,6 @@ pub const WORSE_IMAGE_SIZE: u32 = 640;
|
|||||||
/// usage by UIs.
|
/// usage by UIs.
|
||||||
pub const MAX_RCVD_IMAGE_PIXELS: u32 = 50_000_000;
|
pub const MAX_RCVD_IMAGE_PIXELS: u32 = 50_000_000;
|
||||||
|
|
||||||
// Key for the folder configuration version (see below).
|
|
||||||
pub(crate) const DC_FOLDERS_CONFIGURED_KEY: &str = "folders_configured";
|
|
||||||
// this value can be increased if the folder configuration is changed and must be redone on next program start
|
|
||||||
pub(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 5;
|
|
||||||
|
|
||||||
// If more recipients are needed in SMTP's `RCPT TO:` header, the recipient list is split into
|
// If more recipients are needed in SMTP's `RCPT TO:` header, the recipient list is split into
|
||||||
// chunks. This does not affect MIME's `To:` header. Can be overwritten by setting
|
// chunks. This does not affect MIME's `To:` header. Can be overwritten by setting
|
||||||
// `max_smtp_rcpt_to` in the provider db.
|
// `max_smtp_rcpt_to` in the provider db.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::constants::{self, DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSI
|
|||||||
use crate::contact::{Contact, ContactId};
|
use crate::contact::{Contact, ContactId};
|
||||||
use crate::debug_logging::DebugLogging;
|
use crate::debug_logging::DebugLogging;
|
||||||
use crate::events::{Event, EventEmitter, EventType, Events};
|
use crate::events::{Event, EventEmitter, EventType, Events};
|
||||||
use crate::imap::{FolderMeaning, Imap, ServerMetadata};
|
use crate::imap::{Imap, ServerMetadata};
|
||||||
use crate::key::self_fingerprint;
|
use crate::key::self_fingerprint;
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::logged_debug_assert;
|
use crate::logged_debug_assert;
|
||||||
@@ -29,7 +29,7 @@ use crate::net::tls::TlsSessionStore;
|
|||||||
use crate::peer_channels::Iroh;
|
use crate::peer_channels::Iroh;
|
||||||
use crate::push::PushSubscriber;
|
use crate::push::PushSubscriber;
|
||||||
use crate::quota::QuotaInfo;
|
use crate::quota::QuotaInfo;
|
||||||
use crate::scheduler::{ConnectivityStore, SchedulerState, convert_folder_meaning};
|
use crate::scheduler::{ConnectivityStore, SchedulerState};
|
||||||
use crate::sql::Sql;
|
use crate::sql::Sql;
|
||||||
use crate::stock_str::StockStrings;
|
use crate::stock_str::StockStrings;
|
||||||
use crate::timesmearing::SmearedTimestamp;
|
use crate::timesmearing::SmearedTimestamp;
|
||||||
@@ -623,17 +623,10 @@ impl Context {
|
|||||||
let mut session = connection.prepare(self).await?;
|
let mut session = connection.prepare(self).await?;
|
||||||
|
|
||||||
// Fetch IMAP folders.
|
// Fetch IMAP folders.
|
||||||
// Inbox is fetched before Mvbox because fetching from Inbox
|
let folder = connection.folder.clone();
|
||||||
// may result in moving some messages to Mvbox.
|
connection
|
||||||
for folder_meaning in [FolderMeaning::Inbox, FolderMeaning::Mvbox] {
|
.fetch_move_delete(self, &mut session, &folder)
|
||||||
if let Some((_folder_config, watch_folder)) =
|
.await?;
|
||||||
convert_folder_meaning(self, folder_meaning).await?
|
|
||||||
{
|
|
||||||
connection
|
|
||||||
.fetch_move_delete(self, &mut session, &watch_folder, folder_meaning)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update quota (to send warning if full) - but only check it once in a while.
|
// Update quota (to send warning if full) - but only check it once in a while.
|
||||||
// note: For now this only checks quota of primary transport,
|
// note: For now this only checks quota of primary transport,
|
||||||
@@ -644,7 +637,7 @@ impl Context {
|
|||||||
DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT,
|
DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
&& let Err(err) = self.update_recent_quota(&mut session).await
|
&& let Err(err) = self.update_recent_quota(&mut session, folder).await
|
||||||
{
|
{
|
||||||
warn!(self, "Failed to update quota: {err:#}.");
|
warn!(self, "Failed to update quota: {err:#}.");
|
||||||
}
|
}
|
||||||
@@ -884,23 +877,6 @@ impl Context {
|
|||||||
Err(err) => format!("<key failure: {err}>"),
|
Err(err) => format!("<key failure: {err}>"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mvbox_move = self.get_config_int(Config::MvboxMove).await?;
|
|
||||||
let only_fetch_mvbox = self.get_config_int(Config::OnlyFetchMvbox).await?;
|
|
||||||
let folders_configured = self
|
|
||||||
.sql
|
|
||||||
.get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let configured_inbox_folder = self
|
|
||||||
.get_config(Config::ConfiguredInboxFolder)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_else(|| "<unset>".to_string());
|
|
||||||
let configured_mvbox_folder = self
|
|
||||||
.get_config(Config::ConfiguredMvboxFolder)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_else(|| "<unset>".to_string());
|
|
||||||
|
|
||||||
let mut res = get_info();
|
let mut res = get_info();
|
||||||
|
|
||||||
// insert values
|
// insert values
|
||||||
@@ -976,14 +952,6 @@ impl Context {
|
|||||||
.await?
|
.await?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
res.insert("mvbox_move", mvbox_move.to_string());
|
|
||||||
res.insert("only_fetch_mvbox", only_fetch_mvbox.to_string());
|
|
||||||
res.insert(
|
|
||||||
constants::DC_FOLDERS_CONFIGURED_KEY,
|
|
||||||
folders_configured.to_string(),
|
|
||||||
);
|
|
||||||
res.insert("configured_inbox_folder", configured_inbox_folder);
|
|
||||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
|
||||||
res.insert("mdns_enabled", mdns_enabled.to_string());
|
res.insert("mdns_enabled", mdns_enabled.to_string());
|
||||||
res.insert("bcc_self", bcc_self.to_string());
|
res.insert("bcc_self", bcc_self.to_string());
|
||||||
res.insert("sync_msgs", sync_msgs.to_string());
|
res.insert("sync_msgs", sync_msgs.to_string());
|
||||||
@@ -1283,12 +1251,6 @@ ORDER BY m.timestamp DESC,m.id DESC",
|
|||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if given folder name is the name of the "DeltaChat" folder.
|
|
||||||
pub async fn is_mvbox(&self, folder_name: &str) -> Result<bool> {
|
|
||||||
let mvbox = self.get_config(Config::ConfiguredMvboxFolder).await?;
|
|
||||||
Ok(mvbox.as_deref() == Some(folder_name))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
|
pub(crate) fn derive_blobdir(dbfile: &Path) -> PathBuf {
|
||||||
let mut blob_fname = OsString::new();
|
let mut blob_fname = OsString::new();
|
||||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||||
|
|||||||
274
src/imap.rs
274
src/imap.rs
@@ -27,13 +27,14 @@ use crate::calls::{
|
|||||||
use crate::chat::{self, ChatId, ChatIdBlocked, add_device_msg};
|
use crate::chat::{self, ChatId, ChatIdBlocked, add_device_msg};
|
||||||
use crate::chatlist_events;
|
use crate::chatlist_events;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{self, Blocked, DC_VERSION_STR};
|
use crate::constants::{Blocked, DC_VERSION_STR};
|
||||||
use crate::contact::ContactId;
|
use crate::contact::ContactId;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::ensure_and_debug_assert;
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||||
use crate::log::{LogExt, warn};
|
use crate::log::{LogExt, warn};
|
||||||
use crate::message::{self, Message, MessageState, MessengerMessage, MsgId};
|
use crate::message::{self, Message, MessageState, MsgId};
|
||||||
use crate::mimeparser;
|
use crate::mimeparser;
|
||||||
use crate::net::proxy::ProxyConfig;
|
use crate::net::proxy::ProxyConfig;
|
||||||
use crate::net::session::SessionStream;
|
use crate::net::session::SessionStream;
|
||||||
@@ -91,6 +92,9 @@ pub(crate) struct Imap {
|
|||||||
|
|
||||||
oauth2: bool,
|
oauth2: bool,
|
||||||
|
|
||||||
|
/// Watched folder.
|
||||||
|
pub(crate) folder: String,
|
||||||
|
|
||||||
authentication_failed_once: bool,
|
authentication_failed_once: bool,
|
||||||
|
|
||||||
pub(crate) connectivity: ConnectivityStore,
|
pub(crate) connectivity: ConnectivityStore,
|
||||||
@@ -162,7 +166,6 @@ pub enum FolderMeaning {
|
|||||||
/// Spam folder.
|
/// Spam folder.
|
||||||
Spam,
|
Spam,
|
||||||
Inbox,
|
Inbox,
|
||||||
Mvbox,
|
|
||||||
Trash,
|
Trash,
|
||||||
|
|
||||||
/// Virtual folders.
|
/// Virtual folders.
|
||||||
@@ -174,19 +177,6 @@ pub enum FolderMeaning {
|
|||||||
Virtual,
|
Virtual,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderMeaning {
|
|
||||||
pub fn to_config(self) -> Option<Config> {
|
|
||||||
match self {
|
|
||||||
FolderMeaning::Unknown => None,
|
|
||||||
FolderMeaning::Spam => None,
|
|
||||||
FolderMeaning::Inbox => Some(Config::ConfiguredInboxFolder),
|
|
||||||
FolderMeaning::Mvbox => Some(Config::ConfiguredMvboxFolder),
|
|
||||||
FolderMeaning::Trash => None,
|
|
||||||
FolderMeaning::Virtual => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UidGrouper<T: Iterator<Item = (i64, u32, String)>> {
|
struct UidGrouper<T: Iterator<Item = (i64, u32, String)>> {
|
||||||
inner: Peekable<T>,
|
inner: Peekable<T>,
|
||||||
}
|
}
|
||||||
@@ -263,6 +253,11 @@ impl Imap {
|
|||||||
let addr = ¶m.addr;
|
let addr = ¶m.addr;
|
||||||
let strict_tls = param.strict_tls(proxy_config.is_some());
|
let strict_tls = param.strict_tls(proxy_config.is_some());
|
||||||
let oauth2 = param.oauth2;
|
let oauth2 = param.oauth2;
|
||||||
|
let folder = param
|
||||||
|
.imap_folder
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "INBOX".to_string());
|
||||||
|
ensure_and_debug_assert!(!folder.is_empty(), "Watched folder name cannot be empty");
|
||||||
let (resync_request_sender, resync_request_receiver) = async_channel::bounded(1);
|
let (resync_request_sender, resync_request_receiver) = async_channel::bounded(1);
|
||||||
Ok(Imap {
|
Ok(Imap {
|
||||||
transport_id,
|
transport_id,
|
||||||
@@ -273,6 +268,7 @@ impl Imap {
|
|||||||
proxy_config,
|
proxy_config,
|
||||||
strict_tls,
|
strict_tls,
|
||||||
oauth2,
|
oauth2,
|
||||||
|
folder,
|
||||||
authentication_failed_once: false,
|
authentication_failed_once: false,
|
||||||
connectivity: Default::default(),
|
connectivity: Default::default(),
|
||||||
conn_last_try: UNIX_EPOCH,
|
conn_last_try: UNIX_EPOCH,
|
||||||
@@ -485,7 +481,7 @@ impl Imap {
|
|||||||
/// that folders are created and IMAP capabilities are determined.
|
/// that folders are created and IMAP capabilities are determined.
|
||||||
pub(crate) async fn prepare(&mut self, context: &Context) -> Result<Session> {
|
pub(crate) async fn prepare(&mut self, context: &Context) -> Result<Session> {
|
||||||
let configuring = false;
|
let configuring = false;
|
||||||
let mut session = match self.connect(context, configuring).await {
|
let session = match self.connect(context, configuring).await {
|
||||||
Ok(session) => session,
|
Ok(session) => session,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.connectivity.set_err(context, &err);
|
self.connectivity.set_err(context, &err);
|
||||||
@@ -493,14 +489,6 @@ impl Imap {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let folders_configured = context
|
|
||||||
.sql
|
|
||||||
.get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
|
|
||||||
.await?;
|
|
||||||
if folders_configured.unwrap_or_default() < constants::DC_FOLDERS_CONFIGURED_VERSION {
|
|
||||||
self.configure_folders(context, &mut session).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,15 +501,15 @@ impl Imap {
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
watch_folder: &str,
|
watch_folder: &str,
|
||||||
folder_meaning: FolderMeaning,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
ensure_and_debug_assert!(!watch_folder.is_empty(), "Watched folder cannot be empty");
|
||||||
if !context.sql.is_open().await {
|
if !context.sql.is_open().await {
|
||||||
// probably shutdown
|
// probably shutdown
|
||||||
bail!("IMAP operation attempted while it is torn down");
|
bail!("IMAP operation attempted while it is torn down");
|
||||||
}
|
}
|
||||||
|
|
||||||
let msgs_fetched = self
|
let msgs_fetched = self
|
||||||
.fetch_new_messages(context, session, watch_folder, folder_meaning)
|
.fetch_new_messages(context, session, watch_folder)
|
||||||
.await
|
.await
|
||||||
.context("fetch_new_messages")?;
|
.context("fetch_new_messages")?;
|
||||||
if msgs_fetched && context.get_config_delete_device_after().await?.is_some() {
|
if msgs_fetched && context.get_config_delete_device_after().await?.is_some() {
|
||||||
@@ -549,14 +537,7 @@ impl Imap {
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
folder: &str,
|
folder: &str,
|
||||||
folder_meaning: FolderMeaning,
|
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
if should_ignore_folder(context, folder, folder_meaning).await? {
|
|
||||||
info!(context, "Not fetching from {folder:?}.");
|
|
||||||
session.new_mail = false;
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let folder_exists = session
|
let folder_exists = session
|
||||||
.select_with_uidvalidity(context, folder)
|
.select_with_uidvalidity(context, folder)
|
||||||
.await
|
.await
|
||||||
@@ -573,9 +554,7 @@ impl Imap {
|
|||||||
|
|
||||||
let mut read_cnt = 0;
|
let mut read_cnt = 0;
|
||||||
loop {
|
loop {
|
||||||
let (n, fetch_more) = self
|
let (n, fetch_more) = self.fetch_new_msg_batch(context, session, folder).await?;
|
||||||
.fetch_new_msg_batch(context, session, folder, folder_meaning)
|
|
||||||
.await?;
|
|
||||||
read_cnt += n;
|
read_cnt += n;
|
||||||
if !fetch_more {
|
if !fetch_more {
|
||||||
return Ok(read_cnt > 0);
|
return Ok(read_cnt > 0);
|
||||||
@@ -590,7 +569,6 @@ impl Imap {
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
folder: &str,
|
folder: &str,
|
||||||
folder_meaning: FolderMeaning,
|
|
||||||
) -> Result<(usize, bool)> {
|
) -> Result<(usize, bool)> {
|
||||||
let transport_id = self.transport_id;
|
let transport_id = self.transport_id;
|
||||||
let uid_validity = get_uidvalidity(context, transport_id, folder).await?;
|
let uid_validity = get_uidvalidity(context, transport_id, folder).await?;
|
||||||
@@ -660,13 +638,7 @@ impl Imap {
|
|||||||
info!(context, "Deleting locally deleted message {message_id}.");
|
info!(context, "Deleting locally deleted message {message_id}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let _target;
|
let target = if delete { "" } else { folder };
|
||||||
let target = if delete {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
_target = target_folder(context, folder, folder_meaning, &headers).await?;
|
|
||||||
&_target
|
|
||||||
};
|
|
||||||
|
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
@@ -694,18 +666,9 @@ impl Imap {
|
|||||||
// message, move it to the movebox and then download the second message before
|
// message, move it to the movebox and then download the second message before
|
||||||
// downloading the first one, if downloading from inbox before moving is allowed.
|
// downloading the first one, if downloading from inbox before moving is allowed.
|
||||||
if folder == target
|
if folder == target
|
||||||
// Never download messages directly from the spam folder.
|
&& prefetch_should_download(context, &headers, &message_id, fetch_response.flags())
|
||||||
// If the sender is known, the message will be moved to the Inbox or Mvbox
|
.await
|
||||||
// and then we download the message from there.
|
.context("prefetch_should_download")?
|
||||||
// Also see `spam_target_folder_cfg()`.
|
|
||||||
&& folder_meaning != FolderMeaning::Spam
|
|
||||||
&& prefetch_should_download(
|
|
||||||
context,
|
|
||||||
&headers,
|
|
||||||
&message_id,
|
|
||||||
fetch_response.flags(),
|
|
||||||
)
|
|
||||||
.await.context("prefetch_should_download")?
|
|
||||||
{
|
{
|
||||||
if headers
|
if headers
|
||||||
.get_header_value(HeaderDef::ChatIsPostMessage)
|
.get_header_value(HeaderDef::ChatIsPostMessage)
|
||||||
@@ -1621,13 +1584,8 @@ impl Session {
|
|||||||
// Store new encrypted device token on the server
|
// Store new encrypted device token on the server
|
||||||
// even if it is the same as the old one.
|
// even if it is the same as the old one.
|
||||||
if let Some(encrypted_device_token) = new_encrypted_device_token {
|
if let Some(encrypted_device_token) = new_encrypted_device_token {
|
||||||
let folder = context
|
|
||||||
.get_config(Config::ConfiguredInboxFolder)
|
|
||||||
.await?
|
|
||||||
.context("INBOX is not configured")?;
|
|
||||||
|
|
||||||
self.run_command_and_check_ok(&format_setmetadata(
|
self.run_command_and_check_ok(&format_setmetadata(
|
||||||
&folder,
|
"INBOX",
|
||||||
&encrypted_device_token,
|
&encrypted_device_token,
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
@@ -1672,117 +1630,6 @@ impl Session {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to configure mvbox.
|
|
||||||
///
|
|
||||||
/// Tries to find any folder examining `folders` in the order they go.
|
|
||||||
/// This method does not use LIST command to ensure that
|
|
||||||
/// configuration works even if mailbox lookup is forbidden via Access Control List (see
|
|
||||||
/// <https://datatracker.ietf.org/doc/html/rfc4314>).
|
|
||||||
///
|
|
||||||
/// Returns first found folder name.
|
|
||||||
async fn configure_mvbox<'a>(
|
|
||||||
&mut self,
|
|
||||||
context: &Context,
|
|
||||||
folders: &[&'a str],
|
|
||||||
) -> Result<Option<&'a str>> {
|
|
||||||
// Close currently selected folder if needed.
|
|
||||||
// We are going to select folders using low-level EXAMINE operations below.
|
|
||||||
self.maybe_close_folder(context).await?;
|
|
||||||
|
|
||||||
for folder in folders {
|
|
||||||
info!(context, "Looking for MVBOX-folder \"{}\"...", &folder);
|
|
||||||
let res = self.examine(&folder).await;
|
|
||||||
if res.is_ok() {
|
|
||||||
info!(
|
|
||||||
context,
|
|
||||||
"MVBOX-folder {:?} successfully selected, using it.", &folder
|
|
||||||
);
|
|
||||||
self.close().await?;
|
|
||||||
// Before moving emails to the mvbox we need to remember its UIDVALIDITY, otherwise
|
|
||||||
// emails moved before that wouldn't be fetched but considered "old" instead.
|
|
||||||
let folder_exists = self.select_with_uidvalidity(context, folder).await?;
|
|
||||||
ensure!(folder_exists, "No MVBOX folder {:?}??", &folder);
|
|
||||||
return Ok(Some(folder));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Imap {
|
|
||||||
pub(crate) async fn configure_folders(
|
|
||||||
&mut self,
|
|
||||||
context: &Context,
|
|
||||||
session: &mut Session,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut folders = session
|
|
||||||
.list(Some(""), Some("*"))
|
|
||||||
.await
|
|
||||||
.context("list_folders failed")?;
|
|
||||||
let mut delimiter = ".".to_string();
|
|
||||||
let mut delimiter_is_default = true;
|
|
||||||
let mut folder_configs = BTreeMap::new();
|
|
||||||
|
|
||||||
while let Some(folder) = folders.try_next().await? {
|
|
||||||
info!(context, "Scanning folder: {:?}", folder);
|
|
||||||
|
|
||||||
// Update the delimiter iff there is a different one, but only once.
|
|
||||||
if let Some(d) = folder.delimiter()
|
|
||||||
&& delimiter_is_default
|
|
||||||
&& !d.is_empty()
|
|
||||||
&& delimiter != d
|
|
||||||
{
|
|
||||||
delimiter = d.to_string();
|
|
||||||
delimiter_is_default = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let folder_meaning = get_folder_meaning_by_attrs(folder.attributes());
|
|
||||||
let folder_name_meaning = get_folder_meaning_by_name(folder.name());
|
|
||||||
if let Some(config) = folder_meaning.to_config() {
|
|
||||||
// Always takes precedence
|
|
||||||
folder_configs.insert(config, folder.name().to_string());
|
|
||||||
} else if let Some(config) = folder_name_meaning.to_config() {
|
|
||||||
// only set if none has been already set
|
|
||||||
folder_configs
|
|
||||||
.entry(config)
|
|
||||||
.or_insert_with(|| folder.name().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drop(folders);
|
|
||||||
|
|
||||||
info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
|
|
||||||
|
|
||||||
let fallback_folder = format!("INBOX{delimiter}DeltaChat");
|
|
||||||
let mvbox_folder = session
|
|
||||||
.configure_mvbox(context, &["DeltaChat", &fallback_folder])
|
|
||||||
.await
|
|
||||||
.context("failed to configure mvbox")?;
|
|
||||||
|
|
||||||
context
|
|
||||||
.set_config_internal(Config::ConfiguredInboxFolder, Some("INBOX"))
|
|
||||||
.await?;
|
|
||||||
if let Some(mvbox_folder) = mvbox_folder {
|
|
||||||
info!(context, "Setting MVBOX FOLDER TO {}", &mvbox_folder);
|
|
||||||
context
|
|
||||||
.set_config_internal(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
for (config, name) in folder_configs {
|
|
||||||
context.set_config_internal(config, Some(&name)).await?;
|
|
||||||
}
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.set_raw_config_int(
|
|
||||||
constants::DC_FOLDERS_CONFIGURED_KEY,
|
|
||||||
constants::DC_FOLDERS_CONFIGURED_VERSION,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!(context, "FINISHED configuring IMAP-folders.");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
@@ -1916,15 +1763,7 @@ async fn spam_target_folder_cfg(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if needs_move_to_mvbox(context, headers).await?
|
Ok(Some(Config::ConfiguredInboxFolder))
|
||||||
// If OnlyFetchMvbox is set, we don't want to move the message to
|
|
||||||
// the inbox where we wouldn't fetch it again:
|
|
||||||
|| context.get_config_bool(Config::OnlyFetchMvbox).await?
|
|
||||||
{
|
|
||||||
Ok(Some(Config::ConfiguredMvboxFolder))
|
|
||||||
} else {
|
|
||||||
Ok(Some(Config::ConfiguredInboxFolder))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `ConfiguredInboxFolder` or `ConfiguredMvboxFolder` if
|
/// Returns `ConfiguredInboxFolder` or `ConfiguredMvboxFolder` if
|
||||||
@@ -1935,16 +1774,12 @@ pub async fn target_folder_cfg(
|
|||||||
folder_meaning: FolderMeaning,
|
folder_meaning: FolderMeaning,
|
||||||
headers: &[mailparse::MailHeader<'_>],
|
headers: &[mailparse::MailHeader<'_>],
|
||||||
) -> Result<Option<Config>> {
|
) -> Result<Option<Config>> {
|
||||||
if context.is_mvbox(folder).await? {
|
if folder == "DeltaChat" {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if folder_meaning == FolderMeaning::Spam {
|
if folder_meaning == FolderMeaning::Spam {
|
||||||
spam_target_folder_cfg(context, headers).await
|
spam_target_folder_cfg(context, headers).await
|
||||||
} else if folder_meaning == FolderMeaning::Inbox
|
|
||||||
&& needs_move_to_mvbox(context, headers).await?
|
|
||||||
{
|
|
||||||
Ok(Some(Config::ConfiguredMvboxFolder))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -1965,36 +1800,6 @@ pub async fn target_folder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn needs_move_to_mvbox(
|
|
||||||
context: &Context,
|
|
||||||
headers: &[mailparse::MailHeader<'_>],
|
|
||||||
) -> Result<bool> {
|
|
||||||
let has_chat_version = headers.get_header_value(HeaderDef::ChatVersion).is_some();
|
|
||||||
if !context.get_config_bool(Config::MvboxMove).await? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if headers
|
|
||||||
.get_header_value(HeaderDef::AutocryptSetupMessage)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
// do not move setup messages;
|
|
||||||
// there may be a non-delta device that wants to handle it
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_chat_version {
|
|
||||||
Ok(true)
|
|
||||||
} else if let Some(parent) = get_prefetch_parent_message(context, headers).await? {
|
|
||||||
match parent.is_dc_message {
|
|
||||||
MessengerMessage::No => Ok(false),
|
|
||||||
MessengerMessage::Yes | MessengerMessage::Reply => Ok(true),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to get the folder meaning by the name of the folder only used if the server does not support XLIST.
|
/// Try to get the folder meaning by the name of the folder only used if the server does not support XLIST.
|
||||||
// TODO: lots languages missing - maybe there is a list somewhere on other MUAs?
|
// TODO: lots languages missing - maybe there is a list somewhere on other MUAs?
|
||||||
// however, if we fail to find out the sent-folder,
|
// however, if we fail to find out the sent-folder,
|
||||||
@@ -2325,21 +2130,6 @@ async fn get_modseq(context: &Context, transport_id: u32, folder: &str) -> Resul
|
|||||||
.unwrap_or(0))
|
.unwrap_or(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to ignore fetching messages from a folder.
|
|
||||||
///
|
|
||||||
/// This caters for the [`Config::OnlyFetchMvbox`] setting which means mails from folders
|
|
||||||
/// not explicitly watched should not be fetched.
|
|
||||||
async fn should_ignore_folder(
|
|
||||||
context: &Context,
|
|
||||||
folder: &str,
|
|
||||||
folder_meaning: FolderMeaning,
|
|
||||||
) -> Result<bool> {
|
|
||||||
if !context.get_config_bool(Config::OnlyFetchMvbox).await? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Ok(!(context.is_mvbox(folder).await? || folder_meaning == FolderMeaning::Spam))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a list of sequence/uid sets. The returned sets have each no more than around 1000
|
/// Builds a list of sequence/uid sets. The returned sets have each no more than around 1000
|
||||||
/// characters because according to <https://tools.ietf.org/html/rfc2683#section-3.2.1.5>
|
/// characters because according to <https://tools.ietf.org/html/rfc2683#section-3.2.1.5>
|
||||||
/// command lines should not be much more than 1000 chars (servers should allow at least 8000 chars)
|
/// command lines should not be much more than 1000 chars (servers should allow at least 8000 chars)
|
||||||
@@ -2399,23 +2189,5 @@ impl std::fmt::Display for UidRange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn get_watched_folder_configs(context: &Context) -> Result<Vec<Config>> {
|
|
||||||
let mut res = vec![Config::ConfiguredInboxFolder];
|
|
||||||
if context.should_watch_mvbox().await? {
|
|
||||||
res.push(Config::ConfiguredMvboxFolder);
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn get_watched_folders(context: &Context) -> Result<Vec<String>> {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
for folder_config in get_watched_folder_configs(context).await? {
|
|
||||||
if let Some(folder) = context.get_config(folder_config).await? {
|
|
||||||
res.push(folder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod imap_tests;
|
mod imap_tests;
|
||||||
|
|||||||
@@ -115,11 +115,7 @@ impl Session {
|
|||||||
|
|
||||||
impl Imap {
|
impl Imap {
|
||||||
/// Idle using polling.
|
/// Idle using polling.
|
||||||
pub(crate) async fn fake_idle(
|
pub(crate) async fn fake_idle(&mut self, context: &Context, watch_folder: &str) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
context: &Context,
|
|
||||||
watch_folder: String,
|
|
||||||
) -> Result<()> {
|
|
||||||
let fake_idle_start_time = tools::Time::now();
|
let fake_idle_start_time = tools::Time::now();
|
||||||
|
|
||||||
info!(context, "IMAP-fake-IDLEing folder={:?}", watch_folder);
|
info!(context, "IMAP-fake-IDLEing folder={:?}", watch_folder);
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ fn test_build_sequence_sets() {
|
|||||||
|
|
||||||
async fn check_target_folder_combination(
|
async fn check_target_folder_combination(
|
||||||
folder: &str,
|
folder: &str,
|
||||||
mvbox_move: bool,
|
|
||||||
chat_msg: bool,
|
chat_msg: bool,
|
||||||
expected_destination: &str,
|
expected_destination: &str,
|
||||||
accepted_chat: bool,
|
accepted_chat: bool,
|
||||||
@@ -108,16 +107,10 @@ async fn check_target_folder_combination(
|
|||||||
setupmessage: bool,
|
setupmessage: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
println!(
|
println!(
|
||||||
"Testing: For folder {folder}, mvbox_move {mvbox_move}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}"
|
"Testing: For folder {folder}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}"
|
||||||
);
|
);
|
||||||
|
|
||||||
let t = TestContext::new_alice().await;
|
let t = TestContext::new_alice().await;
|
||||||
t.ctx
|
|
||||||
.set_config(Config::ConfiguredMvboxFolder, Some("DeltaChat"))
|
|
||||||
.await?;
|
|
||||||
t.ctx
|
|
||||||
.set_config(Config::MvboxMove, Some(if mvbox_move { "1" } else { "0" }))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if accepted_chat {
|
if accepted_chat {
|
||||||
let contact_id = Contact::create(&t.ctx, "", "bob@example.net").await?;
|
let contact_id = Contact::create(&t.ctx, "", "bob@example.net").await?;
|
||||||
@@ -164,42 +157,33 @@ async fn check_target_folder_combination(
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected,
|
expected,
|
||||||
actual.as_deref(),
|
actual.as_deref(),
|
||||||
"For folder {folder}, mvbox_move {mvbox_move}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}: expected {expected:?}, got {actual:?}"
|
"For folder {folder}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}: expected {expected:?}, got {actual:?}"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// chat_msg means that the message was sent by Delta Chat
|
// chat_msg means that the message was sent by Delta Chat
|
||||||
// The tuples are (folder, mvbox_move, chat_msg, expected_destination)
|
// The tuples are (folder, chat_msg, expected_destination)
|
||||||
const COMBINATIONS_ACCEPTED_CHAT: &[(&str, bool, bool, &str)] = &[
|
const COMBINATIONS_ACCEPTED_CHAT: &[(&str, bool, &str)] = &[
|
||||||
("INBOX", false, false, "INBOX"),
|
("INBOX", false, "INBOX"),
|
||||||
("INBOX", false, true, "INBOX"),
|
("INBOX", true, "INBOX"),
|
||||||
("INBOX", true, false, "INBOX"),
|
("Spam", false, "INBOX"), // Move classical emails in accepted chats from Spam to Inbox, not 100% sure on this, we could also just never move non-chat-msgs
|
||||||
("INBOX", true, true, "DeltaChat"),
|
("Spam", true, "INBOX"),
|
||||||
("Spam", false, false, "INBOX"), // Move classical emails in accepted chats from Spam to Inbox, not 100% sure on this, we could also just never move non-chat-msgs
|
|
||||||
("Spam", false, true, "INBOX"),
|
|
||||||
("Spam", true, false, "INBOX"), // Move classical emails in accepted chats from Spam to Inbox, not 100% sure on this, we could also just never move non-chat-msgs
|
|
||||||
("Spam", true, true, "DeltaChat"),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// These are the same as above, but non-chat messages in Spam stay in Spam
|
// These are the same as above, but non-chat messages in Spam stay in Spam
|
||||||
const COMBINATIONS_REQUEST: &[(&str, bool, bool, &str)] = &[
|
const COMBINATIONS_REQUEST: &[(&str, bool, &str)] = &[
|
||||||
("INBOX", false, false, "INBOX"),
|
("INBOX", false, "INBOX"),
|
||||||
("INBOX", false, true, "INBOX"),
|
("INBOX", true, "INBOX"),
|
||||||
("INBOX", true, false, "INBOX"),
|
("Spam", false, "Spam"),
|
||||||
("INBOX", true, true, "DeltaChat"),
|
("Spam", true, "INBOX"),
|
||||||
("Spam", false, false, "Spam"),
|
|
||||||
("Spam", false, true, "INBOX"),
|
|
||||||
("Spam", true, false, "Spam"),
|
|
||||||
("Spam", true, true, "DeltaChat"),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_target_folder_incoming_accepted() -> Result<()> {
|
async fn test_target_folder_incoming_accepted() -> Result<()> {
|
||||||
for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
for (folder, chat_msg, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
||||||
check_target_folder_combination(
|
check_target_folder_combination(
|
||||||
folder,
|
folder,
|
||||||
*mvbox_move,
|
|
||||||
*chat_msg,
|
*chat_msg,
|
||||||
expected_destination,
|
expected_destination,
|
||||||
true,
|
true,
|
||||||
@@ -213,10 +197,9 @@ async fn test_target_folder_incoming_accepted() -> Result<()> {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_target_folder_incoming_request() -> Result<()> {
|
async fn test_target_folder_incoming_request() -> Result<()> {
|
||||||
for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_REQUEST {
|
for (folder, chat_msg, expected_destination) in COMBINATIONS_REQUEST {
|
||||||
check_target_folder_combination(
|
check_target_folder_combination(
|
||||||
folder,
|
folder,
|
||||||
*mvbox_move,
|
|
||||||
*chat_msg,
|
*chat_msg,
|
||||||
expected_destination,
|
expected_destination,
|
||||||
false,
|
false,
|
||||||
@@ -231,17 +214,9 @@ async fn test_target_folder_incoming_request() -> Result<()> {
|
|||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_target_folder_outgoing() -> Result<()> {
|
async fn test_target_folder_outgoing() -> Result<()> {
|
||||||
// Test outgoing emails
|
// Test outgoing emails
|
||||||
for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
for (folder, chat_msg, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
||||||
check_target_folder_combination(
|
check_target_folder_combination(folder, *chat_msg, expected_destination, true, true, false)
|
||||||
folder,
|
.await?;
|
||||||
*mvbox_move,
|
|
||||||
*chat_msg,
|
|
||||||
expected_destination,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -249,10 +224,9 @@ async fn test_target_folder_outgoing() -> Result<()> {
|
|||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_target_folder_setupmsg() -> Result<()> {
|
async fn test_target_folder_setupmsg() -> Result<()> {
|
||||||
// Test setupmessages
|
// Test setupmessages
|
||||||
for (folder, mvbox_move, chat_msg, _expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
for (folder, chat_msg, _expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
||||||
check_target_folder_combination(
|
check_target_folder_combination(
|
||||||
folder,
|
folder,
|
||||||
*mvbox_move,
|
|
||||||
*chat_msg,
|
*chat_msg,
|
||||||
if folder == &"Spam" { "INBOX" } else { folder }, // Never move setup messages, except if they are in "Spam"
|
if folder == &"Spam" { "INBOX" } else { folder }, // Never move setup messages, except if they are in "Spam"
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -56,9 +56,37 @@ pub enum EnteredCertificateChecks {
|
|||||||
AcceptInvalidCertificates2 = 3,
|
AcceptInvalidCertificates2 = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Login parameters for a single server, either IMAP or SMTP
|
/// Login parameters for a single IMAP server.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct EnteredServerLoginParam {
|
pub struct EnteredImapLoginParam {
|
||||||
|
/// Server hostname or IP address.
|
||||||
|
pub server: String,
|
||||||
|
|
||||||
|
/// Server port.
|
||||||
|
///
|
||||||
|
/// 0 if not specified.
|
||||||
|
pub port: u16,
|
||||||
|
|
||||||
|
/// Folder to watch.
|
||||||
|
///
|
||||||
|
/// If empty, user has not entered anything and it shuold expand to "INBOX" later.
|
||||||
|
pub folder: String,
|
||||||
|
|
||||||
|
/// Socket security.
|
||||||
|
pub security: Socket,
|
||||||
|
|
||||||
|
/// Username.
|
||||||
|
///
|
||||||
|
/// Empty string if not specified.
|
||||||
|
pub user: String,
|
||||||
|
|
||||||
|
/// Password.
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Login parameters for a single SMTP server.
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct EnteredSmtpLoginParam {
|
||||||
/// Server hostname or IP address.
|
/// Server hostname or IP address.
|
||||||
pub server: String,
|
pub server: String,
|
||||||
|
|
||||||
@@ -86,10 +114,10 @@ pub struct EnteredLoginParam {
|
|||||||
pub addr: String,
|
pub addr: String,
|
||||||
|
|
||||||
/// IMAP settings.
|
/// IMAP settings.
|
||||||
pub imap: EnteredServerLoginParam,
|
pub imap: EnteredImapLoginParam,
|
||||||
|
|
||||||
/// SMTP settings.
|
/// SMTP settings.
|
||||||
pub smtp: EnteredServerLoginParam,
|
pub smtp: EnteredSmtpLoginParam,
|
||||||
|
|
||||||
/// TLS options: whether to allow invalid certificates and/or
|
/// TLS options: whether to allow invalid certificates and/or
|
||||||
/// invalid hostnames
|
/// invalid hostnames
|
||||||
@@ -101,6 +129,8 @@ pub struct EnteredLoginParam {
|
|||||||
|
|
||||||
impl EnteredLoginParam {
|
impl EnteredLoginParam {
|
||||||
/// Loads entered account settings.
|
/// Loads entered account settings.
|
||||||
|
///
|
||||||
|
/// This is a legacy API for loading from separate config parameters.
|
||||||
pub(crate) async fn load(context: &Context) -> Result<Self> {
|
pub(crate) async fn load(context: &Context) -> Result<Self> {
|
||||||
let addr = context
|
let addr = context
|
||||||
.get_config(Config::Addr)
|
.get_config(Config::Addr)
|
||||||
@@ -117,6 +147,10 @@ impl EnteredLoginParam {
|
|||||||
.get_config_parsed::<u16>(Config::MailPort)
|
.get_config_parsed::<u16>(Config::MailPort)
|
||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// There is no way to set custom folder with this legacy API.
|
||||||
|
let mail_folder = String::new();
|
||||||
|
|
||||||
let mail_security = context
|
let mail_security = context
|
||||||
.get_config_parsed::<i32>(Config::MailSecurity)
|
.get_config_parsed::<i32>(Config::MailSecurity)
|
||||||
.await?
|
.await?
|
||||||
@@ -175,14 +209,15 @@ impl EnteredLoginParam {
|
|||||||
|
|
||||||
Ok(EnteredLoginParam {
|
Ok(EnteredLoginParam {
|
||||||
addr,
|
addr,
|
||||||
imap: EnteredServerLoginParam {
|
imap: EnteredImapLoginParam {
|
||||||
server: mail_server,
|
server: mail_server,
|
||||||
port: mail_port,
|
port: mail_port,
|
||||||
|
folder: mail_folder,
|
||||||
security: mail_security,
|
security: mail_security,
|
||||||
user: mail_user,
|
user: mail_user,
|
||||||
password: mail_pw,
|
password: mail_pw,
|
||||||
},
|
},
|
||||||
smtp: EnteredServerLoginParam {
|
smtp: EnteredSmtpLoginParam {
|
||||||
server: send_server,
|
server: send_server,
|
||||||
port: send_port,
|
port: send_port,
|
||||||
security: send_security,
|
security: send_security,
|
||||||
@@ -344,14 +379,15 @@ mod tests {
|
|||||||
let t = TestContext::new().await;
|
let t = TestContext::new().await;
|
||||||
let param = EnteredLoginParam {
|
let param = EnteredLoginParam {
|
||||||
addr: "alice@example.org".to_string(),
|
addr: "alice@example.org".to_string(),
|
||||||
imap: EnteredServerLoginParam {
|
imap: EnteredImapLoginParam {
|
||||||
server: "".to_string(),
|
server: "".to_string(),
|
||||||
port: 0,
|
port: 0,
|
||||||
|
folder: "".to_string(),
|
||||||
security: Socket::Starttls,
|
security: Socket::Starttls,
|
||||||
user: "".to_string(),
|
user: "".to_string(),
|
||||||
password: "foobar".to_string(),
|
password: "foobar".to_string(),
|
||||||
},
|
},
|
||||||
smtp: EnteredServerLoginParam {
|
smtp: EnteredSmtpLoginParam {
|
||||||
server: "".to_string(),
|
server: "".to_string(),
|
||||||
port: 2947,
|
port: 2947,
|
||||||
security: Socket::default(),
|
security: Socket::default(),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::config::Config;
|
|||||||
use crate::contact::{Contact, ContactId, Origin};
|
use crate::contact::{Contact, ContactId, Origin};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::key::Fingerprint;
|
use crate::key::Fingerprint;
|
||||||
use crate::login_param::{EnteredCertificateChecks, EnteredLoginParam, EnteredServerLoginParam};
|
use crate::login_param::{EnteredCertificateChecks, EnteredImapLoginParam, EnteredLoginParam};
|
||||||
use crate::net::http::post_empty;
|
use crate::net::http::post_empty;
|
||||||
use crate::net::proxy::{DEFAULT_SOCKS_PORT, ProxyConfig};
|
use crate::net::proxy::{DEFAULT_SOCKS_PORT, ProxyConfig};
|
||||||
use crate::token;
|
use crate::token;
|
||||||
@@ -41,7 +41,7 @@ pub(crate) const DCBACKUP_SCHEME_PREFIX: &str = "DCBACKUP";
|
|||||||
|
|
||||||
/// Version written to Backups and Backup-QR-Codes.
|
/// Version written to Backups and Backup-QR-Codes.
|
||||||
/// Imports will fail when they have a larger version.
|
/// Imports will fail when they have a larger version.
|
||||||
pub(crate) const DCBACKUP_VERSION: i32 = 4;
|
pub(crate) const DCBACKUP_VERSION: i32 = 5;
|
||||||
|
|
||||||
/// Scanned QR code.
|
/// Scanned QR code.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -812,7 +812,7 @@ pub(crate) async fn login_param_from_account_qr(
|
|||||||
|
|
||||||
let param = EnteredLoginParam {
|
let param = EnteredLoginParam {
|
||||||
addr,
|
addr,
|
||||||
imap: EnteredServerLoginParam {
|
imap: EnteredImapLoginParam {
|
||||||
password,
|
password,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -832,7 +832,7 @@ pub(crate) async fn login_param_from_account_qr(
|
|||||||
|
|
||||||
let param = EnteredLoginParam {
|
let param = EnteredLoginParam {
|
||||||
addr: email,
|
addr: email,
|
||||||
imap: EnteredServerLoginParam {
|
imap: EnteredImapLoginParam {
|
||||||
password,
|
password,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ use anyhow::{Context as _, Result, bail};
|
|||||||
use deltachat_contact_tools::may_be_valid_addr;
|
use deltachat_contact_tools::may_be_valid_addr;
|
||||||
|
|
||||||
use super::{DCLOGIN_SCHEME, Qr};
|
use super::{DCLOGIN_SCHEME, Qr};
|
||||||
use crate::login_param::{EnteredCertificateChecks, EnteredLoginParam, EnteredServerLoginParam};
|
use crate::login_param::{
|
||||||
|
EnteredCertificateChecks, EnteredImapLoginParam, EnteredLoginParam, EnteredSmtpLoginParam,
|
||||||
|
};
|
||||||
use crate::provider::Socket;
|
use crate::provider::Socket;
|
||||||
|
|
||||||
/// Options for `dclogin:` scheme.
|
/// Options for `dclogin:` scheme.
|
||||||
@@ -178,14 +180,15 @@ pub(crate) fn login_param_from_login_qr(
|
|||||||
} => {
|
} => {
|
||||||
let param = EnteredLoginParam {
|
let param = EnteredLoginParam {
|
||||||
addr: addr.to_string(),
|
addr: addr.to_string(),
|
||||||
imap: EnteredServerLoginParam {
|
imap: EnteredImapLoginParam {
|
||||||
server: imap_host.unwrap_or_default(),
|
server: imap_host.unwrap_or_default(),
|
||||||
port: imap_port.unwrap_or_default(),
|
port: imap_port.unwrap_or_default(),
|
||||||
|
folder: "INBOX".to_string(),
|
||||||
security: imap_security.unwrap_or_default(),
|
security: imap_security.unwrap_or_default(),
|
||||||
user: imap_username.unwrap_or_default(),
|
user: imap_username.unwrap_or_default(),
|
||||||
password: imap_password.unwrap_or(mail_pw),
|
password: imap_password.unwrap_or(mail_pw),
|
||||||
},
|
},
|
||||||
smtp: EnteredServerLoginParam {
|
smtp: EnteredSmtpLoginParam {
|
||||||
server: smtp_host.unwrap_or_default(),
|
server: smtp_host.unwrap_or_default(),
|
||||||
port: smtp_port.unwrap_or_default(),
|
port: smtp_port.unwrap_or_default(),
|
||||||
security: smtp_security.unwrap_or_default(),
|
security: smtp_security.unwrap_or_default(),
|
||||||
|
|||||||
44
src/quota.rs
44
src/quota.rs
@@ -9,7 +9,6 @@ use async_imap::types::{Quota, QuotaResource};
|
|||||||
use crate::chat::add_device_msg_with_importance;
|
use crate::chat::add_device_msg_with_importance;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::imap::get_watched_folders;
|
|
||||||
use crate::imap::session::Session as ImapSession;
|
use crate::imap::session::Session as ImapSession;
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
@@ -48,26 +47,24 @@ pub struct QuotaInfo {
|
|||||||
|
|
||||||
async fn get_unique_quota_roots_and_usage(
|
async fn get_unique_quota_roots_and_usage(
|
||||||
session: &mut ImapSession,
|
session: &mut ImapSession,
|
||||||
folders: Vec<String>,
|
folder: String,
|
||||||
) -> Result<BTreeMap<String, Vec<QuotaResource>>> {
|
) -> Result<BTreeMap<String, Vec<QuotaResource>>> {
|
||||||
let mut unique_quota_roots: BTreeMap<String, Vec<QuotaResource>> = BTreeMap::new();
|
let mut unique_quota_roots: BTreeMap<String, Vec<QuotaResource>> = BTreeMap::new();
|
||||||
for folder in folders {
|
let (quota_roots, quotas) = &session.get_quota_root(&folder).await?;
|
||||||
let (quota_roots, quotas) = &session.get_quota_root(&folder).await?;
|
// if there are new quota roots found in this imap folder, add them to the list
|
||||||
// if there are new quota roots found in this imap folder, add them to the list
|
for qr_entries in quota_roots {
|
||||||
for qr_entries in quota_roots {
|
for quota_root_name in &qr_entries.quota_root_names {
|
||||||
for quota_root_name in &qr_entries.quota_root_names {
|
// the quota for that quota root
|
||||||
// the quota for that quota root
|
let quota: Quota = quotas
|
||||||
let quota: Quota = quotas
|
.iter()
|
||||||
.iter()
|
.find(|q| &q.root_name == quota_root_name)
|
||||||
.find(|q| &q.root_name == quota_root_name)
|
.cloned()
|
||||||
.cloned()
|
.context("quota_root should have a quota")?;
|
||||||
.context("quota_root should have a quota")?;
|
// replace old quotas, because between fetching quotaroots for folders,
|
||||||
// replace old quotas, because between fetching quotaroots for folders,
|
// messages could be received and so the usage could have been changed
|
||||||
// messages could be received and so the usage could have been changed
|
*unique_quota_roots
|
||||||
*unique_quota_roots
|
.entry(quota_root_name.clone())
|
||||||
.entry(quota_root_name.clone())
|
.or_default() = quota.resources;
|
||||||
.or_default() = quota.resources;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(unique_quota_roots)
|
Ok(unique_quota_roots)
|
||||||
@@ -123,10 +120,13 @@ impl Context {
|
|||||||
/// As the message is added only once, the user is not spammed
|
/// As the message is added only once, the user is not spammed
|
||||||
/// in case for some providers the quota is always at ~100%
|
/// in case for some providers the quota is always at ~100%
|
||||||
/// and new space is allocated as needed.
|
/// and new space is allocated as needed.
|
||||||
pub(crate) async fn update_recent_quota(&self, session: &mut ImapSession) -> Result<()> {
|
pub(crate) async fn update_recent_quota(
|
||||||
|
&self,
|
||||||
|
session: &mut ImapSession,
|
||||||
|
folder: String,
|
||||||
|
) -> Result<()> {
|
||||||
let quota = if session.can_check_quota() {
|
let quota = if session.can_check_quota() {
|
||||||
let folders = get_watched_folders(self).await?;
|
get_unique_quota_roots_and_usage(session, folder).await
|
||||||
get_unique_quota_roots_and_usage(session, folders).await
|
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(stock_str::not_supported_by_provider(self).await))
|
Err(anyhow!(stock_str::not_supported_by_provider(self).await))
|
||||||
};
|
};
|
||||||
|
|||||||
185
src/scheduler.rs
185
src/scheduler.rs
@@ -17,7 +17,7 @@ use crate::context::Context;
|
|||||||
use crate::download::{download_known_post_messages_without_pre_message, download_msgs};
|
use crate::download::{download_known_post_messages_without_pre_message, download_msgs};
|
||||||
use crate::ephemeral::{self, delete_expired_imap_messages};
|
use crate::ephemeral::{self, delete_expired_imap_messages};
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::imap::{FolderMeaning, Imap, session::Session};
|
use crate::imap::{Imap, session::Session};
|
||||||
use crate::location;
|
use crate::location;
|
||||||
use crate::log::{LogExt, warn};
|
use crate::log::{LogExt, warn};
|
||||||
use crate::smtp::{Smtp, send_smtp_messages};
|
use crate::smtp::{Smtp, send_smtp_messages};
|
||||||
@@ -211,25 +211,19 @@ impl SchedulerState {
|
|||||||
/// Indicate that the network likely has come back.
|
/// Indicate that the network likely has come back.
|
||||||
pub(crate) async fn maybe_network(&self) {
|
pub(crate) async fn maybe_network(&self) {
|
||||||
let inner = self.inner.read().await;
|
let inner = self.inner.read().await;
|
||||||
let (inboxes, oboxes) = match *inner {
|
let inboxes = match *inner {
|
||||||
InnerSchedulerState::Started(ref scheduler) => {
|
InnerSchedulerState::Started(ref scheduler) => {
|
||||||
scheduler.maybe_network();
|
scheduler.maybe_network();
|
||||||
let inboxes = scheduler
|
scheduler
|
||||||
.inboxes
|
.inboxes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|b| b.conn_state.state.connectivity.clone())
|
.map(|b| b.conn_state.state.connectivity.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>()
|
||||||
let oboxes = scheduler
|
|
||||||
.oboxes
|
|
||||||
.iter()
|
|
||||||
.map(|b| b.conn_state.state.connectivity.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
(inboxes, oboxes)
|
|
||||||
}
|
}
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
drop(inner);
|
drop(inner);
|
||||||
connectivity::idle_interrupted(inboxes, oboxes);
|
connectivity::idle_interrupted(inboxes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicate that the network likely is lost.
|
/// Indicate that the network likely is lost.
|
||||||
@@ -318,7 +312,10 @@ impl Drop for IoPausedGuard {
|
|||||||
struct SchedBox {
|
struct SchedBox {
|
||||||
/// Address at the used chatmail/email relay
|
/// Address at the used chatmail/email relay
|
||||||
addr: String,
|
addr: String,
|
||||||
meaning: FolderMeaning,
|
|
||||||
|
/// Folder name
|
||||||
|
folder: String,
|
||||||
|
|
||||||
conn_state: ImapConnectionState,
|
conn_state: ImapConnectionState,
|
||||||
|
|
||||||
/// IMAP loop task handle.
|
/// IMAP loop task handle.
|
||||||
@@ -330,8 +327,6 @@ struct SchedBox {
|
|||||||
pub(crate) struct Scheduler {
|
pub(crate) struct Scheduler {
|
||||||
/// Inboxes, one per transport.
|
/// Inboxes, one per transport.
|
||||||
inboxes: Vec<SchedBox>,
|
inboxes: Vec<SchedBox>,
|
||||||
/// Optional boxes -- mvbox.
|
|
||||||
oboxes: Vec<SchedBox>,
|
|
||||||
smtp: SmtpConnectionState,
|
smtp: SmtpConnectionState,
|
||||||
smtp_handle: task::JoinHandle<()>,
|
smtp_handle: task::JoinHandle<()>,
|
||||||
ephemeral_handle: task::JoinHandle<()>,
|
ephemeral_handle: task::JoinHandle<()>,
|
||||||
@@ -400,40 +395,11 @@ async fn inbox_loop(
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert folder meaning
|
|
||||||
/// used internally by [fetch_idle] and [Context::background_fetch].
|
|
||||||
///
|
|
||||||
/// Returns folder configuration key and folder name
|
|
||||||
/// if such folder is configured, `Ok(None)` otherwise.
|
|
||||||
pub async fn convert_folder_meaning(
|
|
||||||
ctx: &Context,
|
|
||||||
folder_meaning: FolderMeaning,
|
|
||||||
) -> Result<Option<(Config, String)>> {
|
|
||||||
let folder_config = match folder_meaning.to_config() {
|
|
||||||
Some(c) => c,
|
|
||||||
None => {
|
|
||||||
// Such folder cannot be configured,
|
|
||||||
// e.g. a `FolderMeaning::Spam` folder.
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let folder = ctx
|
|
||||||
.get_config(folder_config)
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("Failed to retrieve {folder_config} folder"))?;
|
|
||||||
|
|
||||||
if let Some(watch_folder) = folder {
|
|
||||||
Ok(Some((folder_config, watch_folder)))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) -> Result<Session> {
|
async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session) -> Result<Session> {
|
||||||
|
let folder = imap.folder.clone();
|
||||||
// Update quota no more than once a minute.
|
// Update quota no more than once a minute.
|
||||||
if ctx.quota_needs_update(session.transport_id(), 60).await
|
if ctx.quota_needs_update(session.transport_id(), 60).await
|
||||||
&& let Err(err) = ctx.update_recent_quota(&mut session).await
|
&& let Err(err) = ctx.update_recent_quota(&mut session, folder).await
|
||||||
{
|
{
|
||||||
warn!(ctx, "Failed to update quota: {:#}.", err);
|
warn!(ctx, "Failed to update quota: {:#}.", err);
|
||||||
}
|
}
|
||||||
@@ -471,7 +437,7 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
|
|||||||
.await
|
.await
|
||||||
.context("Failed to register push token")?;
|
.context("Failed to register push token")?;
|
||||||
|
|
||||||
let session = fetch_idle(ctx, imap, session, FolderMeaning::Inbox).await?;
|
let session = fetch_idle(ctx, imap, session).await?;
|
||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,32 +446,17 @@ async fn inbox_fetch_idle(ctx: &Context, imap: &mut Imap, mut session: Session)
|
|||||||
/// This function performs all IMAP operations on a single folder, selecting it if necessary and
|
/// This function performs all IMAP operations on a single folder, selecting it if necessary and
|
||||||
/// handling all the errors. In case of an error, an error is returned and connection is dropped,
|
/// handling all the errors. In case of an error, an error is returned and connection is dropped,
|
||||||
/// otherwise connection is returned.
|
/// otherwise connection is returned.
|
||||||
async fn fetch_idle(
|
async fn fetch_idle(ctx: &Context, connection: &mut Imap, mut session: Session) -> Result<Session> {
|
||||||
ctx: &Context,
|
let watch_folder = connection.folder.clone();
|
||||||
connection: &mut Imap,
|
|
||||||
mut session: Session,
|
|
||||||
folder_meaning: FolderMeaning,
|
|
||||||
) -> Result<Session> {
|
|
||||||
let Some((folder_config, watch_folder)) = convert_folder_meaning(ctx, folder_meaning).await?
|
|
||||||
else {
|
|
||||||
// The folder is not configured.
|
|
||||||
// For example, this happens if the server does not have Sent folder
|
|
||||||
// but watching Sent folder is enabled.
|
|
||||||
connection.connectivity.set_not_configured(ctx);
|
|
||||||
connection.idle_interrupt_receiver.recv().await.ok();
|
|
||||||
bail!("Cannot fetch folder {folder_meaning} because it is not configured");
|
|
||||||
};
|
|
||||||
|
|
||||||
if folder_config == Config::ConfiguredInboxFolder {
|
session
|
||||||
session
|
.store_seen_flags_on_imap(ctx)
|
||||||
.store_seen_flags_on_imap(ctx)
|
.await
|
||||||
.await
|
.context("store_seen_flags_on_imap")?;
|
||||||
.context("store_seen_flags_on_imap")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the watched folder.
|
// Fetch the watched folder.
|
||||||
connection
|
connection
|
||||||
.fetch_move_delete(ctx, &mut session, &watch_folder, folder_meaning)
|
.fetch_move_delete(ctx, &mut session, &watch_folder)
|
||||||
.await
|
.await
|
||||||
.context("fetch_move_delete")?;
|
.context("fetch_move_delete")?;
|
||||||
|
|
||||||
@@ -539,7 +490,7 @@ async fn fetch_idle(
|
|||||||
ctx,
|
ctx,
|
||||||
"IMAP session does not support IDLE, going to fake idle."
|
"IMAP session does not support IDLE, going to fake idle."
|
||||||
);
|
);
|
||||||
connection.fake_idle(ctx, watch_folder).await?;
|
connection.fake_idle(ctx, &watch_folder).await?;
|
||||||
return Ok(session);
|
return Ok(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,7 +502,7 @@ async fn fetch_idle(
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
{
|
{
|
||||||
info!(ctx, "IMAP IDLE is disabled, going to fake idle.");
|
info!(ctx, "IMAP IDLE is disabled, going to fake idle.");
|
||||||
connection.fake_idle(ctx, watch_folder).await?;
|
connection.fake_idle(ctx, &watch_folder).await?;
|
||||||
return Ok(session);
|
return Ok(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,73 +522,6 @@ async fn fetch_idle(
|
|||||||
Ok(session)
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simplified IMAP loop to watch non-inbox folders.
|
|
||||||
async fn simple_imap_loop(
|
|
||||||
ctx: Context,
|
|
||||||
started: oneshot::Sender<()>,
|
|
||||||
inbox_handlers: ImapConnectionHandlers,
|
|
||||||
folder_meaning: FolderMeaning,
|
|
||||||
) {
|
|
||||||
use futures::future::FutureExt;
|
|
||||||
|
|
||||||
info!(ctx, "Starting simple loop for {folder_meaning}.");
|
|
||||||
let ImapConnectionHandlers {
|
|
||||||
mut connection,
|
|
||||||
stop_token,
|
|
||||||
} = inbox_handlers;
|
|
||||||
|
|
||||||
let ctx1 = ctx.clone();
|
|
||||||
|
|
||||||
let fut = async move {
|
|
||||||
let ctx = ctx1;
|
|
||||||
if let Err(()) = started.send(()) {
|
|
||||||
warn!(
|
|
||||||
ctx,
|
|
||||||
"Simple imap loop for {folder_meaning}, missing started receiver."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut old_session: Option<Session> = None;
|
|
||||||
loop {
|
|
||||||
let session = if let Some(session) = old_session.take() {
|
|
||||||
session
|
|
||||||
} else {
|
|
||||||
info!(ctx, "Preparing new IMAP session for {folder_meaning}.");
|
|
||||||
match connection.prepare(&ctx).await {
|
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
ctx,
|
|
||||||
"Failed to prepare {folder_meaning} connection: {err:#}."
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Ok(session) => session,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match fetch_idle(&ctx, &mut connection, session, folder_meaning).await {
|
|
||||||
Err(err) => warn!(ctx, "Failed fetch_idle: {err:#}"),
|
|
||||||
Ok(session) => {
|
|
||||||
info!(
|
|
||||||
ctx,
|
|
||||||
"IMAP loop iteration for {folder_meaning} finished, keeping the session"
|
|
||||||
);
|
|
||||||
old_session = Some(session);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
stop_token
|
|
||||||
.cancelled()
|
|
||||||
.map(|_| {
|
|
||||||
info!(ctx, "Shutting down IMAP loop for {folder_meaning}.");
|
|
||||||
})
|
|
||||||
.race(fut)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn smtp_loop(
|
async fn smtp_loop(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
started: oneshot::Sender<()>,
|
started: oneshot::Sender<()>,
|
||||||
@@ -740,7 +624,6 @@ impl Scheduler {
|
|||||||
let (location_interrupt_send, location_interrupt_recv) = channel::bounded(1);
|
let (location_interrupt_send, location_interrupt_recv) = channel::bounded(1);
|
||||||
|
|
||||||
let mut inboxes = Vec::new();
|
let mut inboxes = Vec::new();
|
||||||
let mut oboxes = Vec::new();
|
|
||||||
let mut start_recvs = Vec::new();
|
let mut start_recvs = Vec::new();
|
||||||
|
|
||||||
for (transport_id, configured_login_param) in ConfiguredLoginParam::load_all(ctx).await? {
|
for (transport_id, configured_login_param) in ConfiguredLoginParam::load_all(ctx).await? {
|
||||||
@@ -752,30 +635,17 @@ impl Scheduler {
|
|||||||
task::spawn(inbox_loop(ctx, inbox_start_send, inbox_handlers))
|
task::spawn(inbox_loop(ctx, inbox_start_send, inbox_handlers))
|
||||||
};
|
};
|
||||||
let addr = configured_login_param.addr.clone();
|
let addr = configured_login_param.addr.clone();
|
||||||
|
let folder = configured_login_param
|
||||||
|
.imap_folder
|
||||||
|
.unwrap_or_else(|| "INBOX".to_string());
|
||||||
let inbox = SchedBox {
|
let inbox = SchedBox {
|
||||||
addr: addr.clone(),
|
addr: addr.clone(),
|
||||||
meaning: FolderMeaning::Inbox,
|
folder,
|
||||||
conn_state,
|
conn_state,
|
||||||
handle,
|
handle,
|
||||||
};
|
};
|
||||||
inboxes.push(inbox);
|
inboxes.push(inbox);
|
||||||
start_recvs.push(inbox_start_recv);
|
start_recvs.push(inbox_start_recv);
|
||||||
|
|
||||||
if ctx.should_watch_mvbox().await? {
|
|
||||||
let (conn_state, handlers) =
|
|
||||||
ImapConnectionState::new(ctx, transport_id, configured_login_param).await?;
|
|
||||||
let (start_send, start_recv) = oneshot::channel();
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
let meaning = FolderMeaning::Mvbox;
|
|
||||||
let handle = task::spawn(simple_imap_loop(ctx, start_send, handlers, meaning));
|
|
||||||
oboxes.push(SchedBox {
|
|
||||||
addr,
|
|
||||||
meaning,
|
|
||||||
conn_state,
|
|
||||||
handle,
|
|
||||||
});
|
|
||||||
start_recvs.push(start_recv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let smtp_handle = {
|
let smtp_handle = {
|
||||||
@@ -802,7 +672,6 @@ impl Scheduler {
|
|||||||
|
|
||||||
let res = Self {
|
let res = Self {
|
||||||
inboxes,
|
inboxes,
|
||||||
oboxes,
|
|
||||||
smtp,
|
smtp,
|
||||||
smtp_handle,
|
smtp_handle,
|
||||||
ephemeral_handle,
|
ephemeral_handle,
|
||||||
@@ -822,7 +691,7 @@ impl Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn boxes(&self) -> impl Iterator<Item = &SchedBox> {
|
fn boxes(&self) -> impl Iterator<Item = &SchedBox> {
|
||||||
self.inboxes.iter().chain(self.oboxes.iter())
|
self.inboxes.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_network(&self) {
|
fn maybe_network(&self) {
|
||||||
@@ -876,7 +745,7 @@ impl Scheduler {
|
|||||||
let timeout_duration = std::time::Duration::from_secs(30);
|
let timeout_duration = std::time::Duration::from_secs(30);
|
||||||
|
|
||||||
let tracker = TaskTracker::new();
|
let tracker = TaskTracker::new();
|
||||||
for b in self.inboxes.into_iter().chain(self.oboxes.into_iter()) {
|
for b in self.inboxes {
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
tracker.spawn(async move {
|
tracker.spawn(async move {
|
||||||
tokio::time::timeout(timeout_duration, b.handle)
|
tokio::time::timeout(timeout_duration, b.handle)
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ use std::{iter::once, ops::Deref, sync::Arc};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use humansize::{BINARY, format_size};
|
use humansize::{BINARY, format_size};
|
||||||
|
|
||||||
|
use crate::context::Context;
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::imap::{FolderMeaning, get_watched_folder_configs};
|
|
||||||
use crate::quota::{QUOTA_ERROR_THRESHOLD_PERCENTAGE, QUOTA_WARN_THRESHOLD_PERCENTAGE};
|
use crate::quota::{QUOTA_ERROR_THRESHOLD_PERCENTAGE, QUOTA_WARN_THRESHOLD_PERCENTAGE};
|
||||||
use crate::stock_str;
|
use crate::stock_str;
|
||||||
use crate::{context::Context, log::LogExt};
|
|
||||||
|
|
||||||
use super::InnerSchedulerState;
|
use super::InnerSchedulerState;
|
||||||
|
|
||||||
@@ -67,40 +66,33 @@ enum DetailedConnectivity {
|
|||||||
|
|
||||||
/// Connection is established and is idle.
|
/// Connection is established and is idle.
|
||||||
Idle,
|
Idle,
|
||||||
|
|
||||||
/// The folder was configured not to be watched or configured_*_folder is not set
|
|
||||||
NotConfigured,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DetailedConnectivity {
|
impl DetailedConnectivity {
|
||||||
fn to_basic(&self) -> Option<Connectivity> {
|
fn to_basic(&self) -> Connectivity {
|
||||||
match self {
|
match self {
|
||||||
DetailedConnectivity::Error(_) => Some(Connectivity::NotConnected),
|
DetailedConnectivity::Error(_) => Connectivity::NotConnected,
|
||||||
DetailedConnectivity::Uninitialized => Some(Connectivity::NotConnected),
|
DetailedConnectivity::Uninitialized => Connectivity::NotConnected,
|
||||||
DetailedConnectivity::Connecting => Some(Connectivity::Connecting),
|
DetailedConnectivity::Connecting => Connectivity::Connecting,
|
||||||
DetailedConnectivity::Working => Some(Connectivity::Working),
|
DetailedConnectivity::Working => Connectivity::Working,
|
||||||
DetailedConnectivity::InterruptingIdle => Some(Connectivity::Working),
|
DetailedConnectivity::InterruptingIdle => Connectivity::Working,
|
||||||
|
|
||||||
// At this point IMAP has just connected,
|
// At this point IMAP has just connected,
|
||||||
// but does not know yet if there are messages to download.
|
// but does not know yet if there are messages to download.
|
||||||
// We still convert this to Working state
|
// We still convert this to Working state
|
||||||
// so user can see "Updating..." and not "Connected"
|
// so user can see "Updating..." and not "Connected"
|
||||||
// which is reserved for idle state.
|
// which is reserved for idle state.
|
||||||
DetailedConnectivity::Preparing => Some(Connectivity::Working),
|
DetailedConnectivity::Preparing => Connectivity::Working,
|
||||||
|
|
||||||
// Just don't return a connectivity, probably the folder is configured not to be
|
DetailedConnectivity::Idle => Connectivity::Connected,
|
||||||
// watched, so we are not interested in it.
|
|
||||||
DetailedConnectivity::NotConfigured => None,
|
|
||||||
|
|
||||||
DetailedConnectivity::Idle => Some(Connectivity::Connected),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_icon(&self) -> String {
|
fn to_icon(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
DetailedConnectivity::Error(_)
|
DetailedConnectivity::Error(_) | DetailedConnectivity::Uninitialized => {
|
||||||
| DetailedConnectivity::Uninitialized
|
"<span class=\"red dot\"></span>".to_string()
|
||||||
| DetailedConnectivity::NotConfigured => "<span class=\"red dot\"></span>".to_string(),
|
}
|
||||||
DetailedConnectivity::Connecting => "<span class=\"yellow dot\"></span>".to_string(),
|
DetailedConnectivity::Connecting => "<span class=\"yellow dot\"></span>".to_string(),
|
||||||
DetailedConnectivity::Preparing
|
DetailedConnectivity::Preparing
|
||||||
| DetailedConnectivity::Working
|
| DetailedConnectivity::Working
|
||||||
@@ -120,7 +112,6 @@ impl DetailedConnectivity {
|
|||||||
DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Idle => {
|
DetailedConnectivity::InterruptingIdle | DetailedConnectivity::Idle => {
|
||||||
stock_str::connected(context).await
|
stock_str::connected(context).await
|
||||||
}
|
}
|
||||||
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +130,6 @@ impl DetailedConnectivity {
|
|||||||
DetailedConnectivity::InterruptingIdle
|
DetailedConnectivity::InterruptingIdle
|
||||||
| DetailedConnectivity::Preparing
|
| DetailedConnectivity::Preparing
|
||||||
| DetailedConnectivity::Idle => stock_str::last_msg_sent_successfully(context).await,
|
| DetailedConnectivity::Idle => stock_str::last_msg_sent_successfully(context).await,
|
||||||
DetailedConnectivity::NotConfigured => "Not configured".to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +141,6 @@ impl DetailedConnectivity {
|
|||||||
DetailedConnectivity::Working => false,
|
DetailedConnectivity::Working => false,
|
||||||
DetailedConnectivity::InterruptingIdle => false,
|
DetailedConnectivity::InterruptingIdle => false,
|
||||||
DetailedConnectivity::Preparing => false, // Just connected, there may still be work to do.
|
DetailedConnectivity::Preparing => false, // Just connected, there may still be work to do.
|
||||||
DetailedConnectivity::NotConfigured => true,
|
|
||||||
DetailedConnectivity::Idle => true,
|
DetailedConnectivity::Idle => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,9 +169,6 @@ impl ConnectivityStore {
|
|||||||
pub(crate) fn set_preparing(&self, context: &Context) {
|
pub(crate) fn set_preparing(&self, context: &Context) {
|
||||||
self.set(context, DetailedConnectivity::Preparing);
|
self.set(context, DetailedConnectivity::Preparing);
|
||||||
}
|
}
|
||||||
pub(crate) fn set_not_configured(&self, context: &Context) {
|
|
||||||
self.set(context, DetailedConnectivity::NotConfigured);
|
|
||||||
}
|
|
||||||
pub(crate) fn set_idle(&self, context: &Context) {
|
pub(crate) fn set_idle(&self, context: &Context) {
|
||||||
self.set(context, DetailedConnectivity::Idle);
|
self.set(context, DetailedConnectivity::Idle);
|
||||||
}
|
}
|
||||||
@@ -190,7 +176,7 @@ impl ConnectivityStore {
|
|||||||
fn get_detailed(&self) -> DetailedConnectivity {
|
fn get_detailed(&self) -> DetailedConnectivity {
|
||||||
self.0.lock().deref().clone()
|
self.0.lock().deref().clone()
|
||||||
}
|
}
|
||||||
fn get_basic(&self) -> Option<Connectivity> {
|
fn get_basic(&self) -> Connectivity {
|
||||||
self.0.lock().to_basic()
|
self.0.lock().to_basic()
|
||||||
}
|
}
|
||||||
fn get_all_work_done(&self) -> bool {
|
fn get_all_work_done(&self) -> bool {
|
||||||
@@ -201,27 +187,14 @@ impl ConnectivityStore {
|
|||||||
/// Set all folder states to InterruptingIdle in case they were `Idle` before.
|
/// Set all folder states to InterruptingIdle in case they were `Idle` before.
|
||||||
/// Called during `dc_maybe_network()` to make sure that `all_work_done()`
|
/// Called during `dc_maybe_network()` to make sure that `all_work_done()`
|
||||||
/// returns false immediately after `dc_maybe_network()`.
|
/// returns false immediately after `dc_maybe_network()`.
|
||||||
pub(crate) fn idle_interrupted(inboxes: Vec<ConnectivityStore>, oboxes: Vec<ConnectivityStore>) {
|
pub(crate) fn idle_interrupted(inboxes: Vec<ConnectivityStore>) {
|
||||||
for inbox in inboxes {
|
for inbox in inboxes {
|
||||||
let mut connectivity_lock = inbox.0.lock();
|
let mut connectivity_lock = inbox.0.lock();
|
||||||
// For the inbox, we also have to set the connectivity to InterruptingIdle if it was
|
|
||||||
// NotConfigured before: If all folders are NotConfigured, dc_get_connectivity()
|
|
||||||
// returns Connected. But after dc_maybe_network(), dc_get_connectivity() must not
|
|
||||||
// return Connected until DC is completely done with fetching folders; this also
|
|
||||||
// includes scan_folders() which happens on the inbox thread.
|
|
||||||
if *connectivity_lock == DetailedConnectivity::Idle
|
|
||||||
|| *connectivity_lock == DetailedConnectivity::NotConfigured
|
|
||||||
{
|
|
||||||
*connectivity_lock = DetailedConnectivity::InterruptingIdle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for state in oboxes {
|
|
||||||
let mut connectivity_lock = state.0.lock();
|
|
||||||
if *connectivity_lock == DetailedConnectivity::Idle {
|
if *connectivity_lock == DetailedConnectivity::Idle {
|
||||||
*connectivity_lock = DetailedConnectivity::InterruptingIdle;
|
*connectivity_lock = DetailedConnectivity::InterruptingIdle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to send ConnectivityChanged, the user-facing connectivity doesn't change because
|
// No need to send ConnectivityChanged, the user-facing connectivity doesn't change because
|
||||||
// of what we do here.
|
// of what we do here.
|
||||||
}
|
}
|
||||||
@@ -234,9 +207,7 @@ pub(crate) fn maybe_network_lost(context: &Context, stores: Vec<ConnectivityStor
|
|||||||
let mut connectivity_lock = store.0.lock();
|
let mut connectivity_lock = store.0.lock();
|
||||||
if !matches!(
|
if !matches!(
|
||||||
*connectivity_lock,
|
*connectivity_lock,
|
||||||
DetailedConnectivity::Uninitialized
|
DetailedConnectivity::Uninitialized | DetailedConnectivity::Error(_)
|
||||||
| DetailedConnectivity::Error(_)
|
|
||||||
| DetailedConnectivity::NotConfigured,
|
|
||||||
) {
|
) {
|
||||||
*connectivity_lock = DetailedConnectivity::Error("Connection lost".to_string());
|
*connectivity_lock = DetailedConnectivity::Error("Connection lost".to_string());
|
||||||
}
|
}
|
||||||
@@ -273,9 +244,8 @@ impl Context {
|
|||||||
let stores = self.connectivities.lock().clone();
|
let stores = self.connectivities.lock().clone();
|
||||||
let mut connectivities = Vec::new();
|
let mut connectivities = Vec::new();
|
||||||
for s in stores {
|
for s in stores {
|
||||||
if let Some(connectivity) = s.get_basic() {
|
let connectivity = s.get_basic();
|
||||||
connectivities.push(connectivity);
|
connectivities.push(connectivity);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
connectivities
|
connectivities
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -386,7 +356,7 @@ impl Context {
|
|||||||
.map(|b| {
|
.map(|b| {
|
||||||
(
|
(
|
||||||
b.addr.clone(),
|
b.addr.clone(),
|
||||||
b.meaning,
|
b.folder.clone(),
|
||||||
b.conn_state.state.connectivity.clone(),
|
b.conn_state.state.connectivity.clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -411,7 +381,6 @@ impl Context {
|
|||||||
// [======67%===== ]
|
// [======67%===== ]
|
||||||
// =============================================================================================
|
// =============================================================================================
|
||||||
|
|
||||||
let watched_folders = get_watched_folder_configs(self).await?;
|
|
||||||
let incoming_messages = stock_str::incoming_messages(self).await;
|
let incoming_messages = stock_str::incoming_messages(self).await;
|
||||||
ret += &format!("<h3>{incoming_messages}</h3><ul>");
|
ret += &format!("<h3>{incoming_messages}</h3><ul>");
|
||||||
|
|
||||||
@@ -433,41 +402,14 @@ impl Context {
|
|||||||
let folders = folders_states
|
let folders = folders_states
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(folder_addr, ..)| *folder_addr == transport_addr);
|
.filter(|(folder_addr, ..)| *folder_addr == transport_addr);
|
||||||
for (_addr, folder, state) in folders {
|
for (_addr, _folder, state) in folders {
|
||||||
let mut folder_added = false;
|
let detailed = &state.get_detailed();
|
||||||
|
ret += &*detailed.to_icon();
|
||||||
if let Some(config) = folder.to_config().filter(|c| watched_folders.contains(c)) {
|
ret += " <b>";
|
||||||
let f = self.get_config(config).await.log_err(self).ok().flatten();
|
ret += &*domain_escaped;
|
||||||
|
ret += ":</b> ";
|
||||||
if let Some(foldername) = f {
|
ret += &*escaper::encode_minimal(&detailed.to_string_imap(self).await);
|
||||||
let detailed = &state.get_detailed();
|
ret += "<br />";
|
||||||
ret += &*detailed.to_icon();
|
|
||||||
ret += " <b>";
|
|
||||||
if folder == &FolderMeaning::Inbox {
|
|
||||||
ret += &*domain_escaped;
|
|
||||||
} else {
|
|
||||||
ret += &*escaper::encode_minimal(&foldername);
|
|
||||||
}
|
|
||||||
ret += ":</b> ";
|
|
||||||
ret += &*escaper::encode_minimal(&detailed.to_string_imap(self).await);
|
|
||||||
ret += "<br />";
|
|
||||||
|
|
||||||
folder_added = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !folder_added && folder == &FolderMeaning::Inbox {
|
|
||||||
let detailed = &state.get_detailed();
|
|
||||||
if let DetailedConnectivity::Error(_) = detailed {
|
|
||||||
// On the inbox thread, we also do some other things like scan_folders and run jobs
|
|
||||||
// so, maybe, the inbox is not watched, but something else went wrong
|
|
||||||
|
|
||||||
ret += &*detailed.to_icon();
|
|
||||||
ret += " ";
|
|
||||||
ret += &*escaper::encode_minimal(&detailed.to_string_imap(self).await);
|
|
||||||
ret += "<br />";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(quota) = quota.get(&transport_id) else {
|
let Some(quota) = quota.get(&transport_id) else {
|
||||||
|
|||||||
21
src/sql.rs
21
src/sql.rs
@@ -9,7 +9,6 @@ use rusqlite::{Connection, OpenFlags, Row, config::DbConfig, types::ValueRef};
|
|||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::chat::add_device_msg;
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::DC_CHAT_ID_TRASH;
|
use crate::constants::DC_CHAT_ID_TRASH;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
@@ -18,13 +17,11 @@ use crate::ephemeral::start_ephemeral_timers;
|
|||||||
use crate::imex::BLOBS_BACKUP_NAME;
|
use crate::imex::BLOBS_BACKUP_NAME;
|
||||||
use crate::location::delete_orphaned_poi_locations;
|
use crate::location::delete_orphaned_poi_locations;
|
||||||
use crate::log::{LogExt, warn};
|
use crate::log::{LogExt, warn};
|
||||||
use crate::message::Message;
|
|
||||||
use crate::message::MsgId;
|
use crate::message::MsgId;
|
||||||
use crate::net::dns::prune_dns_cache;
|
use crate::net::dns::prune_dns_cache;
|
||||||
use crate::net::http::http_cache_cleanup;
|
use crate::net::http::http_cache_cleanup;
|
||||||
use crate::net::prune_connection_history;
|
use crate::net::prune_connection_history;
|
||||||
use crate::param::{Param, Params};
|
use crate::param::{Param, Params};
|
||||||
use crate::stock_str;
|
|
||||||
use crate::tools::{SystemTime, delete_file, time};
|
use crate::tools::{SystemTime, delete_file, time};
|
||||||
|
|
||||||
/// Extension to [`rusqlite::ToSql`] trait
|
/// Extension to [`rusqlite::ToSql`] trait
|
||||||
@@ -830,12 +827,6 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_add_mvbox_move_deprecation_message(context)
|
|
||||||
.await
|
|
||||||
.context("maybe_add_mvbox_move_deprecation_message")
|
|
||||||
.log_err(context)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
if let Err(err) = incremental_vacuum(context).await {
|
if let Err(err) = incremental_vacuum(context).await {
|
||||||
warn!(context, "Failed to run incremental vacuum: {err:#}.");
|
warn!(context, "Failed to run incremental vacuum: {err:#}.");
|
||||||
}
|
}
|
||||||
@@ -895,18 +886,6 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds device message about `mvbox_move` config deprecation
|
|
||||||
/// if the user has it enabled.
|
|
||||||
async fn maybe_add_mvbox_move_deprecation_message(context: &Context) -> Result<()> {
|
|
||||||
if !context.get_config_bool(Config::OnlyFetchMvbox).await?
|
|
||||||
&& context.get_config_bool(Config::MvboxMove).await?
|
|
||||||
{
|
|
||||||
let mut msg = Message::new_text(stock_str::mvbox_move_deprecation(context).await);
|
|
||||||
add_device_msg(context, Some("mvbox_move_deprecation"), Some(&mut msg)).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of a column `idx` of the `row` as `Vec<u8>`.
|
/// Get the value of a column `idx` of the `row` as `Vec<u8>`.
|
||||||
pub fn row_get_vec(row: &Row, idx: usize) -> rusqlite::Result<Vec<u8>> {
|
pub fn row_get_vec(row: &Row, idx: usize) -> rusqlite::Result<Vec<u8>> {
|
||||||
row.get(idx).or_else(|err| match row.get_ref(idx)? {
|
row.get(idx).or_else(|err| match row.get_ref(idx)? {
|
||||||
|
|||||||
@@ -2323,8 +2323,52 @@ ALTER TABLE contacts ADD COLUMN name_normalized TEXT;
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add UNIQUE bound to token, in order to avoid saving the same token multiple times
|
|
||||||
inc_and_check(&mut migration_version, 148)?;
|
inc_and_check(&mut migration_version, 148)?;
|
||||||
|
if dbversion < migration_version {
|
||||||
|
sql.execute_migration_transaction(
|
||||||
|
|transaction| {
|
||||||
|
let only_fetch_mvbox = transaction
|
||||||
|
.query_row(
|
||||||
|
"SELECT value FROM config WHERE keyname='only_fetch_mvbox'",
|
||||||
|
(),
|
||||||
|
|row| {
|
||||||
|
let value: String = row.get(0)?;
|
||||||
|
Ok(value)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.optional()?
|
||||||
|
.as_deref()
|
||||||
|
== Some("1");
|
||||||
|
|
||||||
|
if only_fetch_mvbox {
|
||||||
|
let mvbox_folder = transaction
|
||||||
|
.query_row(
|
||||||
|
"SELECT value FROM config WHERE keyname='configured_mvbox_folder'",
|
||||||
|
(),
|
||||||
|
|row| {
|
||||||
|
let value: String = row.get(0)?;
|
||||||
|
Ok(value)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.optional()?
|
||||||
|
.unwrap_or_else(|| "DeltaChat".to_string());
|
||||||
|
|
||||||
|
transaction.execute(
|
||||||
|
"UPDATE transports
|
||||||
|
SET entered_param=json_set(entered_param, '$.imap.folder', ?1),
|
||||||
|
configured_param=json_set(configured_param', '$.imap_folder', ?1)",
|
||||||
|
(mvbox_folder,),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
migration_version,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add UNIQUE bound to token, in order to avoid saving the same token multiple times
|
||||||
|
inc_and_check(&mut migration_version, 149)?;
|
||||||
if dbversion < migration_version {
|
if dbversion < migration_version {
|
||||||
sql.execute_migration(
|
sql.execute_migration(
|
||||||
"CREATE TABLE tokens_new (
|
"CREATE TABLE tokens_new (
|
||||||
|
|||||||
@@ -413,11 +413,6 @@ https://delta.chat/donate"))]
|
|||||||
#[strum(props(fallback = "Messages in this chat use classic email and are not encrypted."))]
|
#[strum(props(fallback = "Messages in this chat use classic email and are not encrypted."))]
|
||||||
ChatUnencryptedExplanation = 230,
|
ChatUnencryptedExplanation = 230,
|
||||||
|
|
||||||
#[strum(props(
|
|
||||||
fallback = "You are using the legacy option \"Settings → Advanced → Move automatically to DeltaChat Folder\".\n\nThis option will be removed in a few weeks and you should disable it already today.\n\nIf having chat messages mixed into your inbox is a problem, see https://delta.chat/legacy-move"
|
|
||||||
))]
|
|
||||||
MvboxMoveDeprecation = 231,
|
|
||||||
|
|
||||||
#[strum(props(fallback = "Outgoing audio call"))]
|
#[strum(props(fallback = "Outgoing audio call"))]
|
||||||
OutgoingAudioCall = 232,
|
OutgoingAudioCall = 232,
|
||||||
|
|
||||||
@@ -1296,11 +1291,6 @@ pub(crate) async fn chat_unencrypted_explanation(context: &Context) -> String {
|
|||||||
translated(context, StockMessage::ChatUnencryptedExplanation).await
|
translated(context, StockMessage::ChatUnencryptedExplanation).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stock string: `You are using the legacy option "Move automatically to DeltaChat Folder`…
|
|
||||||
pub(crate) async fn mvbox_move_deprecation(context: &Context) -> String {
|
|
||||||
translated(context, StockMessage::MvboxMoveDeprecation).await
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Viewtype {
|
impl Viewtype {
|
||||||
/// returns Localized name for message viewtype
|
/// returns Localized name for message viewtype
|
||||||
pub async fn to_locale_string(&self, context: &Context) -> String {
|
pub async fn to_locale_string(&self, context: &Context) -> String {
|
||||||
|
|||||||
@@ -567,7 +567,6 @@ impl TestContext {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
ctx.set_config(Config::BccSelf, Some("1")).await.unwrap();
|
ctx.set_config(Config::BccSelf, Some("1")).await.unwrap();
|
||||||
ctx.set_config(Config::SyncMsgs, Some("0")).await.unwrap();
|
ctx.set_config(Config::SyncMsgs, Some("0")).await.unwrap();
|
||||||
ctx.set_config(Config::MvboxMove, Some("0")).await.unwrap();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use crate::config::Config;
|
|||||||
use crate::configure::server_params::{ServerParams, expand_param_vector};
|
use crate::configure::server_params::{ServerParams, expand_param_vector};
|
||||||
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
|
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
use crate::ensure_and_debug_assert;
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::login_param::EnteredLoginParam;
|
use crate::login_param::EnteredLoginParam;
|
||||||
use crate::net::load_connection_timestamp;
|
use crate::net::load_connection_timestamp;
|
||||||
@@ -163,22 +164,30 @@ pub(crate) struct ConfiguredLoginParam {
|
|||||||
/// `From:` address that was used at the time of configuration.
|
/// `From:` address that was used at the time of configuration.
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
|
|
||||||
|
/// List of IMAP candidates to try.
|
||||||
pub imap: Vec<ConfiguredServerLoginParam>,
|
pub imap: Vec<ConfiguredServerLoginParam>,
|
||||||
|
|
||||||
// Custom IMAP user.
|
/// Custom IMAP user.
|
||||||
//
|
///
|
||||||
// This overwrites autoconfig from the provider database
|
/// This overwrites autoconfig from the provider database
|
||||||
// if non-empty.
|
/// if non-empty.
|
||||||
pub imap_user: String,
|
pub imap_user: String,
|
||||||
|
|
||||||
pub imap_password: String,
|
pub imap_password: String,
|
||||||
|
|
||||||
|
// IMAP folder to watch.
|
||||||
|
//
|
||||||
|
// If not stored, should be interpreted as "INBOX".
|
||||||
|
// If stored, should be a folder name and not empty.
|
||||||
|
pub imap_folder: Option<String>,
|
||||||
|
|
||||||
|
/// List of SMTP candidates to try.
|
||||||
pub smtp: Vec<ConfiguredServerLoginParam>,
|
pub smtp: Vec<ConfiguredServerLoginParam>,
|
||||||
|
|
||||||
// Custom SMTP user.
|
/// Custom SMTP user.
|
||||||
//
|
///
|
||||||
// This overwrites autoconfig from the provider database
|
/// This overwrites autoconfig from the provider database
|
||||||
// if non-empty.
|
/// if non-empty.
|
||||||
pub smtp_user: String,
|
pub smtp_user: String,
|
||||||
|
|
||||||
pub smtp_password: String,
|
pub smtp_password: String,
|
||||||
@@ -199,6 +208,13 @@ pub(crate) struct ConfiguredLoginParam {
|
|||||||
pub(crate) struct ConfiguredLoginParamJson {
|
pub(crate) struct ConfiguredLoginParamJson {
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
pub imap: Vec<ConfiguredServerLoginParam>,
|
pub imap: Vec<ConfiguredServerLoginParam>,
|
||||||
|
|
||||||
|
/// IMAP folder to watch.
|
||||||
|
///
|
||||||
|
/// Defaults to "INBOX" if unset.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub imap_folder: Option<String>,
|
||||||
|
|
||||||
pub imap_user: String,
|
pub imap_user: String,
|
||||||
pub imap_password: String,
|
pub imap_password: String,
|
||||||
pub smtp: Vec<ConfiguredServerLoginParam>,
|
pub smtp: Vec<ConfiguredServerLoginParam>,
|
||||||
@@ -545,6 +561,7 @@ impl ConfiguredLoginParam {
|
|||||||
Ok(Some(ConfiguredLoginParam {
|
Ok(Some(ConfiguredLoginParam {
|
||||||
addr,
|
addr,
|
||||||
imap,
|
imap,
|
||||||
|
imap_folder: None,
|
||||||
imap_user: mail_user,
|
imap_user: mail_user,
|
||||||
imap_password: mail_pw,
|
imap_password: mail_pw,
|
||||||
smtp,
|
smtp,
|
||||||
@@ -569,11 +586,18 @@ impl ConfiguredLoginParam {
|
|||||||
pub(crate) fn from_json(json: &str) -> Result<Self> {
|
pub(crate) fn from_json(json: &str) -> Result<Self> {
|
||||||
let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
|
let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
|
||||||
|
|
||||||
|
ensure_and_debug_assert!(
|
||||||
|
json.imap_folder
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|folder| !folder.is_empty()),
|
||||||
|
"Configured watched folder name cannot be empty"
|
||||||
|
);
|
||||||
let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
|
let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
|
||||||
|
|
||||||
Ok(ConfiguredLoginParam {
|
Ok(ConfiguredLoginParam {
|
||||||
addr: json.addr,
|
addr: json.addr,
|
||||||
imap: json.imap,
|
imap: json.imap,
|
||||||
|
imap_folder: json.imap_folder,
|
||||||
imap_user: json.imap_user,
|
imap_user: json.imap_user,
|
||||||
imap_password: json.imap_password,
|
imap_password: json.imap_password,
|
||||||
smtp: json.smtp,
|
smtp: json.smtp,
|
||||||
@@ -611,6 +635,7 @@ impl From<ConfiguredLoginParam> for ConfiguredLoginParamJson {
|
|||||||
imap: configured_login_param.imap,
|
imap: configured_login_param.imap,
|
||||||
imap_user: configured_login_param.imap_user,
|
imap_user: configured_login_param.imap_user,
|
||||||
imap_password: configured_login_param.imap_password,
|
imap_password: configured_login_param.imap_password,
|
||||||
|
imap_folder: configured_login_param.imap_folder,
|
||||||
smtp: configured_login_param.smtp,
|
smtp: configured_login_param.smtp,
|
||||||
smtp_user: configured_login_param.smtp_user,
|
smtp_user: configured_login_param.smtp_user,
|
||||||
smtp_password: configured_login_param.smtp_password,
|
smtp_password: configured_login_param.smtp_password,
|
||||||
@@ -629,9 +654,16 @@ pub(crate) async fn save_transport(
|
|||||||
configured: &ConfiguredLoginParamJson,
|
configured: &ConfiguredLoginParamJson,
|
||||||
add_timestamp: i64,
|
add_timestamp: i64,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
|
ensure_and_debug_assert!(
|
||||||
|
configured
|
||||||
|
.imap_folder
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|folder| !folder.is_empty()),
|
||||||
|
"Configured watched folder name cannot be empty"
|
||||||
|
);
|
||||||
|
|
||||||
let addr = addr_normalize(&configured.addr);
|
let addr = addr_normalize(&configured.addr);
|
||||||
let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
|
let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
|
||||||
|
|
||||||
let mut modified = context
|
let mut modified = context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
@@ -824,6 +856,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
user: "alice".to_string(),
|
user: "alice".to_string(),
|
||||||
}],
|
}],
|
||||||
|
imap_folder: None,
|
||||||
imap_user: "".to_string(),
|
imap_user: "".to_string(),
|
||||||
imap_password: "foo".to_string(),
|
imap_password: "foo".to_string(),
|
||||||
smtp: vec![ConfiguredServerLoginParam {
|
smtp: vec![ConfiguredServerLoginParam {
|
||||||
@@ -932,6 +965,7 @@ mod tests {
|
|||||||
user: user.to_string(),
|
user: user.to_string(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
imap_folder: None,
|
||||||
imap_user: "alice@posteo.de".to_string(),
|
imap_user: "alice@posteo.de".to_string(),
|
||||||
imap_password: "foobarbaz".to_string(),
|
imap_password: "foobarbaz".to_string(),
|
||||||
smtp: vec![
|
smtp: vec![
|
||||||
@@ -1045,6 +1079,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
user: addr.clone(),
|
user: addr.clone(),
|
||||||
}],
|
}],
|
||||||
|
imap_folder: None,
|
||||||
imap_user: addr.clone(),
|
imap_user: addr.clone(),
|
||||||
imap_password: "foobarbaz".to_string(),
|
imap_password: "foobarbaz".to_string(),
|
||||||
smtp: vec![ConfiguredServerLoginParam {
|
smtp: vec![ConfiguredServerLoginParam {
|
||||||
|
|||||||
Reference in New Issue
Block a user