顯示具有 Python 標籤的文章。 顯示所有文章
顯示具有 Python 標籤的文章。 顯示所有文章

星期日, 6月 16, 2013

Chained XOR Encoding

通常以單一的XOR作為Key,很容易就被發現了。

因此發展另一概念:
1. 以Content的頭或尾作為第一把XOR Key
2. 用這把Key去XOR,產生的OUTPUT作為下一個Byte的Key

PoC Python Code

a = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]
key = a[-1]
e = []

for i in range(len(a)):
    out = a[i] ^ key
    e.append(out)
    key = out

print "Encoded Bytes: ", e

d = []
for i in range(len(e)-1, 0, -1):
    d.append(e[i] ^ e[i-1])
d.append(e[0] ^ d[0])
d.reverse()
print "Decoded Bytes: ", d

星期四, 6月 09, 2011

pkg_resources.DistributionNotFound: pip==0.8.2

今天pip突然怪怪的,出現下列訊息:
cacaegg:~ cacaegg$ pip
Traceback (most recent call last):
File "/usr/local/bin/pip", line 5, in
from pkg_resources import load_entry_point
File "build/bdist.macosx-10.3-fat/egg/pkg_resources.py", line 2607, in
File "build/bdist.macosx-10.3-fat/egg/pkg_resources.py", line 565, in resolve
pkg_resources.DistributionNotFound: pip==0.8.2
所以就看一下:
cacaegg:~ cacaegg$ cat /usr/local/bin/pip
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==0.8.2','console_scripts','pip'
__requires__ = 'pip==0.8.3'
import sys
from pkg_resources import load_entry_point

sys.exit(
load_entry_point('pip==0.8.3', 'console_scripts', 'pip')()
)
都改成0.8.3就可以了。

星期四, 3月 17, 2011

setup numpy and matplotlib under mac os x 10.6

當然先用pip安裝好來
$pip install numpy
$pip install matplotlib
然後想測試時,發生如下的import error
$ python
Python 2.6.6 (r266:84374, Aug 31 2010, 11:00:51)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import matplotlib.pyplot as plt
/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/matplotlib/__init__.py:62: DeprecationWarning: the md5 module is deprecated; use hashlib instead
import md5, os, re, shutil, sys, warnings
/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/pytz/tzinfo.py:5: DeprecationWarning: the sets module is deprecated
from sets import Set
Traceback (most recent call last):
File "", line 1, in
File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/matplotlib/pyplot.py", line 6, in
from matplotlib.figure import Figure, figaspect
File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/matplotlib/figure.py", line 10, in
from axes import Axes, Subplot, PolarSubplot, PolarAxes
File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/matplotlib/axes.py", line 6, in
import matplotlib.numerix.npyma as ma
File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/matplotlib/numerix/__init__.py", line 166, in
__import__('ma', g, l)
File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/matplotlib/numerix/ma/__init__.py", line 16, in
from numpy.core.ma import *
ImportError: No module named ma
找不到ma模組,找了一下後發現改位置了
>>> import numpy
>>> print numpy.__version__
1.5.1
>>> print numpy.core.ma
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'module' object has no attribute 'ma'
>>> print numpy.ma

>>> quit()
因此去修改下列檔案
$ vim /Library/Frameworks/Python.framework/Versions/Current/lib/python2.6/site-packages/matplotlib/numerix/npyma/__init__.py
$ vim /Library/Frameworks/Python.framework/Versions/Current/lib/python2.6/site-packages/matplotlib/numerix/npyma/__init__.py
裡面有import numpy.core.ma的都改一下
try:
from numpy.core.ma import *
except ImportError:
from numpy.ma import *
最後就可以成功了
$ python
Python 2.6.6 (r266:84374, Aug 31 2010, 11:00:51)
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import matplotlib.pyplot as plt
/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/matplotlib/__init__.py:62: DeprecationWarning: the md5 module is deprecated; use hashlib instead
import md5, os, re, shutil, sys, warnings
/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/pytz/tzinfo.py:5: DeprecationWarning: the sets module is deprecated
from sets import Set
>>> plt.plot([1,2,3,4])
[]
>>> plt.ylabe('some numbers')
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'module' object has no attribute 'ylabe'
>>> plt.ylabel('some numbers')

