(Originally posted on the offlineimap mailing list on 15 Jan 2012)
I’ve finally got offlineimap’s ‘nametrans’ feature to do what I want.
Part of the problem was that I found the documentation and examples to be somewhat ambiguous in terms of which direction the translation would happen.
Anyway, I’ve got it working, so here are my notes which will hopefully be helpful to others.
The ‘nametrans’ configuration option allows conversion between different folder names on the local and remote repositories.
nametrans specifies a Python expression which, given each folder name from the repository in turn, returns the equivalent name on the other repository.
In the configuration of the local repository, the nametrans code receives each local folder name, and returns the name of the equivalent remote folder.
In the remote repository configuration, the nametrans code gets the remote folder names, one at a time, and returns the equivalent local names.
Here’s a real example. My remote repository is on my ISP’s server. The ISP’s spam filter puts spam emails into a folder called ‘Spam’, and deleted emails into ‘Trash’. The local repository, on a Debian server running Dovecot IMAP, is accessed by users via Outlook, which likes to use the folder name ‘Junk E-mail’ for spam and ‘Deleted Items’ for deleted emails.
So to avoid confusion, I’ve set up name translations:
<code>[Repository LocalIMAP] type = IMAP remotehost = localhost ... nametrans = lambda foldername: re.sub ('^Junk E-mail$', 'Spam', re.sub ('^Deleted Items$', 'Trash', foldername)) [Repository RemoteIMAP] type = IMAP remotehost = isp.example.com ... nametrans = lambda foldername: re.sub ('^Spam$', 'Junk E-mail', re.sub ('^Trash$', 'Deleted Items', foldername))</code>
I won’t explain Python lambdas and regular expressions here. But beginners should note
- the variable name after the word ‘lambda’ can be anything you like, but must be the same as is used in the expression after the ‘:’; it makes sense to call it ‘foldername’, or just ‘f’ if you don’t like typing.
- the re.sub function takes three arguments:
- regular expression to match against the third argument
- the string to return if there’s a match
- the value to match against — here the folder name.
- the use of ^ and $ to mark the beginning and end of the match, i.e. to make sure the whole folder name is matched.
- if there is no match, re.sub() returns the folder name unchanged.
- re.sub() can be nested to allow more than one translation.
With this configuration, offlineimap will synchronise the local ‘Junk E-mail’ folder with the remote ‘Spam’ folder, and local ‘Deleted Items’ with remote ‘Trash’ in both directions.
Problems will occur if the ‘foreign’ name exists as a folder. For example, if ‘Trash’ exists on the local repository, you’ll get this error message:
<code>ERROR: INFINITE FOLDER CREATION DETECTED! Folder 'Trash' (repository 'LocalIMAP') would be created as folder 'Trash' (repository 'RemoteIMAP'). The latter becomes 'Deleted Items' in return, leading to infinite folder creation cycles. SOLUTION: 1) Do set your nametrans rules on both repositories so they lead to identical names if applied back and forth. 2) Use folderfilter settings on a repository to prevent some folders from being created on the other side.</code>
I found that message only partially helpful: does it mean use both solutions 1) and 2), or either?
Solution 3) would be “Remove folder ‘Trash’ from the local repository”, but if you don’t want to do that, then solution 2) is required: add a folderfilter to stop the local ‘Trash’ from being copied to the remote side, like this (in the local repository configuration):
<code>folderfilter = lambda foldername: foldername not in ['Trash']</code>
That folderfilter is only required, in this example, if ‘Trash’ already exists locally, or if there is any danger of it being created in the future. It’s always possible that a translated folder name could be created by the user, so it’s safest to add folderfilters for any translated folder names. To complete my real-world example from above, I need:
<code>[Repository LocalIMAP] # Has folders: Inbox, Junk E-mail, Deleted Items, etc. type = IMAP remotehost = localhost ... nametrans = lambda foldername: re.sub ('^Junk E-mail$', 'Spam', re.sub ('^Deleted Items$', 'Trash', foldername)) folderfilter = lambda foldername: foldername not in ['Spam', 'Trash'] [Repository RemoteIMAP] # Has folders: Inbox, Spam, Trash, etc. type = IMAP remotehost = isp.example.com ... nametrans = lambda foldername: re.sub ('^Spam$', 'Junk E-mail', re.sub ('^Trash$', 'Deleted Items', foldername)) folderfilter = lambda foldername: foldername not in ['Junk E-mail', 'Deleted Items'] </code>
I hope that’s clear, and that it’s helpful to people. Perhaps some of it could be included in the offlineimap documentation.