通过隐藏状态栏来最大化你的 NetBeans 可视空间

2012年10月19日 由 admin 22 条评论 »

前言

好久没更新博客了,平时太忙!周末则太懒!从这周开始,公司有了一些变化,让我有一些空闲时间来做一些喜欢做的事情,其中之一就是多多更新一下博客,别白白浪费了这么好的服务器资源 :-)

正题

作为一个键盘手,敲键盘的时候的时候我喜欢把其他无关的东西都关掉,这样能让我注意力达到最大的集中。

现在最喜欢的开发环境是 NetBeans (支持Java、Scala、 C/C++、Erlang、PHP、Ruby…简直无所不能啊,Eclipse 是强大,但实在是用不惯),使用的时候我会开全屏,然而对我来说全屏还是不够,因为 NetBeans 全屏后还是有一些用处不大的部件是会一直显示的,没错,我说的就是今天的主角:状态栏。这状态栏用处不大也就算了,还会时不时地弹出一些影响我注意力的东西来烦我,如图。

NetBeans的状态栏

今天这篇博客呢,就是要分享一下怎么去掉这个没太大用处的状态栏。我用的是 NetBeans for Mac,但是其他平台的 NetBeans 应该也可以用类似的方法来做,但我没环境,没做实验。

步骤如下

  • 用文本编辑器打开 /Applications/NetBeans/NetBeans\ 7.0.1.app/Contents/Resources/NetBeans/etc/netbeans.conf
  • 找变量 netbeansdefaultoptions
  • 在变量里加上 “-J-Dnetbeans.winsys.statusLine.in.menuBar=true”

改完后是这样:

# ${HOME} will be replaced by JVM user.home system property netbeans_default_userdir=”${HOME}/.netbeans/7.0″ # Options used by NetBeans launcher by default, can be overridden by explicit # command line switches: netbeans_default_options=”-J-Dnetbeans.winsys.statusLine.in.menuBar=true -J-Dcom.sun.mysql.startcommand=/usr/local/mysql/support-files/mysql-admin.server …”

保存后重新打开 NetBeans,状态栏已然消失了,现在全屏感觉更爽了 :-)

隐藏了状态栏的NetBeans

[HOWTO]如何让字典排在 Spotlight 搜索结果前面

2012年5月31日 由 admin 16 条评论 »

问题

Spotlight 是我最喜欢的 Mac OS X 特性之一,对我而言,最常用的用途是:

  • 启动程序(好用,早就放弃 QuickSilverQSB 了)
  • 查询字典(方便、快速、好用)
  • 快速计算(是的你没看错,试试在搜索框里输入 sin(1)^2+cos(1)^2 看看)
  • 搜索文档(是不是有点本末倒置了。。。)

其中查询字典是我第二常用功能,在 Snow Leopard 和 Mac OS X Leopard 中,字典查询结果都是排在前面的,用起来非常方便,但是升级到了 Lion 后,发现字典查询结果居然给排到最后面去了,还没办法调整,WTF?! 这是 Lion 中最让我不爽的地方!Google 了一把,嗯,很高兴看到不少人也在被这个问题折磨:

Google得到的结果是:到目前为止,无解。

真的只能这么忍受下去了吗??答案是:不!

今天我决心开始寻找解决办法,花了我不少时间,还好最终老天不负有心人,最终找到了方法。

寻找过程中的各种细节就不多说,只套用我党的一句话:道路是曲折的,成果是辉煌的!

解决方案

下面详细描述我找到的解决方案。

先说明一下,要使用这个方法自己修改,你首选需要安装几个G大小的 Xcode… (WTF?!, again.)。原因是需要使用 Xcode 里的 plist 编辑器来编辑一个 plist 文件(觉悟吧,骚年。放开手里的 Vim 神器吧!普通文本编辑器是不行的,这是一个二进制 plist 文件)。当然,你也可以直接下载我修改的好的 plist 文件

解决步骤

  • 使用 plist 编辑器打开 ~/Library/Preferences/com.apple.spotlight.plist
  • 在 orderedItems 下添加一个 Dictionary 类型的条目,值为:
    enabled: YES (boolean 类型), name: “MENU_DEFINITION” (string 类型)

    编辑plist文件

  • 保存 com.apple.spotlight.plist

  • 打开 系统设置 -> Spotlight,你将会看到一个空白的条目,按照你的需求,把这个条目拖放到合适的位置。我把它放在了第二位。

    系统设置

  • DONE!!!

成果展示

网易有才评论围观[3](少儿不宜,未成年人慎入)

2011年2月28日 由 翻墙砖家 没有评论 »

喜欢网易新闻,不仅是它和性浪等国内其他门户相比更关注民生、言论尺度大、有态度,更重要的是网易上集聚了很多有才的网友,每每在不经意间发表出一些让人眼前一亮的评论来,所以很多人戏称说在网易上的网友评论比新闻本身更有价值。。。

比如这个:

网易江苏省苏州市网友 ip:221.224..2011-02-28 17:26:17 发表

网易新疆网友 [闲来扯蛋] 的原贴:1

手中无套也操劳, 管它鱼龙或假冒。 一泄千里享快感, 人生处处有良宵。 --扯蛋群之塞外老狼

