2008年10月14日

悠言悠闲:linux 下玩/wine 東方地霊殿


  1. 东方地灵殿(東方地霊殿 〜 Subterranean Animism.)是日本同人社团上海爱莉丝幻乐团制作的一款弹幕系射击游戏
  2. wine 的版本为 1.1.5
  3. 无法启动 th11.exe,需要下载,或者从 Windows 下面复制一个 d3dx9_36.dll 文件,
    $ cp d3dx9_36.dll ~/.wine/drive_c/windows/system32/
  4. 运行地灵殿的窗口模式的时候,分辨率似乎被限制在了640x480。没有找到解决办法
  5. 如果使用虚拟桌面(Virtual Desktop) + 地灵殿的全屏模式,感觉会有点卡,帧速降到50fps左右
  6. 如果使用虚拟桌面 + 窗口模式,游戏界面黑屏有声音
  7. 如果取消虚拟桌面 + 窗口模式,感觉上速度和 Windows 下面差不多,不过限速 60fps,实际差多少也不知道就对了
  8. 运行正确的话,wine 应该只会输出下面这几行消息,
    $ wine th11.exe 
    fixme:win:EnumDisplayDevicesW ((null),0,0x32f8d4,0x00000000), stub!
    fixme:d3d:WineD3D_ChoosePixelFormat Add OpenGL context recreation support to SetDepthStencilSurface
    fixme:d3d:WineD3D_ChoosePixelFormat Add OpenGL context recreation support to SetDepthStencilSurface
    fixme:win:WINNLSEnableIME hUnknown1 (nil) bUnknown2 1: stub!
    
  9. 有一篇更加详细的说明,東方地霊殿に関するメモ(日语)

2008年9月20日

有备无患:豆瓣离线 - 利用 Google Gears 实现离线浏览豆瓣的 GreaseMonkey 脚本

豆瓣离线(Douban Offline)是一个可以让用户可以离线浏览豆瓣的 GreaseMonkey 脚本。它可以在网络无法使用的情况下,浏览保存在本地的页面。它也可以用来收藏或备份豆瓣上的条目、小组和友邻等页面。

豆瓣离线使用 Google Gears API 来实现离线浏览的功能。Google Gears 是一款 Google 开发的软件。Gears 通过 SQLite 数据库让客户端能够把网页暂存起来,并通过内部服务器(Local server)把数据库中暂存的网页重现,从而让用户实现离线上网的功能。Google DocsGoogle Reader 都支持用 Gears 将本地暂存的资料与网络做同步。

豆瓣离线现在版本为 0.1,还有许多的不完善之处,欢迎大家的建议、意见和 Bug 报告:-)


功能

  • 保存豆瓣上的某个页面,以及相关的图片、CSS 和 Javascript 脚本
  • 按分类浏览已缓存的页面,目前的分类有:条目,小组和用户

使用方法

  • 如果希望保存当前页面,我们先找到导航栏“退出”附近的“离线”。点击“离线”,待离线状态栏展开完毕后,点击“离线”下面的“收藏此页面”。注意,点击之后不要马上关闭窗口,因为相关的文件可能还在下载中。另外我们也可以打开 FireBug 的终端(Console)来看看究竟那些文件被下载下来了。
  • 如果希望浏览已缓存的页面,点击 Firefox -> File(文件) -> Work Offline(离线工作)来强制 Firefox 进入离线工作的状态。点击“离线” -> “离线浏览”,然后在缓存页面目录,我们可以点击其中的链接访问已被缓存的页面。
  • 如果希望恢复在线浏览,在同样的地方点击“在线浏览”。
  • 第一次使用豆瓣离线脚本时,Gears 会弹出两个安全警告对话框,都选择 Allow(允许)就可以了。

安装需求

  1. Firefox (>=3.0.0): http://www.mozilla.com/en-US/firefox/
  2. GreaseMonkey (>=0.8):https://addons.mozilla.org/en-US/firefox/addon/748
  3. Google Gears (>=0.4):http://gears.google.com/

下载


大家可以从我的 GitHub Repo 中检出最新的脚本试用
git clone git://github.com/wuyuntao/douban-offline.git

或者从 UserScripts 网站上下载
下载


2008-09-23 UPDATE 0.2:

豆瓣离线脚本更新为 0.2,有下面这些改动:
  1. 可以手动切换上线/离线浏览
  2. 可以更新缓存页
  3. 新增两个分类,日记和相册
  4. 选中分类有高亮


2008-10-15 下载链接更新:

祸不单行。github 前几天数据库出了问题,到现在还没有恢复; userscripts.org 貌似被 GFW 给封了;今天 GAE 提供的四个 IP 中的又一个阵亡了。。。不管怎样,上面两个下载的链接都不管用了,现在提供一个本地的下载链接。
下载

2008-10-22 UPDATE 0.4:

豆瓣离线脚本更新为 0.4,有下面这些改动:
  1. 增加了翻页的功能
  2. 增加了评论和豆邮两个标签
  3. 增加了一条 Douban Helper 的控制台命令:write(wyt:Douban Helper 请安装最新版本
  4. 改正了相册日记等页面离线按钮位置不正确的bug

2008年9月14日

有备无患:Google Reader 风的豆瓣广播 widget 更新

今天为 Google Reader 风的豆瓣广播 widget 新增了三个选项:手动设置宽度 width,隐藏标题 hidetitle 和隐藏主页链接 hidefooter。谢谢 cowasiapan建议先。

使用方法和以前的说明一样,只需要将下面这段代码插入到你的 blog 模板中,并替换其中的参数。

<script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?username=wyt&amp;maxresults=16&amp;style=blue"></script>

参数列表如下

  • username: 用户名,或者用户 id。(必填)
  • maxresults: 输出广播条目的数量。最大值为50,默认为10。
  • style: widget 的配色样式,默认为绿色。其他样式包括 black(黑)、blue(蓝)、gray(灰)、green(绿)、khaki(卡其色)、pink(粉红)、slate(蓝灰) 和 none(无)。其中,none 为不带任何样式的原始 HTML,适合希望自定义广播风格的 bloggers。
  • width: 手动设定 widget 的宽度,输入应为宽度的像素值,如 width=300。默认为自适应宽度(推荐)。
  • hidetitle: 如果设 hidetitle=true,将隐藏顶部标题。默认为 false。
  • hidefooter: 如果设 hidefooter=true,将隐藏底部豆瓣主页的链接。默认为 false。

隐藏标题和主页的效果如下

2008年9月12日

有备无患:升级到 Git 1.6.01 之后无法启动 git-daemon 的解决办法

git 升级到 1.6 之后,发现 git-daemon 不能启动,自然也不能作 clone 或 push 之类的操作。原因是原来的 /usr/bin/git-daemon 搬到了 /usr/libexec/git-core 目录,但是 Gentoo 的启动脚本没有随之更新。不过有人已经提交了这个 bug

自己也可动手,将启动脚本 /etc/init.d/git-daemon 中的 start 函数修改如下。
start() {
    ebegin "Starting git-daemon"
        start-stop-daemon --start --quiet --background \
        --exec /usr/libexec/git-core/git-daemon -- ${GITDAEMON_OPTS}
    eend $?
}

另外,我用来架设 Git 版本库的 gitosis 也和 git 1.6 有一点兼容问题,原因是 /usr/bin/git-shell 也搬到了 /usr/libexec/git-core 目录。好在已经有 patch 推出,只需要修改 /usr/lib/python2.5/site-packages/gitosis/serve.py,将其中调用 git-shell 的指令,替换成调用 git shell 即可。
--- a/gitosis/serve.py
+++ b/gitosis/serve.py
@@ -201,6 +201,6 @@ class Main(app.App):
             sys.exit(1)
         main_log.debug('Serving %s', newcmd)
-        os.execvp('git-shell', ['git-shell', '-c', newcmd])
+        os.execvp('git', ['git', 'shell', '-c', newcmd])
         main_log.error('Cannot execute git-shell.')
         sys.exit(1)

2008年9月10日

南言北哲:Battier or Artest,谁将首发?

1,Battier or Artest,谁将首发?

Artest。联盟最好的人盯人防守者之一,上赛季场均20.5分的进攻水准,以及38%的三分球命中率。加上曾在主教练 Rick Adelman 手底下打过一年球,对火箭现在这套动态进攻的体系了然在胸。如果在训练营和季前赛中一切顺利,他极有可能会取代 Shane Battier 成为火箭的正印前锋。

有同学认为,Artest 应妨 Luis Scola 之例,先从替补打起,走循序渐进的路线。可是你要知道,Scola 虽然在 FIBA 摸爬滚打了多年,但是他也需要时间来适应 NBA,另一方面主教练也需要时间来了解他的技术特点,所以 Scola 必须从替补打起。Artest 却是已经在联盟混迹了整九年,别说 Adelman,就是联盟其余的29位主教练也同样对他知根知底。

另外,虽然 Artest 曾经表过态,说愿意从替补打起。可是,以史为鉴,Artest 不是甘居替补之人——05年他炮轰 Jermaine O'Neal 出手权多和进攻效率低,被交易到 Sacramento 之后,又与 Mike Bibby 争当“国王”。(wyt: 最终都以阿泰“抢班夺权”失败而告终。)

2,如果 Battier 做替补,能不能胜任第六人的角色?

像 Battier 这样的防守型球员也一样可以胜任第六人,这甚至还有可能成为一股潮流。

联盟的大多数第六人都是长于进攻的球员,比如被交易回国王的 Bobby Jackson,湖人签下肥约的 Sasha Vujacic,小牛老将 Jerry Stackhouse,还有掘金的 J.R. Smith 等,他们的主要作用是在主力下场的时候带领其他替补球员,提供持续的进攻力,并为主力争取更多的休息时间。(wyt:我不喜欢把 Manu Ginobili 和 Jason Terry 归类到第六人。如果非要说,我更愿意称他们为“伪第六人”,或者,“联盟第六人奖项欺诈师”)

但是对于拥有所谓的“三巨头”的球队,这个角色常常是由“三巨头”的其中之一所担任,比如凯尔特人的 Ray Allen 和马刺的 Manu Ginobili。因而这些球队对第六人在进攻端的要求也有所不同。以 Boston 为例,上赛季第六人就是场均只有7.4分进帐的防守悍将 James Posey。总决赛中出色的表现,也让黄蜂弃用原来的第六人 Jannero Pargo,花重金将 Posey 挖来作为下赛季他们的第六人。另外,湖人下赛季也有可能让 Odom 出任第六人。

Battier 之于火箭,就像 Posey 之于 Boston 和 New Olean。而且 Battier 有更出色的协防能力和不错的背身技巧,可以想见新赛季的第四节,我们经常会看到 Battier 和 Artest 一起打完最后一分钟。

3,如果 Artest 做先发,出手次数会不会不够?

这很可能是杞人忧天。

上赛季,有三支球队的核心球员被称作“三巨头”,分别是凯尔特人的 Garnett、Pierce 和 Allen,马刺的 Duncan、Parker 和 Ginobili,以及湖人的 Kobe、Odom 和 Gasol。我们首先来看看他们的出手分配情况,并和火箭作一下对比。


表格中的 FGA 为出手次数 (Field Goal Attempts),FTA 为罚球次数 (Free Throw Attempts),TP 为球队节奏 (Team Pace),TSA 为每100次球权的真实出手次数 (True Shooting Attempts),所使用的统计公式为 TSA = (FGA + 0.44 * FTA) / TP * 100。要说明的,湖人的数据是“打劫” Gasol 以后的全明星之后的数据,而其他球队是整个赛季的数据。



从图表中,我们可以看出光是姚明,T-Mac 和 Battier 三个人的出手次数,就已经和其他几个三巨头基本持平。所以即使 Artest 取代 Battier 成为先发,只要姚明和 T-Mac 愿意牺牲自己的球权,火箭的出手次数对于 Artest 来说是绰绰有余的。

另外,由于伤病的关系,姚明和 T-Mac 都有相当长一段时间单独带队,所以也有同学质疑,这段时间他们的出手数是不是突增的,会不会影响到最后的统计结果。我也捎带统计了一下他们同时带队的10月、11月和2月份,姚明的出手数是20.0次,而 T-Mac 的是 24.4次。相比整个赛季并没有太大的变化,这主要是因为无论是姚明还是 T-Mac 带队,他们都是以火箭这个团队来进攻,谁也没有像 Kobe 在 Bynum 和 Gasol 缺席的时候那样贸然增加自己的出手。

2008年9月5日

有备无患:用 Gaupol 生成文本字幕

Gaupol 是一个用 Python 编写的文本字幕文件的编辑工具,支持包括.ssa、.ass、.srt和.sub在内的多种字幕文件格式,并提供文本校正的方法以及时间处理。其用户界面基于 pyGTK,注重对多文档和翻译的批量处理。最新版本为 0.13

下面的命令示范了利用 Gaupol 生成一个 SubRip 格式的字幕文件,以作备忘。

$ python
>>> from gaupol.files.subrip import SubRip
>>> from gaupol import NEWLINE
>>> srt = SubRip('/home/wyt/demo.srt', encoding='utf-8', newline=NEWLINE.UNIX)
>>> srt.write(starts=[u'00:00:5.000', u'00:00:15.000', u'00:00:25.000'], \
              ends=[u'00:00:10.000', u'00:00:20.000', u'00:00:30.000'], \
              texts=[u'字幕测试一', u'字幕测试二', u'字幕测试三'])

生成的 .srt 字幕文件如下

1
00:00:5,000 --> 00:00:10,000
字幕测试一

2
00:00:15,000 --> 00:00:20,000
字幕测试二

3
00:00:25,000 --> 00:00:30,000
字幕测试三

2008年8月10日

有备无患:truncatehanzi - 按字数截取中文的 Django template filter

Django 自带的一个模板过滤器(Template Filter)truncatewords,可以按单词数截取字符串生成摘要。不过,truncatewords 只能用空格来分隔单词,对汉字就无能为力了。所以,我写了另一个模板过滤器 truncatehanzi,它通过 unicode 编码来区分西方字符和 CJK 汉字,对包括英语字母在内的西方字符以空格或其他符号来分隔并计算单词数,而对 CJK 汉字则是按字数计算。

使用方法和 truncatewords 一样,
{{ value|truncatehanzi:6 }}
如果 value 为“截取English、Γρεεκ等字母语言和CJK汉字。”,输出“截取English、Γρεεκ等字...”。

代码分为两个部分,截取汉字的脚本 truncate_hanzi.py 和生成过滤器的脚本 filters.py。

truncate_hanzi.py

 # -*- coding: UTF-8 -*-

import re
from django.utils.encoding import force_unicode
from django.utils.functional import allow_lazy

def truncate_hanzi(s, num):
    s = force_unicode(s)
    length = int(num)
    if length <= 0:
        return u'...'
    # Set up regex for alphanumeric characters
    # \u00c0-\u02af: Latin
    # \u0370-\u1fff: Greek and alphabet characters in other language
    re_alnum = re.compile(ur'[a-zA-Z0-9_\-\u00c0-\u02af\u0370-\u1fff]', re.U)
    # Set up regex for hanzi
    # \u3040-\ufaff: CJK characters
    re_hanzi = re.compile(ur'[\u3040-\ufaff]', re.U)
    hanzi = u''
    hanzi_len = 0
    word_temp = u''
    for char in s:
        # Check for alphabet characters
        if re_alnum.match(char):
            word_temp += char
            continue
        if word_temp:
            hanzi += word_temp
            hanzi_len += 1
            word_temp = ''
        # Check for length
        if hanzi_len >= length:
            if not hanzi.endswith('...'):
                hanzi += '...'
                break
        # Check for hanzi
        if re_hanzi.match(char):
            hanzi_len += 1
        hanzi += char
    hanzi += word_temp
    return hanzi
truncate_hanzi = allow_lazy(truncate_hanzi, unicode)

def demo():
    print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 6)
    print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 11)
    print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 20)

