232014
 

之前提到过,我用GMail来阅读RSS Feed。因为工作的原因,我经常使用email,在email中阅读其它的各种短文也就再好不过了。

RSS有一个很大的缺点,就是网站可以选择不全文输出RSS,只输出摘要。这样一来,即使在邮箱中能收到feed,也得点回网站去看完整的内容,非常不方便。不过自从前一阵子发现了Google Apps Script,这个问题就迎刃而解了。

点这里查看全文RSS。有了全文RSS之后,再用IFTTT就可以安心得使用GMail来看新闻啦……

脚本代码还在优化过程中,暂时不公开。

152014
 

WordPress从3.7开始支持自动更新功能,默认的设定是自动更新小版本,比如从3.8.2更新到3.8.3。更新完之后会发一封邮件过来,如下图所示:

通常小版本更新都是一些安全补丁,并不影响功能,应当及时地安装上去,所以Wordpress的这个功能相当实用。

当然,如果你想把所有的更新都设置成自动的,也是可以的,在wp-config.php里添加一行即可:

define( 'AUTOMATIC_UPDATER_DISABLED', true );

AUTOMATIC_UPDATER_DISABLED的值可以是true、false或者minor。默认值是minor。

112014
 

很久以来Google Drive都没有复制文件夹的功能,而且它的复制文件功能也很弱,只能在当前文件夹下创建出一个备份。如果想要复制一个文件夹,只能在客户端中进行复制,再传到服务器中,不过这样就很浪费带宽了。原因大概是复制功能的界面做起来比较复杂,使用的人也不多,于是Google就一直没想着做加强这方面。

不过自从有了Apps Script之后,我们可以自己在网页端复制文件夹了。只是需要写一点代码。主程序如下,必要的注释都标记在代码中了。

// Folder id是一个字符串,形如"0B4TitHEtT0w_aGxTeTljME5kZnc"
// 随便打开一个Drive的文件夹,可以在URL中找到这一串东西
// URL形如"https://drive.google.com/#folders/0B4TitHEtT0w_aGxTeTljME5kZnc"
//
// TODO: 目前此方法只能复制文件,不能复制子文件夹。如果有需求可自行添加。
function copyFolder(srcFolderId, destFolderId) {
  var srcFolder = DriveApp.getFolderById(srcFolderId);
  var destFolder = DriveApp.getFolderById(destFolderId);
  var srcFolderName = srcFolder.getName();
  var destFolderName = destFolder.getName();
  
  // 如果目标文件夹和源文件夹不同名,在创建一个同名的子文件夹
  if (destFolderName != srcFolderName) {
    var subFolders = destFolder.getFoldersByName(srcFolderName);
    if (subFolders.hasNext()) {
      // 使用现有的同名子文件夹
      destFolder = subFolders.next();
    } else {
      // 创建同名子文件夹
      var newFolder = destFolder.createFolder(srcFolderName);
      destFolder = newFolder;
    }
    destFolderId = destFolder.getId();
    destFolderName = destFolder.getName();
  }
  
  var files = null;
  var cache = CacheService.getPrivateCache();
  var key = srcFolderId + "_" + destFolderId;
  var token = cache.get(key);
  if (token != null) {
    try {
      files = DriveApp.continueFileIterator(token);
    } catch (e) { }
  }
  if (!files) {
    files = srcFolder.getFiles();
  }
  
  var checkExistence = true;
  while (files.hasNext()) {
    var file = files.next();
    if (checkExistence) {
      // 检查文件是否已经存在,如存在则跳过
      var fileName = file.getName();
      if (destFolder.getFilesByName(fileName).hasNext()) {
        continue;
      } else {
        checkExistence = false;
      }
    }
    file.makeCopy(destFolder);
    // 缓存ContinuationToken以加速下一次运行。
    // App Script一次最长能运行5分钟,实践中最多可以复制50个文件,
    // 如果要复制超过50个文件,需要运行此Script多次。
    cache.put(key, files.getContinuationToken(), 600);
  }
}

如果需要复制一个文件夹,只需要运行下面的方法。这种方式只有一个问题,就是Apps Script的每次运行时限为5分钟,实测下来一次可以复制50个文件左右,如果文件夹中多于50个文件,需要运行多次才可以解决。

function copy() {
  copyFolder("0B4TitHEtT0w_NDdhZHd0YXQ5R1k", "0B4TitHEtT0w_Q2FEMXZiRDB3SlE");
}
092014
 

2014.4.16更新: mod_spdy需要单独更新,详见下文.

今天一大早收到了一堆关于SSL的邮件,原来前几天,OpenSSL中暴出了一个严重安全问题,被称为“Heartbleed”。