网易黑龙江省齐齐哈尔市网友 [chengbaohai1] 的原贴:2

人生处处有良宵 宠洋媚外为哪糟 此物本是中华造 只要坚挺魂照销 —-扯蛋群之天下狼族

网易浙江省台州市网友 [无花] 的原贴:3

做好措施防中招? 这等小技不用瞧, 徽宗当年没用TAO。 师师照样把魂销。

网易新疆网友 [闲来扯蛋] 的原贴:4

师师照样把魂销, 前俯后仰扭蛮腰。 却把此物枕下放, 巫山云雨不觉晓。

网易黑龙江省齐齐哈尔市网友 [chengbaohai1] 的原贴:5

巫山云雨不觉晓 套套质量可得好 一不小心中了招 命根可能保不了

网易河南省网友(115.54.*.*)的原贴:6

命根可能保不了, 防止性病不能少, 深入战斗趣味妙, 身体也要保证好

网易广东省网友(183.57.*.*)的原贴:7

你们厉害啊,淫一手好湿不难,最难得的是淫了一被好的好湿

网易广东省手机网友(183.57.*.*)的原贴:8

身体也要保证好 一晚三个精确数 逢年过节欢兴时 一把套套献宝宝

网易山东省手机网友 [网易山东省手机网友] 的原贴:9

一把套套献宝宝, 日13还得趁年少; 三更勃起五更倒, 秋日弟妹冬日嫂。

网易辽宁省大连市网友 [十指如歌] 的原贴:10

秋日弟妹冬日嫂, 你这彪子么得了; 如果这样干下去, 精尽人亡鸡巴小。

网易山东省网友 [名字没啥用] 的原贴:11

精尽人亡鸡巴小, 禁忌荒淫无度好, 提放一时良宵欢, 身强体健最重要。

网易广东省广州市网友 [mantou101] 的原贴:12

身强体健最重要 进出时间控制好 魂消九天直求饶 每日不停处成机

网易广东省东莞市网友 [劉波] 的原贴:13

丢你个老母,都他-娘-的是人才!老子只有羡慕的份!!!

网易广西南宁市手机网友 [hqs_sl18] 的原贴:14

都是淫才啊

网易山东省手机网友 [网易山东省手机网友] 的原贴:15

没日不停处成机 保重身体才重要 年少不知精子贵 老来望比空流泪

没日不停处成机 保重身体才重要 年少不知精子贵 老来望比空流泪

原始新闻链接:杰士邦安全套被控冒充外国品牌遭索赔

[转][绝对实用]一个成功的Git分支模型

2011年1月5日 由 翻墙砖家 4 条评论 »

本文译自 http://nvie.com/posts/a-successful-git-branching-model (by Vincent Driessen)

本文中我会展示一种开发模型,一年前该模型就已经被我用在所有的项目中(包括工作中的项目和私有项目),结果是非常成功的。我早就想为此写点东西,可直到现在才有时间。本文不会讲述任何项目的细节,只会涉及到分支策略和发布管理。

git branching model

本文使用Git作为所有源码的版本控制工具。

为什么是Git?

要全面了解Git与其它集中式版本控制系统相比的优劣,可以参考这个页面。这方面的争论可谓是硝烟弥漫。作为一个开发者,所有这些工具中我最钟情于Git。Git的的确确改变了人们考虑合并及分支的方式。在我之前所处的经典CVS/Subversion世界中,合并/分支总是被认为是有点可怕的事情(”小心合并冲突,丫会恶心到你”),因此你只应偶尔干这种事情。

但有了Git,这类事情就变得非常简单,分支及合并甚至被认为是你日常版本控制操作的核心之一。例如,在CVS/Subversion的书中,分支及合并往往在后面的章节才被介绍(针对高级用户),但在每一本Git的书中,该内容已经在前3章中介绍(基础)。

简单及易重复性带来的好处就是,分支及合并变得不再可怕。版本控制工具本该帮助我们方便的进行和分支及合并操作。

简单介绍下工具后,让我们来看开发模型。我讲介绍的模型本质上只是一组步骤,每个团队成员都必须遵循这些步骤以形成一个可靠管理的软件开发过程。

去中心化但仍保持中心化

在这个分支模型中我们使用的,且被证实工作得很好的仓库配置,其核心是一个中心”真理”仓库。注意只有该仓库才被认为是中心库(由于Git是DVCS [分布式版本控制系统],在技术层面没有中心库这一东西)。之后我们用origin指代该仓库,因为大多数Git用户都熟悉这个名称。

每个开发者都对origin做push和pull操作。不过除了这种中心化的push-pull关系外,每个开发者还可以从其他开发者或者小组处pull变更。例如,可能两个或更多的开发者一起开发一个大的特性,在往origin永久性的push工作代码之前,他们之间可以执行一些去中心化的操作。在上图中,分别有Alice和Bob、Alice和David、Clair和David这些小组。

从技术上来说,这仅仅是Alice定义一个Git remote,名字为bob,指向Bob的仓库,反过来也一样。

主要分支

git branching model 3