if __name__ == '__main__':
    demo()

filters.py

 # -*- coding: UTF-8 -*-
from django.template import Library
from django.template.defaultfilters import stringfilter

register = Library()

@stringfilter
def truncatehanzi(value, arg):
    """
    Truncates a string after a certain number of words including
    alphanumeric and CJK characters.

    Argument: Number of words to truncate after.
    """
    from truncate_hanzi import truncate_hanzi
    try:
        length = int(arg)
    except ValueError: # Invalid literal for int().
        return value # Fail silently.
    return truncate_hanzi(value, length)
truncatehanzi.is_safe = True

register.filter('truncatehanzi', truncatehanzi)

2008年8月6日

有备无患:一个 jQuery 的时间选择插件

Yet another jQuery time picker plugin. Google 上兜了一圈,没找到中意的时间选择(Time Picker)插件,于是自己写了一个。timepicker 是一个弹出式的对话框,分为上下两个部分,上半部分是可以分别设定时分秒的计数器,下半部分提供一些常用的时间选项,比如“Now”,“Noon”,“8 p.m.”等。通过聚焦(focus)到相应的 input 控件,可以激活 timepicker。选择好时间后,点击对话框底部的 Close 按钮会将结果保存到 input 控件中,而点击 Clear 按钮会清除 input 控件中已存的时间。键盘上的 Tab 键和 ESC 键也有与之对应的功能。

