imapsave

imapsave simply copies the content of an imap account into mbox text files (one file per folder). It does not do incremental backups.

I wrote imapsave when I realized imapbackup could not use SSL encryption.

[download imapsave]

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
 
# Licensed under the Python License (see http://www.python.org/psf/license/)
# Copyright (C) 2008 Pierre Duquesne <stackp@online.fr>
#
# Changelog:
#   20080722 * disable spinning when output is redirected to a file
#   20080718 * Use BODY.PEEK[] in the fetch command as specified in RFC 3501
#              (fix by Stefan)
#   20080313 * Initial release
 
import getpass
import imaplib
import email
import email.Parser
import re
import sys
import getopt
from os.path import basename
 
USAGE = """\
usage: %s [--ssl] [--port num] [--password pass] [server] [user]
 
    server
        The hostname of your imap server
 
    user
        Your login
 
    --ssl
        Use an encrypted connection
 
    -p num
    --port num
        Use port number num instead of default port
        (Defaults are 143 for unencrypted connection and 993 for ssl)
 
    -P pass
    --password pass
        Password for the server
 
""" % basename(sys.argv[0])
 
class Spinner:
    """A class to show some activity on a character terminal."""
 
    def __init__(self, message=''):
        self.message = message
        self.symbols = list('-\|/')
        self.nsym = len(self.symbols)
        self.n = 0
 
    def spin(self):
        print '\r', self.message + self.symbols[self.n],
        sys.stdout.flush()
        self.n = (self.n + 1) % self.nsym
 
    def stop(self, stop_message=''):
        print '\r', self.message + stop_message
 
    def set_message(self, message):
        self.message = message
 
class DummySpinner:
    """A class that mock Spinner and do not spin.
 
    Used when output is not a character terminal (e.g. a log file).
 
    """   
    def __init__(self, message=''):
        self.message = message
 
    def spin(self):
        pass
 
    def stop(self, stop_message=''):
        print self.message + stop_message
 
    def set_message(self, message):
        self.message = message
 
 
def foldname_to_filename(folder):
    """Deal with weird characters in folder names."""
    filename = re.sub('"(.*)"', ur'\1', folder)
    filename = filename.replace('/', '.')
    return filename
 
try:
    # Parse command line arguments
    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:], 'pP:h',
                                       ['port=', 'password=', 'ssl', 'help'])
    except getopt.GetoptError, e:
        print 'Error:', e
        sys.exit(1)
 
    ssl = False
    port = None
    password = None
    for o, a in opts:
        if o == '--ssl':
            ssl = True
        if o == '-p' or o == '--port':
            port = int(a)
        if o == '-P' or o == '--password':
            password = a
        elif o in ['-h', '--help']:
            print USAGE
            sys.exit(0)
 
    try:
        servername = args[0]
    except IndexError:
        servername = raw_input("Enter the server hostname: ")
    try:
        username = args[1]
    except IndexError:
        username = raw_input("Enter your username: ")
 
    # Connect
    print 'Connecting to %s as user %s ...' % (servername, username)
    if ssl:
        IMAP = imaplib.IMAP4_SSL
    else:
        IMAP = imaplib.IMAP4
    try:
        if port:
            server = IMAP(servername, port)
        else:
            server = IMAP(servername)
        if not password:
            password = getpass.getpass()
        server.login(username, password)
    except Exception, e:
        print 'Error:', e
        sys.exit(1)
 
    # Retrieve folder list
    folders = []
    foldptn = re.compile('\([^\)]*\) "[^"]*" ([^"]*)')
    for fold_desc in server.list()[1]:
        folder = foldptn.sub(ur'\1', fold_desc)
        folders.append(folder)
 
    # Save messages in a separate file for each folder
    if sys.stdout.isatty():
        spinner = Spinner()
    else:
        spinner = DummySpinner()
    parser = email.Parser.Parser()
    succeed = []
    failed = []
    for folder in folders:
        msg = '%s ... ' % folder
        spinner.set_message(msg)
        try:
            resp, info = server.select(folder)
            if resp != 'OK':
                raise imaplib.IMAP4.error(' - '.join(info))
            filename = foldname_to_filename(folder)
            fp = open(filename, 'w')
            resp, items = server.search(None, "ALL")
            numbers = items[0].split()
            for num in numbers:
                resp, data = server.fetch(num, "(BODY.PEEK[])")
                text = data[0][1]
                mess = parser.parsestr(text)
                fp.write(mess.as_string(unixfrom=True))
                spinner.spin()
            succeed.append(folder)
            spinner.stop('Done.')
            fp.close()
        except imaplib.IMAP4.error, e:
            failed.append(folder)
            spinner.stop('Error! (' + str(e) + ')')
    server.logout()
 
    # Print the folders which failed, if any
    if failed != []:
        import textwrap
        wrapper = textwrap.TextWrapper(initial_indent='    ',
                                       subsequent_indent='    ')
        print
        print 'WARNING - The following folders could not be saved:'
        print wrapper.fill(', '.join(failed))
        sys.exit(1)
 
except KeyboardInterrupt:
    print ''
    print '^C received, stopping.'

CSS+Javascript pulsating progress bar

A bit like spinner but for the web in html+css+javascript. No ajax involved. It just pulsates perpetually:

[download progress-bar.html]

<style type="text/css">
        /* inspired by http://digitalbush.com/projects/progress-bar-plugin */
	/* progress bar container */
	#progressbar{
		border:1px solid black;
		width:200px;
		height:20px;
		position:relative;
	}
	/* color bar */
	#progress{
		position:absolute;
		left: 20px;
		width:10%;
		height:100%;
		overflow:hidden;
		background-color:#369;
	}