此开发模型的核心主要受现有的模型启发。中心仓库包含了两个主要分支,这两个分支的寿命是无限的:

  • master
  • develop

每个Git用于都应该熟悉origin上的master分支。与master分支平行存在的,是另外一个名为develop的分支。

我们认为origin/develop分支上的HEAD源码反映了开发过程中最新的提交变更。有人会称之为”集成分支”。该分支是自动化每日构建的代码源。

当develop分支上的源码到达一个稳定的状态时,就可以发布版本。所有develop上的变更都应该以某种方式合并回master分支,并且使用发布版本号打上标签。稍后我们会讨论具体操作细节。

因此,每次有变化被合并到master分支时,根据定义这就是一次新的产品版本发布。我们趋向于严格遵守该规范,所以理论上来说,每次master有提交时,我们都可以使用一个Git钩子(hook)脚本来自动构建并部署软件至产品环境服务器。

支持性分支

紧接着主要分支master和develop,我们的开发模型使用多种支持性分支来帮助团队成员间实现并行开发、追踪产品特性、准备产品版本发布、以及快速修复产品问题。与主要分支不同的是,这些分支的寿命是有限的,它们最终都会被删除。

我们会用到的分支有这几类:

  • 特性分支(feature branch)
  • 发布分支(release branch)
  • 热补丁分支(hotfix branch)

上述每种分支都有特定的用途,它们各自关于源自什么分支、合并回什么分支,都有严格的规定。稍后我们逐个进行介绍。

从技术角度来说,这些分支一点都不”特殊”。分支按照我们对其的使用方式进行分类。技术角度它们都一样是平常的Git分支。

特性分支

git branching model 4

可能的分支来源:develop 必须合并回:develop

分支命令约定:任何除master, develop, release-, 或 hotfix-以外的名称

特性分支(有时也被称作topic分支)是用来为下一发布版本开发新特性。当开始开发一个特性的时候,该特性会成为哪个发布版本的一部分,往往还不知道。特性分支的重点是,只要特性还在开发,该分支就会一直存在,不过它最终会被合并回develop分支(将该特性加入到发布版本中),或者被丢弃(如果试验的结果令人失望)。

特性分支往往只存在于开发者的仓库中,而不会出现在origin。

创建一个特性分支

开始开发新特性的时候,从develop分支创建特性分支。

$ git checkout -b myfeature develop
Switch to a new branch "myfeature"
合并完成的特性回develop

完成的特性应该被合并回develop分支以将特性加入到下一个发布版本中: $ git checkout develop Switch to branch ‘develop’ $ git merge -no-ff myfeature Updating ea1b82a..05e9557 (Summary of changes) $ git branch -d myfeature Deleted branch myfeature (was 05e9557). $ git push origin develop

上述代码中的-no-ff标记会使合并永远创建一个新的commit对象,即使该合并能以fast-forward的方式进行。这么做可以避免丢失特性分支存在的历史信息,同时也能清晰的展现一组commit一起构成一个特性。比较下面的图:

git branching model 5

在第2张图中,已经无法一眼从Git历史中看到哪些commit对象构成了一个特性——你需要阅读日志以获得该信息。在这种情况下,回退(revert)整个特性(一组commit)就会比较麻烦,而如果使用了-no-diff就会简单很多。

是的,这么做会造成一些(空的)commit对象,但这么做是利大于弊的。

可惜的是,我没能找到方法让-no-diff成为默认的git merge行为参数,但其实应该这么做。

发布分支

可能的分支来源:develop 必须合并回:develop和master 分支命名约定:release-*

发布分支为准备新的产品版本发布做支持。它允许你在最后时刻检查所有的细节。此外,它还允许你修复小bug以及准备版本发布的元数据(例如版本号,构建日期等等)。在发布分支做这些事情之后,develop分支就会显得比较干净,也方便为下一大版本发布接受特性。

从develop分支创建发布分支的时间通常是develop分支(差不多)能反映新版本所期望状态的时候。至少说,这是时候版本发布所计划的特性都已经合并回了develop分支。而未来其它版本发布计划的特性则不应该合并,它们必须等到当前的版本分支创建好之后才能合并。

正是在发布分支创建的时候,对应的版本发布才获得一个版本号——不能更早。在该时刻之前,develop分支反映的是”下一版本”的相关变更,但不知道这”下一版本”到底会成为0.3还是1.0,直到发布分支被创建。版本号是在发布分支创建时,基于项目版本号规则确定的。

创建一个发布分支

发布分支从develop分支创建。例如,假设1.1.5是当前的产品版本,同时我们即将发布下个版本。develop分支的状态已经是准备好”下一版本”发布了,我们也决定下个版本是1.2(而不是1.1.6或者2.0)。因此我们创建发布分支,并且为其赋予一个能体现新版本号的名称:

$ git checkout -b releases-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully. version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed. 1 insertions(+). 1 deletions(-)

创建新分支并转到该分支之后,我们设定版本号。这里的bump-version.sh是一个虚构的shell脚本,它修改一些本地工作区的文件以体现新的版本号。(当然这也可以手动完成——这里只是说要有一些文件变更)接着,提交新版本号。