这是我的第一个 jQuery 插件,欢迎大家使用,我也会继续完善这个插件。如果有任何的 Bug,建议和意见,欢迎在文章后留言,或者 Email 给我,或者在插件的主页写下你的评论。:-)

使用方法

$('#time-picker').timepicker();

截图


Yet another jQuery time picker plugin.

演示

演示网址



下载

你可以选择下载压缩档。
下载

或者用 Git 从架设在 GitHub 的软件仓库中检出源代码使用。
$ git clone git://github.com/wuyuntao/timepicker.git

2008年8月4日

有备无患:Google Reader 风的豆瓣广播 widget

最近,豆瓣连续发布了几个重要的 API 更新,包括了备受期待的友邻广播相关的接口。特别是广播的 API,自从用豆瓣广播取代了 Twitter 当作吐口水的工具之后,我一直期待广播也有像 TwitterFox 一样的客户端,有个 widget 可以在 blog 上显示最近的状态,而这些都是要以广播的 API 为基础的。

言归正传,我写了一个类似 Google Reader 的分享阅读剪辑(Shared Item Clips)风格的豆瓣广播 widget,可以放在 blog 的侧边栏。使用的方法很简单,只需要将下面这段代码插入到你的 blog 模板中。(wyt:如果不知道怎么操作的话,可以参考豆瓣秀指南。)

<script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?username=wyt&amp;maxresults=16&amp;style=blue"></script>

注意上面代码中粗体的部分,首先我们要将``wyt``换成自己的用户名;``maxresults``为 widget 显示的广播条数(最大值为50,默认为10,可以省略);``style``表示 widget 的配色,和 Google Reader 分享阅读的配色基本一致,包括:black、blue、gray、green、khaki、pink、slate 和 none。默认为 green,如果输入的 style 不在上述配色中,则返回 none。none 为不带任何 style 的原始 HTML,适合希望自定义广播风格的 blogger。

最终效果如下图。想看看实物 demo 的话,可以点击这里。要说明的是,看得到漂亮的圆角效果的只有 Firefox 用户(wyt:这也是 Google Reader 的风格之一,:p),IE、OperaSafari 用户对不住了。如果有任何的建议或意见,欢迎留言 :-)

2008年7月18日

有备无患:在 Django 上使用 reCAPTCHA 生成验证码