>>> plt.show()

Happy hacks ; )

星期二, 3月 15, 2011

Mac OS 安裝MySQL-Python

前幾天升級到Python2.6.6時候原本正常的MySQLdb突然出現如下訊息
$ python -c 'import MySQLdb'
Traceback (most recent call last):
File "", line 1, in
File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/MySQLdb/__init__.py", line 19, in
import _mysql
ImportError: dlopen(/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/_mysql.so, 2): Symbol not found: _mysql_affected_rows
Referenced from: /Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/_mysql.so
Expected in: dynamic lookup

花了整晚時間東找patch西找版本,真是難搞...

最後想不到去把mysql重新安裝成universal就輕鬆解決了。

pip uninstall MySQL-python
brew uninstall mysql
brew install mysql --universal
pip install MySQL-python

參考

星期四, 1月 20, 2011

Generic Relation in Django

有時候,Model中的某個欄位是ForeignKey,

而偏偏又不能確定該欄位是指向什麼Model的時候該怎麼辦呢?

Generic Relation是在Django裡的好辦法!

使用方法如下:
1.給Model一個ForeignKey欄位,指向ContentType
2.再給Model一個欄位,用來儲存所要指向的Model的Primary。
在這裡注意一下,如果型態給IntegerField,那就不能指向PrimaryKey是CharField的Model囉!
3.再一個欄位,型態是GericForeignKey,然後把上面兩個欄位給它。就可以了。

文件中有個簡單的範例:
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')

def __unicode__(self):
return self.tag
可以看到tag本身當然還不確定會去tag什麼model,所以就用Generic Relation是再好不過了!
執行時再決定所指向的是什麼就可以了。
>>> from django.contrib.auth.models import User
>>> guido = User.objects.get(username='Guido')
>>> t = TaggedItem(content_object=guido, tag='bdfl')
>>> t.save()
>>> t.content_object


參考自:http://docs.djangoproject.com/en/1.2/ref/contrib/contenttypes/

星期三, 11月 10, 2010

修改已經存在的table -- django-evolution

在manage.py syncdb中,他並不會修改已經存在的table。

所幸有位救星出來了:django-evolution

他可以在修改model後,新增或減少欄位,都直接去alter table。

安裝方法如下:
easy_install -U django_evolution

使用步驟如後:
1. Add django_evolution to the INSTALLED_APPS for your project
2. Run ./manage.py syncdb
3. Make modifications to the model files in your project
4. Run ./manage.py evolve --hint --execute

記得,新增的欄位要給default值,否則要設定null=True,
否則會出現initial value沒有指定的錯誤。

參考:django-evolution

星期四, 9月 16, 2010

execl vs excvp

These two will search for prog on current PATH
execlp(prog, arg0, arg1, arg2, ...)
execvp(prog, argList)

簡單說
execvp可以傳argument list,而exclp卻需要把參數一個一個傳進去。

星期日, 3月 28, 2010

Download File and Check MD5