新的发布分支可能存在一段时间,直到该版本明确对外交付。这段时间内,该分支上可能会有一些bug的修复(而不是在develop分支上)。在该分支上添加新特性是严格禁止的。新特性必须合并到develop分支,然后等待下一个版本发布。

结束一个特性分支

当特性分支达到一个可以正式发布的状态时,我们就需要执行一些操作。首先,将发布分支合并至master(记住,我们之前定义master分支上的每一个commit都对应一个新版本)。接着,master分支上的commit必须被打上标签(tag),以方便将来寻找历史版本。最后,发布分支上的变更需要合并回develop,这样将来的版本也能包含相关的bug修复。

前两步在Git中的操作:

$ git checkout master
Switched to branch 'master'
$ git merge -no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2

现在版本发布完成了,而且为未来的查阅提供了标签。

提醒: 你可能同时也会想要用 -s 或者 -u <key> 来对标签进行签名。

为了能保留发布分支上的变更,我们还需要将分支合并回develop。在Git中:

$ git checkout develop
Switched to branch 'develop'
$ git merge -no-ff release-1.2
Merge made by recursive.
(Summary of changes)

这一操作可能会导致合并冲突(可能性还很大,因为我们改变了版本号)。如果发现,则修复之并提交。

现在工作才算真正完成了,最后一步是删除发布分支,因为我们已不再需要它:

$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

热补丁分支

git branching model 6

可能的分支来源:master 必须合并回:develop和master 分支命名约定:hotfix-*

热补丁分支和发布分支十分类似,它的目的也是发布一个新的产品版本,尽管是不在计划中的版本发布。当产品版本发现未预期的问题的时候,就需要理解着手处理,这个时候就要用到热补丁分支。当产品版本的重大bug需要立即解决的时候,我们从对应版本的标签创建出一个热补丁分支。

使用热补丁分支的主要作用是(develop分支上的)团队成员可以继续工作,而另外的人可以在热补丁分支上进行快速的产品bug修复。

创建一个热补丁分支

热补丁分支从master分支创建。例如,假设1.2是当前正在被使用的产品版本,由于一个严重的bug,产品引起了很多问题。同时,develop分支还处于不稳定状态,无法发布新的版本。这时我们可以创建一个热补丁分支,并在该分支上修复问题:

$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1";
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1";
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

不要忘了在创建热补丁分之后设定一个新的版本号!

然后,修复bug并使用一个或者多个单独的commit提交。

$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)
结束一个热补丁分支

修复完成后,热补丁分支需要合并回master,但同时它还需要被合并回develop,这样相关的修复代码才会同时被包含在下个版本中。这与我们完成发布分支很类似。

首先,更新master分支并打上标签。

$ git checkout master
Switched to branch 'master'
$ git merge -no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1

提醒: 你可能同时也会想要用 -s 或者 -u <key> 来对标签进行签名。

接着,将修复代码合并到develop:

$ git checkout develop
Switched to branch 'develop'
$ git merge -no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

这里还有个例外情况,如果这个时候有发布分支存在,热补丁分支的变更则应该合并至发布分支,而不是develop。将热补丁合并到发布分支,也意味着当发布分支结束的时候,变更最终会被合并到develop。(如果develop上的开发工作急需热补丁并无法等待发布分支完成,这时你也已经可以安全地将热补丁合并到develop分支。)

最后,删除临时的热补丁分支:

$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

小结

虽然这个分支模型中没有什么特别新鲜的东西,但本文起始处的”全景图”事实上在我们的项目中起到了非常大的作用。它帮助建立了优雅的,易理解的概念模型,使得团队成员能够快速建立并理解一个公用的分支和发布过程。

我同时也提供了一个该图对应的高质量PDF版本。你可以打印出来并挂在墙上,随时参考。

转载自:http://www.juvenxu.com/2010/11/28/a-successful-git-branching-model/

虾米网的一个可以随意修改其他用户密码的高危漏洞

2011年1月4日 由 翻墙砖家 10 条评论 »

我现在很喜欢在虾米网上听歌(只可惜在线试听的只是96kbps的,真小气),还喜欢让浏览器记住密码,然后每次就不用自己输入用户名密码了,很方便,方便到我会忘记密码 :-( 。于是有一天,我清理了浏览器 cookies,结果当登陆的时候就悲剧了,密码忘了。。。还好虾米网有重置密码的功能,前提是你还没忘记注册邮箱地址。

于是很自然的,我就去重置密码了。当我收到虾米网发到我邮箱的重置密码链接的时候,我震惊了 :-) ,虾米网发给我的信息是这样的:

Hi,翻墙砖家
请点击下面的链接重新设置你的虾米密码:

http://www.xiami.com/member/resetpassword?reset_id=72037

注意:此地址只在一星期内生效!

点击链接进去后不需要其他任何信息就可以修改密码。我马上意识到这里面可能潜在一个严重的bug,为了验证我的想法,我马上又到虾米网连续做了两次找回密码操作,虾米网发给我的两条信息是这样的:

Hi,翻墙砖家
请点击下面的链接重新设置你的虾米密码:

http://www.xiami.com/member/resetpassword?reset_id=72040

注意:此地址只在一星期内生效!

