loading …

Archive

Archive for the ‘技术杂项’ Category

Arch Linux:文泉驿正黑字体在升级后崩坏

一月 23rd, 2010 edikud 2 Comments/210 hits

      最近在自己的Arch Linux上运行过pacman -Syu升级软件包的同学要注意了,如果你们打开FireFox发现字体变得发虚(变成了点阵宋体),说明在升级文泉驿正黑字体的时候,配置被往非常规的方向改动了。

      如果确实是这个原因,那么/etc/fonts/conf.d/文件夹里面会多出一个66-wqy-zenhei-sharp.conf。打开这个文件,有这么几句:

<edit name=”antialias” mode=”assign”><bool>false</bool></edit>
<edit name=”embeddedbitmap” mode=”assign”><bool> true </bool></edit>
<edit name=”hinting” mode=”assign”><bool>false</bool></edit>

      看到那两个被我highlight出来的值没有?它们的顺序应该是反过来的,因为升级之前的文泉驿正黑默认是关闭内嵌点阵(embeddedbitmap)字体的(参考资料)。所以我们需要改动一下,将antialias那一项的值改为true,之后将embeddedbitmap那一项的值改成false,如下所示:

<edit name=”antialias” mode=”assign”><bool>true</bool></edit>
<edit name=”embeddedbitmap” mode=”assign”><bool> false </bool></edit>
<edit name=”hinting” mode=”assign”><bool>false</bool></edit>

      这样就能让可爱的矢量字体回来啦!(参考引用

收到O’REILLY寄来的礼物

十二月 21st, 2009 edikud 3 Comments/303 hits

      今晚回宿舍的时候在楼下被楼管叫住,说有我的信件,拿到手一看,竟然是O'REILLY寄来的。里面是一本颇精美的记事本^_^,放图:

091221A004

      现在才相信O'REILLY的书后面都有的那张小明信片不是用来骗小孩的,确实可以寄回去换礼物,我今晚收到的这份礼物应该得益于《AI for Game Developer》那本书后面的明信片(我在半年前抱着试试看的心态将其寄了回去)。

      最近对O'REILLY出版的书都特别有爱,特别是在实习的时候经常翻阅的那本《Perl语言入门(第四版)》的复印本(没办法,该版书已绝版,我只能从图书馆借出来复印)。呵呵,话说今年购买或复印的O'REILLY图书也有5、6本了,而每本书的内容质量至少都能算是中上水平。O'REILLY这家出版商,就如它对自身的简介那样,具有浓厚的计算机专业背景,也因此形成了一个非常不同于其他出版商的出版方针——紧密地与计算机业界联系,出版市场上真正需要的计算机图书。它的编辑团队成员都是程序员或顶尖级技术专家出身,同时具有许多由相关领域的技术专家、咨询专家组成的固定作者群体。而阅读O'REILLY出版的图书确实有一种“直入主题”的感觉,能找到自己真正需要的信息,协助解决自己所关注的问题。

    现在顺便推荐几本O'REILLY出版的书:

  1. 刚刚提到的《Perl语言入门(第四版)》,也就是俗称的“小骆驼书”,该版的中文版由O'REILLY Taiwan公司翻译的,翻译水平相当的赞,内容更是经典,除了可以用它来快速学习Perl语言之外(同时记得自己多动手去实践),也可以作为一本Perl的小字典,如果忘了Perl的一些知识点可以用来速查。(PS:现在该书的第五版中文版可以在卓越网买到,但换了译者,我没读过,不好评论)

     

  2. 《集体智慧编程》 ,这是一本实战性较强的书籍,它通过将一些机器学习与人工智能的理论知识(公式)结合实际的例子生动地转化为一行行python代码,将智能分析的实际应用在读者面前展开,几乎称得上“手把手教你玩机器学习”。机器学习、模式分类等理论作为Web2.0的智能基石之一,它让长尾理论渗透于互联网的各个角落,Amazon豆瓣就是很经典的例子。这本书就是这方面应用的绝佳入门书(因为它确实比《模式分类》这种类教科书有趣多了)。

     

  3. 《代码之美》,这本书就不用我多说了,里面的大部分代码都很经典,皆出于大牛之手。它重复了一个观点:漂亮的代码真的可以让人窒息,写代码也是一种艺术!

     

  4. 《学习vi编辑器(第六版)》,vi作为Unix下面的标配文本编辑器,它有多重要我就不用说了。这本书可以推荐给vi或vim的初学者,毕竟这个强大的编辑器的使用方式确实太诡异了。(该书已绝版,华工北校区图书馆有得借,如果E文不错,也可以选择购买《学习vi和vim编辑器(影印版)》

     

  5. 《sed与awk(第二版)》,sed与awk这两个工具之于那些在Unix/Linux的Shell命令行下工作的系统工程师,就如同计算器之于会计,所以这本书是推荐给有志于从事Unix/Linux系统工程师这个行当的同学滴。(该书与前一本书一样杯具地绝版了,华工北校区图书馆有得借)

     

在Arch Linux上使用fcitx输入法

十二月 7th, 2009 edikud 0 Comments/516 hits

      在Arch Linux上我选择fcitx作为我的中文输入法,因为fcitx对Kdemod的兼容性较好。安装fcitx很简单,pacman一下就可以了:

      $sudo pacman –S fcitx

      之后在~/.kdemod4/Autostart中加入一个脚本,例如叫openfcitx, 里面只需要一行命令:

      fcitx &

      之后赋予该脚本可执行权限:

       $sudo chmod a+x openfcitx

      这样系统在进入桌面后就会自动在后台启动fcitx输入法。但是fcitx还无法在需要输入中文时被激活,因为没有设置系统变量将X input method指定为fcitx,于是要在/etc/profile文件中加入这几行:

      export XIM=fcitx
      export XMODIFIERS=@im=fcitx
      export GTK_IM_MODULE=xim
      export QT_IM_MODULE=xim
      export XIM_PROGRAM=fcitx

      这样fcitx就可以通过Ctrl+Space激活与切换输入法了。

      好好享受fcitx吧!^_^

解决由slim引导的kdemod无法启动声音服务的问题

十二月 4th, 2009 edikud 0 Comments/216 hits

      自从安装了Arch linux之后,一直都是通过在rc.conf的DAEMONS中添加kdm服务来引导kdemod桌面环境。但是后来觉得使用slim会更轻巧,就尝试着用slim来引导,结果进入桌面都会无法启动声音服务。

      用alsaconfig配置过也没有用,依然是连声音控制图标都没有,只会浮出一个报错信息说声音设备无法工作。这个现象应该不会是声卡驱动或者alsa的问题,因为使用kdm引导进入桌面都不会有问题。应该是配置出了问题。

     后来在网上找了一些资料,说是kdemod不需要预先启动alsa服务,于是在rc.conf中将alsa在DAEMONS中注释掉。但依然还是没能打开声音设备。最后是在archlinux的wiki中找到了答案,要将进入桌面的用户加入到audio用户组中:sudo gpasswd –a username audio,这样进入桌面后声音设备就正常工作了。

     现在来分析一下这个问题:在使用kdm引导的时候,由于kdm本身就是在通过init启动的,所以应该是以root权限启动kdemod桌面环境,那么要启动声音服务就轻而易举啦~但是在通过slim启动桌面的时候,slim是需要用户登录之后,在用户的目录中执行.xinitrc来启动指定的桌面环境,这样的话,就是用户本身在启动kedmod,如果用户没有被加入audio用户组中,那么就没有权限启动声音设备。这是我这个小菜鸟对该问题的推断,请大家指教哈!

      在这里也建议大家在安装配置archlinux的时候尽量参考Arch_新手安装指南_(简体中文),里面建议将将storage、audio、video、optical和wheel加入到你的用户组中──特别是在你打算使用一个完整功能的桌面环境的情况下。

改进eDIKUD制作的iNove主题Category页面Title

十一月 29th, 2009 edikud 2 Comments/233 hits

      只从将blog升级到Micolog0.6后就一直在使用eDIKUD同学制作的iNove主题,但是在使用的时候发现这个主题的category页面没有将category的名称显示在title处。

      浏览了一下该页面的模板,会发现这一行代码:

      {% block title %} {{entry.title}} - {{blog.title}} {% endblock %}

      也就是说,当GAE在组合页面的时候,会将entry.title的值填入到页面的标题中,然而category页面并不是用来显示一篇文章的,entry实体也就没有传入到模板的渲染中,在这个页面的标题应该是现实category的名称才对,eDIKUD同学应该是一时疏忽没能注意到这一点。

      要改进也很简单,只需要把前面的那句模板代码改成:

      {% block title %} {{category.name}} - {{blog.title}} {% endblock %}

      这样category页面的标题就会显示为”category名称 - blog名称”这样的格式了。

      同时也再次感谢eDIKUD同学为我们制作了这么精美主题!(该主题下载地址

Perl通过Telnet远程执行命令

十一月 3rd, 2009 edikud 0 Comments/488 hits

      Perl脚本在远程的机器上执行命令的方法有几种,例如直接通过system函数调用rsh、通过NET::SSH:Perl模块通过ssh登录远程机器来执行或者是通过NET::Telnet模块通过telnet登录到远程机器上执行。笔者比较喜欢Telnet登录这种方法,因为它比rsh强大,却又足够灵活,既能达到SSH的使用水平,同时又足够轻巧(只需要一个模块:Telnet.pm就够了)。

      NET::Telnet模块的详细使用指南可以参考该模块的CPAN官方文档。我们在这里只简单地结合代码来介绍其使用方法。

01 use Net::Telnet;
02
03 my $t = new Net::Telnet(Timeout => 30,Prompt => '/[\$#>]/', Errmode => "return");
04 $t -> max_buffer_length("10485760");
05
06 my $ok = $t->open("192.168.1.12");
07
08 unless($ok eq 1){
09          my $errmsg = $t->errmsg();
10          print STDERR "$errmsg\n";
11 }
12
13 $ok = $t->login($username,$passwd);
14
15 unless($ok eq 1){
16          my $errmsg = $t->errmsg();
17          print STDERR "$errmsg\n";
18 }
19
20 my @temp = $t->cmd(String => "PS1=\"@@@\"",Prompt => '/@@@/'); #change the Prompt temporality
21 $t->buffer_empty;
22
23 while(my $cmd=<>){         #Exec each cmd in the cmds list
24         my @results = $t->cmd(String => $2, Prompt  => '/@@@/');
25         print $results[-1];
26         $t->buffer_empty;
27 }

 

     现在我们来对以上的代码进行解释,其中第3行就是建立一个telnet对象,其构造参数用于指定给对象所建立的连接将用到的参数,在这里我们指定了超时时间Timeout、错误模式Errmode,以及远程机器上的命令提示符Prompt。Timeout指的是Telnet登录或者执行命令的超时时限,我们这里赋予了30,也就是说如果远程机器在30秒内没有回显出命令提示符,telnet的连接就等于超时断开;Errmode有两种值:die和return,默认是die,如果选择的是die,则该对象所执行的方法出了错误,整个perl脚本就die掉并会把错误信息输出到标准输出中,如果选择的是return,则在出错的时候会直接返回一个undefined的标量值或一个空向量,并将错误信息放在对象中,可以通过对象的errmsg()方法来获取;Prompt这个参数很重要,因为NET::Telnet模块是通过命令提示符来判断登录成功与否以及命令是否执行结束,通过正则表达式来设定,我们这里设定为 "/[\$#>]/”,也就是说远程机器的命令提示符包含了’$’、’#’、’>’这三种字符之一,如果设定的命令提示符不与该正则表达式match,那么在登录或者执行命令的时候,会因为等不到命令提示符而超时。

      在第4行,我们更改了这个Telnet对象的缓冲区,单位是K字节,该缓冲区是用来存放远程机器上的输出结果的,这里的10485760也就是10MB(绝对够用的啦)。

      第6行,通过传入远程机器的IP地址调用Telnet对象的open方法来连接远程机器,如果连接成功,则返回值等于1。在第13行,通过用户名和密码登录到远程机器的系统中。

     在第20行,我们通过Telnet对象的cmd方法将要在远程机器上运行的命令传递到远程机器上面去。这里我们通过向远程机器传递PS1=“@@@”这条命令临时地改变了远程机器的命令提示符,之所以这样做,是因为Telnet模块是根据命令提示符来判断传上去的命令是否执行完毕的,如果我们传上去的命令中包含了可以与Telnet对象的Prompt参数match的字符串,那么cmd方法会直接返回(好囧啊)。于是乎,我们在这里临时改变了其命令提示符为@@@(应该不会有一个变态的命令会用到这种字符串吧)。由于命令提示符改变了,因此我们在cmd方法中还要传入Prompt参数@@@,否则下场就如同Telnet对象的Prompt属性没设对…。

     第21行,清空Telnet对象的缓冲区,否则有可能出现命令输出错位,这个读者可以自己试一下是怎样的效果。

      23行至27行,就是重复地读取命令,再将命令放到远程机器上执行,之后将结果输出。记得执行这些命令的时候要使用新的命令提示符作为分割点哦,也不要忘了在获取返回结果之后清空缓冲区哈。

Perl中处理“跨多天”时间的一点小技巧

九月 14th, 2009 edikud 0 Comments/141 hits

      这段时间在用perl写一个处理log的脚本,需要在文本中截取出以特定格式描述的时间点,之后根据时间点判断是否在某个duration内。

      由于时间的记数周期不一,例如月份的周期,所以,如果要按照年月日时分秒这样来算,是颇痛苦的,我们可以直接将其做一个转化,转成纪元秒,也就是UNIX世界中的一个统一个时间单位,之后再以秒为单位进行计算,最后再转回人类所使用的格式。

      在这里,我们先读取到以文本描述的时间,提取各个时间单位,从秒到年,存入变量中,之后转化为纪元秒(epoch second):

1 $begin_epoch = timelocal(0,
2                          $begin_mm,
3                          $begin_hh,
4                          $begin_day,
5                          $begin_month-1,
6                          $begin_year);

 

      在这里,将秒、分、时、天、月、年几个参数传入timelocal这个函数,该函数会返回相应的纪元秒。值得注意的是,参数中的月份是从0~11,这是和C函数库一致的,而$begin_month这个变量依然是用1~12月来表示,所以这里要减去1。timelocal这个函数在Time::Local这个模块里面。

      还有一个地方需要注意:

      1. 如果年份参数大于999,该参数就直接表示年份;

      2. 如果参数的大小介于100~999之间,就代表是1900年的一个offset,也就是说112指向的是2012年;

      3. 如果参数值介于0~99,就比较奇妙了,其英文描述是:“interpreted as shorthand for years in the rolling ‘current century,’ defined as 50 years on either side of the current year.”,在1999年,这个参数如果是0,代表的就是2000年,这个参数是55,就代表1955年,但是在2019年的时候,55代表2055年。

     接下来可以对这个纪元秒进行处理,例如,加上D天,H个小时,M分钟可以用这样来表达:

     $end_epoch = $begin_epoch+$D*24*3600+$H*3600+$M*60;

 

     这样的话,$end_epoch就代表了D天H个小时M分钟后的纪元秒,可以将其分解为人类常用的表示格式。用的就是localtime这个函数了,这个是perl的标准库的函数,不需要额外引入模块。用法也很简单:

     @end_time = localtime($begin_epoch);

 

     那么在end_time这个列表里面,就按顺序就包含了秒,分,时,日,月,年,星期几,年中的第几天,通过下面的代码可以形象的描述:

     ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

 

     要注意的就是,这里的月份范围依然是0~11。

使用Guice玩依赖注入

四月 21st, 2009 edikud 1 Comments/120 hits

        Guice是Google的一个轻量级的依赖注入框架,使用这个框架可以很容易的实现模块间的依赖注入,有效提高模块之间的解耦和程度,使模块之间的交互更灵活、更容易维护。

      下面先来看一下Guice的运行机制:

        guice

      然后通过一个非常简单的例子来说明Guice的使用:

      首先,建立一个接口,这个接口的实现就是需要被注入的服务:

1 public interface Im {
2      public void say(String s);
3      public boolean connect();
4      public boolean disconnect();
5 }

      然后,我们编写来个实现该接口的具体实现:

实现1,ImImpl:

01 public class ImImpl implements Im {
02     @Override
03     public boolean connect() {
04           System.out.println("ImImpl connected!");
05           return true;
06     }  
07    
08     @Override
09     public boolean disconnect() {
10           System.out.println("ImImpl disconnected!");
11           return true;
12     }
13       
14     @Override
15     public void say(String s) {
16           System.out.println("ImImpl say:"+s);
17     }  
18 }

 

实现2,ImMock:

01 public class ImMock implements Im {
02   
03     @Override
04     public boolean connect() {
05         System.out.println("ImMock connected!");
06         return true;
07     }
08
09     @Override
10     public boolean disconnect() {
11         System.out.println("ImMock disconnected!");
12         return true;
13     }
14  
15     @Override
16     public void say(String s) {
17         System.out.println("ImMock say:"+s);
18     } 
19 }

 

      接下来,我们编写一个依赖于Im接口的类,MyIm:

01 import com.google.inject.Inject;
02 import edu.scut.emos.im.Im;
03  
04 public class MyIm{   
05
06     Im impl;
07     
08     @Inject
09     public MyIm(Im impl){
10         this.impl = impl;
11     }
12
13     public void doit(String detail){
14         impl.connect();
15         impl.say(detail);
16         impl.disconnect();
17     }
18     
19 }

 

      在第8行处,我们使用了@Inject注解,该注解用来告诉Guice框架,这里是一个注入点。

      接着,我们要编写一个配置模块,用来配置被注入的具体服务以及注入的目标:

01 import com.google.inject.Binder;
02 import com.google.inject.Module;
03 import com.google.inject.Scopes;
04 import edu.scut.emos.im.Im;
05 import edu.scut.emos.im.ImImpl;
06 import edu.scut.emos.im.ImMock;
07
08 public class MyModule implements Module {
09
10     Class<? extends Im> theClass = ImImpl.class;
11     
12     public void setInstance(int i){
13         if(i>0){
14              theClass = ImImpl.class;
15         }else{
16              theClass = ImMock.class;
17         }
18     } 
19
20     @Override
21     public void configure(Binder binder) {
22         binder.bind(Im.class).to(theClass).in(Scopes.SINGLETON);      
23     }
24    
25 }

 

      该配置模块实现了Guice的Module接口,实现它的cofigure,根据之前给出的时序图,我们知道Guice就是通过这个方法来实现注入的,所以能通过重写这个方法来绑定输入的具体服务和注入点。第22行代码详细描述了这个情况:将Im接口(Im.class)和具体的实现(theClass)绑定,范围指定是Scopes.SINGLETON,也就是说,每一次注入都是使用最初生成的那个服务实体对象,而不用另外再新建一个实体对象来注入,除非我们改变了被注入的服务。

        最后来一个演示程序:

01 import com.google.inject.Guice;
02 import com.google.inject.Injector;
03
04 public class Main {
05     public static void main(String[] args) {
06         MyModule module = new MyModule();
07         module.setInstance(1);
08         Injector inj = Guice.createInjector(module);
09         MyIm myIm = inj.getInstance(MyIm.class);
10         myIm.doit("test");
11        
12         module.setInstance(0);
13         inj = Guice.createInjector(module);
14         myIm = inj.getInstance(MyIm.class);
15         myIm.doit("test");
16     }
17 }

        程序的执行结果就是:

ImImpl connected!
ImImpl say:test
ImImpl disconnected!
ImMock connected!
ImMock say:test
ImMock disconnected!

      前三行是由ImImpl输出的,后三行是由ImMock生成的,我们在程序的执行流程中,通过改变配置模块(myModule)的内容,通过Guice来为MyIm注入具体的Im实现,得到我们想要的MyIm对象。这就是Guice可以带给我们的最基本的注入功能,可以很方便很快捷地实现依赖注入,Guice还提供了很多的注入类型和配置选项,建议大家都去试试看。

      Guice官方网站:http://code.google.com/p/google-guice/

在GAE中使用Django 1.0

四月 7th, 2009 edikud 1 Comments/384 hits

        Google App Engine可以运行任何WSGI兼容的程序。由于Django支持该标准,所以在Google App Engine上建立(或者移植既成的)Django程序是完全可能的。

        目前的Django版本是1.0.2,我们可以在http://www.djangoproject.com/这里下载到。在GAE环境中,我们没有SQL数据库,因此我们没法使用Django的Model类,同时也限制了Django部分模块的导入是使用。

        因此我们需要有一个Helper程序来兼容两种环境,这个Helper的project地址是:http://code.google.com/p/google-app-engine-django,但是很可惜,目前这个helper没有提供支持django1.0版本的直接下载,要获取支持1.0版本的helper我们要通过SVN从代码仓库中获取:

svn checkout http://google-app-engine-django.googlecode.com/svn/trunk/ google-app-engine-django-read-only

        之后可以将获取到的google-app-engine-django-read-only文件夹里面的文件都复制到我们要创建的工程文件夹里,如果这个文件夹是使用django-admin.py生成的话,就直接覆盖掉之前由django框架生成的文件。也可以直接拿google-app-engine-django-read-only文件夹作为工程的根文件夹,将其改个名就行了。里面已经包含了一个完整的Django工程基本文件。

        接着把整个django库复制到工程文件夹中,可以删除掉该django文件夹中conf/locale/里的大部分语言支持(保留确实要用的几个),以及/contrib/admin文件夹(GAE不支持django自带的后台管理)。

        最后就是修改app.yaml里头的application id,改成你的应用的id。执行./manage.py runserver 8080,如无意外,你就成功地运行开发服务器了。

参考文章:

http://code.google.com/appengine/articles/appengine_helper_for_django.html

参考视频:

     http://www.youtube.com/watch?v=v1gTI4BOPUw

 

        PS:我在搭建这个环境的时候出现找不到antlr3这个module的情况,如果有人和我遭遇同样的问题,可以在这里下载antlr3模块: 

http://www.antlr.org/download/Python/antlr_python_runtime-3.1.zip

使用GAE的memcache实现Session

四月 3rd, 2009 edikud 4 Comments/376 hits

        之前一直在寻找Google App Engine自带的WEB框架里面用来记录Session的API,但是除了一个没有什么用的webapp.RequestHandler.request.cookie之外,似乎没有类似功能的接口,可能GAE将该玩意忽略了,所以只好使用别的方法来解决Session信息的记录。

        后来选择了使用GAE的memcache来解决这个问题,直接把所有需要记下来的session信息写入到memcache中,至于使用什么东西来标识不同的session,我选择了用户的IP:webapp.RequestHandler.request.remote_addr。

        现在来一个例子:

1 if not memcache.get(self.request.remote_addr+"count"):
2        nv = count()          
3        memcache.set(self.request.remote_addr,nv,120)
4 else:
5        nv = memcache.get(self.request.remote_addr+"count")

 

        这段代码可以实现一个简单的访问计数,nv这个变量用来记录该用户的访问序号,count()函数用来将访问数增加1。第一行代码用来获取memcache中关于该用户的IP的访问数,而要在用户IP地址的基础所加上字符串count就是用来区别其他session信息的标识,也就是说count是本session中访问数的标识值。所以,用户IP和字符串count组成的字符串就是该session的用户访问数在memcache中的的标识值。

        如果在memcache中没有该用户的访问序号信息,就调用count()函数,之后将访问序号记入memcache中(第三行代码),所调用的memcache.set函数的第三个参数就是该数据的过期时间,这里使用的是120,代表数据在120秒之后就过期,这个期限可以按实际需要调整,过期机制使我们不用花精力来删除过期的session数据。

        这种实现方式和普通的session最大不同的地方在于,传统的session对象的生存周期是从访问开始到访问结束,而本实现的session数据的生存周期在于我们设置的过期时间(在某种程度上,这样是不科学的,因为我们不知道用户的会话会持续多久)。而且,在本实现中,不同的session之间不是分割的,我们还可以获取其他访问对话的session数据(只要知道对方的IP就可以获取),而传统的session之间是互相独立的。