每次都要重複下載,使用md5sum來確認等重複的動作,乾脆寫個script來直接作吧!
#!/usr/bin/python
import os, sys, re, subprocess
#argument checking
if len(sys.argv) <= 1:
        print "Usage:%s URL [md5]" % (sys.argv[0])
        sys.exit(0)
]
print "Downloading..."
args = ["/usr/bin/wget", sys.argv[1]
p = subprocess.Popen(args)
p.wait()

print "Checking MD5..."
filename = re.split("/", sys.argv[1])[-1]
if len(sys.argv) == 3:
        md5file = open("tmp.md5", "w+")
        md5file.write("%s  %s\n" % (sys.argv[2], filename))
        md5file.close()
        p = subprocess.Popen(["/usr/bin/md5sum", "-c", "tmp.md5"])
        p.wait()
        os.remove("tmp.md5")
整個script做的動作是
1.下載檔案,並解析該檔名
2.如果有提供MD5就進行MD5 check

另外subprocess是2.4版後用來執行系統中程式用的
class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
可以只給arg,用sequence的形式,好處是都可以直接自行指定stdin, stdout, stderr。

星期日, 2月 21, 2010

自訂Python的import path

在import時,python會去搜尋sys.path中的每個entry。
而sys.path又是從下述所串接起來的
1.執行程式的目錄
2.PYTHONPATH環境變數
3.標準程式庫目錄(安裝時候就已經決定了)

所以若要自行新增,就需增加PYTHONPATH這環境變數。

cacaegg@cacabook:~$ export PYTHONPATH="/home/cacaegg/lib"
cacaegg@cacabook:~$ echo $PYTHONPATH
/home/cacaegg/lib
cacaegg@cacabook:~$ python
Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15)
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.path
['', '/home/cacaegg/lib', '/usr/lib/python2.6', '/usr/lib/python2.6/plat-linux2', '/usr/lib/python2.6/lib-tk', '/usr/lib/python2.6/lib-old', '/usr/lib/python2.6/lib-dynload', '/usr/lib/python2.6/dist-packages', '/usr/lib/python2.6/dist-packages/Numeric', '/usr/lib/python2.6/dist-packages/PIL', '/usr/lib/python2.6/dist-packages/gst-0.10', '/usr/lib/pymodules/python2.6', '/usr/lib/python2.6/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.6/gtk-2.0', '/usr/lib/python2.6/dist-packages/wx-2.6-gtk2-unicode', '/usr/local/lib/python2.6/dist-packages', '/usr/local/lib/python2.6/dist-packages/pygoogle']
>>> import python.lang
>>> quit()
cacaegg@cacabook:~$ ls lib/python/
__init__.py __init__.pyc lang.py lang.pyc


此外需注意,由於從python.lang這樣方式去import的,所以python目錄底下需要__init__.py檔案才行。(空白的就好)

星期五, 2月 19, 2010

用Vim開發Python

找了一下網路上文件,一共會有些plugin需要使用。
1.minibufexpl.vim
可以開多個檔案的時候會有tab出現,需要切換的話control+ww然後移到要切換的tab上即可,
或是用滑鼠點要切換的tab也可以。

2.taglist.vim
可以出現檔案中的函式,member等的樹狀結構。
安裝此plugin需要ctags,所以就先

$http://vim.sourceforge.net/scripts/script.php?script_id=273

再下載下來安裝。解壓縮後會有兩個檔案,一個是doc,另外一個是plugin。
plugin就放在$HOME/.vim/plugin/裏面,再把doc放到$HOME/.vim/doc裏面,
在此目錄下執行vim以後,輸入

:helptags .

如此以後才可以有help查。
接著就可以使用taglist了!要使用只要輸入:TlistToggle就可以切換出視窗了。

3.VimPdb
可以直接在Vim裏面debug囉。抓下來後把VimPdb.vim & VimPdb.py直接放到.vim/plugin資料夾中即可。
如果不行
a.先檢查Vim是否支援Python
:python print "hellp world"
看是否有錯誤訊息
b.如果出現如下錯誤訊息

Error message:

Error detected while processing /home/user/.vim/plugin/VimPdb.vim:
line 19:
E492: Not an editor command: import sys
line 20:
E319: Sorry, the command is not available in this version: python
sys.path.insert(0, r"/home/user/.vim/plugin")
line 21:
E492: Not an editor command: import VimPdb
Error detected while processing function PdbInitialize:
line 11:
E319: Sorry, the command is not available in this version: ^Ipython import sys
line 12:
E319: Sorry, the command is not available in this version: python
sys.path.insert(0, r"")
line 13:
E319: Sorry, the command is not available in this version: ^Ipython import
VimPdb
line 15:
.....省略

應該是vimpdb.vim的檔案格式有問題,用vim打開它,然後輸入