Hi,翻墙砖家
请点击下面的链接重新设置你的虾米密码:

http://www.xiami.com/member/resetpassword?reset_id=72042

注意:此地址只在一星期内生效!

这验证了我的想法:

虾米网在重置密码表中使用一个自増列来生成/保存 reset_id,而且重置密码的时候没有做其他任何安全检查

这直接导致了一个非常严重的漏洞:只要我知道某一个用户的注册邮箱(坑蒙拐骗+威逼利诱…不会太难得到的),我就可以用他(或她,或者它?)的注册邮箱来进行找回密码操作,虾米网会给这个邮箱发送一个重置密码链接,上面的 reset_id 是什么我们不知道,但是可以根据虾米网当前的 reset_id 的值猜测出这个这重置密码链接上的 reset_id 所在范围。

比如,我先用自己的邮箱进行一次密码找回操作,得到虾米网发送的 reset_id(我们暂称这个 reset_id 为 id1),然后马上在使用目标用户的邮箱进行一次密码找回操作,虾米网也会发送一个 reset_id 给目标用户的邮箱(我们称这个 reset_id 为 id2),那么,这两次密码找回操作中虾米网生成的 reset_id 不会相差太远(如果在凌晨等用户不活跃的时刻进行,那么很可能 id2 = id1 + 1 ),明白了吧?

根据猜测到的 reset_id,自己构造一个重置密码链接,用浏览器打开,嘿嘿,就可以直接修改目标用户的密码啦!

经朋友同意,我在我朋友的帐号上进行了测试并获得成功,我只知道他的注册邮箱,就成功的修改了他的帐号密码。

PS:

  • 在用户活跃高峰期,可能两次生成的 reset_id 相差会比较大,这时候可以适当的加大猜测范围,多试几次就可以了,就是会误伤到某个也在找回密码的未知用户,人家的密码莫名其妙的就被你改掉了 :-)
  • 此漏洞在本文章发表之前,我已经通知虾米网,虾米网也已经修复这个 bug。现在虾米网的找回密码链接是这样的:

    http://www.xiami.com/member/resetpassword?reset_id=f/dEEGCE4jo1O******

    貌似没什么大问题了。

  • 虾米网还真是小气,这么大一个 bug,居然就给我留个言说“感谢您提交的bug”,连个感谢信都没 :-( 要是哥缺德一点儿直接公布出去了,哼哼~~~~

例行公事:原创文章,转载请保留原文链接。

RESTful style url in CodeIgniter

2011年1月3日 由 翻墙砖家 没有评论 »

我一直很喜欢 Rails 风格的 url 样式,现在我在使用 CodeIgniter 框架开发东西,也想使用 Rails 那样的 url 样式。为了避免再一次发明轮子,我网上搜了下,发现很多人也有这个需求,而且也有了好几个相应的实现()。但是,这些实现要么配置麻烦,要么代码量太多改动太大,和 CodeIgniter 的 Write Less, Do More 的口号有点不协调。

所以,我只好自己发明一个轮子啦 :-)

“发明”完毕后,和其他实现对比了下,我自认我的实现比现有的我所能找到的实现都更简洁、逻辑清晰、容易配置、好用。不是我自夸,我自己用着觉得确实好用,觉得是个好东西(媳妇是别人的好,孩子是自己的好?:-) ),所以发出来给大家分享分享,欢迎大家指教。

先看一下怎么使用这个轮子:

routes.php 配置文件后面添加以下内容:

// 在 application/config/routes.php 后面(或合适位置)加上这一行
map_resources('users');

///////////////////////////////////////////////////////////////////////////////
// 如果你不需要自定义额外的 url 规则,请无视下面的代码。。。
///////////////////////////////////////////////////////////////////////////////

// custom action handles GET request
map_resources('GET', 'users/(:id)/custom_action', 'users/custom_action/$1'); 

// some other custom action mappings...
// custom action handles PUT request
//map_resources('PUT', 'users/(:id)/custom_action', 'sync_sessions/custom_action/$1');

// custom action handles all requests (GET, POST, PUT, DELETE)
//map_resources('users/(:id)/custom_action', 'sync_sessions/custom_action/$1');

// ...

然后 controller 里就可以像 Rails 一样啦(除了 new, 因为 php 语言的限制,没办法 ):

<?php
class Users extends Controller
{

    function Users()
    {
        parent::Controller();   
    }

    // GET /users
    function index()
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users\n";
    }

    // GET /users/12345
    function show($id)
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}\n";
    }

    // GET /users/new
    // I have to use new_form as method name because "new" is a keyword in php
    function new_form()
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/new\n";
    }

    // GET /users/12345/edit
    function edit($id)
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}/edit\n";
    }

    // POST /users
    function create()
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users\n";
    }

    // PUT /users/12345
    function update($id)
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}\n";
    }

    // DELETE /users/12345
    function delete($id)
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}\n";
    }

    // GET /users/12345/custom_action
    function custom_action($id)
    {
        echo __METHOD__ . "<br>\n" . $_SERVER['REQUEST_METHOD'] . " /users/{$id}/custom_action\n";
    }
}

