Random Tech Thoughts

Programming Language, Apple, Linux

我的第一个 OS X 程序 PinYinNick

去年买了 Dialvetica 却发现这个应用不支持用拼音搜索。十一回家的时候写了个汉字转拼音的库,跟作者联系希望能加入拼音的支持。未果,失望之余也无他法。后来了解到 MacRuby 可以直接调用 Cocoa Framework,写了个 Ruby 脚本给所有联系人添加了拼音缩写,在 Dialvetica 里打开昵称搜索后用的也挺欢的。一个没想到的好处是其他程序比如 Address Book 里要找联系人也可以用昵称,iPhone 上凡是搜索联系人的地方也都能用拼音缩写。

我那个 Ruby 脚本只能在装了 MacRuby 的机器上用。这次五一回去的时候学了下 Cocoa 编程,写了个 native 的程序来做这事。

代码在 Github 上,项目为 PinYinNick。编译好的文件也已上传,可点此下载。欢迎使用。

我几乎从来不写 GUI 程序,写这个程序的过程中得到了不少以往没有的体验。自从不碰 Java 以来一直都是用 Vim 写代码,这次觉得有个好用的 IDE 还是能提高不少效率的。 Interface Builder 真心好用,跟 Cocoa 的 key value coding/observing 结合使可以少写很多代码(当然这个程序的界面还是相当简单的)。静态程序分析对代码的检查的确可以避免一些错误,我刚开始用 ARC,有些内存管理的问题 Xcode 直接提示我解决掉了。最大的缺点是 Xcode 4.3 挺容易崩溃的。

虽然我没什么 GUI 编程的经验,不过 Cocoa 用起来还算顺手,感觉很方便、功能很强大。(比本科时用 Java Swing 方便太多了。)或许以后会再尝试写些程序来满足自己的需求。

QEMU 源代码笔记

自己的代码过段时间不看就会忘,看别人的代码忘的更快。折腾 QEMU 很久了,经常会要回头看看过的代码,然后发现忘记各种细节。几次之后终于开始做笔记。

今天又多记了一笔,干脆稍微整理一下发布出来,有兴趣的点此访问

由于是想到才记,所以没有统揽全局的那种 overview,非常零碎。有几个 QEMU 的模块比较复杂,用 LucidChart 画了几张图,看起来可能会方便一些。

跟 tips 一样,会不定期更新。

让 Safari 的 Google Search 使用 HTTPS

Google Plus 我很少用,但是 +1 我觉得还是一个不错的功能。以前看到不错的页面会存书签,后来发现这些书签平时不会用到,想搜东西的时候一来不会去搜书签,二来书签不包含内容也搜不到。而 +1 过的页面以后再次 Google 的时候会更容易找到,可以很方便的用来 mark 自己觉得有价值的网页。(Twitter 的 search 太差。Evernote 的 webclip 不错,不过比起单击一下就能 mark 还是麻烦了一点。所以只有非常有价值的内容我才会用 webclip。)

不过在使用 Safari 时,即使登录了 Google,搜索页面也没有 +1 按钮。原因是 Safari 的 Google Search 默认用了 http,而 +1 按钮只有使用 https 时才会显示。我通过修改 SafariOminibar 的 search provider 文件使 Google Search 默认使用 https。

SafariOminibar 是一个使 Safari 地址栏和 Chrome 地址栏行为类似的一个 plugin,个人使用下来很稳定,推荐使用。注意它不是 extension,而是通过 SIMBL 利用 InputManager system 注入代码这种 hack的方式实现的一个插件。下载安装之后修改 /Library/Application Support/SIMBL/Plugins/SafariOmnibar.bundle/Contents/Resources/SearchProviders.plist 这个文件,把 Google 的 url 全部改成 https 开头即可达到目的。

由于 Safari 5.2 将实现 Ominibar 的功能,因此 SafariOminibar 已经停止开发。等 Safari 5.2 发布之后可能得寻找其他方法。