:set fileformat=unix
:w

就可以正常運作了。

4.pythoncomplete
怎麼可以少了autocomplete呢!
下載下來放在plugin資料夾後,需要autocomplete時,再按Ctrl+x Control+o就可以了,還有文件說明,好厲害!

5.自動縮排
vim能自動偵測*.py後,就可以.vimrc中加上
filetype indent on
就可以自動縮排了!

星期日, 11月 22, 2009

htmlParser malformed start tag errors

今天寫的時候遇到了這樣的問題

Traceback (most recent call last):
File "/home/cacaegg/programs/script/spider.py", line 22, in
par.feed(htmlSource)
File "/usr/lib/python2.6/HTMLParser.py", line 108, in feed
self.goahead(0)
File "/usr/lib/python2.6/HTMLParser.py", line 148, in goahead
k = self.parse_starttag(i)
File "/usr/lib/python2.6/HTMLParser.py", line 226, in parse_starttag
endpos = self.check_for_whole_start_tag(i)
File "/usr/lib/python2.6/HTMLParser.py", line 301, in check_for_whole_start_tag
self.error("malformed start tag")
File "/usr/lib/python2.6/HTMLParser.py", line 115, in error
raise HTMLParseError(message, self.getpos())
HTMLParseError: malformed start tag, at line 46, column 3275

所以參考了http://bugs.python.org/issue736428來解決
主要就是做兩件事
1.override the class's error function

def error(self, message):
print message


2.add a line after line 301 in HTMLParser.py

self.updatepos(i, j)
self.error("malformed start tag")
return j # ADDED THIS LINE


暫時就先這樣解決吧!

星期日, 7月 19, 2009

IMAP in Python - 8 (Reading Flags)

Twisted提供簡單的函式可以直接讀取message flag:


class IMAPLogic:
def __init__(self, proto):
self.proto = proto
self.factory = proto.factory

d = self.proto.login(self.factory.username, self.factory.password)
d.addCallback(lambda x: self.proto.examine('INBOX'))
d.addCallback(lambda x: self.proto.fetchFlags('1:*'))
d.addCallback(self.handleflags)
d.addCallback(self.logout)
d.addCallback(self.stopreactor)
d.addErrback(self.errorhappened)
def handleflags(self, flags):
for num, flaglist in flags.items():
print "Message %s has flags %s" % (num, ", ".join(flaglist['FLAGS']))

使用fetcgFlags(range)就可以取得範圍內message的flag。當然Twisted也提供了setFlags()、addFlags()、removeFlags(),都只需要提供一個list of strings,每個string都是一個flag即可。OUTPUT:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ tflag.py mx.mgt.ncu.edu.tw cacaegg
Enter password:
Message 1 has flags \Recent
Message 2 has flags \Recent


程式連結

星期六, 7月 18, 2009

IMAP in Python - 7 (Downloading Message Individually)

由於一次全部下載完,信件一多就浪費許多記憶體,因此來分封下載。可以先發出request來查看message number,來看程式碼:

d = self.proto.login(self.factory.username, self.factory.password)
d.addCallback(lambda x: self.proto.examine('INBOX'))
d.addCallback(lambda x: self.proto.fetchUID('1:*'))
d.addCallback(self.handleuids)

可以看到選定folder以後,我們呼叫fetchUID來抓取folder所有的UID,但是回傳的結構其實是如此的dictionary[uid -> Dictionary[]]。也就是每封信本身也都是一個dictionary,來看看如何處理:

def handleuids(self, uids):
dlist = []
destfd = open(sys.argv[3], "at")
for data in uids.values():
uid = data['UID']
d = self.proto.fetchSpecific(uid, uid =1, peek = 1)
d.addCallback(self.gotmessage, destfd, uid)
dlist.append(d)
dl = defer.DeferredList(dlist)
dl.addCallback(lambda x, fd: fd.close(), destfd)
return dl