看到这里如果你感兴趣的话,可以继续往下看怎么实现了。其实非常简单,只有一个 MY_Router.php 文件,不到 100 行的代码(不计算空行和注释的话)。安装也很简单:只需要把这个 MY_Router.php 放到 application/libraries 目录下就万事大吉了。 代码逻辑我就不一一解释了,里面有注释,逻辑也简单,相信每个人都能看懂。

<?php
/**
 * RESTful-CodeIgniter
 *
 * A RESTful url router library for CodeIgniter
 *
 * @author      ZHENJiNG LiANG (liangzhenjing_N_O_S_P_A_M@gmail.com)
 * @license     BSD license
 * @link        http://liang.eu/web-dev/php/restful-style-url-in-codeigniter
 */

///////////////////////////////////////////////////////////////////////////////
// NOTE: 
// This Library added two macros:
// :id as [^/]+ in order to match non-numeric id field,
// :uuid to match UUID (aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
//
// :any and :num remain the same
// See code below for details
// You can change at will
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////
// map_resources()                              --> Return all routes
// map_resources($controller_name)              --> Map default restful routes
// map_resources($method, $pattern, $replace)   --> Add a custom route
///////////////////////////////////////////////////////////////////////////
function map_resources()
{
    static $routes = array();

    $arg_num = func_num_args();
    if(0 == $arg_num)
    {
        return $routes;
    }
    elseif(1 == $arg_num)
    {
        // map_resources($controller_name)   --> return all routes
        $controller = func_get_arg(0);

        // Generate RESTful url match patterns
        $routes['GET']["{$controller}"]             = "{$controller}/index";
        $routes['GET']["{$controller}/new"]         = "{$controller}/new_form";
        $routes['GET']["{$controller}/(:id)"]       = "{$controller}/show/$1";
        $routes['GET']["{$controller}/(:id)/edit"]  = "{$controller}/edit/$1";

        $routes['POST']["{$controller}"]            = "{$controller}/create";
        $routes['PUT' ]["{$controller}/(:id)"]      = "{$controller}/update/$1";
        $routes['DELETE']["{$controller}/(:id)"]    = "{$controller}/delete/$1";
    }
    elseif(2 == $arg_num)
    {
        // Custom url routing
        $args    = func_get_args();
        $pattern = $args[0];
        $replace = $args[1];

        $routes['GET'][$pattern]    = $replace;
        $routes['POST'][$pattern]   = $replace;
        $routes['PUT'][$pattern]    = $replace;
        $routes['POST'][$pattern]   = $replace;
    }
    elseif(3 == $arg_num)
    {
        // Custom url routing
        $args    = func_get_args();
        $method  = $args[0];
        $pattern = $args[1];
        $replace = $args[2];

        $routes[$method][$pattern] = $replace;
    }
}

///////////////////////////////////////////////////////////////////////////
// RESTful style url router
///////////////////////////////////////////////////////////////////////////
class MY_Router extends CI_Router
{
    function MY_Router()
    {
        // HACK: support overridding REQUEST_METHOD by posting a _method parameter
        if($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['_method']))
        {
            $_SERVER['REQUEST_METHOD'] = strtoupper($_POST['_method']);
        }
        elseif(isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']))
        {
            $_SERVER['REQUEST_METHOD'] = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
        }

        parent::CI_Router();
    }

    // Overrides CI_Router::_parse_routes. 
    // RESTful style url matching
    function _parse_routes()
    {
        $rest_routes = map_resources();

        // we do this for performence
        if (empty($rest_routes) && count($this->routes) == 1)
        {
            $this->_set_request($this->uri->segments);
            return;
        }

        // also this...
        $uri = implode('/', $this->uri->segments);
        if (isset($this->routes[$uri]))
        {
            $this->_set_request(explode('/', $this->routes[$uri]));
            return;
        }

        // RESTful url matching...
        $request_method = $_SERVER['REQUEST_METHOD'];
        $routes = (isset($rest_routes[$request_method]) && $rest_routes[$request_method]) ? $rest_routes[$request_method] : array();
        foreach($routes as $pattern => $replace)
        {
            $pattern = str_replace(':id',   '[^/]+',  $pattern); // use this to match non-numeric id field
            $pattern = str_replace(':any',  '.+',     $pattern);
            $pattern = str_replace(':num',  '[0-9]+', $pattern);
            $pattern = str_replace(':uuid', '[a-zA-Z0-9]{8}(-[a-zA-Z0-9]{4}){3}-[a-zA-Z0-9]{12}', $pattern);

            // Does the RegEx match?
            if (preg_match("#^{$pattern}$#", $uri))
            {
                // Do we have a back-reference?
                if (strpos($replace, '$') !== FALSE && strpos($pattern, '(') !== FALSE)
                {
                    $replace = preg_replace("#^{$pattern}$#", $replace, $uri);
                }

                // we are done
                $this->_set_request(explode('/', $replace));
                return;
            }
        }

        // if non of the rules match, then go on...
        parent::_parse_routes();
    }
}

这就是所有的代码,我相信足够简洁、易用了。 测试程序我也提供了,在 github 上的restful-codeigniter 项目里有完整代码,使用 Git clone 一份下来就可以进行测试了。