Heartbleed可导致SSL客户端(比如HTTPS)访问服务器中任何的64k大小的内存数据,也就是说,很多重要数据都可能被窃取,比如SSL的服务器端的密钥。有了服务器端的密钥,可以伪造出一个一模一样的HTTPS钓鱼网站。所以这是非常严重的安全问题。

如果你的博客/网站支持HTTPS,请尽快升级OpenSSL组件。Amazon Linux AMI中的升级方法是:sudo yum update openssl。其它的Linux版本大同小异吧。

另外,如果你的博客使用了mod_spdy,那么你还需要特地更新一下SPDY组件,原因是mod_spdy中有一份OpenSSL的拷贝。

有关Heartbleed的更多信息,请参见:

  1. Heartbleed Bug: http://heartbleed.com/
  2. CVE-2014-0160: https://www.openssl.org/news/secadv_20140407.txt
242014
 

在网页中加入一张图片很简单,只需要插入一个<img>标签就可以了。对于大多数图片来说,使用<img>标签很方便,但它有两个小问题:

  • 需要上传一张图片,可能需要额外的图床,管理起来相对麻烦;
  • 对于网速比较慢的用户来说,需要多开一个连接下载图片,有时候会下载不到,影响用户体验。

所以,一些小图片,与其放在图床上,还不如直接嵌在网页里。图片本身就小(比如小于32k),下载速度很快,而且省去了建立连接的时间,方便快捷。唯一不好的地方是无法设置图片缓存,因此不能对重复出现的图片做这种优化。

怎么把图片直接嵌在网页里?只需要使用Data URI标记就可以了,它形如<img src=”data:image/png,base64,xxxx”/>。其中xxx是图片的base64编码,image/png表示图片是png格式,你也可以使用其它格式。示例如下,图片是本页地址的二维码,查看源代码就可以看到它的base64形式。

QR Code

这种方式在主流的浏览器(IE、Chrome、Firefox)中都支持,放心使用。

212014
 

作为向Google Cloud转移的一部分,我最近开始尝试使用App Engine。

Google Cloud最大的缺点在于不自带DNS,所有的域名设置都是通过CName来做的。据说Windows Azure也是这样,三大云服务里只有AWS有自己的DNS。使用CName的缺点是无法绑定裸域名(Naked Domain)。裸域名就是形如leonax.net一样的域名,而www.leonax.net则是一个二级域名。裸域名的好处很明显,可以少打几个字符。现在的主流网站基本都使用裸域名,则些年流行的“www”现在渐渐没有价值了。由于Google Cloud只支持CName转发,用户就只能通过类似www.leonax.net的地址访问,直接访问leonax.net会得到一个“网址无法解析”的错误。这对于用户体验有很大的负作用。

于是乎,GoDaddy就提供了一个域名转向(Domain Forwarding)的功能。其功能就是把裸域名自动中转到一个自定义的二级域名上,这样用户访问裸域名的时候,也可以看到实际的内容。不过没关系,我们可以通过Apache自己来搞个转向功能。

看上去GoDaddy的作法是把裸域名解析到了一个特定的IP地址,估计是GoDaddy自己的。只要把这个地址改成一个可以自己操控的,比如leonax.net现在用的176.34.51.96,然后去主机上使用.htaccess进行下转向就行了,代码如下:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^leonax.net [NC]
RewriteRule ^(.*)$ http://www.leonax.net$1 [R=301,L]

这样所有的leonax.net的访问请求,都会被自动中转到www.leonax.net,无须用户操心。估计GoDaddy自己也是这么干的

192014
 

昨天想用Eclipse写得东西的时候,发现Google的Plugin不能用了,原因是Eclipse使用的是JRE 6,但是Google Plugin需要至少JRE 7才可以启动。

百思不得其解,我在Eclipse里已经设置了默认的JRE是7.0的,而且在Terminal中执行java -version也显示的是7u51。虽然机器上同时装有JDK 6和7,难道Eclipse死盯着一个上的?然后我把机器中的JDK 6给删掉了,接着就看到以下对话框:

说是没检测到JDK 6,请问是不是要装一个。Eclipse还真够死脑筋的。

搜索了一下发现有一个Bug,Kepler曾经出过这样的问题。但是这个bug在去年11月份已经修好了,我用的是今天1月份的版本。真是相当地无语。

后来在StackOverflow看到有人手动启动Eclipse的launcher,这样可以指定使用哪一个Java VM。照抄了一下发现可用,索性就分享一下:

java -Xmx8G
     -XX:MaxPermSize=1G
     -Xdock:icon=/Applications/eclipse/Eclipse.app/Contents/Resources/Eclipse.icns
     -XstartOnFirstThread
     -Dorg.eclipse.swt.internal.carbon.smallFonts
     -Dosgi.requiredJavaVersion=1.6
     -jar /Applications/eclipse/plugins/org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar

