From 191d11e719a243a9d51c222646f63b6801dd4a9b Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 3 Jul 2019 18:59:56 +0200 Subject: [PATCH] export and import full DC database state --- python/src/deltachat/account.py | 43 ++++++++++++++++++++++++++++++++- python/tests/test_account.py | 19 +++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index e5ccb028b..e911889d5 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -2,6 +2,7 @@ from __future__ import print_function import threading +import os import re import time import requests @@ -46,6 +47,7 @@ class Account(object): deltachat.set_context_callback(self._dc_context, self._process_event) self._threads = IOThreads(self._dc_context) self._configkeys = self.get_config("sys.config_keys").split() + self._imex_completed = threading.Event() def _check_config_key(self, name): if name not in self._configkeys: @@ -272,6 +274,32 @@ class Account(object): msg_ids = [msg.id for msg in messages] lib.dc_delete_msgs(self._dc_context, msg_ids, len(msg_ids)) + def export_to_dir(self, backupdir): + """return after all delta chat state is exported to a new file in + the specified directory. + """ + l = os.listdir(backupdir) + self._imex_completed.clear() + lib.dc_imex(self._dc_context, 11, as_dc_charpointer(backupdir), ffi.NULL) + if not self._threads.is_started(): + lib.dc_perform_imap_jobs(self._dc_context) + self._imex_completed.wait() + for x in os.listdir(backupdir): + if x not in l: + return os.path.join(backupdir, x) + + def import_from_file(self, path): + """import delta chat state from the specified backup file. + + The account must be in unconfigured state for import to attempted. + """ + assert not self.is_configured(), "cannot import into configured account" + self._imex_completed.clear() + lib.dc_imex(self._dc_context, 12, as_dc_charpointer(path), ffi.NULL) + if not self._threads.is_started(): + lib.dc_perform_imap_jobs(self._dc_context) + self._imex_completed.wait() + def start_threads(self): """ start IMAP/SMTP threads (and configure account if it hasn't happened). @@ -291,9 +319,19 @@ class Account(object): self._evlogger(evt_name, data1, data2) method = getattr(self._evhandler, evt_name.lower(), None) if method is not None: + # handle sync methods return method(data1, data2) or 0 + else: + # handle async methods + method = getattr(self, "on_" + evt_name.lower(), None) + if method is not None: + method(data1, data2) return 0 + def on_dc_event_imex_progress(self, data1, data2): + if data1 == 1000: + self._imex_completed.set() + class IOThreads: def __init__(self, dc_context): @@ -301,8 +339,11 @@ class IOThreads: self._thread_quitflag = False self._name2thread = {} + def is_started(self): + return len(self._name2thread) > 0 + def start(self, imap=True, smtp=True): - assert not self._name2thread + assert not self.is_started() if imap: self._start_one_thread("imap", self.imap_thread_run) if smtp: diff --git a/python/tests/test_account.py b/python/tests/test_account.py index c9249675c..837816261 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -202,6 +202,25 @@ class TestOfflineAccount: with pytest.raises(ValueError): ac1.configure(addr="123@example.org") + def test_import_export_one_contact(self, acfactory, tmpdir): + backupdir = tmpdir.mkdir("backup") + ac1 = acfactory.get_configured_offline_account() + contact1 = ac1.create_contact("some1@hello.com", name="some1") + chat = ac1.create_chat_by_contact(contact1) + msg = chat.send_text("msg1") + contact = msg.get_sender_contact() + assert contact == ac1.get_self_contact() + assert not backupdir.listdir() + + path = ac1.export_to_dir(backupdir.strpath) + assert os.path.exists(path) + ac2 = acfactory.get_unconfigured_account() + ac2.import_from_file(path) + l = ac2.get_contacts(query="some1") + assert len(l) == 1 + contact2 = l[0] + assert contact2.addr == "some1@hello.com" + class TestOnlineAccount: def test_one_account_init(self, acfactory):