如果发现什么不好的地方或者bug,或者有更好的实现方法,非常欢迎探讨指教。

共产主义好:朝鲜国宝级校花 个个背景不简单(组图)

2011年1月2日 由 翻墙砖家 2 条评论 »

共产主义真的是一个好东西,人民当家作主,百姓安居乐业,公仆们都自称尽职尽责。

原文链接:朝鲜国宝级校花 个个背景不简单(组图)

在这颗明珠里还有一象牙塔,里面全是百里挑一的美女、才女。她们外界是完全隔离的,唯一任务的是给高官们演出。

以我小人之心不负责地推测,其实“演出”两字可以任意换成其他动词的。。。。

绝无刹车痕迹,为了钱,这样伤天害理的事情都做得出来,太可怕了!

2010年12月31日 由 翻墙砖家 没有评论 »

绝对没有刹车迹象!上帝啊!!!

起吧,谁来个头?

[HOW TO] complie intl extension for php 5.3.2 under Mac OS X 10.6

2010年12月29日 由 翻墙砖家 2 条评论 »

近期我在研究 Symfony2,这是一个全新的、个人认为很有前途的 php 框架。和 Symfony 1.x 相比基本是推倒重来的一个版本,官方号称这是这是最快的 php 框架之一。具体可以自己上官方网站看看。

然而 Symfony2 要求 php 5.3.2+ 以上版本,还需要 php intl 库,而 Mac OS X 10.6 默认自带的 php 没有 intl 扩展,我只好自己安装。php 扩展嘛,最方便的安装方法莫过于用 pecl 安装了,然而:

$ sudo pecl install intl
downloading intl-1.1.2.tgz ...
...
/usr/temp/intl/collator/collator_class.c:92: error: duplicate ‘static’
/usr/temp/intl/collator/collator_class.c:96: error: duplicate ‘static’
/usr/temp/intl/collator/collator_class.c:101: error: duplicate ‘static’
/usr/temp/intl/collator/collator_class.c:107: error: duplicate ‘static’
make: *** [collator/collator_class.lo] Error 1
ERROR: `make' failed

Google 了一番,没什么结果,好多人在问这个问题,但就是没有一个的解决方法。

那就自力更生吧,经过一番“研究”,发现原来是 php 5.3 的使用了新的内存回收方式,zval 结构体也和 php 5.2 不一样了,一些宏也改变了,ZEND_BEGIN_ARG_INFO_EX 这个宏在 php 5.2 是这样的:

#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \
    zend_arg_info name[] = { \
        { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },

在 php 5.3 下是这样的:

#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args)   \
    static const zend_arg_info name[] = {                                                                       \
        { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },

而 intl 1.1.2 中很多地方都用到了这个宏,比如上面出错的 collator/collator_class.c 文件:

static
ZEND_BEGIN_ARG_INFO_EX( collator_0_args, 0, 0, 0 )
ZEND_END_ARG_INFO()

看见没有,php 5.3 中 ZEND_BEGIN_ARG_INFO_EX 的定义中已经那个包含了一个 static 关键字,而 intl 1.1.2 中用到这个宏的地方还是按照 php 5.2 的方式使用,这个宏经过预处理后,导致出现 static 关键字重复,编译出错。 知道了问题原因,就试着修改下吧。由于 intl 1.1.2 库中是用这个宏的地方很多,不好一个一个该,我就用了一个邪恶的方法,在 intl_common.h 中重新定义这个宏:

// HACK!!!
#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args)   \
    const zend_arg_info name[] = {                                                                      \
        { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },

保存再编译,这次上面的那个错误没有了,但出现了别的错误:

/Users/liang/apps/intl-1.1.2/resourcebundle/resourcebundle_class.c:215: error:‘zval’ has no member named ‘refcount’;
make: *** [resourcebundle/resourcebundle_class.lo] Error 1

这个就是上面说的 zval 结构体的改变了。php 5.3 使用了新的 GC 方式,zval 结构体中的 refcount 成员改名为 refcount__gc,is_ref 改名为 is_ref__gc,而且也有配套的管理这个新成员的宏。经过研究,intl 1.1.2 中只有 3 个地方使用 php 5.2 的方式直接访问了 refcount 成员:

$ grep -r refcount *
msgformat/msgformat_format.c:#define Z_ADDREF_P(z) ((z)->refcount++)
msgformat/msgformat_format.c:       /* TODO: needs refcount increase here? */
resourcebundle/resourcebundle_class.c:  retval->refcount--;
resourcebundle/resourcebundle_iterator.c:   object->refcount--;
resourcebundle/resourcebundle_iterator.c:   object->refcount++;

其中 msgformat/msgformat_format.c 文件已经和 php 5.3 兼容了,所以只需要修把后面的三个地方修改为 php 5.3 的方式,那这个错误应该就修改好了。 修改方法很简单,分别打开上面的几个 resourcebundle/resourcebundle_class.c 文件和 resourcebundle/resourcebundle_iterator.c 文件,按照下面的方式修改:

retval->refcount--;     => Z_DELREF_P(retval);
bject->refcount--;      => Z_DELREF_P(object);
object->refcount++;     => Z_ADDREF_P(object);

完成后重新编译,OH YEAH~~~~能通过编译了。不过别高兴得太早,可能还有问题呢。先修改一下 php.ini 中的 intl 配置为:

[intl]
intl.default_locale = zh-CN

然后运行一下测试自带的测试用例,发现有很多测试用例没通过。。。 :-(

$ make test
=====================================================================
TEST RESULT SUMMARY
---------------------------------------------------------------------
Exts skipped    :    0
Exts tested     :   55
---------------------------------------------------------------------
Number of tests :   78                78
Tests skipped   :    0 (  0.0%) --------
Tests warned    :    0 (  0.0%) (  0.0%)
Tests failed    :   17 ( 21.8%) ( 21.8%)
Expected fail   :    0 (  0.0%) (  0.0%)
Tests passed    :   61 ( 78.2%) ( 78.2%)
---------------------------------------------------------------------
Time taken      :   10 seconds
=====================================================================

=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
get_locale() [tests/collator_get_locale.phpt]
collator_get_sort_key() [tests/collator_get_sort_key.phpt]
datefmt_get_pattern_code and datefmt_set_pattern_code() [tests/dateformat_get_set_pattern.phpt]
datefmt_localtime_code() [tests/dateformat_localtime.phpt]
datefmt_parse_code() [tests/dateformat_parse.phpt]
datefmt_parse_localtime() with parse pos [tests/dateformat_parse_localtime_parsepos.phpt]
datefmt_parse_timestamp_code()  with parse pos [tests/dateformat_parse_timestamp_parsepos.phpt]
datefmt_set_timezone_id_code() [tests/dateformat_set_timezone_id.phpt]
numfmt_format() [tests/formatter_format.phpt]
numfmt_format_currency() [tests/formatter_format_currency.phpt]
grapheme() [tests/grapheme.phpt]
locale_get_display_name() [tests/locale_get_display_name.phpt]
locale_get_display_region() [tests/locale_get_display_region.phpt]
locale_get_display_script() [tests/locale_get_display_script.phpt]
locale_get_display_variant() [tests/locale_get_display_variant.phpt]
locale_get_region() [tests/locale_get_region.phpt]
locale_parse_locale() [tests/locale_parse_locale.phpt]
=====================================================================

因为这些测试用例本身也是 php 程序,我就自己在命令行下运行了一遍检查为什么会导致测试失败。发现大部分输出都是正确的,但有一些情况下输出和测试用例中的断言不匹配。为什么会这样呢?我没有深入研究,也不知道为什么。哪位朋友如果熟悉这方面请赐教。

我最后的结论: 我使用了这个自己编译的版本用了一段时间,没发现有什么不正常的地方,所以这种 patch 方式应该是可行的。但是,由于存在部分测试用例没有通过,可能会隐藏着一些意想不到的bug,所以不建议在生产环境下使用。

懒人有福了,patch 后的版本我放到了 github 上,大家可以直接 clone 一份下来编译,不用自己手动修改代码了,链接在此: https://github.com/liangzhenjing/php53-intl

虾米不做咱自己做:自制虾米网 “Mac版客户端”

2010年12月21日 由 翻墙砖家 5 条评论 »

虾米网是一个非常不错的音乐网站,有一个非常齐全的音乐库,基本上我能想到的歌曲全都有,而且都是完整的专辑,而且,而且貌似都是正版的,非常的不错。它的音乐品味推荐也是一个不错的功能,能根据我现有的喜好给我推荐新的歌曲,非常的 NICE。

我这段时间一直在上面听歌,不过我不太喜欢使用浏览器来听歌,因为我需要频繁地打开关闭浏览器窗口,于是有时候一不小心就 Commond+W 顺手关掉播放器窗口了,很不爽,特别是听某一首很劲爆的歌曲听得正 HI 的时候。。。。

于是我就去找虾米网的客户端,但虾米网只有一个叫做 Shark 的桌面版客户端,For Windows 版本。Mac 版嘛。。。。你见过国内哪家公司有推出 Mac 版软件的么(除了 10 美分的 QQ),我就不奢望了。。。

我又尝试了使用 CrossOver 来运行 Shark,没戏~~~~ Windows Only 简直就是兲朝挨踢业的猪蹄思想 :-(

只好想别的办法。正好前段时间用过一个叫做 Fluid 的程序,能为一个网站生成一个专用的本地客户端,挺有意思,虽然也仅仅是一个内嵌的浏览器,但使用起来感觉比直接使用浏览器要好很多,也可能仅仅是我的心理作用,呵呵。

经过一番折腾和研究,我发现 Fluid 还真很适合用来做这种“伪客户端”一类的东西,而且使用效果也很不错:

  1. 驻留后台,不会干扰其他浏览器的使用
  2. 有系统栏菜单,可以快速打开播放器(见下图右上角的虾米图标)
  3. 如果你是键盘控,它还可以使用全局快捷键来激活
  4. 支持自定义 userscript,如果你是 javascript 高手,估计会觉得这东西有用

先截一个效果图先,这是播放器页面:

自制虾米网客户端

» 阅读更多: 虾米不做咱自己做:自制虾米网 “Mac版客户端”