如果你的机器没那么大的内存,可以用2G或者4G代替,影响不大。

152014
 

整数的奇偶性大家都知道,就是看它能不能被2整除:能整除就是偶数,不能整数就是奇数。

那么我们来研究一下按位的奇偶性:一个整数的二进制表示中,如果‘1’的个数为偶数,那就定义这个数是偶数,反之是奇数。完成以下函数,如果这个整数按位是偶数,返回0,奇数返回1。

int parity(uint64_t x) {}

很容易想到的一个方法是,按位运算嘛,把其中的’1′的个数算出来,再算个数的奇偶性就行了,比如:

int parity(uint64_t x) {
  int count = 0;
  while (x) {
    count += (x & 1);
    x >>= 1;
  }
  return (count & 1);
}

很容易证明上述的方法是正确的。但是它足够快吗?uint64_t是64的无符号整数,平均情况下也要几十次迭代才可以算出结果。

有没有更快的方法呢?上面的方法实际上可以算出一个整数的二进制中有多少个”1″,但我们并不需要这多余的信息。仔细一想,不用加法,异或就可以了。在异或运算中,两个1异或变成0,两个0依然是0,但是1和0在一起就是1,并且关键的是异或运算满足交换率,就是多个0和1的异或,不论顺序怎样,算出来都是同样的结果。

于是可以推导出,只要每次把这个整数折半,一半异或另一半,这样在6次之内算出结果。比之前的几十次要好很多了。

能不能再给力一点?

结论是可以的。上述的方法中,需要计算长度为2、4、8、16、32位的整数的异或,如果我们能预先算出所有的16位整数的运算结果,保存起来。由于运算结果只是0或1,只需要32K的字节的空间,不算多。这些保存的结果,可以把上面的方法优化到3次计算。如果这个函数被经常使用,这种打表的方式还是很有效果的。

具体的代码就不附了,也就是一堆的异或运算……

142014
 

今天一早Google Drive降价了,还降得蛮多的:

之前的价格是100G、200G、400G、1T这样的档次上升的,而且200G的价格就是9.99美刀一个月。现在价格不变的情况下容量升了5倍,相当超值。同样的价格在Dropbox只能买到100G,OneDrive只有200G。

虽然1T的空间一年还是需要100刀以上的费用,比起买一个1T的硬盘来说还是贵的,但是在线存储有它自身的好处。

比如数据不会丢失:离线的硬盘虽然可以用RAID-5之类的技术来作备份,在一个硬盘出问题的情况下,数据可以从剩下的硬盘中恢复出来。但是问题不在于恢复数据,而在于很难知道硬盘什么时候坏。大量的资料数据,平时并不会经常检查,通常只是需要用的时候看一眼,不用的时候放在那里几个月几年都不会动,这样一来,硬盘的损坏是没办法即时知道的,而RAID-5只能顶得住一个硬盘坏,如果两个硬盘同时损坏,数据就丢失了。

而在线存储,比如Google Drive这种的服务,它有专人维护,硬件有问题会及时更换,可以确保数据不丢失。从这个角度来说,在线的存储更安全。如果实在担心泄露的风险的话,可以打个加密的压缩包扔在上面,定期改改密码就好了。

如果感兴趣的话,现就可以试试

122014
 

虽然警告对话框(alert)在现代网页设计中已经很少用到了,但总有一些奇葩的设计师(比如Google Calendar)会想去用它。警告对话框奇葩的地方在于,它不是网页的一部分,而是一个模态窗口。警告对话框不仅会使当前网页不能点击,还会造成阻塞对的Tab访问。

在使用WebDriver测试的时候,如果碰到有警告对话框,不要着急,WebDriver已经考虑到这一点了。参考以下代码:

public void testAlert() {
  webDriver.navigate().to(testUrl); // 加载测试页面
  waitUntilPageLoad(); // 等待页面加载完成
  try {
    Alert alert = webDriver.switchTo().alert(); // 把焦点切换到警告对话框
    String actualText = alert.getText(); // 获取警告对话框的实际内容
    assertEquals(expectedText, actualText); // 和期望的内容进行对比
    alert.accept(); // 点击“OK”(或者“确定”)
  } catch (NoAlertPresentException e) { // 警告对话框没有出现
    throw new IllegalStateException(e);
  }
}

关键部分就是webDriver.switchTo().alert(),它会控制WebDriver切换到警告对话框,如果当前没有警告对话框,WebDriver会扔出一个NoAlertPresentException。Alert还有其它方法,参见官方文档