對於每封信的dictionary,我們取用其中的'UID'部份,而fetchSpectific的uid = 1是告訴此函式把第一個參數當UID看而非message number,最後加到deferred list中。deferred list會等到所有deferred結束後,再把結果一次傳給第一個Callback。最後來看gotmessage():

def gotmessage(self, data, destfd, uid):
print "Received message UID", uid
for key, value in data.items():
print "Writing message", key
i = value[0].index('BODY') + 2
msg = email.message_from_string(value[0][i])
destfd.write(msg.as_string(unixfrom = 1))
destfd.write("\n")

與之前不同的是,這次找message body text並不是寫死的固定位置,因為可能會因server不同而回傳資料有所不同,不過相同的是text都是在'body'後面的第二個位置,所以就直接用算的算出來。最後來看程式的output:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ tdownload.py mx.mgt.ncu.edu.tw cacaegg mailbox
Enter password:
Received message UID 15024
Writing message 1
Received message UID 15025
Writing message 2


程式連結

IMAP in Python - 6 (Downloading an Entire Mailbox)

開始來下載信件囉!最簡單的方式,就是一次全部下載,只是此方式,twisted需要把信件cache在記憶體中,因此對於大信箱來說,此種方式不太可行。

來看看怎麼使用吧:

d = self.proto.login(self.factory.username, self.factory.password)

d.addCallback(lambda x: self.proto.examine('INBOX'))
d.addCallback(lambda x: self.proto.fetchSpecific('1:*', peek = 1))
d.addCallback(self.gotmessages)
d.addCallback(self.logout)
d.addCallback(self.stopreactor)

可以看到使用protocol class的fetchSpecific()就可以抓取信件。而第一個參數'1:*'則表示抓取的範圍是從第1封到最後一封,也就是所有的信件;第二個參數把peek設定為1,是要server不要在我們把信抓下來後就加上/seen已讀的flag(預設是會加的)。
再來就是看看gotmessages如何處理信件:

def gotmessages(self, data):
destfd = open(sys.argv[3], 'at')
for key, value in data.items():
print "Writing message", key
msg = email.message_from_string(value[0][2])
destfd.write(msg.as_string(unixfrom = 1))
destfd.write("\n")
destfd.close()

fetchSpecific傳給data,data會是dictionary。key是message number,而value就是components of the list。我們把value中[0][2]是代表entire message直接寫入目標檔案中,就算完成囉!
來看output:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ tdlbig.py mx.mgt.ncu.edu.tw cacaegg mailbox
Enter password:
Writing message 1
Writing message 2
Writing message 3

程式連結

IMAP in Python - 5 (Summary Information)

在對folder進行select或examine以後,server會回傳summary information。

來看看如何使用:

class IMAPLogic:
def __init__(self, proto):
self.proto = proto
self.factory = proto.factory
d = self.proto.login(self.factory.username, self.factory.password)
d.addCallback(lambda x:self.proto.examine('INBOX'))
d.addCallback(self.examineresult)
d.addCallback(self.logout)
d.addCallback(self.stopreactor)

d.addErrback(self.errorhappened)

def examineresult(self, data):
for key, value in data.items():
if isinstance(value, tuple):
print "%s: %s" % (key, ",".join(value))
else:
print "%s: %s" % (key, value)

在此例中,檢查了INBOX後,把結果傳給了examineresult,examineresult會把結果顯示出來。
程式跑出來的結果如下:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ texamine.py mx.mgt.ncu.edu.tw cacaegg
Enter password:
EXISTS: 0
PERMANENTFLAGS:
READ-WRITE: 0
UIDNEXT: 15020
FLAGS: \Answered,\Flagged,\Deleted,\Seen,\Draft,Junk,NonJunk,$MDNSent,$Forwarded,NotJunk
UIDVALIDITY: 1153809236
RECENT: 0

來解釋一下常用的item
EXISTS:folder中的message數目
FLAGS:可以放在此folder上的flag
RECENT:上次select之後,大約有多少新進的message
UIDVALIDITY:將此id與上次session比較,就可以用做uid驗證,確保uid相同的一樣的message

程式連結

