Commit 5b11ee95 authored by Gaute Hope's avatar Gaute Hope

doc and fix replace chars

parent 7c32ec7d
# gmailieer
Checks and fetches e-mail from gmail accounts to a maildir, can synchronize
maildir flags and notmuch tags back to gmail.
This program can pull email and labels (and changes to labels) from your GMail
account and store them locally in a maildir with the labels synchronized with a
[notmuch]( database. The changes to tags in the notmuch database may be pushed
back remotely to your GMail account.
It will and can not:
* Add or delete messages on your remote account
* Modify messages other than their labels
# disclaimer
This does not modify or delete your email remotely, but it can modify the
message labels remotely. So those may be lost if all goes wrong. Also, whether
you will actually get all your email is not proven. **In short: No
# usage
this assumes your root mail folder is in `~/.mail`, all `gmailieer` commands
should be run from the `gmailieer` storage unless otherwise specified.
1. get an api key for a CLI application and store the client_secret.json file
somewhere gmailieer can find it (most likely in the current directory).
1. make a directory for the gmailieer storage and state files
$ cd ~/.mail
$ mkdir gmailieer
$ cd gmailieer/
1. make sure this directory is ignored by `notmuch new` as the messages will be
handled by `gmailieer`, in .notmuch-config:
1. initialize the mail storage:
$ gmailieer init
if you haven't done `gmailieer auth` already, your browser will open and you have to
give gmailieer access to the necessary permissions.
you're now set up, and you can do the initial pull.
# pull
will pull down all remote changes since last time, overwriting any local tag
changes of the affected messages.
$ gmailieer pull
# push
will push up all changes since last push, overwriting any remote changes since
the previous pull of the affected messages.
$ gmailieer push
......@@ -45,6 +45,9 @@ class Gmailieer:
parser.add_argument ('--limit', type = int, default = None,
help = 'Maximum number of messages to synchronize (soft limit, gmail may return more)')
parser.add_argument ('--replace-slash-with-dot', action = 'store_true', default = False,
help = 'This will replace \'/\' with \'.\' in gmail labels (make sure you know the implications)')
args = parser.parse_args (sys.argv[1:])
self.args = args
......@@ -55,6 +58,10 @@ class Gmailieer:
self.account = args.account
self.force = args.force
self.limit = args.limit
self.replace_slash_with_dot = args.replace_slash_with_dot
if self.replace_slash_with_dot and self.action != 'init':
print ("--replace-slash-with-dot can only be specified with init")
if self.dry_run:
print ("dry-run: ", self.dry_run)
......@@ -73,7 +80,7 @@ class Gmailieer:
self.push ()
elif self.action == 'init':
self.local.initialize_repository ()
self.local.initialize_repository (self.replace_slash_with_dot)
def push (self):
self.remote.get_labels ()
......@@ -111,12 +118,15 @@ class Gmailieer:
messages = messages[:self.limit]
# push changes
bar = tqdm (leave = True, total = len(messages), desc = 'pushing changes')
bar = tqdm (leave = True, total = len(messages), desc = 'pushing, 0 changed')
changed = 0
for m in messages:
self.remote.update (m)
if self.remote.update (m): changed += 1
bar.set_description ('pushing, %d changed' % changed)
bar.update (1)
bar.close ()
self.local.notmuch.close ()
if not self.dry_run:
self.local.state.set_lastmod (rev)
......@@ -23,9 +23,14 @@ class Local:
labels_translate = { v: k for k, v in translate_labels.items () }
replace_slash_with_dot = True
ignore_labels = set (['attachment', 'encrypted', 'signed', 'new'])
ignore_labels = set ([
class RepositoryException (Exception):
......@@ -40,6 +45,8 @@ class Local:
# this is the last modification id of the notmuch db when the previous push was completed.
lastmod = 0
replace_slash_with_dot = False
def __init__ (self, state_f):
self.state_f = state_f
......@@ -51,12 +58,14 @@ class Local:
self.last_historyId = self.json.get ('last_historyId', 0)
self.lastmod = self.json.get ('lastmod', 0)
self.replace_slash_with_dot = self.json.get ('replace_slash_with_dot', False)
def write (self):
self.json = {}
self.json['last_historyId'] = self.last_historyId
self.json['lastmod'] = self.lastmod
self.json['replace_slash_with_dot'] = self.replace_slash_with_dot
with open (self.state_f, 'w') as fd:
json.dump (self.json, fd)
......@@ -121,7 +130,7 @@ class Local:
self.notmuch.close ()
self.notmuch = None
def initialize_repository (self):
def initialize_repository (self, replace_slash_with_dot):
Sets up a local repository
......@@ -135,6 +144,7 @@ class Local:
raise Local.RepositoryException ("'mail' exists: this repository seems to already be set up!")
self.state = Local.State (self.state_f)
self.state.replace_slash_with_dot = replace_slash_with_dot
self.state.write ()
os.makedirs (
os.makedirs (os.path.join (, '../new'))
......@@ -152,7 +162,7 @@ class Local:
if 'DRAFT' in labels:
info += 'D'
if 'IMPORTANT' in labels:
if 'STARRED' in labels:
info += 'F'
if 'TRASH' in labels:
......@@ -205,7 +215,7 @@ class Local:
labels = [self.translate_labels.get (l, l) for l in labels]
# this is my weirdness
if self.replace_slash_with_dot:
if self.state.replace_slash_with_dot:
labels = [l.replace ('/', '.') for l in labels]
if fname is None:
......@@ -235,7 +245,7 @@ class Local:
# message is already in db, set local tags to match remote tags
otags = nmsg.get_tags ()
otags = list(nmsg.get_tags ())
if set(otags) != set (labels):
if not self.dry_run:
nmsg.freeze ()
......@@ -228,7 +228,7 @@ class Remote:
labels = [self.gmailieer.local.translate_labels.get (l, l) for l in labels]
# this is my weirdness
if self.gmailieer.local.replace_slash_with_dot:
if self.gmailieer.local.state.replace_slash_with_dot:
labels = [l.replace ('/', '.') for l in labels]
labels = set(labels)
......@@ -246,7 +246,7 @@ class Remote:
add = [self.gmailieer.local.labels_translate.get (k, k) for k in add]
rem = [self.gmailieer.local.labels_translate.get (k, k) for k in rem]
if self.gmailieer.local.replace_slash_with_dot:
if self.gmailieer.local.state.replace_slash_with_dot:
add = [a.replace ('.', '/') for a in add]
rem = [r.replace ('.', '/') for r in rem]
......@@ -256,6 +256,10 @@ class Remote:
self.__push_tags__ (mid, add, rem)
return True
return False
def __push_tags__ (self, mid, add, rem):
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment