2Dec
11
2010
6

OSD Lyrics 0.3 发布

OSD Lyrics 是一个第三方歌词程序,它能为Linux下的多款播放器提供类似Windows下QQ音乐的歌词显示功能,并能自动从网络上下载歌词。

OSD Lyrics当前支持Amarok、Audacious、Banshee、Exaile、MOC 2.5、 Quod Libet、MPD、Rhythmbox、Songbird、XMMS2,可以从搜狗或千千上下载歌词。

自 OSD Lyrics 0.2 版发布以来,已经过了快半年了。在这半年里,OSD Lyrics 有了不小的进步。在新年到来之际,我认为积累的改动足以把版本号加上0.1了(简单来说,因为过年了,所以版本升级了:))。

Category: osd-lyrics | Tags: osd-lyrics
2Dec
7
2010
0

OSD Lyrics 添加Quod Libet支持

今天OSD Lyrics添加了第10个播放器支持——Quod Libet。Quod Libet的支持有些不完善的地方,主要是:

  • 不支持停止——因为Quod Libet里根本就没这个概念
  • 不支持跳转到指定时间——它的dbus接口没有提供这一选项。虽然现在用不到,不过很快就会有功能要用到它了
  • 不支持获取播放文件的地址——也就是说,不能根据音乐文件地址来查找歌词了

不过Quod Libet似乎要支持MPRIS,如果支持的版本出来了,那么就可以完美了

Category: osd-lyrics | Tags: osd-lyrics quod libet
2Dec
5
2010
0

使用Emacs daemon

什么是 Emacs daemon

关于Emacs有一个很著名的笑话,就是Emacs = Emacs Makes A Computer Slow。Emacs启用慢是一个人尽皆知的事实。由于启动时要加载大量的脚本和插件,使得Emacs在启动时往往需要数秒之久。因此我在进行一些快速简单的任务,如svn commit时,都是使用nano或者vim来进行的。

Emacs daemon就是为了这个而诞生的。它将Emacs变成了一个C/S模型——只需要启动一个服务器在后台作为守护进程(daemon)跑着,之后启动的每个emacs都是一个客户端,它连接上服务器进行工作。这样一来,只要在启动服务器时运行初始化脚本,客户端启动无需运行脚本,实现启动时间从Firefox到Chrome的转变。

使用 Emacs daemon

要使用Emacs daemon,至少需要Emacs 23。它提供了一个emacsclient程序,用于启动客户端。

有客户端就必然要有服务端。要启动一个daemon,需要在运行emacs时加入一个--daemon参数。幸运的是,我们不用手动启动服务端,而是可以利用emacsclient的-a参数。emacsclient的-a参数用于指定连接不上服务器时使用的别的编辑器(alternate editor),当把这项留空时,它会自动启动服务端。如果不想指定-a,也可以将ALTERNATE_EDITOR环境变量设为""。

除了-a参数,emacsclient还要手工指定使用终端还是X来启动。要从终端启动,需要使用-t参数:

 

emacsclient -t -a ""

 

从X启动则是-c:

 

emacsclient -c -a ""

 

方便些,再方便些

每次都要输入上面那些命令太烦了,不符合*nix的终极目标——偷懒

首先把终端版本设一个alias。编辑~/.bashrc,在最后加入

 

#用ec来快速启动emacs client
alias ec='emacsclient -t -a ""'
#现在可以将emacs设为默认编辑器啦:P
export EDITOR="ec"

 

以后在终端下输入ec就可以启动emacs的终端客户端了:)

在X下,我习惯用gnome-do来启动程序。因此最方便的方法就是建立一个菜单项,名叫Fastmacs,内容自然就是X模式启动emacsclient的命令啦

配置,还是配置

俗说说得好,.emacs说有多少就有多少+1。如果在.emacs里对X相关的选项(字体什么的)直接进行设置,那么会发现用emacsclient启动时,这些设置都失效了。这是因为这些设置是在X下的frame创建时才有效的,而启动服务器的时候是没有创建frame的。

解决方法有两种,一种是使用after-make-frame-functions这个hook,在创建一个frame之后才进行设置。代码如下

 

(defun frame-setting ()
    (set-frame-font "文泉驿等宽微米黑 8")
    (set-fontset-font "fontset-default"
		      'gb18030 '("文泉驿等宽微米黑" . "unicode-bmp")))

(if (and (fboundp 'daemonp) (daemonp))
    (add-hook 'after-make-frame-functions
	      (lambda (frame)
		(with-selected-frame frame
		  (frame-setting))))
  (frame-setting))

 

需要判断是否以daemon模式启动,分别进行处理

另一种方法是设置window-system-default-frame-alist,直接设置参数的默认值

 

(setq window-system-default-frame-alist
      '(
        ;; if frame created on x display
        (x
	 (menu-bar-lines . 1)
	 (tool-bar-lines . nil)
	 ;; mouse
	 (mouse-wheel-mode . 1)
	 (mouse-wheel-follow-mouse . t)
	 (mouse-avoidance-mode . 'exile)
	 ;; face
	 (font . "文泉驿等宽微米黑 8")
	 )
        ;; if on term
        (nil
	 (menu-bar-lines . 0) (tool-bar-lines . 0)
	 ;; (background-color . "black")
	 ;; (foreground-color . "white")
	 )
	)
      )

 

可以设置的参数见Emacs Lisp Reference > Frames > Frame Parameters

推荐使用第二种方法,启动客户端时直接就使用所设置的字体了

参考文章

http://www.wanglianghome.org/blog/2009/01/customization-tips-for-emacs-daemon.html

http://jackycxh.blog.35.cn/2009/07/22/emacs-daemon-and-font/

 

Category: emacs | Tags: emacs DAEMON
2Dec
2
2010
2

将CNNIC证书清出Chromium for Linux

update:发现还需要禁用entrust的证书,但是entrust被内建在chromium里了,有什么办法能禁用它呢……

这就是所谓的“老鼠过街,人人喊打”吧。

废话少说,进入正题。由于Chromium在Linux上使用的是系统的证书,所以要使用系统自带的工具来清除。

具体方法参考自http://code.google.com/p/chromium/wiki/LinuxCertManagement

安装工具

Ubuntu/Debian:

sudo apt-get install libnss3-tools

 Fedora:

su -c "yum install nss-tools"

Gentoo:

su -c "echo 'dev-libs/nss utils' >> /etc/portage/package.use && emerge dev-libs/nss"

Opensuse:

sudo zypper install mozilla-nss-tools

列出证书

certutil -d sql:$HOME/.pki/nssdb -L

在我机子上结果如下:

Certificate Nickname                                         Trust Attributes
                                                             SSL,S/MIME,JAR/XPI
…………………………
Starfield Secure Certification Authority #2                  ,,   
CNNIC SSL                                                    ,,   
VeriSign, Inc.                                               ,,   
……………………

删除证书

将CNNIC赶出系统:

certutil -d sql:$HOME/.pki/nssdb -D -n "CNNIC SSL"

完成后再列出证书,可以发现没有CNNIC的身影了。

重启Chromium,登录CNNIC认证的一个网站:www.enum.cn/en/,是不是已经不受信任了?

一些补充

  • 在Gentoo下,要将certutil命令改为nsscertutil
  • 此方法只是删除CNNIC证书,目前我并不清楚它会不会又回到系统中。如果有将其彻底屏蔽的方法,请告诉我
Category: linux | Tags: CNNIC chromium chrome
2Dec
1
2010
0

OSD Lyrics 0.2.20100201

忙了一个多星期,终于把这个版本给弄出来了

没有什么新功能增加,只是修复了一个严重的bug:下载歌词时,如果网络不好导致DNS超时,OSD Lyrics 100%会崩溃。

Bug原因很清楚,也很严重,之所以拖了那么久才解决,是因为修复这个bug要伤筋动骨一番。最后把歌词下载模块改成了多进程方式,这回应该可以根除它了。

另外根据zuolun同学的建议,提供文字轮廓宽度调节选项。

Category: osd-lyrics | Tags: osd-lyrics
1Dec
31
2010
0

回归多进程下载模型

用多进程下载模型可耻地失败之后,OSD Lyrics使用了多线程模型来对歌词进行下载。在完成之后发现每当DNS超时时,程序都会崩溃。经过不断地调试,确定是curl的问题。看了文档后发现,原来curl在多线程环境下,由于DNS查找超时是使用信号实现的,所以在多线程环境下是不安全的。当时想了两个方法:在自己的程序里包含curl,启用异步DNS查询cares;或者大幅修改程序,使用curl的异步查询接口重写下载引擎,把程序重新变成一个单进程、单进程的程序。第一种方法会让代码的大小和编译时间大幅增长,第二种方法要把两个下载引擎(搜狗和千千的)给大幅改写,过于复杂。于是一直纠结……

后来请教了 @henry_ 老师,一句话把我打回原点——用多进程才是王道。

进程vs线程

重新审视自己的设计和多进程模型失败的原因:因为在子进程中使用了X的操作。为什么会有X的操作呢?看看下载一个歌词的步骤:

  1. 搜索歌词,并将搜索结果返回
  2. 如果有多个搜索结果,弹出选择窗口
  3. 下载选中的歌词
  4. 下载成功,显示歌词

之所以会有X操作,是因为第2步弹出了一个窗口。由于子线程同样不能操作GUI元素,因此多线程模型同样要在主线程中处理弹出窗口。我在设计中将下载过程给模块化了,有一个专门的下载模块,将具体的下载引擎和主程序分开。线程化后的下载过程是这样的:

  1. 主程序向下载模块注册搜索和下载回调函数
  2. 主程序发起搜索请求
  3. 下载模块建立搜索线程,搜索线程调用下载引擎的搜索函数,返回后唤醒下载模块在主线程的回调函数
  4. 下载模块的回调函数调用所有注册了的搜索回调函数
  5. 主程序的搜索回调函数弹出选择窗口
  6. 下载模块建立下载线程,下载线程调用下载引擎的下载函数,返回后唤醒下载模块在主线程的回调函数
  7. 下载模块的回调函数调用所有注册了的下载回调函数
  8. 主线程的下载回调函数显示歌词

也就是说,只是将原来的第1步和第3步线程化了,而这两步是没有X操作的。而且由于将下载过程都放在下载模块里了,因此多线程部分完全是在下载模块里实现的,主程序和下载引擎一点也不知道多线程的存在。

既然如此,为何不讲它们进程化呢?把实现中的线程改成进程,就是多进程模型了。一旦这样做的话:

  • 子进程中没有X操作
  • 只需要修改下载模块,主程序和下载引擎不需要修改,工作量小,不易出错
  • 多进程不会再有信号的问题。更进一步来说,子进程你爱挂就挂吧,对父进程半点影响都没有,多健壮啊
  • 进程之间没有公共数据,无需同步,不怕数据修改
  • 我管你子进程怎么分配内存,反正一退出全回收了
  • ……………………

明白自己要什么

首先搞清楚自己需要怎样的多进程操作:

  • Unix的fork风格
  • 异步函数调用形式,在fork时指定回调函数,子进程退出后自动调用回调函数
  • 子进程能返回数据到回调函数中,不只是退出状态

简单来说,就是要一个刚好能替代现有线程操作的方案,而且它是和现在的线程操作类似的,这样使用起来直观,而且修改难度小。

然而我遇到了一个问题。

如何从子进程中返回数据

从子进程中返回数据的问题思考了很久,因为进程间通信不像线程间通信那么简单,直接传一个指针完事。@henry_ 老师给我指明了两条路:使用管道或共享内存。

就此思考了一番。管道需要将返回数据序列化,再通过管道传送,最后反序列化还原数据。共享内存不需要序列化,但是需要自己管理内存,而且不知道要开多大的共享内存,内存的分配与释放都要仔细考虑。

最后的决定是使用管道。虽然要实现序列化操作,但是过程简单清晰。如果使用共享内存,那么设计会变得很复杂,容易出错,而且内存相互分离的优点也没了。

具体设计

把一切都想清楚之后设计就很清晰了。

  • fork前打开管道
  • fork时指定回调函数
  • 子进程使用管道写入数据,然后退出
  • 父进程从管道读入数据,然后调用回调函数并将数据传入
  • fork模块只负责传递数据,不负责序列化操作

最后的设计只有一个函数:

pid_t ol_fork (OlForkCallback callback, void *userdata);

回调函数的定义如下:

typedef void (*OlForkCallback) (void *ret_data,                                                                                  
                                size_t ret_size,                                                                                 
                                int status,                                                                                      
                                void *userdata);

同时,为子进程提供了用于返回数据的文件描述符和文件流:

extern int ret_fd;
extern FILE *fret;

实现细节

剩下的最大问题就是如何在子进程退出时调用回调函数了。最直接的想法就是处理SIGCHLD信号。由于不同的子进程可能需要调用不同的回调函数,也有不同的用户数据,因此需要有一个子进程ID和回调函数的对应表。感谢GTK、GDK、GLib,现在只需要用g_child_watch_add就一切都搞定了。

关于序列化,本来是想用protobuf的,它的C绑定似乎还直接提供了RPC的支持。由于我只是简单的需要一个IPC的数据传递,干脆自己写一个来练手好了。

序列化的协议很简单。因为只有两种结构需要序列化——数组和结构体,所以就简单地定义了一下。对于结构体,将其字段按一定顺序输出,以换行符间隔。对于数组,先输出一行总数,再输出各元素,每个元素后再加上一个换行符。由于是程序内部用的IPC数据,因此只要单元测试通过了,数据的正确性就可以完全信赖,不需要复杂的容错机制。

一些弯路

一开始在考虑子进程返回的触发时,@henry_ 提示可以用select或者poll。实现了之后发现有两个问题,一是如果子进程的返回数据是分段输出的话,回调函数可能会调用几次,每次收到一段数据;二是如果子进程没有返回数据(也就是返回数据为空),那么回调函数根本就不会运行。因此还是老老实实采用子进程退出来触发回调函数。

开始的设计是向回调函数传递一个fd,让回调函数自己读取数据。这么做是为了能让回调函数利用fgets来读取一行。思考之后觉得这样很不自然,而且回调函数要考虑的东西反而多了,复杂了,于是改成了现在这个样子。

在实现序列化的时候没有想清楚,把不需要序列化的OlMusicInfo给序列化了,白做工了。

在反序列化结构时,一开始是返回true or false,后来发现一个问题,就是在反序列化数组时,不知道一个元素反序列化后,剩下未被反序列化的数据从哪开始。于是把接口改成了返回指向剩下数据开头的指针,如果失败就返回NULL。

结论

把fork模块和序列化做好之后,修改代码就很简单了,只需要把对应的地方改一下,程序很欢快地跑了起来。

使用多进程的设计,避免了修改curl使用方式的缺点,而且使得程序更健壮。由于Linux轻量级进程的优势,多进程和多线程的性能应该没有太大的差别,而且实际上下载也不是程序的瓶颈。

此次设计进行了仔细考虑,而没有像之前边做边迭代的方法。在明确自己的目标,排除多余的东西,对各方面作出权衡和折衷之后,得到了一个比较简洁的接口和实现,自己觉得还是很满意的。

实现的过程中对新的代码做了充足的单元测试,发现了一些问题,同时修正了设计,最后用起来自己也相当放心。在代码不是立刻可以整合到系统中的时候,单元测试往往是能立刻看到成果的方式,而且也能检验自己的设计是否合理。

从多进程到多线程,最后又回到多进程,貌似绕了个大弯又回到原点。但是这两个多进程的设计高度完全不一样。在实现了多线程机制后,对整个下载的过程和实现有了充分的了解,现在的多进程流程也是在多线程的设计中定下的。如果没有多线程模型这个探索,也就很难有现在这个设计。所谓螺旋式上升嘛,说不定以后又会改成多线程,但是可以肯定的一点是,一定会比现在的要更好。

1Dec
9
2010
1

OSD Lyrics 正式支持 MOC

我真是闲得蛋疼了,在后天就要考试的情况下还有工夫来做MOC支持。

之前所说,MOC目前我还没有找到IPC的方法来获取信息,只能用命令行来获取。

而fork+管道+mocp本身的开销实在不小,在0.1秒间隔的频率下对CPU的占用比其他的播放器要高不少。不过话虽如此,还是能用的。

另外,在aqtccgh同学的帮助下,终于定位了会导致之前可以搜索却不能下载的原因所在。现在应该没有那么多失败的情况了吧。

关于网络不好时崩溃的问题,是因为DNS解析超时加上多线程而造成的一个bug,要留到放假再解决了,在此提供一个临时解决方案:

 

su
echo 118.228.148.185 mp3.sogou.com >> /etc/hosts
echo 125.39.78.33 www.qianqian.com >> /etc/hosts

 

于是下一个目标:Quod Libet

 

Category: osd-lyrics | Tags: osd-lyrics
1Dec
9
2010
0

解决MOC的乱码问题

MOC(MOCP)是一个不错的命令行下播放器,但是不少人会遇到歌词的tag乱码问题。在网上给出了一大堆删MP3的tag的不靠谱的方案。在此特意记录下正确的方法,使用此方法可以保留MP3的tag并能正确显示。

MOC正常显示中文MP3的tag有两个条件:第一,tag是用UTF-8编码的;第二,不存在ID3 v1 tag(就是旧版的MP3 Tag信息)

首先需要python-mutagen工具,在debian/ubuntu下可以用下面命令来安装:

sudo apt-get install python-mutagen

如果你的MP3文件在其他播放器下也显示乱码,那么需要首先转成UTF-8

mid3v2 -e GBK 你的MP3文件

如果要批量转换,可以用如下命令

find [音乐目录] -iname "*.mp3" -exec mid3v2 -e GBK {} \;

然后删除ID3 v1 tag

find [音乐目录] -iname '*.mp3' -exec mid3iconv --remove-v1 {} \;

Now, enjoy you MOC

Category: linux | Tags: 乱码 moc mocp
1Dec
7
2010
0

一个类型提升引发的bug

今天OSD Lyrics收到了一个issue,说是在歌手不存在的情况下xmms2搜索歌词成功,下载歌词失败。之前在Exaile下也有类似的bug,不过这个bug的问题不同(应该说Exaile下也还是会有这个问题)。

OSD Lyrics对歌词文件名的查找是基于模板的,模板中有一些占位符,例如%t代表歌名,%p代表歌手。有一个函数ol_path_get_lrc_pathname,负责给定路径模板和文件名模板以及歌曲信息,输出根据模板转换后的完整文件路径。成功时返回输出的路径长度,失败则返回-1。

但是在程序日志中发现,当歌手为空时,文件名模板“%p-%t”应该是失败的,然而却依然被当作成功来使用。这样一来,匹配的结果就成了只有目录,缺少文件名的路径,把歌词下载的目标地址设成本机的一个目录,那肯定是会失败的嘛。

把ol_path_get_lrc_pathname相关的函数里外查了一遍,没有任何问题,出错时也的确返回了-1,于是目光落到调用这个函数的地方:

if ((ol_path_get_lrc_pathname (
     path_list[i], name_list[j], info, 
     file_name, MAX_PATH_LEN)) >= 0)
{
  /* do something */
}

发现把>=0改成!=-1就正常了。

为什么会这样?原因就在于ol_path_get_lrc_pathname的返回类型是size_t,而size_t在我的系统里的定义是unsigned int。任意一个unsigned类型都不会小于0。

那么为什么改成!=-1后,反而又可以了呢?任意一个unsigned类似应该都不会等于-1呀。

这就涉及到C语言的类型提升问题了。在C语言中,那个操作数的类型不同时,会把操作数都转化成两个操作数中能表示的范围最大的类型。就表示的类型来说,shot<=int<=long<float<double,这个是没有任何问题的。问题在于,当出现了unsigned后,怎样计算。具体来说,两个操作数是unsigned int 和 int时,如何转化?

如果是unsigned short int和short int就好办了,直接转成int就行。然而很可惜,在32位机子里,int = long,把unsigned int和int转成long不能解决任何问题。

怎么办呢?很简单,两个都转成unsigned int。

在这个例子中,两个规则是一样的,因为返回的-1变成了size_t后,变成了最大的unsigned long,所以-1也跟着被转成了unsigned long,变成了0xFFFFFFFF,两个刚好又相等了:)

作为一个例子,请看下面一段代码:

#include <stdio.h>

int main()
{
        unsigned long a = 0;
        long b = -1;
        printf ("%s\n", a > b ? "Greater" : "Less");
        return 0;
}

会输出什么呢?gcc 4.4.1给出的答案是Less,因为-1被转型了:(

 

 

 

Category: 程序设计 | Tags: c osd-lyrics
1Dec
6
2010
5

XMMS2支持终于到位

各位使用XMMS2的同学不用再寻问会不会支持了。

这两天搞定了XMMS2支持,同时搞定的还有关于Exaile的一个bug。如果使用Exaile有问题的话可以试着更新到SVN最新版。

不使用XMMS2的可以在configure时用--disable-xmms2屏蔽。

需要依赖xmms-client库(还好不需要xmms-client-glib),这个库在Ubuntu下的包名叫libxmmsclient-dev,Gentoo官方软件仓库没有xmms2,在gentoo-taiwan的overlay里有,安装xmms2后自动有这个包。另外这里也介绍了Gentoo下的另外两个xmms2 overlay,不过我没有测试。希望其他系统的同学能告诉我这个库对应的包名。

计划等到完成MOC支持再发布新版本,现在只能下载源代码或用SVN版本来编译。

=======================碎碎念的分割线=======================

之前在查阅MPRIS的资料的时候,发现它就是在XMMS2的wiki上的,于是一直以为,XMMS2是支持MPRIS的。然而各方面的证据无情地打破了我的乐观猜想。可以说,如果XMMS2支持MPRIS的话,它将是和Audacious、Amarok 2同一批被支持的播放器。

之后对于XMMS2这种C/S架构的播放器没有什么概念,第一次尝试使用可耻地失败,导致之后一直不愿去碰它。后来MPD也是因为不了解而没有去尝试。

直到之后看到一篇介绍MPD的文章,才对它有兴趣起来。之前挣扎了很久的依赖项问题也通过编译参数来解决了。libmpd很好用,半天就完成了对MPD的支持。

完成之后又想到了XMMS2,同样作为C/S架构的XMMS2的文档应该也差不到哪去。在官网上转了一圈之后收集到了不少资料,让我兴奋的是xmms-client的例子比libmpd还丰富。于是不禁在twitter上夸了夸XMMS2的文档化。

然而做起来了才发现xmms-client的设计和文档化实在是不如人意。xmms-client的设计不像xmms-client一样完全屏蔽了网络的操作概念,每个函数的返回值都是一个类似GValue的通用指针。说实话我对用C实现通用类型一直没什么好感,虽然对这个设计来说是必要的。且不论xmms-client这种设计的优劣,毕竟通用性高的结果必然会造成方便性降低,取决于设计者的权衡。但是让我不满的是,xmms-client的文档里,没有一个函数说明它返回的真正类型是什么,让人无法使用。好吧我可以猜测xmmsc_playback_playtime返回的是一个int,但是我如何可以猜到xmmsc_medialib_get_info返回的东西是什么?就算例子里有如何从里面得到title和artist,我又凭什么会猜测到音轨号是叫tracknr而不是track-number?xmms-client这样的文档简直是在对别人说“去看我们的例子,如果没有,那就去看代码吧”。好在有google codesearch,让我可以从前人的成果里找到这些函数的用法。另一个问题就是xmms-client居然没有一个可以简单判断连接有没有失效的方法,差点逼着我用上xmms-client-glib,而且这家伙还一点文档都没有。

抱怨了那么多,说点好的方面,XMMS2终于是又一个支持毫秒级的播放器了,内牛满面啊。我有个梦想,终有一天,能让我的elapse emulator彻底消失……

另外说说Exaile,出错的是一个十分……呃,怎么说呢……囧的地方。在获取歌曲信息的时候,如果其中某一项没有值,会返回一个错误而不是NULL或者空字符串。这里有点违反直觉,我之前是在出错时直接就判断为出错了(播放器可能关掉了嘛),结果导致Exaile在播放无歌手信息的文件时OSD Lyrics会重新检测播放器而不是搜索歌词。这个应该算是我测试不足导致的吧。

Category: osd-lyrics | Tags: osd-lyrics

© is-Programmer.com All rights reserved. | Power by Chito 1.1.4 | Theme: Aeros 2.0 by TheBuckmaker.com