星期五, 7月 17, 2009

IMAP in Python - 4 (Scaning the Folder List)

IMAP支援多個mailbox,也就是多個folder。可以用protocol class中的list(ref, pattern)來取得。來看一下例子:

class IMAPLogic:
def __init__(self, proto):
self.proto = proto
self.factory = proto.factory
d = self.proto.login(self.factory.username, self.factory.password)
d.addCallback(lambda x: self.proto.list('', '*'))
d.addCallback(self.listresult)
d.addCallback(self.logout)
d.addCallback(self.stopreactor)
d.addErrback(self.errorhappened)

def listresult(self, data):
print "%-35s %-5s %-37s" % ('Mailbox Name', 'Delim', 'Mailbox Flags')
print '-' * 35, '-' * 5, '-' * 37
for box in data:
flags, delim, name = box
print "%-35s %-5s %-37s" % (name, delim, ','.join(flags))

可以看到呼叫了proto.list()並且傳了兩個參數,第一個是reference,有某些特殊用途,例如要取得Usenet等,大多時候是留白的;第二個參數是folder pattern,也就是要找的folder名字是什麼。在此支援wild card,所以*就表示了所有的folder都要。

來看一下程式輸出:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ tlist.py mx.mgt.ncu.edu.tw cacaegg
Enter password:
Mailbox Name Delim Mailbox Flags
----------------------------------- ----- -------------------------------------
virus-mail / \NoInferiors,\UnMarked
spam-mail / \NoInferiors,\UnMarked
mail-trash / \NoInferiors,\UnMarked
Sent Items / \NoInferiors,\UnMarked
Deleted Items / \NoInferiors,\UnMarked
Junk E-mail / \NoInferiors,\UnMarked
Drafts~ / \NoInferiors,\UnMarked
sent-mail-feb-2008 / \NoInferiors,\UnMarked
INBOX / \NoInferiors,\UnMarked

可以看到list()回傳的是"a list of tuples"。而每組tuple都有3個item,分別是folder name、Delimiter以及Flag。在此簡介一下常看到的flag:
\Noinferiors:沒有subfolder也不可能會有subfolder。
\Noselect:不能進行select or examine,也就是folder內沒有訊息。
\unmarked:沒有新訊息(相對的\marked就可能是有,不過要看server怎麼用)。

程式連結

IMAP in Python - 3 (Error Handling)

前面的例子無法處理錯誤出現的情形,如果出現了錯誤,程式就會因為沒有呼叫到reactor.stop()造成程式卡住,再來看看twisted中如何處理錯誤呢?由於錯誤發生的時候,控制權可能已經不是當下那個function的了,所以一樣使用callback,而處理錯誤的callback稱之為errback。

直接來看例子:

class IMAPLogic:
def __init__(self, proto):
self.proto = proto
self.factory = proto.factory
self.logintries = 1

d = self.login()
d.addCallback(self.loggedin)
d.addErrback(self.loginerror)
d.addCallback(self.logout)
d.addCallback(self.stopreactor)

d.addErrback(self.errorhappen)

print "IMAPLogic.__init__returning."

只要對defered object呼叫addErrback,此errback function就會負責處理當時以上的function,也就是說loggedin()發生的錯誤由loginerror()處理,而logout()與stopreactor()的錯誤也由errorhappen()來處理。其實,如果在login()發生了在loginerror()無法處理的錯誤時,twisted會直接把錯誤丟給下一個errback,此例是loginerror()無法處理的錯誤就傳給errorhappen()>
那就再來看一下那兩個errback function:

def loginerror(self, failure):
print "Your loging failed (attemp %d times)." % self.logintries
if self.logintries >= 3:
print "You have tried to log in three times; I'm giving up."
return failure
self.logintries += 1

sys.stdout.write("username:")
self.factory.username = sys.stdin.readline().strip()
self.factory.password = getpass.getpass("password:")

d = self.login()
d.addErrback(self.loginerror)
return d

