1# Copyright 2020 Patrick Ulbrich <zulu99@gmx.net>
2# Copyright 2016 Timo Kankare <timo.kankare@iki.fi>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17# MA 02110-1301, USA.
18#
19
20"""Implementation of local mailboxes, like mbox and maildir."""
21
22import email
23import mailbox
24import logging
25import os.path
26
27from Mailnag.backends.base import MailboxBackend
28
29class MBoxBackend(MailboxBackend):
30	"""Implementation of mbox mail boxes."""
31
32	def __init__(self, name = '', path=None, **kw):
33		"""Initialize mbox mailbox backend with a name and path."""
34		self._name = name
35		self._path = path
36		self._opened = False
37
38
39	def open(self):
40		"""'Open' mbox. (Actually just checks that mailbox file exists.)"""
41		if not os.path.isfile(self._path):
42			raise IOError('Mailbox {} does not exist.'.format(self._path))
43		self._opened = True
44
45
46	def close(self):
47		"""Close mbox."""
48		self._opened = False
49
50
51	def is_open(self):
52		"""Return True if mailbox is opened."""
53		return self._opened
54
55
56	def list_messages(self):
57		"""List unread messages from the mailbox.
58		Yields pairs (folder, message) where folder is always ''.
59		"""
60		mbox = mailbox.mbox(self._path, create=False)
61		folder = ''
62		try:
63			for msg in mbox:
64				if 'R' not in msg.get_flags():
65					yield (folder, msg, {})
66		finally:
67			mbox.close()
68
69
70	def request_folders(self):
71		"""mbox does not suppoert folders."""
72		raise NotImplementedError("mbox does not support folders")
73
74
75	def supports_mark_as_seen(self):
76		return False
77
78
79	def mark_as_seen(self, mails):
80		# TODO: local mailboxes should support this
81		raise NotImplementedError
82
83
84	def notify_next_change(self, callback=None, timeout=None):
85		raise NotImplementedError("mbox does not support notifications")
86
87
88	def cancel_notifications(self):
89		raise NotImplementedError("mbox does not support notifications")
90
91
92class MaildirBackend(MailboxBackend):
93	"""Implementation of maildir mail boxes."""
94
95	def __init__(self, name = '', path=None, folders=[], **kw):
96		"""Initialize maildir mailbox backend with a name, path and folders."""
97		self._name = name
98		self._path = path
99		self._folders = folders
100		self._opened = False
101
102
103	def open(self):
104		"""'Open' mailbox. (Actually just checks that maildir directory exists.)"""
105		if not os.path.isdir(self._path):
106			raise IOError('Mailbox {} does not exist.'.format(self._path))
107		self._opened = True
108
109
110	def close(self):
111		"""Close mailbox."""
112		self._opened = False
113
114
115	def is_open(self):
116		"""Return True if mailbox is opened."""
117		return self._opened
118
119
120	def list_messages(self):
121		"""List unread messages from the mailbox.
122		Yields pairs (folder, message).
123		"""
124		folders = self._folders if len(self._folders) != 0 else ['']
125		root_maildir = mailbox.Maildir(self._path, factory=None, create=False)
126		try:
127			for folder in folders:
128				maildir = self._get_folder(root_maildir, folder)
129				for msg in maildir:
130					if 'S' not in msg.get_flags():
131						yield folder, msg
132		finally:
133			root_maildir.close()
134
135
136	def request_folders(self):
137		"""Lists folders from maildir."""
138		maildir = mailbox.Maildir(self._path, factory=None, create=False)
139		try:
140			return [''] + maildir.list_folders()
141		finally:
142			maildir.close()
143
144
145	def notify_next_change(self, callback=None, timeout=None):
146		raise NotImplementedError("maildir does not support notifications")
147
148
149	def cancel_notifications(self):
150		raise NotImplementedError("maildir does not support notifications")
151
152
153	def _get_folder(self, maildir, folder):
154		"""Returns folder instance of the given maildir."""
155		if folder == '':
156			return maildir
157		else:
158			return maildir.get_folder(folder)
159
160