2009-06-15

六年,七年

上周即议于此周日晚一聚.

下午林海峰答辩, 和尹超一起去围观. 时间比预想的慢了一个小时, 林海峰留下, 我和尹超回来看会电影, 牛钧便开始喊大家出发. 十几个人聚在门前闲聊带等的过了半个小时, 一起四五一组坐车去嘉陵江畔.

席间交错, 有些兴头, 酒便多起来, 头有些晕乎. 想起明天是工作日, 便和另几个同学先坐车回来了.

最后与一个寝室的牛钧, 林海峰碰杯, 牛钧哭道: ``老蒋, 六年了.''

认识牛钧兄七年, 自一起做本科生科研后, 走得近些, 算来也是六年.

牛钧兄达人, 识人多, 常有我辈意外之举, 诗人, 战士几成2002级数院本科生周知的典故.

本科生科研时, 牛钧和我常聚与图书馆自习, 所聊者为数学时甚少, 效率不是一般的低. 我不务正业的看 Knuth 的 The TeXBook, 即而对计算机引起兴趣. 牛兄也有自己的事情, 只偶尔看看, 一起讨论. 有时因为赶时间, 晚上常一起去南门的城隍庙, 也常是坐下来, 闲聊一会, 吃点东西, 又走了.

牛兄随身带着一个精致笔记本, 记些事情. 我翻过几页, 但有几页是牛兄不让看的.

进入研究生后, 与牛兄一个寝室. 牛兄两次出行, 得美人, 闪婚后生下一女, 两个月时眉目间即与牛兄极其相类, 观看的没有不大笑的. 前一阵子阿雅来校, 牛兄以其惊人的能量与其相抱.

我们常坐的新书阅览室前那张桌子, 桌子上提醒牌子后面各自写下有一段话, 引起好几篇留言, 保留约有几年, 现在不在了.

美丽有两种, 一是深刻又动人的方程, 一是你泛着倦意淡淡的笑容.

看一看冷月的桥影, 数一数螺细的花纹, 我倚暖了石栏的青笞, 青笞凉透了我的心坎.

匆匆一别, 默愿牛兄美国求学顺利, 新加坡孩子健康.

2009-05-03

Gmail 联系人信息与 vCard 格式转换的 Python 脚本

地址簿格式, 个人偏好 vCard 格式. 这个格式比较软件中立, 方便在各种格式间转换, 且被大都软件接受为导入或导出格式之一. 大部分手机也支持此种格式交换联系人信息.

如是, 在本地建立一个版本控制的目录, 全是 vCard 文件, 可以方便地做些简单查找, 合并及批量修改.

整理通讯信息, 自然少不了导入导出. 这个 Online vCard Converter 在转换 Gmail 等的联系人信息时十分方便. 我开始便是用这个转换. 但后来数据多了, 教育网又要找代理, 便自己写了几个脚本. 放此备份, 或有同样需求的.

以下为从 Gmail-CSV 到 vCard 转换的脚本.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import csv
import sys
import re

def load_csv(fn):
    f = open(fn,'rb')
    csvs = csv.reader(f)
    head = csvs.next()
    rows = [row for row in csvs]
    for row in rows:
        print to_vcard(head, row)

def get_n_from_fn(fn):
    fn=fn.decode('utf8')
    if ' ' in fn:
        (given, family) = fn.rsplit(' ', 1)
    if re.match('[a-zA-Z0-9_]', fn[0]):
        (given, family) = (fn, '')
    else:
        (family, given) = (fn[0], fn[1:])
    return ('%s;%s;;;' % (family, given)).encode('utf8')

def tel_types_str(*types):
    itypes=[]
    for type in types:
        type=type.upper()
        if type == 'MOBILE':
            itypes.append('CELL')
            continue
        if type in ['OTHER', 'PHONE'] :
            continue
        itypes.append(type)
    ts = ','.join(itypes)
    if ts: return ';TYPE=%s' % ts
    return ''