reCAPTCHA 是由 CAPTCHA 的原作者,也是人类计算学(Human Computation)的专家 Luis von Ahn 所设计一个免费的 anti-bot 服务,它在随机提取的单词上加上一些扭曲和可阻碍电脑自动识别的噪声,以利用人类远强于电脑的图形识别能力来检查屏幕前坐着的是人类,还是 bot 或自动脚本。因此,reCAPTCHA 经常被用于生成网站注册或者文章评论的验证码。

除此之外,reCAPTCHA 还有一项非常公益的设计。虽然 OCR (全称 Optical Character Recognitio,中译光学字元识别)早已被投入使用,但是对那些印刷模糊或分辨度不足的实体书籍而言,OCR 的识别率难说完美。而 reCAPTCHA 将这些上无法由电脑自动识别的单词扫描下来,以图片的形式存入数据库,并在网络上以验证码(CAPTCHA)的形式交由人类来破译,而破译的结果将被用于帮助世界上的实体书加快数字化的进程。

你可能会问,既然电脑无法识别这些单词,那么 reCAPTCHA 系统又怎么知道用户填写的是正确的验证码呢?reCAPTCHA 的网站上有详细的解释

一个不能被 OCR 正确识别的新单词,总是会和另一个已知答案的单词一起提交给用户。接下来用户需要同时识别这两个单词。如果用户正确识别出其中已知答案的那个单词,系统即假设用户所识别出的未知答案的新单词也可能是正确的。如果其他的用户也识别出相同的答案,系统则逐步提高这种可能性。最终当这种可能性超过某个阈值时,系统就可以将其认作已知答案的单词了。

言归正传,reCAPTCHA 有丰富的 API,包括对 PHP、JAVA、Ruby,自然还有 Python 的支持。如果要在 Django 上使用 reCAPTCHA,我们可以从 PyPI 下载 reCAPTCHA 的 Python 客户端,或者直接用 easy_install 安装。


# easy_install recaptcha-client

不知道为什么,虽然安装成功了,但是我不能导入 recaptcha 模块(wyt:import captcha 或 import recaptcha 都没有用,哪位如果顺利导入过的话,能不能分享一下你的经验呢?先谢了),所以我是把 egg 中的脚本直接复制到我的 Django 项目目录下了。

接下来,我们需要在 reCAPTCHA 注册并申请一对公钥/私钥,并将其保存在 settings.py 中。

settings.py

1 # reCAPTCHA keys
2 RECAPTCHA_PUBLIC_KEY = "6LdqgAIAAAUSHDmo4IIBmsjUsduAUMUoBDZc3J_T"
3 RECAPTCHA_PRIVATE_KEY = "6LdqgAIAAbjsJKLbj2KOjPO6D4isfJ_AzLSO_256"

然后,我们要为注册表单添加验证码了。在 Django Snippets 上,oggy 已经将 reCAPTCHA 抽象成一个 RecaptchaForm 类,所以我们只要让注册表单(Registration Form)继承这个类,就可以为注册表单添加验证码的功能了。由于 Django newform 默认是根据 field 的定义顺序来生成表单,所以在继承 RecaptchaForm 的时候,也应该把它放在最后继承。

recaptcha/forms.py

01 from django import newforms as forms
02 from django.newforms import ValidationError
03 from django.conf import settings
04 from recaptcha import captcha
05 
06 
07 class RecaptchaWidget(forms.Widget):
08     """ A Widget which "renders" the output of captcha.displayhtml """
09     def render(self, *args, **kwargs):
10         return captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY)
11 
12 class DummyWidget(forms.Widget):
13     """
14     A dummy Widget class for a placeholder input field which will
15     be created by captcha.displayhtml
16 
17     """
18     # make sure that labels are not displayed either
19     is_hidden=True
20     def render(self, *args, **kwargs):
21         return ''
22 
23 class RecaptchaForm(forms.Form):
24     """
25     A form class which uses reCAPTCHA for user validation.
26    
27     If the captcha is not guessed correctly, a ValidationError is raised
28     for the appropriate field
29     """
30     recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
31     recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
32 
33     def __init__(self, request, *args, **kwargs):
34         super(RecaptchaForm, self).__init__(*args, **kwargs)
35         self._request = request
36 
37     def clean_recaptcha_response_field(self):
38         if 'recaptcha_challenge_field' in self.cleaned_data:
39             self.validate_captcha()
40         return self.cleaned_data['recaptcha_response_field']
41 
42     def clean_recaptcha_challenge_field(self):
43         if 'recaptcha_response_field' in self.cleaned_data:
44             self.validate_captcha()
45         return self.cleaned_data['recaptcha_challenge_field']
46 
47     def validate_captcha(self):
48         rcf = self.cleaned_data['recaptcha_challenge_field']
49         rrf = self.cleaned_data['recaptcha_response_field']
50         ip_address = self._request.META['REMOTE_ADDR']
51         check = captcha.submit(rcf, rrf, settings.RECAPTCHA_PRIVATE_KEY, ip_address)
52         if not check.is_valid:
53             raise ValidationError('You have not entered the correct words')