Safari 无法使用本地 PAC 文件的原因和解决办法

最近网络环境非常糟糕,无法访问 Google 的情况越来越多。实验室的公用代理经常无法使用,不得不在本地写几个 PAC 文件备用。使用过程中发现用本地的 PAC 文件设定代理 Chrome, Firefox, Twitter For Mac 都能正常使用,唯独 Safari 不行。但是一旦本地开一个 web server 来获取这个文件就一切正常。

Google 一番得到结果,原因是 OS X Lion 的 sandbox 机制限制了 Safari 能够访问的文件,而我指定的 PAC 文件 Safari 没有权限读取。

很多程序的 sandbox configuration 文件位于 /usr/share/sandbox,这些文件限定了程序能够访问什么文件。例如这个目录下的 sshd.sb 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
;; Copyright (c) 2008 Apple Inc.  All Rights reserved.
;;
;; sshd - profile for privilege separated children
;;
;; WARNING: The sandbox rules in this file currently constitute 
;; Apple System Private Interface and are subject to change at any time and
;; without notice.
;;

(version 1)

(deny default)

(allow file-chroot)
(allow file-read-metadata (literal "/var"))

(allow sysctl-read)
(allow mach-per-user-lookup)
(allow mach-lookup
  (global-name "com.apple.system.notification_center")
  (global-name "com.apple.system.logger"))

有没有很惊讶?本来我只会发个推说下 PAC 文件会因为 sandbox 而无法使用,但是看到它的 configuration 使用 Scheme 来定义的时候决定写博客了。

Safari 的 sandbox configuration 位于 /System/Library/PrivateFrameworks/WebKit2.framework/WebProcess.app/Contents/Resources/com.apple.WebProcess.sb

知道 PAC 文件不能使用的原因后解决起来就方便了。修改配置文件当然可以,但是遇到 Safari 升级可能要重新修改。另一个简单点的办法是把 PAC 文件放到 Safari 可以访问的目录,例如 ~/Library/Internet\ Plug-Ins 目录

PAC 文件及其调试

Update on 2012-04-29. 重构 PAC 文件,用数组保存所有需要用代理访问的域名,遍历数组来创建对象以加快查找。

Proxy auto-config (PAC) 文件可指定使用代理服务器的规则。比如在教育网内不能访问国外网站,我们可以通过 PAC 文件指定仅当访问某些国外网站时使用代理服务器,其他时候直接访问。

如果熟悉 PAC 文件,只对调试方法有兴趣,请跳到 调试 PAC 文件

PAC 文件用 JavaScript 编写,必须包含 FindProxyForURL(url, host) 函数。在访问某个网址时,浏览器会调用 FindProxyForURL 根据其返回值来决定该如何访问。该函数的说明如下:

  • 参数:
    • url 为访问的网址
    • host 为从 url 中推出的主机名,例如 url “http://www.reddit.com/r/programming/” 对应的 host 为 “www.reddit.com”
  • 返回值为字符串,告诉浏览器如何访问。下面是一些可用的返回值:
    • "DIRECT" 直接访问,不使用代理
    • "PROXY host:port 使用 HTTP 代理
    • "SOCKS host:port" 使用 SOCKS 代理,因为不支持 DNS 解析,不推荐
    • "SOCKS5 host:port" 使用 SOCKS5 代理
      • 注意,Safari 虽然支持 SOCKS5 代理,但是不支持在 PAC 文件中返回的 SOCKS5,只认 SOCKS
      • 一个 workaround 是返回多个代理服务器配置,把SOCKS5 放在最前面,接一个 SOCKS,这样支持 SOCKS5 返回值的浏览器可以正常使用,而 Safari 会忽略第一个 SOCKS5。例如 SOCKS5 127.0.0.1:1080; SOCKS 127.0.0.1:1080; DIRECT
      • 另一个办法是把 SOCKS 代理用polipo 转成 http 代理
    • 可以返回多个代理服务器,用分号分隔,浏览器会按顺序尝试。例如 "PROXY host:port; SOCKS5 host2:port2; DIRECT"