在這邊處理的是只有登入超過三次失敗的情形,如果超過三次等情形,就會直接return failure把錯誤傳接下去給errorhappen()。在此function的結尾有增加defered object呼叫自己,如果沒成功就繼續回來此function。


def errorhappen(self, failure):
print "An error occurred:", failure.getErrorMessage()
print "Because of the error, I am logging out and stopping reactor..."
d = self.logout()
d.addBoth(self.stopreactor)
return failure

此function是單純把failure的訊息印出來,然後logout(),並且無論logout成功失敗都要呼叫stopreactor()來把reactor.run()中止,避免程式掛著。
以下是程式的兩種output:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ t-error.py mx.mgt.ncu.edu.tw cacaegg
Enter password for cacaegg on mx.mgt.ncu.edu.tw:
I have successfully connected to the server!
Logging in...
IMAPLogic.__init__returning.
connectionMade returning
I'm logged in!
Logging out.
Stopping reactor.
cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ t-error.py mx.mgt.ncu.edu.tw cacaegg
Enter password for cacaegg on mx.mgt.ncu.edu.tw:
I have successfully connected to the server!
Logging in...
IMAPLogic.__init__returning.
connectionMade returning
Your loging failed (attemp 1 times).
username:ewfwfewf
password:
Logging in...
Your loging failed (attemp 2 times).
username:efwfew
password:
Logging in...
Your loging failed (attemp 3 times).
You have tried to log in three times; I'm giving up.
An error occurred: Authentication failed.
Because of the error, I am logging out and stopping reactor...
Logging out.
Unhandled error in Deferred:
Traceback (most recent call last):
Failure: twisted.mail.imap4.IMAP4Exception: Authentication failed.
Stopping reactor.


程式連結

IMAP in Python - 2 (Logging In)

再來看如何使用twisted來進行登入,以及defered object的chain callback。

此次分成3個主要的class:Protocol、Factory、Logic。分別來看一下。
Protocol Class:

class IMAPClient(IMAP4Client):
def connectionMade(self):
print "I have successfully connected to the server!"
IMAPLogic(self)
print "connectionMake returning"

主要就是把邏輯部份從原本的protocol中分離出來了,此protocol class現在變成主要就是處理關於協定的callback。


class IMAPFactory(protocol.ClientFactory):
protocol = IMAPClient

def __init__(self, username, password):
self.username = username
self.password = password

def clientConnectionFailed(self, connector, reason):
print "Client connection failed:", reason
reactor.stop()

此class會在__init__時就把帳號密碼存入,以待登入時使用。


class IMAPLogic:
def __init__(self, proto):
self.proto = proto
self.factory = proto.factory

d = self.proto.login(self.factory.username, self.factory.password)
d.addCallback(self.loggedin)
d.addCallback(self.stopreactor)

print "IMAPLogic.__init__returning."
def loggedin(self, data):
print "I'm logged in!"
return self.logout()
def logout(self):
print "Logging out."
d = self.proto.logout()
return d
def stopreactor(self, data = None):
print "Stopping reactor."
reactor.stop()

此邏輯class在__init__時,就會先把protocol class & factory變成member。然後呼叫protocol的login(),在此會回傳第1個defered object,對此object設定兩個callback。這樣就成為"Chain callback",對於每個子callback,如果還有其他defered物件,twisted會將其都處理完才會呼叫原parent defered object的下一個子callback,且把最後一次callback的結果當作參數送給下一個。因此在此例子中,執行的順序是loggedin() --> logout() --> proto.logout() --> stopreactor() --> reactor.stop(),如此執行。

以下為程式output:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ tlogin.py mx.mgt.ncu.edu.tw cacaegg
Enter password for cacaegg on mx.mgt.ncu.edu.tw
I have successfully connected to the server!
IMAPLogic.__init__returning.
connectionMake returning
I'm logged in!
Logging out.
Stopping reactor.


程式連結

星期四, 7月 16, 2009

IMAP in Python - 1 (Understanding Twisted Basics)

Python本身也有imaplib可供操作,不過在此使用一套挺有名的framework--Twisted。