user/forms.py

01 from myproject.recaptcha.forms import RecaptchaForm
02 
03 class RegistrationForm(forms.Form):
04     """
05     Form for registering a new user account.
06      
07     """
08 
09 class RegistrationFormWithRecaptcha(RegistrationForm, RecaptchaForm):
10     """
11     Subclass of ``RegistrationForm`` and ``RecaptchaForm`` which can tell whether its user
12     is a human or a computer.
13 
14     """

2008年7月4日

有备无患:浏览器端缓存(基于 jQuery)

最近在翻《Ajax 设计模式》,所以想把其中的一部分模式实现出来练手 jQuery,今天写了一个简单的引入 LRU 算法的浏览器端缓存(Browser Side Cache)。

浏览器端缓存用来保留服务器返回的查询结果。这种缓存是一个 Javascript 里类似映射的对象,存储成对的查询结果;查询是缓存的键(Key),服务器返回的结果是缓存的值(Value)。因此,每当浏览器需要查询服务器时,先检查缓存。如果该查询是缓存中的一个键,则与键对应的值将被当作结果,而不必再向服务器查询。

LRU (Least Recently Used)是将存储在缓存中的自上一次获取之后,最长时间未被使用的项目(Item)丢弃的一种算法。可以用两个数组(Array)来实现,其一是用来存储查询结果的键值对(Key-Value Pairs),其二是一个先进先出(FIFO)的队列,每一个新项目被塞入队列的尾部,并随着后续项目的跟进,逐渐逼近队列的头部。当队列全满时,每次向尾部塞入一个新项目,就要从头部弹出一个旧项目。而每当一个项目被缓存查询时,它会被送回到队列的尾部,这样可以确保最长时间未被使用的项目总是在开头处。

另外,还有一种常用的缓存算法,LFU (Least Frequently Used),它将自上一次获取后最少被使用的项目丢弃。

顺便一说,下面贴出的代码是由代码发芽网生产的纯 HTML。代码发芽网是一个“无需插件支持 Blog 代码高亮,支持近百种编程语言,多种配色主题支持,代码版本管理”的代码片段管理网站。因为不是由 Javascript 脚本来高亮处理,所以在 feed 里也一样可以看到效果,这一点很赞。不过,由于用处不大的 id 和 class 也包含在生成的 HTML 里,所以体积偏大。比如说下面这段代码,在删除了多余的 id 和 class,套上代码专用的 precode 标签,并将 &nbsp; 还原成空格之后,体积可以缩小一半仅17KB(原来35KB),更适合 blog 来贴代码。


cache.js

01 function Cache(size) {
02     /* A browser side LRU cache 
03      * Author: Wu Yuntao <http://luliban.com/blog/>
04      * License: GPLv3
05      *
06      * Usage:
07      * var cache = new Cache(10);   // create a new cache object
08      * cache.put('w', 'wiki');      // put an item into cache
09      * cache.get('w');              // get the value of item with key
10      * cache.remove('w');           // remove an item with specified key
11      * cache.initialize()           // re-initialize the cache
12      * cache.size(10)               // resize the cache
13      */
14     this.initialize(size);
15 }
16 
17 Cache.prototype = {
18     initialize: function(size) {
19         /* Initialize cache.
20          * ``size`` is the number of maxmium items this cache should hold.
21          * Default is maxium integer.
22          */
23         this._keys = new Array();
24         this._items = new Array();
25         this._size = size || this._size || Number.MAX_VALUE;
26     },
27 
28     is_empty: function() {
29         /* Check if cache is empty
30          */
31         return (this._keys.length == 0 && this._items.length == 0);
32     },
33 
34     size: function(size) {
35         /* Resize cache if ``size`` is specified, or return accual size of cache.
36          */
37         if (typeof size == 'undefined') return this._keys.length;
38         return this._size = size;
39     },
40 
41     put: function(key, value) {
42         /* Put a new item into cache, if the size of cache reaches limit,
43          * cache will remove the least recently used (LRU) automatically.
44          * ``key`` of an item should be a string.
45          * ``value`` of an item could be anything, string, array or object.
46          * If ``value`` is not defined, returns null.
47          */
48         if (typeof value != 'undefined') {
49             this._keys.push(key);
50             this._items[key] = value;
51 
52             if (this._keys.length > this._size) {
53                 this.remove_least();
54             }
55         }
56         return value;
57     },
58 
59     get: function(key) {
60         /* Retrieve an item by its key and move it to the tail of cache.
61          * If the item of ``key`` does not exist, returns null.
62          */
63         if (typeof this._items[key] == 'undefined') return null;
64         var used_key = this._remove_key(key);
65         this._keys.push(used_key);
66         return this._items[key];
67     },
68 
69     remove_least: function() {
70         /* Manually remove the least recently used item. */
71         if (!this.is_empty()) this.remove(this._keys[0]);
72     },
73 
74     remove: function(key) {
75         /* Remove an item by its key.
76          * If the item of ``key`` does not exist, returns null.
77          */
78         if (typeof this._items[key] == 'undefined') return null;
79         this._remove_key(key);
80         return this._remove_value(key);
81     },
82 
83     _remove_key: function(key) {
84         /* Remove the ``key`` in ``this._keys``.
85          */
86         var i = $.inArray(key, this._keys);
87         return this._keys.splice(i, 1);
88     },
89 
90     _remove_value: function(key) {
91         /* Remove the item in ``this._items``. */
92         var value = this._items[key];
93         delete this._items[key];
94         return value;
95     }
96 
97 };

