aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Gunthorpe <jgg@mellanox.com>2020-01-31 17:08:43 -0400
committerJason Gunthorpe <jgg@mellanox.com>2020-01-31 17:08:43 -0400
commit131d182e00549ad617b979765354f9364e050211 (patch)
tree38c0cce5e4dc69b48f9dba6f819294eae6d6de73
parent4d303a4ab6c839766785d24fd093142e8fef190e (diff)
downloadcloud_mdir_sync-131d182e00549ad617b979765354f9364e050211.tar.gz
cloud_mdir_sync-131d182e00549ad617b979765354f9364e050211.tar.bz2
cloud_mdir_sync-131d182e00549ad617b979765354f9364e050211.zip
Add a --offline option
This allows treating the local message flags as authoritative during program startup. Messages marked locally as deleted will be deleted on the server during startup. For safety, this never considers the absence of a message locally as a deletion. Signed-off-by: Jason Gunthorpe <jgg@mellanox.com>
-rw-r--r--README.md8
-rw-r--r--cloud_mdir_sync/main.py42
2 files changed, 40 insertions, 10 deletions
diff --git a/README.md b/README.md
index 41989a2..2390056 100644
--- a/README.md
+++ b/README.md
@@ -147,6 +147,14 @@ With this design the maildir files are never disturbed. Even if the cloud side
changes UIDs the content hash matching will keep the same filename for the
maildir after re-downloading the message.
+## Offline Mode
+
+The `--offline` command line argument will allow cloud-mdir-sync to trust the
+local message flags. This mode is slightly dangerous as any dual-edit of
+message flags (including deletion or undeletion!) will be resolved in favor of
+the local state, not the cloud state. For message deletion to work with
+offline mode the MUA must use the Trash flag.
+
# Mail User Agent Configuration
cloud-mdir-sync will work with any Maildir based MUA, however things will work
diff --git a/cloud_mdir_sync/main.py b/cloud_mdir_sync/main.py
index 27fc711..54c9363 100644
--- a/cloud_mdir_sync/main.py
+++ b/cloud_mdir_sync/main.py
@@ -11,19 +11,21 @@ import pyinotify
from . import config, mailbox, messages, oauth, office365
-def force_local_to_cloud(cfg: config.Config) -> messages.MBoxDict_Type:
- """Make all the local mailboxes match their cloud content, overwriting any
- local changes."""
-
- # For every cloud message figure out which local mailbox it belongs to
+def route_cloud_messages(cfg: config.Config) -> messages.MBoxDict_Type:
+ """For every cloud message figure out which local mailbox it belongs to"""
msgs: messages.MBoxDict_Type = {}
for mbox in cfg.local_mboxes:
msgs[mbox] = {}
for mbox in cfg.cloud_mboxes:
- for ch,msg in mbox.messages.items():
+ for ch, msg in mbox.messages.items():
dest = cfg.direct_message(msg)
msgs[dest][ch] = msg
+ return msgs
+
+def force_local_to_cloud(cfg: config.Config, msgs: messages.MBoxDict_Type):
+ """Make all the local mailboxes match their cloud content, overwriting any
+ local changes."""
for mbox, msgdict in msgs.items():
if not mbox.same_messages(msgdict):
mbox.force_content(cfg.msgdb, msgdict)
@@ -31,7 +33,8 @@ def force_local_to_cloud(cfg: config.Config) -> messages.MBoxDict_Type:
async def update_cloud_from_local(cfg: config.Config,
- msgs_by_local: messages.MBoxDict_Type):
+ msgs_by_local: messages.MBoxDict_Type,
+ offline_mode=False):
"""Detect differences made by the local mailboxes and upload them to the
cloud."""
msgs_by_cloud: Dict[mailbox.Mailbox, messages.CHMsgMappingDict_Type] = {}
@@ -39,8 +42,12 @@ async def update_cloud_from_local(cfg: config.Config,
msgs_by_cloud[mbox] = {}
for local_mbox, msgdict in msgs_by_local.items():
for ch, cloud_msg in msgdict.items():
- msgs_by_cloud[cloud_msg.mailbox][ch] = (
- local_mbox.messages.get(ch), cloud_msg)
+ lmsg = local_mbox.messages.get(ch)
+ # When doing the first sweep in offline mode ignore missing local
+ # messages, only synchronize message flags.
+ if lmsg is None and offline_mode:
+ continue
+ msgs_by_cloud[cloud_msg.mailbox][ch] = (lmsg, cloud_msg)
await asyncio.gather(*(
mbox.merge_content(msgdict) for mbox, msgdict in msgs_by_cloud.items()
if not mbox.same_messages(msgdict, tuple_form=True)))
@@ -62,10 +69,16 @@ async def synchronize_mail(cfg: config.Config):
for mbox in cfg.all_mboxes()
if mbox.need_update))
+ nmsgs = route_cloud_messages(cfg)
if msgs is not None:
await update_cloud_from_local(cfg, msgs)
+ elif cfg.args.OFFLINE:
+ await update_cloud_from_local(cfg,
+ nmsgs,
+ offline_mode=True)
- msgs = force_local_to_cloud(cfg)
+ force_local_to_cloud(cfg, nmsgs)
+ msgs = nmsgs
except (FileNotFoundError, asyncio.TimeoutError,
aiohttp.client_exceptions.ClientError, IOError,
RuntimeError):
@@ -97,9 +110,18 @@ def main():
dest="CFG",
default="cms.cfg",
help="Configuration file to use")
+ parser.add_argument(
+ "--offline",
+ dest="OFFLINE",
+ default=False,
+ action="store_true",
+ help=
+ "Enable offline mode, local changes to message flags will be considered authoritative."
+ )
args = parser.parse_args()
cfg = config.Config()
+ cfg.args = args
cfg.load_config(args.CFG)
cfg.loop = asyncio.get_event_loop()
with contextlib.closing(pyinotify.WatchManager()) as wm, \