def to_vcard(head, row):
    ln = '\n'
    vc = []
    vc.append('BEGIN:VCARD')
    vc.append('Version:3.0')
    vc.append('FN:%s' % row[0])
    vc.append('N:%s' % get_n_from_fn(row[0]))
    vc.append('EMAIL:%s' % row[1])
    vc.append('NOTES:%s' % row[2].replace('\n', '\\n'))
    idx = 2
    desc = ''
    for field in head[3:]:
        idx += 1
        type = field.split(' - ')[1]
        if idx >= len(row):
            break
        if type == 'Description':
            desc = row[idx].upper()
            continue
        if not row[idx]:
            continue
        if type == 'Email':
            emails = row[idx].split(';')
            for email in emails:
                vc.append('EMAIL;TYPE=%s:%s' % (desc, email.strip()))
            continue
        if type == 'IM':
            ims = row[idx].split(';')
            for im in ims:
                if ':' in im:
                    (imt, ima) = im.split(':', 1)
                else:
                    if '@gmail.com' in im:
                        (imt, ima) = ('X-GTALK', im)
                    elif '@hotmail.com' in im:
                        (imt, ima) = ('X-MSN', im)
                    else:
                        (imt, ima) = ('OTHER', im)
                vc.append('EMAIL;TYPE=%s:%s' % (imt.upper(), ima.strip()))
            continue
        if type in ['Mobile', 'Pager', 'Fax', 'Phone']:
            tels = row[idx].split(';')
            for tel in  tels:
                vc.append('TEL%s:%s' % (tel_types_str(desc, type), tel.strip()))
            continue
        if type == 'Title':
            vc.append('TITLE;TYPE=%s:%s' % (desc, row[idx]))
            continue
        if type == 'Other':
            continue
        if type == 'Company':
            vc.append('ORG;TYPE=%s:%s' % (desc, row[idx]))
            continue
        if type == 'Address':
            vc.append('ADDR;TYPE=%s:%s' % (desc, row[idx].replace(';', '\\;')))
            continue
    vc.append('END:VCARD')
    nvc = []
    for line in vc:
        if re.match('^[^:]*:[ \t]*$', line):
            pass
        else:
            nvc.append(line)
    return ln.join(nvc) + ln

load_csv(sys.argv[1])
以下为从 vCard 到 Gmail CSV 转换的脚本.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import csv
import sys
import re
import vobject

def filter_lines(ls, names, types):
    if not isinstance(names, list): names = [names]
    if not isinstance(types, list): types = [types]
    if names:
        ls = [l for l in ls if l.name in names]
    for type in types:
        if type.istitle():
            type = type.upper()
            ls = [l for l in ls if not type in l.params.get('TYPE', [])]
        else:
            ls = [l for l in ls if type in l.params.get('TYPE', [])]
    return ls

def lines_value(ls):
    return [l.value for l in ls]

def filter_lines_value(ls, names, types):
    ls = filter_lines(ls, names, types)
    return lines_value(ls)

def vcard_to_csv(fp, cw):
    for vcf in vobject.readComponents(fp):
        vcf_to_csv(vcf, cw)

def vcf_to_csv(vcf, cw):
    email = vcf.getChildValue('email') or ''
    fn = vcf.getChildValue('fn') or ''
    note = vcf.getChildValue('note') or ''
    fields = [fn, email, note]
    sections = ['WORK', 'HOME', 'OTHER', 'PERSONAL']
    f_sections=[['WORK'], ['HOME'], ['OTHER'], ['Work', 'Home', 'Other']]
    lines = [l for l in vcf.lines()]
    for idx in range(len(sections)):
        section = sections[idx]
        f_section = f_sections[idx]
        s_email=[v for v in filter_lines_value(lines, 'EMAIL', f_section) if v != email]
        s_im = [l.name.replace('X-', '')+': ' + l.value
                for l in filter_lines(lines, ['X-GTALK', 'X-MSN', 'X-ICQ', 'X-JABBER', 'X-AIM'], f_section)]
        s_mobile = filter_lines_value(lines, 'TEL', f_section + ['CELL'])
        s_pager = filter_lines_value(lines, 'TEL', f_section + ['PAGER'])
        s_fax = filter_lines_value(lines, 'TEL', f_section + ['FAX'])
        s_phone = filter_lines_value(lines, 'TEL', f_section +['Cell', 'Pager', 'Fax'])
        s_label = filter_lines_value(lines, 'LABEL', f_section)
        if section == 'WORK':
            s_org = ' '.join(vcf.getChildValue('org') or [])
            s_title = vcf.getChildValue('title') or ''
        else:
            s_org = ''
            s_title = ''
        if s_email or s_im or s_phone or s_mobile or s_pager or s_fax or s_org or s_title or s_label:
            pass
        else:
            continue
        fields.append(section.title())
        fields.append(' ::: '.join(s_email))
        fields.append(' ::: '.join(s_im))
        fields.append(' ::: '.join(s_phone))
        fields.append(' ::: '.join(s_mobile))
        fields.append(' ::: '.join(s_pager))
        fields.append(' ::: '.join(s_fax))
        fields.append(s_org)# company
        fields.append(s_title)# title
        fields.append('')# other
        fields.append('\n'.join(s_label))
    cw.writerow([field.encode('utf8') for field in fields])