2008年7月2日

有备无患:pinyin-urlify - 根据汉字自动生成拼音 URL

根据文章标题中的英语单词自动生成文章的 URL 是一个很好的体验,比如标题为“Girl's death sparks riot in China”,会被转换成“girls-death-sparks-riot-china”这样与之对应的 URL。这样既可获得 Cool URIs,又便于用户记忆,同时也有利于 SEOBloggerWordpress 也都支持类似体验。而中文的文章标题,也可以先将其中的汉字转化为拼音后再组成相应的 URL,比如标题为“俯卧撑”,可以被转换成“fu-wo-cheng”。

pinyin-urlify 是我这几天写的能根据汉字自动生成拼音 URL 的 Python 脚本。它可以将汉字映射成有英语字母组成的拼音,和多种西方语言的字母(拉丁语、希腊语、俄语等)映射成相近的英语字母。要说明的是,汉字-拼音的映射表是从 pyzh 项目中获得,而其他西方语言的映射表则是从 Django 项目中获得,特表感谢

pinyin-urlify 支持自定义停用词列表(stop words)和保留词列表(reserved words)。停用词列表中的单词都会被过滤,不会出现在生成的 URL 中。生成的 URL 如果和保留词列表中的某一个单词匹配,则会被替换成默认的 URL 字符串。pinyin-urlify 可以指定 URL 的最大长度。不过,如果遇到有较多汉字组成的文章标题的话,实际生成的 URL 可能会比限定的最大长度小一些。我希望能在以后修正这一点。

使用示例

>>> from urlify import urlify
>>> urlify(u'三个俯卧撑引发的血案', default='blog_post',
...        max_length=50, stop_words=[u'is', u'a', u'an'],
...        reserved_words=[u'new', u'edit'])
u'san-ge-fu-wo-cheng-yin-fa-de-xue'


欢迎大家用 svn 检出 pinyin-urlify 的副本来使用。欢迎任何意见或建议 :-)

$ svn checkout http://pinyin-urlify.googlecode.com/svn/trunk/ pinyin-urlify

2008年6月21日

有备无患:在 GitHub 上用不同的 SSH 公钥部署多个应用

GitHub 是一个免费的基于 git 的开源软件仓库托管服务,包括 Ruby on RailsRspecPrototype 在内的许多开源软件都在 GitHub 上架设了软件仓库。


如果你有两个或两个以上的 GitHub 仓库,并且想用不同的公钥/私钥来管理这些仓库的话,会遇到这样一个问题。当你 clone 第一个仓库的时候,git 会使用第一对公钥/私钥,这个时候仍然一切正常。可当你需要用另一对公钥/私钥来 clone 第二个仓库的时候,git 默认仍然会使用第一对公钥/私钥,然后 GitHub 就会返回一个授权错误。


要解决这个问题,首先需要在 ~/.ssh/ 目录下新建一个 config 文件,为不同的项目分别指定公钥/私钥

~/.ssh/config

Host project-1
    HostName github.com
    User git
    IdentityFile /home/wyt/.ssh/id_rsa_project_1

Host project-2
    HostName github.com
    User git
    IdentityFile /home/wyt/.ssh/id_rsa_project_2

然后用 ssh-keygen 创建对应的公钥/私钥。

$ ls -l ~/.ssh
total 24
-rw-r--r-- 1 wyt wyt  244 2008-06-21 13:18 config
-rw------- 1 wyt wyt 1675 2008-06-21 13:04 id_rsa_project_1
-rw-r--r-- 1 wyt wyt  392 2008-06-21 13:04 id_rsa_project_1.pub
-rw------- 1 wyt wyt 1675 2008-05-20 11:24 id_rsa_project_2
-rw-r--r-- 1 wyt wyt  392 2008-05-20 11:24 id_rsa_project_2.pub
-rw-r--r-- 1 wyt wyt  802 2008-05-24 22:44 known_hosts

将公钥上传到 GitHub 上之后,就可以用下面的指令 clone 相应的软件仓库了

$ git clone git@project-1:username/project-1.git
$ git clone git@project-2:username/project-2.git


参考页面

2008年6月17日

悠言悠闲:Firefox 下载日

Download Day 2008

Firefox 3正式下载时间为北京时间六月十八日凌晨一点。下载日活动届时开始,请在北京时间六月十九日凌晨一点之前下载Firefox 3。

2008年5月27日

有备无患:增强豆瓣收藏对话框的 Greasemonkey 脚本

