Skip to content

Commit

Permalink
Locate special folders like Sent or Trash
Browse files Browse the repository at this point in the history
Configuring special folders is often a pain that requires the
owner of an account to give the location of folders himself.

With the help of IMAP extensions and a few assumptions we can try
to guess where these folders are. In practice this is enough for
the vast majority of accounts.

Related to #296
  • Loading branch information
Nicolas Le Manchet committed Jan 15, 2018
1 parent 6dea381 commit 052209e
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
57 changes: 57 additions & 0 deletions imapclient/imapclient.py
Expand Up @@ -76,6 +76,27 @@
DRAFT = br'\Draft'
RECENT = br'\Recent' # This flag is read-only

# Special folders, see RFC6154
# \Flagged is omitted because it is the same as the flag defined above
ALL = br'\All'
ARCHIVE = br'\Archive'
DRAFTS = br'\Drafts'
JUNK = br'\Junk'
SENT = br'\Sent'
TRASH = br'\Trash'

# Personal namespaces that are common among providers
# used as a fallback when the server does not support the NAMESPACE capability
_POPULAR_PERSONAL_NAMESPACES = (("", ""), ("INBOX.", "."))

# Names of special folders that are common among providers
_POPULAR_SPECIAL_FOLDERS = {
SENT: ("Sent", "Sent Items", "Sent items"),
DRAFTS: ("Drafts",),
ARCHIVE: ("Archive",),
TRASH: ("Trash", "Deleted Items", "Deleted Messages"),
JUNK: ("Junk", "Spam")
}

class Namespace(tuple):

Expand Down Expand Up @@ -565,6 +586,42 @@ def _proc_folder_list(self, folder_data):
ret.append((flags, delim, name))
return ret

def find_special_folder(self, folder_flag):
"""Try to locate a special folder, like the Sent or Trash folder.
>>> server.find_special_folder(imapclient.SENT)
'INBOX.Sent'
This function tries its best to find the correct folder (if any) but
uses heuristics when the server is unable to precisely tell where
special folders are located.
Returns the name of the folder if found, or None otherwise.
"""
# Detect folder by looking for known attributes
# TODO: avoid listing all folders by using extended LIST (RFC6154)
if self.has_capability('SPECIAL-USE'):
for folder in self.list_folders():
if folder and len(folder[0]) > 1 and folder[0][1] == folder_flag:
return folder[2]

# Detect folder by looking for common names
# We only look for folders in the "personal" namespace of the user
if self.has_capability('NAMESPACE'):
personal_namespaces = self.namespace().personal
else:
personal_namespaces = _POPULAR_PERSONAL_NAMESPACES

for personal_namespace in personal_namespaces:
for pattern in _POPULAR_SPECIAL_FOLDERS.get(folder_flag, tuple()):
pattern = personal_namespace[0] + pattern
sent_folders = self.list_folders(pattern=pattern)
if sent_folders:
return sent_folders[0][2]

return None


def select_folder(self, folder, readonly=False):
"""Set the current folder on the server.
Expand Down
28 changes: 28 additions & 0 deletions tests/test_imapclient.py
Expand Up @@ -152,6 +152,34 @@ def test_blanks(self):
self.assertEqual(folders, [((br'\HasNoChildren',), b'/', 'last')])


class TestFindSpecialFolder(IMAPClientTest):

def test_find_special_folder_with_special_use(self):
self.client._cached_capabilities = (b'SPECIAL-USE',)
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = (
'LIST', [
b'(\\HasNoChildren) "/" "INBOX"',
b'(\\HasNoChildren \\Sent) "/" "Sent"',
])

folder = self.client.find_special_folder(b'\\Sent')

self.assertEqual(folder, "Sent")

def test_find_special_folder_without_special_use_nor_namespace(self):
self.client._cached_capabilities = (b'FOO',)
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = (
'LIST', [
b'(\\HasNoChildren) "/" "Sent Items"',
])

folder = self.client.find_special_folder(b'\\Sent')

self.assertEqual(folder, "Sent Items")


class TestSelectFolder(IMAPClientTest):

def test_normal(self):
Expand Down

0 comments on commit 052209e

Please sign in to comment.