下面是一个 PAC 文件的例子(这个文件的目的应该很清楚),完整版本见 github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var direct = 'DIRECT';
var http_proxy = 'PROXY host:port; DIRECT';

var blocked_list = [
  "akamai.net",
  "akamaihd.net"
];

var blocked = {};
for (var i = 0; i < blocked_list.length; i += 1) {
  blocked[blocked_list[i]] = true;
}

function host2domain(host) {
  var dotpos = host.lastIndexOf(".");
  if (dotpos === -1)
    return host;
  // Find the second last dot
  dotpos = host.lastIndexOf(".", dotpos - 1);
  if (dotpos === -1)
    return host;
  return host.substring(dotpos + 1);
};

function FindProxyForURL(url, host) {
  return blocked[host2domain(host)] ? http_proxy : direct;
};

发布了收集的一些 Tips

把自己收集的关于 OS X, Linux 还有一些软件使用的 tips 用 Octopress 发布了出来。都是在用 Mac 以后才开始记录的,Spotlight 让我不至于因为不能迅速打开文件而懒得记录。

Tips 列表,直接浏览的话肯定挺无聊的,有些笔记内容非常少。这些笔记会不断的更新,也方便自己在没带笔记本的时候查看。

Octopress 默认的 CSS 对 list 的处理真难看,不过实在不擅长改网页相关的东西,等出更漂亮的主题以后再换吧。

SaaS Class Environment Set Up on OS X

Update

ruby-debug-base19 version 0.11.25 does not work with ruby 1.9.3-p0 on OS X. I found a workaround on WyeWorks Blog. Here’s my modified gist to install the working version with RVM managed ruby-1.9.3-p0


I’m taking the SaaS class. The class project can be done using the pre-created VirtualBox image or on Amazon EC2 virtual machine, but I want to do it directly on my Mac because its more convenient for me. Luckily, there is instructions on how to configure a bare VM.

Looking into the configure script used to configure the ubuntu VM shows that there is nothing special in the software used. It just needs some ruby gems, PostgreSQL, and Sphinx. (The script will also install and configure Vim, Emacs, but that’s not relevant to me.) Configuring the environment on OS X should be easy, if you are not using Xcode 4.3.

各种 Spinlock 实现的性能测试

之前自己写的程序遇到了伸缩性问题,怀疑是自己实现的锁在使用的核数增加时会产生严重的竞争,所以对这篇 Spinlocks and Read-Write Locks 里介绍的各种 spinlock 测试了下性能。(当然,根本的办法是避免在锁上竞争,比较 spinlock 实现的性能更多是出于好奇。)

各种 spinlock 的实现和测试程序都在 github 上。(除了cmpxchg 以外都是 copy 那篇文章里的。)为了使用方便,spinlock 所需要的原子指令等都在各自的头文件里。

首先要说明的是,下面的测试结果以及结论依赖于具体的 CPU。选择 spinlock 的实现的时候还是要自己测试一下

解决 Markdown 转 HTML 中文换行变空格的问题

Update on 2012-04-24: 看到肖之的 给中英文间加个空格 提到了 Jekyll 的 plugin 机制,因此改成用 plugin 机制避免更新 Jekyll 时需要重新 patch 的麻烦。


在 Vim 里编辑文件时我设置行宽不超过 80 个字符,因此编辑 Markdown 文件时一段话中会有多个换行。我用 Octopress 默认配置的 rdiscount 来生成 HTML 文件,这些换行都会保留在最后生成的 HTML 文件中。

浏览器对换行的处理是转成空格,这对英文来说很自然,但对中文就很讨厌了。我在 Stack Overflow 上提了这个问题,从这个详细的回答来看,这是一个历史遗留问题,短期内不会解决。