经常使用 del.icio.us 的人,一定也很熟悉 del.icio.us 的跟随键盘输入自动提示标签(Tag Suggest)的功能。它为用户管理大量的标签带来方便,比如不必去费神记忆单词的拼写,避免为同一语义定义多个标签等。这是靠我们的大脑来记忆管理,或者类似豆瓣只提供10个最常用的标签,不能做到的。

这个 Greasemonkey 脚本的主要功能,是为豆瓣收藏对话框提供类似 del.icio.us 的标签提示功能。另外还重新布置了读过/在读/想读等按钮的布局,并添加了删除收藏的按钮。

目前脚本的版本为0.1,欢迎试用。有什么建议或意见,欢迎留言:-)


Screenshot - Enhanced Douban Collecting Dialog

功能

  • 类似 del.icio.us 的随输入自动提示标签(Tag Suggest)
  • 重新安排了读过/在读/想读等按钮的布局,保证 UI 路径的一致性
  • 增加了删除收藏的按钮

下载

下载脚本


2008-05-31 UPDATE

脚本更新至0.2,有下列更新

  • 增加了对个人收藏页,标签页,豆瓣猜,TOP250等页面中的收藏对话框的支持
  • 修正了之前脚本中已知的 BUG

2008-09-18 UPDATE

脚本更新至0.3

  • 支持更新后的收藏对话框
  • 默认展开标签
  • 支持豆瓣全域的收藏对话框

2008年5月19日

有备无患:在 Gentoo 上部署 Git + Gitosis 服务器的笔记

GitLinus Torvalds 为了帮助管理 Linux 内核开发而开发的分布式版本控制软件(Distributed SCM)。Git 汲取了 Torvalds 在维护大型的分布式项目开发方面的经验和对文件系统性能的丰富知识,正如其文档所描述的,“是一个快速、可扩展的分布式版本控制系统,它具有极为丰富的命令集,对内部系统提供了高级操作和完全访问。”目前,Linux 内核、X.org 服务器和 Ruby on Rails 等开源项目的版本控制系统都已经切换到 Git。

GitosisTommi Virtanen 为了更方便和安全的辅助 Git 架设和管理软件版本库 (Software Repository) 而开发的工具软件。虽然 Git 本身也提供 git-daemon 以架设版本库,但在用户访问控制上做的并不严格。而 Gitosis 允许单个用户帐号管理多个版本库,使用 SSH keys 管理用户认证,不需要 shell 帐号就可以解决多用户访问集中版本库的问题。

需要说明的是,我使用的 Gitosis 是 robbat2 为了部署新的 Gentoo Overlays 而开发的 Gentoo 分支版本 (Gentoo Fork Version),和上游版本 (Origin Upstream Version) 相比有如下不同:

  • 支持 git+ssh://HOST/REPO 风格的相对路径;
  • 支持以命令行参数方式导入 keys,如 gitosis-init --adminkey=FILE --adminname=STRING;
  • 智能处理 SSH keys,支持 SSH1 和 SSH2 keys;
  • 将上游版本默认的目录权限 0750 更改为 0755,以便用 nobody:nobody 运行 git-daemon。

这篇文章详细记录了我在本地安装和部署 Git + Gitosis 的过程和遇到的问题,希望为大家提供一些参考。欢迎 Fix-Me :-)

安装 Git + Gitosis

安装 git 和 gitosis-gentoo。安装完后,Gentoo 会自动添加 git 用户和组,并将版本库的主目录设为 /var/spool/gitosis/repositories。
# echo ">=dev-util/git-1.5.5" >> /etc/portage/package.keywords
# echo ">=dev-util/gitosis-gentoo-0.2_p20080203" >> /etc/portage/package.keywords
# emerge -av git gitosis-gentoo

如果你的主机上没有装 SSH 的话,还要安装 openssh, 以及 keychain 用来管理 ssh-agents(可选)。
# emerge -av openssh keychain

安装完后,启动 sshd,并加入默认启动。
# /etc/init.d/sshd start
# rc-update add sshd default

配置 Gitosis

首先生成一个 ssh key。
$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/wyt/.ssh/id_rsa):(回车)
Created directory '/home/wyt/.ssh'.
Enter passphrase (empty for no passphrase):(直接回车)
Enter same passphrase again: (直接回车)
Your identification has been saved in /home/wyt/.ssh/id_rsa.
Your public key has been saved in /home/wyt/.ssh/id_rsa.pub.
The key fingerprint is:
dd:33:cc:99:33:00:66:77:ff:00:bb:99:00:22:55:88 wyt@gentoo

我把 ssh key 保存在默认的 ~/.ssh/id_rsa,你也可以选择其他地方。接下来把 id_rsa.pub 上传到服务器。其中的root@gentoo,root 是根用户名,gentoo 是主机名,可以从 /etc/conf.d/hostname 得知。
$ cat /etc/conf.d/hostname
hostname="gentoo"
$ scp ~/.ssh/id_rsa.pub root@gentoo:
Password: (输入 root 帐户密码)
id_rsa.pub                                    100%  392     0.