HOME | Python |
Maildir 形式はメッセージを別々のファイルとして保存する電子メール格納方式で、そのファイル名はシステムで一意であることが保障されています。 この性質を利用して、効率的にメールをバックアップするスクリプトを書いてみました。
スクリプトの仕様は以下の通りです。
[code 1] backup_mail.py
001: #!/usr/bin/python3 002: 003: ''' 004: Maildir のバックアップを取るスクリプト 005: Maildir 形式では、ファイル名が一意に決まることを利用して、 006: ファイル名をキーとしてメールファイルのストレージ上での場所と内容を 007: 別々の辞書として保持する。 008: 009: 辞書の実装として gdbm を用いる。 010: メールの内容は内容は zlib で圧縮する。 011: ''' 012: 013: import os, os.path, zlib, re 014: 015: #dbm.gnu がインストールされている必要がある。 016: import dbm.gnu as dbm 017: 018: # 圧縮レベル 019: CLEVEL=3 020: 021: # このサイズを超えると flush する 022: BUF_SIZE=0x100000 # 1 M 023: 024: # バックアップを取らないメールのリストを書いたファイル。HOME 直下に置いておく 025: EXCLUDE='.exclude4mailbackup' 026: 027: 028: def walk(f, d0='.', excludes=('.Junk',)): 029: '''ディレクトリを渡り歩いて、ファイルを処理する。''' 030: def _iter(d): 031: for e in os.listdir(d): 032: if e in excludes: # 除外リストにある場合は処理しない 033: continue 034: p=os.path.join(d,e) 035: if os.path.isdir(p): # ディレクトリの場合は再帰的に処理を続ける 036: _iter(p) 037: elif os.path.isfile(p): # ファイルの場合は関数 f を適用する。 038: f(p) 039: 040: _iter(d0) 041: 042: 043: def get_contents(_path): 044: '''メールの内容を圧縮する''' 045: with open(_path, 'rb') as f: 046: return zlib.compress(f.read(), CLEVEL) 047: 048: 049: RE_U=re.compile(r'[,:]') 050: def get_ukey(p): 051: '''メールのファイル名から一意な文字列を取り出す''' 052: return RE_U.split(os.path.basename(p),1)[0].encode('ascii') 053: 054: 055: def get_status(db,k,v): 056: '''path のdb の状態''' 057: return 'insert' if k not in db \ 058: else 'update' if db[k]!=v \ 059: else 'nothing' 060: 061: 062: def get_exclude(d): 063: '''除外するメールのリストを取得する''' 064: fname=os.path.join(d, EXCLUDE) 065: if os.path.exists(fname): 066: with open(fname) as f: 067: return tuple(line.strip() for line in f if line[0]!='#') 068: else: 069: return ('.Junk',) 070: 071: def is_mail(p): 072: '''メールファイルかどうか判定する''' 073: d=os.path.dirname(p) 074: return any(d.endswith(x) for x in ('cur', 'new', 'tmp')) 075: 076: 077: 078: class MailBackup: 079: '''メールのバックアップを取るクラス''' 080: 081: BACKUP_ROOT='/var/backup_mail' 082: 083: def __init__(self, usr): 084: self.db_path=dbm.open(os.path.join(self.BACKUP_ROOT, usr, 'mail_path.db'), 'cf') 085: self.db_contents=dbm.open(os.path.join(self.BACKUP_ROOT, usr, 'mail_contents.db'), 'cf') 086: self.counter=dict(path=0,contents=0) 087: 088: def __call__(self, p): 089: if is_mail(p): 090: # メールの path と contents を db に保存する 091: bp=p.encode('ascii') 092: ukey=get_ukey(p) 093: status=get_status(self.db_path, ukey, bp) 094: if status in ('insert', 'update'): 095: self.update_path(ukey, bp) 096: if status == 'insert': 097: self.add_contents(ukey, get_contents(p)) 098: 099: def update_path(self, k, p): 100: '''path を更新する''' 101: self.db_path[k]=p 102: self.db_flush('path', len(p)) 103: 104: def add_contents(self, k, c): 105: '''内容を追加する''' 106: self.db_contents[k]=c 107: self.db_flush('contents', len(c)) 108: 109: def close(self): 110: for dbname in ('db_path', 'db_contents'): 111: db=getattr(self, dbname) 112: db.sync() 113: db.close() 114: 115: def db_flush(self, k, n): 116: '''一定量を超えたらストレージに flush する''' 117: if self.counter[k]+n > BUF_SIZE: 118: getattr(self, 'db_'+k).sync() 119: self.counter[k]=0 120: 121: 122: if __name__=='__main__': 123: home, user = [os.getenv(x) for x in ('HOME', 'USER')] 124: os.chdir(os.path.join(home, 'Maildir')) 125: mail_backup=MailBackup(user) 126: walk(mail_backup, '.', get_exclude(home)) 127: mail_backup.close() 128: print ('backup mail for {} done.'.format(user))
su の -l オプションと -c オプションを使って、対象ユーザになってスクリプトを実行します。
[code 2]
001: #!/bin/sh 002: 003: SCRIPT=/full/path/to/backup_mail.py 004: WE="taro hanako saki mai" 005: for ME in ${WE} 006: do 007: su -l $ME -c $SCRIPT 008: done
[code 3]
001: #!/usr/bin/python3 002: 003: '''backup_mail.py で作られたメールバックアップを復元する。''' 004: 005: import os, os.path, zlib, stat 006: import dbm.gnu as dbm 007: 008: 009: def restore_mail(path, contents): 010: '''個々のメールのリストア''' 011: ps=path.decode('ascii') 012: d,f=os.path.split(ps) 013: if not os.path.isdir(d): 014: os.makedirs(d, 0o700) 015: with open(ps, 'wb') as f: 016: f.write(zlib.decompress(contents)) 017: os.chmod(ps, stat.S_IRUSR|stat.S_IWUSR) 018: 019: 020: class MailRestore: 021: 022: BACKUP_ROOT='/var/backup_mail' 023: 024: def __init__(self, usr): 025: dbname_path=os.path.join(self.BACKUP_ROOT, usr, 'mail_path.db') 026: dbname_contents=os.path.join(self.BACKUP_ROOT, usr, 'mail_contents.db') 027: assert all(os.path.exists(p) for p in (dbname_path, dbname_contents)) 028: self.db_path=dbm.open(dbname_path) 029: self.db_contents=dbm.open(dbname_contents) 030: 031: def restore(self): 032: k=self.db_contents.firstkey() 033: while k is not None: 034: if k in self.db_path: 035: restore_mail(self.db_path[k], self.db_contents[k]) 036: else: 037: print ('Path for {} does not exist'.format(k.decode('ascii')), 038: file=sys.stderr) 039: k=self.db_contents.nextkey(k) 040: 041: def close(self): 042: self.db_path.close() 043: self.db_contents.close() 044: 045: 046: if __name__=='__main__': 047: os.chdir(os.path.join(os.getenv('HOME'), 'Maildir')) 048: agent=MailRestore(os.getenv('USER')) 049: agent.restore() 050: agent.close()
.Junk .sale .ready_to_delete
皆さんも機会があったら試してみてください。
HOME | Python |