if __name__== '__main__':
    csv_prev_headers = ['Name', 'E-mail', 'Notes']
    csv_per_headers = ['Description', 'Email', 'IM', 'Phone', 'Mobile', 'Pager', 'Fax',
                       'Company','Title', 'Other', 'Address']
    csv_headers = csv_prev_headers
    for i in [1,2,3,4]:
        csv_headers.extend(['Section %s - %s' % (i, h) for h in csv_per_headers])
    cw = csv.writer(sys.stdout)
    cw.writerow(csv_headers)
    if len(sys.argv) < 2:
        vcard_to_csv(sys.stdin, cw)
    else:
        for f in sys.argv[1:]:
            vcard_to_csv(open(f, 'rb'), cw)
以下为将 vCard 中的生日信息转换为 iCalendar 文件的脚本.

#!/usr/bin/env python
# coding: utf-8

import csv
import sys
import re
import vobject
import datetime
import dateutil

tzlocal = dateutil.tz.tzlocal()

def vcard_bday_to_ical(fp, ic):
    for vcf in vobject.readComponents(fp):
        bday = vcf.getChildValue('bday')
        if not bday: continue
        bday = bday.replace('-', '').replace(' ', '')
        if len(bday) == '4':
            bday = '1980'+bday
        if len(bday) != 8:
            continue
        print >>sys.stderr, 'parsing ', vcf.fn.value, vcf.bday.value
        date = datetime.date(int(bday[0:4]), int(bday[4:6]), int(bday[6:8]))
        v = ic.add('vevent')
        v.add('dtstart').value=date
        v.add('dtend').value=date+datetime.timedelta(1)
        rrule = dateutil.rrule.rruleset()
        rrule.rrule(dateutil.rrule.rrule(dateutil.rrule.YEARLY, dtstart=datetime.datetime(2009,12,12,tzinfo=tzlocal)))
        v.rruleset=rrule
        v.add('uid').value=vcf.uid.value + '-jiangzuoyan@gmail.com'
        v.add('summary').value=' '.join(
            [i for i in
             [vcf.fn.value,
              vcf.getChildValue('email') or '',
              vcf.getChildValue('tel') or '',
              vcf.getChildValue('bday') or ''
              ]
             if i])


if __name__ == '__main__':
    ic = vobject.iCalendar()
    for file in sys.argv[1:]:
        if file == '-':
            fp = sys.stdin
        else:
            fp = open(file, 'rb')
        vcard_bday_to_ical(fp, ic)
    print ic.serialize()

2009-02-15

归校

寒假在家, 诸事皆废, 唯有读史一项差近. 另, 测得母鸡体温41.2摄氏度.

归校途中至友人家小扰.

收拾东西, 收拾自己, 以前的事, 明天的事, 明天再说吧.

2009-01-01

一个标记

你不应该浪费你宝贵的时间来看, 我也不应该写.

这篇是倒着写, 正着的读.

认真去想, 心情很不稳定. 事情也一样. 相信过几个月再来看这个标记感受肯定不一样.

每年开始时, 都会想些东西, 然后在一年中慢慢忘掉.  今年, 我决定将它记下来.