所以还是自己动手吧。我的解决方法是在生成 HTML 前把所有连续的中文行转成一行。(修改 Markdown 的实现也可以,但 rdiscount 用了 C 写的 Markdown 实现 discount,hack 起来比较麻烦。) 在 Ruby 1.9 中,连接中文行可以很方便的用正则表达式实现,注意开头的 #encoding: UTF-8 是必须的。

1
2
3
4
5
6
7
8
9
#encoding: UTF-8

class String
  han = '\p{Han}|[,。?;:‘’“”、!……()]'
  ChineseRegex = /(#{han}) *\n *(#{han})/m
  def join_chinese
    gsub(ChineseRegex, '\1\2')
  end
end

利用 Jekyll 的插件机制可以在它调用 markdown 转换工具前修改需要转换的文本。把下面的代码放在一个文件中,放在 plugins 目录下即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#encoding: UTF-8

require './plugins/post_filters'

class String
  han = '\p{Han}|[,。?;:‘’“”、!……()]'
  @@chinese_regex = /(#{han}) *\n *(#{han})/m
  def join_chinese!
    gsub!(@@chinese_regex, '\1\2')
  end
end

# Use Jekylly's plugin system to modify the content before invoking rdicount
module Jekyll
  class JoinChineseFilter < PostFilter
    def pre_render(post)
      post.content.join_chinese!
    end
  end
end

需要注意的是分类文章的 rss 需要特殊处理一下,我自己的博客分类做得不好,所以分类 rss 应该没什么价值,所以就懒得处理了。需要处理的可以参考肖之的文章。

下面是原来直接修改 Jekyll 的方法,不再推荐。

可以用来转换文件的代码在 gist 上,但我不希望把 Markdown 源文件变成有着巨长行的文本,所以我修改了 Jekyll,在调用 rdiscount 前对内容调用 join_chinese 方法。需要修改 lib/jekyll/converters/markdown.rb,patch 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
--- bak-markdown.rb 2011-12-23 20:14:24.000000000 +0800
+++ markdown.rb  2011-12-23 20:13:19.000000000 +0800
@@ -1,3 +1,15 @@
+#encoding: UTF-8
+
+class String
+  def join_chinese
+    unless @chinese_regex
+      han = '\p{Han}|[,。?;:‘’“”、!……()]'
+      @chinese_regex = Regexp.new("(#{han})\n(#{han})", Regexp::MULTILINE)
+    end
+    gsub(@chinese_regex, '\1\2')
+  end
+end
+
 module Jekyll

   class MarkdownConverter < Converter
@@ -115,7 +127,7 @@
             }).to_html
           end
         when 'rdiscount'
-          RDiscount.new(content, *@rdiscount_extensions).to_html
+          RDiscount.new(content.join_chinese, *@rdiscount_extensions).to_html
         when 'maruku'
           Maruku.new(content).to_html
       end

用 Markdown 来写这种类型的 blog 真是舒服多了。

AI Class 结束了

AI Class 结束了,昨天收到了 Statement of Accomplishment。这学期的很多周末都花在了做作业上,最后看到这个还是挺开心的。

AI Class Statement of Accomplishment

总体来说课程不难,但涉及的面很广。上课的一个很大的感受是,讲课好的老师可以帮你节省很多看书的时间,而且有些方法的思想只有在视频里才可以很好的展示。我买了 Artificial Intelligence A Modern Approach (AIMA) 这本书,在课程开始前按照课程涉及内容看了一些,速度比较慢。课程开始之后比较忙,一般只遇到不太明白的地方才看,明显感觉到比没上过课时看起来轻松很多。可惜的是本科和硕士阶段我很少遇到这样的老师,上课和自己看书的效果差不多。

课程涉及内容的一个不完整的列表如下,帮助我自己记录从这门课里学到哪些东西。有同学想看 AIMA 这本大部头又不知道如何选重点可以参考。