MailSneaker - Part 4: small snipplets to wrap things up

07.03.2007 by kerim in python | tutorial

MailSneaker Part 4: Wrapping it up the simple way

In the last three sessions we made some mailfetchers for pop and imap accounts (session 1 and session 2) and created a small app with a systemtray-/taskbaricon (session 3).
What we need now is a simple way to wrap all those things together into one package.

I made some small code for that. It works, it's NOT good for a general solution but it's ok for a start.
I thought about doing the complete application here but i think that would be too much.

So i rather provide some small code snipplets and everyone can use them as he sees fit.
And if you want something complete you can stick to my "YouHaveMail" aka MailFetcher Version 0.1 the next week(s) ;)

ok.. first some changes.
What we need for mail accounts is a class that stores account data.

class MailAccount:  
     def __init__(self,accname="",sname="", uname="", passwd="",mailURL="",type="imap",port = imaplib.IMAP4_PORT,inboxes=['INBOX']):  
         self.accountname=accname  
         self.servername=sname  
         self.username=uname  
         self.password=passwd  
         self.type=type  
         self.port=port  
         self.inboxes=inboxes  
         self.mailURL=mailURL  
         self.oldMails={}

The fields should be self explanatory. We need "oldMails" only for pop accounts (you should know why by now)

For those that need to load and store such data and don't want to hardcode the accounts i have two methods.
A good question is where to put them. I would put the save method in the class.
Saving:

def saveAccountData(self):  
         file = open(self.accountname+".data", 'w')  
         pickle.dump(self, file)  
         file.close()

As you can see there is a potential security risk here. The pickled files will contain your account and pass in plain text.
So binary encoding should be done.
This can be done in newer versions of python (i think >2.3) by using the following line in the saveAccountData method:
pickle.dump(self, file, protocol=-1, bin=1)
protocol = -1 will use the highest (newest) pickle protocol available, bin=1 will tell the Pickler to store things in binary form.
Keep in mind that still people could read your accounts by using the apropriate loading method on a stored accountfile.

Personally i will put the loading method in the application class und not in the MailAccount. Feel free to do it otherwise:

def loadAccountData(self,accountName):  
     try:  
         file = open(accountName+".data", 'r')  
         mailAccount=pickle.load(file)  
         if mailAccount!=None:  
             self.accountname=mailAccount.accountname  
             self.servername=mailAccount.servername  
             self.username=mailAccount.username  
             self.password=mailAccount.password  
             self.type=mailAccount.type  
             self.port=mailAccount.port  
             self.inboxes=mailAccount.inboxes  
             self.mailURL=mailAccount.mailURL  
             self.oldMails=mailAccount.oldMails  
             return mailAccount  
         file.close()  
     except:  
         print "Mail Account doesn't exist yet or unpickle error"

Now is the time for a first refactoring of the code we already have. If you take a look again at the two fetchers for pop and imap you will see some list of parameters used. Just replace them with a parameter mailaccount.
Of course you can also then replace the members in these classes by mailaccount.
So it looks like this (example for imap):

def __init__(self, mailAccount):  
    self.mailAccount=mailAccount

def connect(self):  
    server = imaplib.IMAP4(self.mailAccount.servername,self.mailAccount.port)  
    server.login(self.mailAccount.username,self.mailAccount.password)  
    return server  
.....

This makes the code more readable and the job easier in case of changes don't you think ?

Now that we have a better structure for the mailaccounts we switch back again to our systemtray icon.
What we must do is running a timer that every now and then checks for mail.
Change the code in the MailFrame here:

def __init__(self, parent, id, title):  
    wx.Frame.__init__(self, parent, -1, title, size = (1, 1),  
        style=wx.FRAME_NO_TASKBAR|wx.NO_FULL_REPAINT_ON_RESIZE)  
    self.fetchers=[]  
    self.CreateFetchers()  
    self.tbicon = MailTaskBarIcon(self)  
    self.tbicon.Bind(wx.EVT_MENU, self.exitApp, id=wx.ID_EXIT)   
    self.timer=wx.Timer(self)  
    self.Bind(wx.EVT_TIMER, self.CheckMail)  
    self.timer.Start(30000) # 30 seconds  
    self.Show(True)

We create a timer that will trigger all 30 seconds (just a sample value).
Every time it triggeres it will call a method called CheckMail.
CheckMail will run through all mailaccounts, and connect to the server, check for mail and set the apropriate icon

def CheckMail(self,event):  
    newMail=False  
    for fetcher in self.fetchers:  
        result=fetcher.fetch()  
        if result != None and len(result)>0:  
            newMail=True  
    if newMail==True:  
        self.tbicon.SetIconImage(True)  
    else:  
        self.tbicon.SetIconImage(False)

This is just incomplete dummy code. As you can see we don't keep track here WHICH account has new mail.
I leave that up to you (and me) for later implementation. Afterall i don't want to make a complete program here in a tutorial ;)

Where does self.fetchers come from ?
In the init method we called CreateFetchers()
That method should create/load all accounts and create fetchers for them.
A simple example to give you a hint:

def CreateFetchers(self):  
    servername='account1.com'  
    username= 'user1'  
    password='pass1'  
    m=MailAccount("TestAccount",servername, username, password, "http://mailURL.com")  
#alternative  
    m2=m.loadAccountData("TestAccount")  
    if m2!=None:  
        m=m2  
    pop=POP3Fetcher(m)      
    self.fetchers.append(pop)

What else is there to do (and won't be done in this tutorial)
1) You should keep track of the accounts that have mail and the method OpenBrowser should then open the urls given in those mailaccounts instead of a fixed page.
2) You should try to create a property page where you can set, change, add, delete mail accounts. the simplest form would be to have a programm pickle MailAccounts once.
The best way would be to include some optionspanel in the application
3) Support for ports (in Mailaccounts the member is already there) and different protocols (https) would be nice.
4) Include timer information in each mailaccount instead of one global timer.
5) Better support for inboxes

Anyway ... i leave that up for you at the moment. There is no sense including it here (at the moment)
I am implementing it and will release something soon i guess.


comments powered by Disqus