1 # -*- coding: UTF-8 -*-
2 # vim: expandtab sw=4 ts=4 sts=4:
7 __author__
= 'Michal Čihař'
8 __email__
= 'michal@cihar.com'
10 Copyright © 2003 - 2010 Michal Čihař
12 This program is free software; you can redistribute it and/or modify it
13 under the terms of the GNU General Public License version 2 as published by
14 the Free Software Foundation.
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 import Wammu
.MailWriter
33 MAILBOX_RE
= re
.compile(r
'\((?P<flags>(\s*\\\w*)*)\)\s+(?P<delim>[^ ]*)\s+(?P<name>.*)')
34 POSSIBLY_QUOTED_RE
= re
.compile(r
'"?([^"]*)"?')
36 def SMSToMailbox(parent
, messages
, contacts
):
38 wildcard
= _('Mailboxes') + ' (*.mbox)|*.mbox|' + _('All files') + ' (*.*)|*.*;*'
41 dlg
= wx
.FileDialog(parent
, _('Select mailbox file...'), os
.getcwd(), "", wildcard
, wx
.SAVE | wx
.OVERWRITE_PROMPT | wx
.CHANGE_DIR
)
43 if dlg
.ShowModal() != wx
.ID_OK
:
47 ext
= exts
[dlg
.GetFilterIndex()]
48 # Add automatic extension if we know one and file does not
50 if (os
.path
.splitext(path
)[1] == '' and
54 parent
.ShowProgress(_('Saving messages to mailbox'))
57 for i
in range(count
):
58 if not parent
.progress
.Update(i
* 100 / count
):
60 parent
.SetStatusText(_('Export terminated'))
64 filename
, data
, unused_msgid
= Wammu
.MailWriter
.SMSToMail(parent
.cfg
, sms
, contacts
, True)
72 wx
.MessageDialog(parent
,
73 _('Creating of file %s failed, bailing out.') % path
,
74 _('Can not create file!'),
75 wx
.OK | wx
.ICON_ERROR
).ShowModal()
77 parent
.SetStatusText(_('Export terminated'))
80 parent
.progress
.Update(100)
82 parent
.SetStatusText(_('%(count)d messages exported to "%(path)s" (%(type)s)') % {'count':count
, 'path':path
, 'type': _('mailbox')})
84 def SMSToMaildir(parent
, messages
, contacts
):
86 dlg
= wx
.DirDialog(parent
, _('Select maildir directory where to save files'), os
.getcwd(),
87 style
=wx
.DD_DEFAULT_STYLE|wx
.DD_NEW_DIR_BUTTON
)
89 if dlg
.ShowModal() != wx
.ID_OK
:
95 if not os
.path
.isdir(os
.path
.join(outpath
, 'new')):
96 res
= wx
.MessageDialog(parent
,
97 _('Selected folder does not contain new subfolder and thus probably is not valid maildir.\n\nDo you want to create new subfolder and export to it?'),
98 _('Folder doesn\'t look like maildir!'),
99 wx
.YES_NO | wx
.YES_DEFAULT | wx
.CANCEL | wx
.ICON_WARNING
).ShowModal()
101 if res
== wx
.ID_CANCEL
:
105 outpath
= os
.path
.join(outpath
, 'new')
109 wx
.MessageDialog(parent
,
110 _('Creating of folder failed, bailing out.'),
111 _('Can not create folder!'),
112 wx
.OK | wx
.ICON_ERROR
).ShowModal()
115 outpath
= os
.path
.join(outpath
, 'new')
117 parent
.ShowProgress(_('Saving messages to maildir'))
118 for i
in range(count
):
119 if not parent
.progress
.Update(i
* 100 / count
):
121 parent
.SetStatusText(_('Export terminated'))
125 filename
, data
, unused_msgid
= Wammu
.MailWriter
.SMSToMail(parent
.cfg
, sms
, contacts
)
127 outfile
= os
.path
.join(outpath
, filename
)
128 if os
.path
.exists(outfile
):
129 res
= wx
.MessageDialog(parent
,
130 _('Output file already exists, this usually means that this message was already saved there.\n\nDo you wish to overwrite file %s?') % outfile
,
131 _('File already exists!'),
132 wx
.YES_NO | wx
.NO_DEFAULT | wx
.CANCEL | wx
.ICON_WARNING
).ShowModal()
134 if res
== wx
.ID_CANCEL
:
136 parent
.SetStatusText(_('Export terminated'))
142 f
= file(outfile
, 'w')
146 wx
.MessageDialog(parent
,
147 _('Creating of file %s failed, bailing out.') % outfile
,
148 _('Can not create file!'),
149 wx
.OK | wx
.ICON_ERROR
).ShowModal()
151 parent
.SetStatusText(_('Export terminated'))
154 parent
.progress
.Update(100)
157 parent
.SetStatusText(_('%(count)d messages exported to "%(path)s" (%(type)s)') % {'count':count
, 'path':path
, 'type': _('maildir')})
159 def ParseIMAPFolder(item
):
161 Parses folder reply from IMAP.
163 match
= MAILBOX_RE
.match(item
)
168 delim
= POSSIBLY_QUOTED_RE
.match(match
.group('delim')).group(1)
169 path
= POSSIBLY_QUOTED_RE
.match(match
.group('name')).group(1)
170 flags
= match
.group('flags')
173 def SMSToIMAP(parent
, messages
, contacts
):
174 imapConfig
= IMAPConfigHelper(parent
.cfg
)
177 value
= IMAPSettingsDialog(parent
, imapConfig
).ShowModal()
178 if value
== wx
.ID_CANCEL
:
181 busy
= wx
.BusyInfo(_('Connecting to IMAP server...'))
183 if imapConfig
.useSSL
:
184 m
= imaplib
.IMAP4_SSL(imapConfig
.server
, int(imapConfig
.port
))
186 m
= imaplib
.IMAP4(imapConfig
.server
, int(imapConfig
.port
))
189 res
= m
.login(imapConfig
.login
, imapConfig
.password
)
196 if wx
.MessageDialog(parent
,
197 _('Can not login, you probably entered invalid login information. Do you want to retry?'),
199 wx
.YES_NO | wx
.YES_DEFAULT | wx
.ICON_ERROR
).ShowModal() == wx
.ID_NO
:
202 busy
= wx
.BusyInfo(_('Listing folders on IMAP server...'))
209 wx
.MessageDialog(parent
,
210 _('Can not list folders on server, bailing out.'),
211 _('Listing failed!'),
212 wx
.OK | wx
.ICON_ERROR
).ShowModal()
213 parent
.SetStatusText(_('Export terminated'))
218 path
, flags
= ParseIMAPFolder(item
)
223 if flags
.find('\\Noselect') != -1:
227 folders
.append(unicode(path
, 'imap4-utf-7'))
228 except UnicodeDecodeError:
229 # Ignore folders which can not be properly converted
234 lastFolder
= parent
.cfg
.Read('/IMAP/LastUsedFolder')
238 folderIndex
= folders
.index(lastFolder
)
242 dlg
= wx
.SingleChoiceDialog(parent
,
243 _('Please select folder on server %s where messages will be stored') % imapConfig
.server
,
245 folders
, wx
.CHOICEDLG_STYLE | wx
.RESIZE_BORDER
)
247 dlg
.SetSelection(folderIndex
)
248 if dlg
.ShowModal() == wx
.ID_CANCEL
:
254 path
= '%s@%s/%s' % (imapConfig
.login
, imapConfig
.server
, folders
[dlg
.GetSelection()])
255 folder
= folders
[dlg
.GetSelection()].encode('imap4-utf-7')
257 parent
.cfg
.Write('/IMAP/LastUsedFolder', folders
[dlg
.GetSelection()])
259 busy
= wx
.BusyInfo(_('Selecting folder on IMAP server...'))
261 res
= m
.select(folder
)
266 wx
.MessageDialog(parent
,
267 _('Can not select folder %s on server, bailing out.') % folder
,
268 _('Selecting failed!'),
269 wx
.OK | wx
.ICON_ERROR
).ShowModal()
270 parent
.SetStatusText(_('Export terminated'))
275 (imapConfig
.backupRead
and sms
['SMS'][0]['State'] == 'Read') \
276 or (imapConfig
.backupSent
and sms
['SMS'][0]['State'] == 'Sent') \
277 or (imapConfig
.backupUnread
and sms
['SMS'][0]['State'] == 'UnRead') \
278 or (imapConfig
.backupUnsent
and sms
['SMS'][0]['State'] == 'UnSent')
280 messages
= filter(msgFilter
, messages
)
281 count
= len(messages
)
283 parent
.ShowProgress(_('Saving messages to IMAP'))
286 count
= len(messages
)
287 for i
in range(count
):
288 if not parent
.progress
.Update(i
* 100 / count
):
290 parent
.SetStatusText(_('Export terminated'))
295 filename
, data
, msgid
= Wammu
.MailWriter
.SMSToMail(parent
.cfg
, sms
, contacts
)
297 if imapConfig
.newMessages
== True:
298 res
, msgnums
= m
.search(None, 'HEADER', '"Message-ID" "' + msgid
+ '"')
299 if len(msgnums
[0].split()) != 0:
302 new_messages_num
+= 1
305 res
= m
.append(folder
, '$SMS', None, data
)
309 wx
.MessageDialog(parent
,
310 _('Can not save message to folder %s on server, bailing out.') % folder
,
312 wx
.OK | wx
.ICON_ERROR
).ShowModal()
313 parent
.progress
.Update(100)
315 parent
.SetStatusText(_('Export terminated'))
318 parent
.progress
.Update(100)
326 if imapConfig
.newMessages
== False:
327 parent
.SetStatusText(_('%(count)d messages exported to "%(path)s" (%(type)s)') % {'count':count
, 'path':path
, 'type': _('IMAP server')})
329 parent
.SetStatusText(_('%(new)d new of %(count)d messages exported to "%(path)s" (%(type)s)') % {'new':new_messages_num
, 'count':count
, 'path':path
, 'type': _('IMAP server')})
331 def SMSExport(parent
, messages
, contacts
):
332 # Select where to export
333 dlg
= wx
.SingleChoiceDialog(parent
, _('Where do you want to export emails created from your messages?'), _('Select export type'),
334 [_('Mailbox file'), _('Maildir folder'), _('IMAP account')], wx
.CHOICEDLG_STYLE | wx
.RESIZE_BORDER
)
335 if dlg
.ShowModal() != wx
.ID_OK
:
338 idx
= dlg
.GetSelection()
343 SMSToMailbox(parent
, messages
, contacts
)
346 SMSToMaildir(parent
, messages
, contacts
)
349 SMSToIMAP(parent
, messages
, contacts
)
351 raise Exception('Not implemented export functionality!')
360 return value
== 'yes'
362 class IMAPConfigHelper
:
364 A small helper to read and write the Wammu config
366 def __init__(self
, WammuConfig
):
367 self
.wcfg
= WammuConfig
373 self
.fromAddress
= self
.wcfg
.Read('/MessageExport/From')
374 self
.server
= self
.wcfg
.Read('/IMAP/Server')
375 self
.port
= self
.wcfg
.Read('/IMAP/Port')
376 self
.login
= self
.wcfg
.Read('/IMAP/Login')
377 self
.password
= self
.wcfg
.Read('/IMAP/Password')
380 self
.rememberPassword
= yn2bool(self
.wcfg
.Read('/IMAP/RememberPassword'))
381 self
.useSSL
= yn2bool(self
.wcfg
.Read('/IMAP/UseSSL'))
382 self
.newMessages
= yn2bool(self
.wcfg
.Read('/IMAP/OnlyNewMessages'))
385 self
.backupRead
= yn2bool(self
.wcfg
.Read('/IMAP/BackupStateRead'))
386 self
.backupSent
= yn2bool(self
.wcfg
.Read('/IMAP/BackupStateSent'))
387 self
.backupUnread
= yn2bool(self
.wcfg
.Read('/IMAP/BackupStateUnread'))
388 self
.backupUnsent
= yn2bool(self
.wcfg
.Read('/IMAP/BackupStateUnsent'))
399 self
.wcfg
.Write('/MessageExport/From', self
.fromAddress
)
400 self
.wcfg
.Write('/IMAP/Server', self
.server
)
401 self
.wcfg
.Write('/IMAP/Port', self
.port
)
402 self
.wcfg
.Write('/IMAP/Login', self
.login
)
403 if self
.rememberPassword
:
404 self
.wcfg
.Write('/IMAP/Password', self
.password
)
406 self
.wcfg
.Write('/IMAP/Password', '')
409 self
.wcfg
.Write('/IMAP/RememberPassword', bool2yn(self
.rememberPassword
))
410 self
.wcfg
.Write('/IMAP/UseSSL', bool2yn(self
.useSSL
))
411 self
.wcfg
.Write('/IMAP/OnlyNewMessages', bool2yn(self
.newMessages
))
414 self
.wcfg
.Write('/IMAP/BackupStateRead', bool2yn(self
.backupRead
))
415 self
.wcfg
.Write('/IMAP/BackupStateSent', bool2yn(self
.backupSent
))
416 self
.wcfg
.Write('/IMAP/BackupStateUnread', bool2yn(self
.backupUnread
))
417 self
.wcfg
.Write('/IMAP/BackupStateUnsent', bool2yn(self
.backupUnsent
))
420 class IMAPSettingsDialog(wx
.Dialog
):
421 def __init__(self
, parent
, imapConfig
):
422 wx
.Dialog
.__init
__(self
, parent
, -1, _("IMAP Settings"), style
= wx
.DEFAULT_DIALOG_STYLE | wx
.RESIZE_BORDER
)
423 self
.cfg
= imapConfig
424 self
.connectionFrameSizer_staticbox
= wx
.StaticBox(self
, -1, _("Connection Details"))
425 self
.preferenciesFrameSizer_staticbox
= wx
.StaticBox(self
, -1, _("Preferences"))
426 self
.stateFrameSizer_staticbox
= wx
.StaticBox(self
, -1, _("Message State Selection"))
427 self
.fromAddressLabel
= wx
.StaticText(self
, -1, _("From Address"))
428 self
.fromAddressTextCtrl
= wx
.TextCtrl(self
, -1, "")
429 self
.serverLabel
= wx
.StaticText(self
, -1, _("Server"))
430 self
.serverTextCtrl
= wx
.TextCtrl(self
, -1, "")
431 self
.portLabel
= wx
.StaticText(self
, -1, _("Port"))
432 self
.portTextCtrl
= wx
.TextCtrl(self
, -1, "")
433 self
.loginLabel
= wx
.StaticText(self
, -1, _("Login"))
434 self
.loginTextCtrl
= wx
.TextCtrl(self
, -1, "")
435 self
.passwordLabel
= wx
.StaticText(self
, -1, _("Password"))
436 self
.passwordTextCtrl
= wx
.TextCtrl(self
, -1, "", style
=wx
.TE_PASSWORD
)
437 self
.rememberCheckBox
= wx
.CheckBox(self
, -1, _("Remember password (insecure)"))
438 self
.useSSLCheckBox
= wx
.CheckBox(self
, -1, _("Use SSL"))
439 self
.newMessagesCheckBox
= wx
.CheckBox(self
, -1, _("Only back-up new messages"))
440 self
.readCheckBox
= wx
.CheckBox(self
, -1, _("Read"))
441 self
.sentCheckBox
= wx
.CheckBox(self
, -1, _("Sent"))
442 self
.unreadCheckBox
= wx
.CheckBox(self
, -1, _("Unread"))
443 self
.unsentCheckBox
= wx
.CheckBox(self
, -1, _("Unsent"))
444 self
.applyButton
= wx
.Button(self
, wx
.ID_APPLY
, "")
445 self
.cancelButton
= wx
.Button(self
, wx
.ID_CANCEL
, "")
446 self
.okButton
= wx
.Button(self
, wx
.ID_OK
, "")
451 self
.Bind(wx
.EVT_CHECKBOX
, self
.OnToggleSSL
, self
.useSSLCheckBox
)
452 self
.Bind(wx
.EVT_BUTTON
, self
.OnOkClick
, self
.okButton
)
453 self
.Bind(wx
.EVT_BUTTON
, self
.OnApplyClick
, self
.applyButton
)
455 def __read_config(self
):
456 self
.fromAddressTextCtrl
.SetValue(self
.cfg
.fromAddress
)
457 self
.serverTextCtrl
.SetValue(self
.cfg
.server
)
458 self
.portTextCtrl
.SetValue(self
.cfg
.port
)
459 self
.loginTextCtrl
.SetValue(self
.cfg
.login
)
460 self
.passwordTextCtrl
.SetValue(self
.cfg
.password
)
462 self
.rememberCheckBox
.SetValue(self
.cfg
.rememberPassword
)
463 self
.useSSLCheckBox
.SetValue(self
.cfg
.useSSL
)
464 self
.newMessagesCheckBox
.SetValue(self
.cfg
.newMessages
)
466 self
.readCheckBox
.SetValue(self
.cfg
.backupRead
)
467 self
.sentCheckBox
.SetValue(self
.cfg
.backupSent
)
468 self
.unreadCheckBox
.SetValue(self
.cfg
.backupUnread
)
469 self
.unsentCheckBox
.SetValue(self
.cfg
.backupUnsent
)
471 def __copy_config(self
):
472 self
.cfg
.fromAddress
= self
.fromAddressTextCtrl
.GetValue()
473 self
.cfg
.server
= self
.serverTextCtrl
.GetValue()
474 self
.cfg
.port
= self
.portTextCtrl
.GetValue()
475 self
.cfg
.login
= self
.loginTextCtrl
.GetValue()
476 self
.cfg
.password
= self
.passwordTextCtrl
.GetValue()
478 self
.cfg
.rememberPassword
= self
.rememberCheckBox
.GetValue()
479 self
.cfg
.useSSL
= self
.useSSLCheckBox
.GetValue()
480 self
.cfg
.newMessages
= self
.useSSLCheckBox
.GetValue()
482 self
.cfg
.backupRead
= self
.readCheckBox
.GetValue()
483 self
.cfg
.backupSent
= self
.sentCheckBox
.GetValue()
484 self
.cfg
.backupUnread
= self
.unreadCheckBox
.GetValue()
485 self
.cfg
.backupUnsent
= self
.unsentCheckBox
.GetValue()
487 def __do_layout(self
):
488 mainSizer
= wx
.BoxSizer(wx
.VERTICAL
)
489 buttonRowSizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
490 stateFrameSizer
= wx
.StaticBoxSizer(self
.stateFrameSizer_staticbox
, wx
.HORIZONTAL
)
491 preferenciesFrameSizer
= wx
.StaticBoxSizer(self
.preferenciesFrameSizer_staticbox
, wx
.HORIZONTAL
)
492 preferenciesGridSizer
= wx
.FlexGridSizer(3, 2, 2, 2)
493 connectionFrameSizer
= wx
.StaticBoxSizer(self
.connectionFrameSizer_staticbox
, wx
.HORIZONTAL
)
494 connectionGridSizer
= wx
.FlexGridSizer(5, 2, 2, 10)
495 connectionGridSizer
.Add(self
.fromAddressLabel
, 0, wx
.ALIGN_CENTER_VERTICAL
, 0)
496 connectionGridSizer
.Add(self
.fromAddressTextCtrl
, 0, wx
.EXPAND
, 0)
497 connectionGridSizer
.Add(self
.serverLabel
, 0, wx
.ALIGN_CENTER_VERTICAL
, 0)
498 connectionGridSizer
.Add(self
.serverTextCtrl
, 0, wx
.EXPAND
, 0)
499 connectionGridSizer
.Add(self
.portLabel
, 0, wx
.ALIGN_CENTER_VERTICAL
, 0)
500 connectionGridSizer
.Add(self
.portTextCtrl
, 0, wx
.EXPAND
, 0)
501 connectionGridSizer
.Add(self
.loginLabel
, 0, wx
.ALIGN_CENTER_VERTICAL
, 0)
502 connectionGridSizer
.Add(self
.loginTextCtrl
, 0, wx
.EXPAND
, 0)
503 connectionGridSizer
.Add(self
.passwordLabel
, 0, wx
.ALIGN_CENTER_VERTICAL
, 0)
504 connectionGridSizer
.Add(self
.passwordTextCtrl
, 0, wx
.EXPAND
, 0)
505 connectionGridSizer
.AddGrowableCol(1)
506 connectionFrameSizer
.Add(connectionGridSizer
, 1, wx
.EXPAND
, 0)
507 mainSizer
.Add(connectionFrameSizer
, 0, wx
.ALL|wx
.EXPAND
, 2)
508 preferenciesGridSizer
.Add((90, 20), 0, 0, 0)
509 preferenciesGridSizer
.Add(self
.rememberCheckBox
, 0, 0, 0)
510 preferenciesGridSizer
.Add((20, 20), 0, 0, 0)
511 preferenciesGridSizer
.Add(self
.useSSLCheckBox
, 0, 0, 0)
512 preferenciesGridSizer
.Add((20, 20), 0, 0, 0)
513 preferenciesGridSizer
.Add(self
.newMessagesCheckBox
, 0, 0, 0)
514 preferenciesGridSizer
.AddGrowableCol(1)
515 preferenciesFrameSizer
.Add(preferenciesGridSizer
, 1, wx
.EXPAND
, 0)
516 mainSizer
.Add(preferenciesFrameSizer
, 0, wx
.ALL|wx
.EXPAND
, 2)
517 stateFrameSizer
.Add(self
.readCheckBox
, 1, wx
.EXPAND
, 0)
518 stateFrameSizer
.Add(self
.sentCheckBox
, 1, wx
.EXPAND
, 0)
519 stateFrameSizer
.Add(self
.unreadCheckBox
, 1, wx
.EXPAND
, 0)
520 stateFrameSizer
.Add(self
.unsentCheckBox
, 1, wx
.EXPAND
, 0)
521 mainSizer
.Add(stateFrameSizer
, 0, wx
.ALL|wx
.EXPAND
, 2)
522 buttonRowSizer
.Add((20, 20), 1, wx
.EXPAND
, 0)
523 buttonRowSizer
.Add(self
.applyButton
, 0, wx
.ALIGN_BOTTOM
, 0)
524 buttonRowSizer
.Add(self
.cancelButton
, 0, wx
.ALIGN_BOTTOM
, 0)
525 buttonRowSizer
.Add(self
.okButton
, 0, wx
.ALIGN_BOTTOM
, 0)
526 mainSizer
.Add(buttonRowSizer
, 1, wx
.ALL|wx
.EXPAND
, 2)
527 self
.SetSizer(mainSizer
)
531 def OnToggleSSL(self
, event
):
532 if self
.useSSLCheckBox
.GetValue():
533 if self
.portTextCtrl
.GetValue() == '143':
534 self
.portTextCtrl
.SetValue('993')
536 if self
.portTextCtrl
.GetValue() == '993':
537 self
.portTextCtrl
.SetValue('143')
540 def OnApplyClick(self
, event
):
544 def OnOkClick(self
, event
):
545 # check for all required fields
548 if self
.fromAddressTextCtrl
.GetValue() == "":
550 error
+= _('%d. From Address invalid\n') % counter
551 if self
.serverTextCtrl
.GetValue() == "":
553 error
+= _('%d. Server incomplete\n') % counter
554 if self
.portTextCtrl
.GetValue() == "" or re
.search('\D', self
.portTextCtrl
.GetValue()) :
556 error
+= _('%d. Port invalid\n') % counter
557 if self
.loginTextCtrl
.GetValue() == "":
559 error
+= _('%d. Login incomplete\n') % counter
560 if self
.passwordTextCtrl
.GetValue() == "":
562 error
+= _('%d. Password incomplete\n') % counter
563 if not self
.readCheckBox
.GetValue() and \
564 not self
.sentCheckBox
.GetValue() and \
565 not self
.unreadCheckBox
.GetValue() and \
566 not self
.unsentCheckBox
.GetValue():
568 error
+= _('%d. No messages to back-up selected. Please tick at least one of the states.') % counter
571 wx
.MessageDialog(self
,
574 wx
.OK | wx
.ICON_ERROR
).ShowModal()
579 # end of class IMAPSettingsDialog