我不知道是有信仰简单, 还是没有信仰简单.

多年来情况没怎么变化, 我也没怎么长大. 小变化的是我不怎么思考. 多年来, 我慢慢接受了一个事实, 人, 是物质上的人. 有人将这看作可耻的事. 我慢慢接受了这种可耻.

年少时, 我比较轻狂, 以为人可以是精神上的人. 我不是一个机械决定论者, 更不是唯心论者, 也不信被迫学了很多年的死了很多年的马克思. 我什么都不是, 我也不需要谁给我一个信仰. 虽然那时的我还偶尔思考活着.

在北大待了这么多年, 变的可能是对事物比较宽容, 但不代表不极端. 我喜欢极端,  因为它简单.

看看天上的月亮, 想起不久前, 父母的生日还是它提醒我的. 月亮不知道我们的新年.

蒋作延
2009-01-01

2008-12-24

pgman: 密码管理软件的实现

经过几次修正及实用, 这个小文件应该可以用了

pgman 的SVN库最新下载

2008-12-18

关于密码管理及软件

需要密码的地方太多了. 记这么多密码是件困难的事, 且有好记性不如烂笔头之说.

如是, 有两种解决方案, 一是所有密码用同一个, 二是将密码记下来.

用同一个密码最大的问题是安全性, 一个密码泄露, 则所有帐户都十分危险. 这个方法的一个折中是弄个简单的生成的规则. 但问题是, 这个规则或者太简单, 别人可以从你的一个密码知道其它的所有密码. 或者太复杂, 至于你也记不住或得不到.

一个简单的例子的是对所有网站, 使用相应的一个字符串, 如 "private_key_such_as_name+URL" 的映射, 如md5, sha. 这样, 一个密码的泄露, 不会太影响其它密码的安全性. 你所有密码的安全性, 依赖于你所选用的私密字符串及HASH函数的逆向困难性.

以上方案对不需要改动密码的地方很好. 但实际情况上是, 你应该周期性的改动密码. 以上方案无法适应这种情况. 如果记录每个网站对应的私密字符串, 不如记录密码.

这样, 另一个方案是, 明文记录所有密码, 将记录文件整个加密. 不少 PasswordManager是这样做的, 他们大多使用AES和Blowfish. 但我想使用 GnuPG.
而且我希望这个文件的明文是可阅读的, 这样在密码管理软件不可运行时, 依然可以查看密码. 或者说, 我希望加密功能与管理功能分开, 并且这个文件的明文可以手动管理.

我没有找到这样的软件. 有人提议直接用GnuPG+编辑器的方式, 这是个好主意. 但设想在你后面有人时, 你如何调出密码登录网站? 所以一个简单的查询界面还是要的.

将密码放在文件里, 这样, 你不需要记密码因而可以使用随机密码. 这样, 密码是你有的, 而不是知道的, 从而极大的提高了密码与个人信息的无关性.

这样做法, 最大的不足是, 查询密码有时不方便. 设想你在网吧, 想查看一下邮件, 却不知道自己邮箱的密码. 想查看一下加密文件, 却没有相应的运行程序.

这是为什么需要将加密与密码明文管理分开的原因之一. 加密软件如 GnuPG是有跨平台的, 明文格式有最小的平台问题.

这些思考的结果, 是练习Python写了一个密码管理脚本, 方便添加, 查看, 编辑, 及密码到Clipboard的应用, 基于 pyme 扩展.

将每个密码项用空行分隔, 每项定义为:

ID:
Username:
Password:
Url:
Notes:
Tags:

这些密码项存储在GPG文件里

2008-11-12

我不想谋生, 我想生活

我不想谋生, 我想生活.

找工作时, 很多人问我为什么找工作. 我知道他们还有一层意思, 为什么学数学却找个别的工作? 每次回答这个问题, 总有心痛的感觉.

我很认真并且理性地回答他们的问题.

感性地的说, 我需要个工作, 需要谋生, 所以我找工作.

将谋生当作生活一部分, 是可以想见的被劝的话. 停止这种妥协吧, 让我们保持对生活更好的理想.

但只是理想.

不知道为什么隔壁说今天是光棍节?