Twisted有一個很大的特色就是以Event-based來進行程式設計,也就是會使用到callback function。

直接用程式碼來簡單介紹如何使用:

#!/usr/bin/env python

from twisted.internet import defer, reactor, protocol
from twisted.mail.imap4 import IMAP4Client
import sys

class IMAPClient(IMAP4Client):
def connectionMade(self):
print "I have successfully connected to the server!"
d = self.getCapabilities()
d.addCallback(self.gotcapabilities)

def gotcapabilities(self, caps):
if caps == None:
print "Server did not return a capability list."
else:
for key, value in caps.items():
print "%s: %s" % (key, str(value))
self.logout()
reactor.stop()
class IMAPFactory(protocol.ClientFactory):
protocol = IMAPClient

def clientConnectionFailed(self, connector, reason):
print "Client connection failed:", reason
reactor.stop()

reactor.connectTCP(sys.argv[1], 143, IMAPFactory())
reactor.run()


分為兩種class:
IMAPClient-作為Protocol class之用途,負責與server的protocol conversation
IMAPFactory-作為Connection class之用途,負責connection的部份

而reactor是Twisted中的Network event handler,在主程式第1行先用reactor.connectTCP進行連線,
成功後會呼叫connectionMade()來進行後續處理。

在connectionMade裏面,呼叫繼承而來的getCapabilities()來取得有關server支援IMAP選項的參數,且會回傳defer物件。要用此物件來告訴reactor等到有事件發生時該呼叫誰,在此使用了d.addCallback告訴reactor等到有事件時,需要呼叫self.gotcapabilities來處理。(注意在此沒有用括號,因為是callback沒有需要立刻呼叫)

接著進行reactor.run(),此function會等到有人呼叫reactor.stop()才會return,否則就不斷等待事件,事件發生了後,就按照預定的呼叫gotcapabilities(),印出有關的參數,然後logout(),最後stop()結束程式。

此程式的output:

cacaegg@cacabook:~/workspace/NetworkProgram/src/IMAP$ sudo ./tconn.py mx.mgt.ncu.edu.tw
I have successfully connected to the server!
SASL-IR: None
SORT: None
THREAD: ['REFERENCES']
STARTTLS: None
UNSELECT: None
NAMESPACE: None
IDLE: None
AUTH: ['PLAIN']
IMAP4rev1: None
LOGIN-REFERRALS: None
MULTIAPPEND: None
LITERAL+: None
CHILDREN: None

星期三, 7月 15, 2009

POP3 in Python - 4 (Deleting Message)

呼叫POP3Object.dele(msgnum)就可以刪除指定的信件了。

只是大多時候,server會等到呼叫quit()時才正式把信件刪除,


mblist = p.list()[1]
dellist = []

for item in mblist:
number, octets = item.split(' ')
log("Downloading message %s (%s bytes)...\n" % (number,octets))

lines = p.retr(number)[1]

msg = email.message_from_string("\n".join(lines))

destfd.write(msg.as_string(unixfrom = 1))
destfd.write("\n")
dellist.append(number)


在此先用一個dellist把所有要刪的存入

在這裡進行刪除:

counter = 0

for number in dellist:
counter += 1
log("Deleting message %d of %d...\n" % (counter, len(dellist)))
p.dele(number)


最後呼叫quit()就可以順利刪除了。

來看看程式的output:

cacaegg@cacabook:~/workspace/NetworkProgram/src/POP$ ./down-and-del.py mx.mgt.ncu.edu.tw cacaegg testbox
Password:
Connecting to mx.mgt.ncu.edu.tw...
Logging on...Success.
Scanning INBOX... 2 messages.
Downloading message 1 (2467 bytes)...
Downloading message 2 (2468 bytes)...
Deleting message 1 of 2...
Deleting message 2 of 2...
Successfully deleted 2 messages from server.
Closing connection... done.



附件:程式連結
[P.S 跑此程式不要用自己還有正在使用的正常信箱,請用測試的]