</style>
<script language="JavaScript">
      marg = 0
      inc = 10
      function update() {
         e = document.getElementById("progress");
	 e.style.left = marg + "%";
	 marg = (marg + inc) % 100 ;
         if (marg == 0 || marg == (100 - inc)) {inc = - inc;}
         setTimeout(update, 150);
      }
      onload=update;
</script>
<div id="progressbar"> <div id="progress"> </div></div>

Spinner: display activity

This was inspired by some code found in imapbackup. Sometimes you need to display activity on the text console to inform the user that the program is actually doing something.

That is the purpose of this small python class. The test() function shows the usage. Here’s what test() displays:
Spinner screencast

[download spinner.py]

# Licensed under the Python License (see http://www.python.org/psf/license/)
# Copyright (C) 2008 Pierre Duquesne <stackp@online.fr>
 
import sys
 
class Spinner:
    """A class to show a spinning ascii animation on a character terminal.
 
    It informs the user that some processing is being done.
    """
 
    def __init__(self, message=''):
        self.message = message
        self.symbols = list('-\|/')
        self.nsym = len(self.symbols)
        self.n = 0
 
    def spin(self):
        print '\r', self.message + self.symbols[self.n],
        sys.stdout.flush()
        self.n = (self.n + 1) % self.nsym
 
    def stop(self, stop_message=''):
        print '\r', self.message + stop_message
 
    def set_message(self, message):
        self.message = message
 
 
class DummySpinner:
    """A class that mock Spinner and do not spin.
 
    To be used when output is not a character terminal (e.g. a log file). 
    For example::
 
        if sys.stdout.isatty():
            s = Spinner()
        else:
            s = DummySpinner()
 
    """   
    def __init__(self, message=''):
        self.message = message
 
    def spin(self):
        pass
 
    def stop(self, stop_message=''):
        print self.message + stop_message
 
    def set_message(self, message):
        self.message = message
 
 
if __name__ == "__main__":
 
    import time
 
    def test():
        if sys.stdout.isatty():
            s = Spinner()
        else:
            s = DummySpinner()
 
        # simple usage
        for i in range(10):
            s.spin()
            time.sleep(0.1)
 
        s.set_message('Initialization ... ')
        for i in range(10):
            s.spin()
            time.sleep(0.1)
        s.stop('Done')
 
        s.set_message('Loading ... ')
        for i in range(10):
            s.spin()
            time.sleep(0.1)
        s.stop('Done')
 
    test()

wifiscan

On linux, display the wireless networks around with a bit of shell magic:

[download wifiscan]

#!/bin/sh

# 20101123: Update to match the new iwlist output format.
# 20080304: Initial release.

IWLIST=/sbin/iwlist

# List wireless interfaces
ifaces=`LANG=C $IWLIST  scan 2>/dev/null | grep "Scan completed" | awk '{print $1}' | tr '\n' ' '`

# Choose only the first one
iface=`echo $ifaces | awk '{print $1}'`

# Extract informations and format output
(echo ESSID~~~Channel~~~Quality~~~Encryption && \
 echo "---~~~---~~~---~~~---" && \
 $IWLIST $iface scan | \
 sed -n -e 's/.*ESSID:\(.*\)/\1/p' \
        -e 's/.*Channel:\([^ ]*\)/~~~\1/p' \
        -e 's/Encryption key:\(.*\)/~~~\1/p' \
        -e 's/.*Quality:\([^ ]*\)\/100.*/~~~\1/p' | \
 xargs -n4 echo) | \
\
awk -F~~~ '{print $1 "~~~" $4 "~~~" $3 "~~~" $2}' | \
column -ntx -s ~~~

Here’s the result:

pierre@abricot:~$ wifiscan
ESSID              Encryption      Quality      Channel
---                ---             ---          ---
kameha             on              88           1
freephonie         on              88           1
                   on              90           1
NEUF_2714          on              75           6
Neuf WiFi          off             75           6
                   on              88           1
Livebox-074c       on              38           10
whiterabbit        on              38           11
                   on              38           11

A better minimal web server in python

The simple web server from the python standard library is easy to improve to:

  • answer several requests at the same time, and
  • cancel a connection when the client stops responding.

[download webserver.py]

#!/usr/bin/env python
 
import SimpleHTTPServer, BaseHTTPServer, SocketServer, socket
 
class ThreadedHTTPServer(SocketServer.ThreadingMixIn,
                         BaseHTTPServer.HTTPServer) :
    """
    New features w/r to BaseHTTPServer.HTTPServer:
    - serves multiple requests simultaneously
    - catches socket.timeout and socket.error exceptions (raised from
      RequestHandler)
    """
 
    def __init__(self, *args):
        BaseHTTPServer.HTTPServer.__init__(self,*args)
 
    def process_request_thread(self, request, client_address):
        """
        Overrides SocketServer.ThreadingMixIn.process_request_thread
        in order to catch socket.timeout
        """
        try:
            self.finish_request(request, client_address)
            self.close_request(request)
        except socket.timeout:
            print 'Timeout during processing of request from',
            print client_address
        except socket.error, e:
            print e, 'during processing of request from',
            print client_address
        except:
            self.handle_error(request, client_address)
            self.close_request(request)
 
 
class TimeoutHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    """
    Abandon request handling when client has not responded for a
    certain time. This raises a socket.timeout exception.
    """
 
    # Class-wide value for socket timeout
    timeout = 3 * 60 
 
    def setup(self):
        "Sets a timeout on the socket"
        self.request.settimeout(self.timeout)
        SimpleHTTPServer.SimpleHTTPRequestHandler.setup(self)
 
 
def main():
    try:
        BaseHTTPServer.test(TimeoutHTTPRequestHandler, ThreadedHTTPServer)
    except KeyboardInterrupt:
        print '^C received, shutting down server'
 
if __name__ == '__main__':
    main()