JavaScript对象当map使

一个转换函数:

getIconURL: function(content) {
    if (typeof content !== 'string') {
        return undefined;
    }
    switch (content) {
    case 'stopped':
        return 'resources/icons/circle_red_l.png';
    case 'running':
        return 'resources/icons/circle_green_l.png';
    case 'pending':
        return 'resources/icons/circle_orange_l.png';
    case 'shutting-down':
        return 'resources/icons/circle_orange_l.png';
    case 'stopping':
        return 'resources/icons/circle_orange_l.png';
    case 'available':
        return 'resources/icons/disconnect.png';
    case 'error':
        return 'resources/icons/delete.png';
    case 'us-east-1':
        return 'resources/icons/flag_us.png';
    case 'us-west-1':
        return 'resources/icons/flag_us.png';
    case 'us-west-2':
        return 'resources/icons/flag_us.png';
    case 'ap-northeast-1':
        return 'resources/icons/flag_jp.png';
    case 'ap-southeast-1':
        return 'resources/icons/flag_sg.png';
    case 'eu-west-1':
        return 'resources/icons/flag_eu.png';
    case 'sa-east-1':
        return 'resources/icons/flag_br.png';
    case 'valid':
        return 'resources/icons/accept.png';
    case 'invalid':
        return 'resources/icons/delete.png';
    default:
        return undefined;
    }
}

新的分支越加越多,就越觉得效率有问题,而且这个函数经常被调用

想使用map来代替switch,但又不想用ExtJS的HashMap
于是把JavaScript的对象当map使,还是非常好用的
速度没测过,但在分支特别多的情况下应该要快上很多

/**
 * @author kuyur
 */

Ext.define('myproject.util', {
	statics: {
		map: {
			'stopped': 'resources/icons/circle_red_l.png',
			'running': 'resources/icons/circle_green_l.png',
			'pending': 'resources/icons/circle_orange_l.png',
			'shutting-down': 'resources/icons/circle_orange_l.png',
			'stopping': 'resources/icons/circle_orange_l.png',
			'available': 'resources/icons/disconnect.png',
			'error': 'resources/icons/delete.png',
			'us-east-1': 'resources/icons/flag_us.png',
			'us-west-1': 'resources/icons/flag_us.png',
			'us-west-2': 'resources/icons/flag_us.png',
			'ap-northeast-1': 'resources/icons/flag_jp.png',
			'ap-southeast-1': 'resources/icons/flag_sg.png',
			'eu-west-1': 'resources/icons/flag_eu.png',
			'sa-east-1': 'resources/icons/flag_br.png',
			'valid': 'resources/icons/accept.png',
			'invalid': 'resources/icons/delete.png'
		},
		getIconURL: function(content) {
			if (typeof content !== 'string') {
				return undefined;
			}
			return myproject.util.map[content];
		},
		...
	}
});
2,384 次浏览 | 没有评论
2012年8月2日 | 归档于 技术, 程序

只提供RESTful API的Java EE工程的多语言化

一个Java EE工程如果只提供RESTful API,多语言化还真是非常简单,虽然Servlet的WEB页面多语言化可能也并不复杂

一开始就上多语言,比半中途才上,可以省掉很多不必要的功夫

提供多语言的类是一个典型的多例模式(http://en.wikipedia.org/wiki/Multiton_pattern),阎宏的《Java与模式》中有一个Demo,但那个Demo的代码写得真叫一个烂,一堆语法错误不说,逻辑都写得莫名其妙的,网上的博客一大抄,导致这种有问题的代码广为流传,真是误人子弟

某工程半中途要上多语言,语言资源文件决定使用xml,另外语言不使用region和locale细分,语言只包括下面四种
ja (日语,locale_ja.xml)
en (英语,locale_en.xml)
zh_CN (简体中文,locale_zh_CN.xml)
zh_TW (繁体中文,locale_zh_TW.xml)

因此从字符串到java.util.Locale对象,首先就要做一次转换。

如果语言文件选择properties文件,文件读取将非常简单
选择xml文件则需要自己写处理逻辑,这方面网上的文章非常多

语言资源需要按照下面的写法组织,推荐保存为无BOM的UTF-8文档

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 
<properties>
  <entry key="LoingFailed">登录失败,错误的用户名或密码。</entry>
</properties>

多语言类源代码,注意包的名字和资源文件路径
资源文件推荐放在工程的resources下,编译后会打包在一起

package info.kuyur.demo;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;

public class LocaleLoader {
	public static final String LOCALE_EN = "en";
	public static final String LOCALE_JA = "ja";
	public static final String LOCALE_ZH_CN = "zh_CN";
	public static final String LOCALE_ZH_TW = "zh_TW";
	private static final String LOCALE_DEFAULT = "ja";
	private static final String LOCALE_PREFIX = "info/kuyur/locales/locale";
	private static final String MESSAGE_NOT_FOUND = "Message not found.";
	private static final String LOCALE_FILE_NOT_EXISTED = "Locale file {0}{1}{2}{3} not existed.";

	private static Map<String, LocaleLoader> instances = new HashMap<String, LocaleLoader>(4);
	private static XMLResourceBundleControl xmlResourceBundleControl = new XMLResourceBundleControl();

	private String locale;
	private ResourceBundle resourceBundle = null;

	private static class XMLResourceBundle extends ResourceBundle {
		private Properties props;
		XMLResourceBundle(InputStream stream) throws IOException {
			props = new Properties();
			props.loadFromXML(stream);
		}

		@Override
		protected Object handleGetObject(String key) {
			return props.getProperty(key);
		}

		@Override
		public Enumeration<String> getKeys() {
			Set<String> handleKeys = props.stringPropertyNames();
			return Collections.enumeration(handleKeys);
		}
	}

	private static class XMLResourceBundleControl extends ResourceBundle.Control {
		@Override
		public List<String> getFormats(String baseName) {
			return Collections.singletonList("xml");
		}

		@Override
		public ResourceBundle newBundle(String baseName, Locale locale, String format,
				ClassLoader loader, boolean reload)
		throws IllegalAccessException, InstantiationException, IOException {
			if ((baseName == null) || (locale == null) || (format == null) || (loader == null)) {
				throw new NullPointerException();
			}
			ResourceBundle bundle = null;
			if (format.equalsIgnoreCase("xml")) {
				String bundleName = this.toBundleName(baseName, locale);
				String resourceName = this.toResourceName(bundleName, format);
				URL url = loader.getResource(resourceName);
				if (url != null) {
					URLConnection connection = url.openConnection();
					if (connection != null) {
						if (reload) {
							connection.setUseCaches(false);
						}
						InputStream stream = connection.getInputStream();
						if (stream != null) {
							BufferedInputStream bis = new BufferedInputStream(stream);
							bundle = new XMLResourceBundle(bis);
							bis.close();
						}
					}
				}
			}
			return bundle;
		}
	}

	// can not be constructed by external
	private LocaleLoader(String locale) {
		this.locale = locale;
		Locale lo = toLocale(locale);
		try {
			resourceBundle = ResourceBundle.getBundle(LOCALE_PREFIX, lo, xmlResourceBundleControl);
		} catch (MissingResourceException e) {
			resourceBundle = null;
		}
	}

	/**
	 * Return a locale object which contains messages.
	 * If parameter is null or emtpy, will return default locale.  
	 * @param locale
	 * @return
	 */
	public synchronized static LocaleLoader getInstance(String locale) {
		locale = toSupportedLocale(locale);
		LocaleLoader instance = instances.get(locale);
		if (instance != null) {
			return instance;
		} else {
			instance = new LocaleLoader(locale);
			instances.put(locale, instance);
			return instance;
		}
	}

	public String getLocale() {
		return locale;
	}

	public String getString(String key) {
		if (resourceBundle == null) {
			return MessageFormat.format(LOCALE_FILE_NOT_EXISTED, LOCALE_PREFIX, "_", locale, ".xml");
		}
		try {
			return resourceBundle.getString(key);
		} catch (Exception e) {
			return MESSAGE_NOT_FOUND;
		}
	}

	private static String toSupportedLocale(String locale) {
		if (LOCALE_JA.equalsIgnoreCase(locale)) {
			return LOCALE_JA;
		}
		if (LOCALE_EN.equalsIgnoreCase(locale)) {
			return LOCALE_EN;
		}
		if (LOCALE_ZH_CN.equalsIgnoreCase(locale)) {
			return LOCALE_ZH_CN;
		}
		if (LOCALE_ZH_TW.equalsIgnoreCase(locale)) {
			return LOCALE_ZH_TW;
		}
		return LOCALE_DEFAULT;
	}

	private Locale toLocale(String locale) {
		if (LOCALE_JA.equals(locale)) {
			return Locale.JAPANESE;
		} else if (LOCALE_EN.equals(locale)) {
			return Locale.ENGLISH;
		} else if (LOCALE_ZH_CN.equals(locale)) {
			return Locale.SIMPLIFIED_CHINESE;
		} else if (LOCALE_ZH_TW.equals(locale)) {
			return Locale.TRADITIONAL_CHINESE;
		}
		return Locale.JAPANESE;
	}
}

这个类被设计成不会出错,即使语言文件不存在或者词条不存在
在代码中写死了支持的语言种类,但考虑到几乎不可能频繁增加新语言,这种写法还是可以接受的
(java.util.Locale中的静态Locale常量也是写死的)

测试用例:

package info.kuyur.demo;

import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import org.junit.BeforeClass;
import org.junit.Test;

public class LocaleLoaderTest {
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
	}

	@Test
	public void testGetString() {
		LocaleLoader ja = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertTrue(LocaleLoader.LOCALE_JA.equals(ja.getLocale()));
		assertTrue("ja".equals(ja.getLocale()));
		System.out.println(ja.getString("LoingFailed"));
		System.out.println(ja.getString("hogehoge"));

		LocaleLoader en = LocaleLoader.getInstance("En");
		assertTrue(LocaleLoader.LOCALE_EN.equals(en.getLocale()));
		assertTrue("en".equals(en.getLocale()));
		System.out.println(en.getString("LoingFailed"));
		System.out.println(en.getString("hogehoge"));

		LocaleLoader zh_CN = LocaleLoader.getInstance("zH_cN");
		assertTrue(LocaleLoader.LOCALE_ZH_CN.equals(zh_CN.getLocale()));
		assertTrue("zh_CN".equals(zh_CN.getLocale()));
		System.out.println(zh_CN.getString("LoingFailed"));
		System.out.println(zh_CN.getString("hogehoge"));

		LocaleLoader zh_TW = LocaleLoader.getInstance("Zh_Tw");
		assertTrue(LocaleLoader.LOCALE_ZH_TW.equals(zh_TW.getLocale()));
		assertTrue("zh_TW".equals(zh_TW.getLocale()));
		System.out.println(zh_TW.getString("LoingFailed"));
		System.out.println(zh_TW.getString("hogehoge"));
	}

	@Test
	public void testIsSameObject() {
		LocaleLoader ja1 = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		LocaleLoader ja2 = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertSame(ja1, ja2);
	}

	@Test
	public void testDefault() {
		LocaleLoader ja = LocaleLoader.getInstance(LocaleLoader.LOCALE_JA);
		assertTrue(LocaleLoader.LOCALE_JA.equals(ja.getLocale()));

		LocaleLoader hogehoge = LocaleLoader.getInstance("hogehoge");
		assertTrue(LocaleLoader.LOCALE_JA.equals(hogehoge.getLocale()));

		assertSame(ja, hogehoge);
	}

	@Test
	public void testNullLocaleName() {
		LocaleLoader hogehoge = LocaleLoader.getInstance("");
		assertTrue(LocaleLoader.LOCALE_JA.equals(hogehoge.getLocale()));

		LocaleLoader gehogeho = LocaleLoader.getInstance(null);
		assertTrue(LocaleLoader.LOCALE_JA.equals(gehogeho.getLocale()));
	}
}

用法用例:
参考测试用例

2,970 次浏览 | 没有评论
2012年7月26日 | 归档于 技术, 程序

定制自己的ExtJS4的timefield控件

需求的变态是没有极限的
某RIA以前使用Silverlight开发,今年3月开始使用ExtJS4写JavaScript版本
两种完全不同的语言,写出来的界面任你再一致,很多特性还是很难移植的
比如Silverlight的timefield控件,支持用键盘的上下方向键调整时间,光标在小时数字的位置时,上下键的作用是增减一小时,光标在分钟数字的位置时,上下键的作用是增减一分钟
于是某测试人员说,既然Silverlight能做到,JS版本也希望实现这个功能
(余抽乃大爷100遍,这种要求何等无理啊,这是和控件自身相关的特性,JS要实现就非得hack ExtJS的代码不可,Silverlight能做到是控件它自身就已经实现,又不是代码写出来的,#$*%^&_+

花了一晚时间,还是把它给实现了,就是下面的定制timefield控件
由于光标位置没法判断,因此键盘操作只能增减一分钟

先是拦截timefield的keypress事件,不起作用,再试着拦截specialkey事件,UP键和DOWN键都成功捕捉到了
UP键的事件能停止,DOWN键的事件死活不能停止
仔细想一下,timefield控件的DOWN键是用来展开下拉列表的,看来默认是不给停止的了,没办法,读源代码

timefield是Ext.form.field.Time的别名
Ext.form.field.Time依次继承自Ext.form.field.Picker,Ext.form.field.Trigger,Ext.form.field.Text
在Ext.form.field.Picker中发现initEvents方法:

    initEvents: function() {
        var me = this;
        me.callParent();

        // Add handlers for keys to expand/collapse the picker
        me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
            down: function() {
                if (!me.isExpanded) {
                    // Don't call expand() directly as there may be additional processing involved before
                    // expanding, e.g. in the case of a ComboBox query.
                    me.onTriggerClick();
                }
            },
            esc: me.collapse,
            scope: me,
            forceKeyDown: true
        });

        // Non-editable allows opening the picker by clicking the field
        if (!me.editable) {
            me.mon(me.inputEl, 'click', me.onTriggerClick, me);
        }

        // Disable native browser autocomplete
        if (Ext.isGecko) {
            me.inputEl.dom.setAttribute('autocomplete', 'off');
        }
    },

很显然,Down键的事件在这里就被写死了

试着从Ext.form.field.Time派生一个新类MyProject.ui.UpDownTimeField,覆盖initEvents,绕过基类的事件处理

	initEvents: function() {
		var me = this;
		me.callParent();
		me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
			down: function() {
				me.decreaseTime();
			},
			up: function(e) {
				me.increaseTime();
			},
			esc: me.collapse,
			scope: me,
			forceKeyDown: true
		});
		if (!me.editable) {
			me.mon(me.inputEl, 'click', me.onTriggerClick, me);
		}
		if (Ext.isGecko) {
			me.inputEl.dom.setAttribute('autocomplete', 'off');
		}
	},

还是失败了
细心一想,UpDownTimeField的initEvents方法中,me.callParent()调用基类方法,也即是Ext.form.field.Picker的initEvents,根本就还没绕过去嘛

于是用Ext.form.field.Text.prototype.initEvents.call(me)代替,成功用自己的事件处理取代了原来的事件处理

最后的UpDownTimeField:

Ext.define('MyProject.ui.UpDownTimeField', {
	extend: 'Ext.form.field.Time',
	alias: 'widget.updowntimefield',
	format: 'H:i',
	increment: 30,
	minValue: '00:00',
	maxValue: '23:59',
	initEvents: function() {
		var me = this;
		Ext.form.field.Text.prototype.initEvents.call(me);
		me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
			pageDown: function() {
				if (!me.isExpanded) {
					me.onTriggerClick();
				}
			},
			down: function() {
				me.decreaseTime();
			},
			up: function(e) {
				me.increaseTime();
			},
			esc: me.collapse,
			scope: me,
			forceKeyDown: true
		});
		if (!me.editable) {
			me.mon(me.inputEl, 'click', me.onTriggerClick, me);
		}
		if (Ext.isGecko) {
			me.inputEl.dom.setAttribute('autocomplete', 'off');
		}
	},
	increaseTime: function() {
		var me = this;
		if (me.isExpanded || !me.isValid()) {
			return;
		}
		var timeString = me.getRawValue();
		if (timeString) {
			var arr = timeString.split(':');
			var h = parseInt(arr[0], 10);
			var m = parseInt(arr[1], 10);
			m++;
			if (m > 59) {
				m = 0;
				h++;
				if (h > 23) {
					h = 0;
				}
			}
			h = h>=10 ? h.toString() : '0'+h;
			m = m>=10 ? m.toString() : '0'+m;
			me.setRawValue(h + ':' + m);
		}
	},
	decreaseTime: function() {
		var me = this;
		if (me.isExpanded || !me.isValid()) {
			return;
		}
		var timeString = me.getRawValue();
		if (timeString) {
			var arr = timeString.split(':');
			var h = parseInt(arr[0], 10);
			var m = parseInt(arr[1], 10);
			m--;
			if (m < 0) {
				m = 59;
				h--;
				if (h < 0) {
					h = 23;
				}
			}
			h = h>=10 ? h.toString() : '0'+h;
			m = m>=10 ? m.toString() : '0'+m;
			me.setRawValue(h + ':' + m);
		}
	}
});

使用:

{
    xtype: 'updowntimefield',
    itemId: 'form-xxx-pickTimeField',
    name: 'LaunchTime',
    fieldLabel: message.YourLable,
    labelWidth: 150,
    labelClsExtra: 'your-css',
    regex: /^\d\d:[0-5]\d$/,
    regexText: message.YourMessage   
}

事实上timefield的合法性验证不是特别严密,可以自己指定regex属性

4,165 次浏览 | 没有评论
2012年7月24日 | 归档于 技术, 程序

Canon EF28mm F1.8 USM试镜

最近入了两定焦镜头,一原厂广角用来拍景,一副厂85mm用来拍人。
原厂头即Canon EF28mm F1.8 USM。
11区这边的价格和国内行货差不多,比香港水货要贵上一大截。

当初不想入EF50mm F1.4,觉得50mm用在半幅机上不上不下,但在试过28mm和85mm后,这个观点可能需要修正
每个焦段都有它的适用范围,别的焦段是无论如何都替代不了这个焦段的
28mm即使有大光圈,拍稍远一点的小东西就非常勉强了
由于连入两颗头有点伤身,过段时间可能会入个40mm饼干头玩玩

出浴照(18-200mm变焦头在57mm焦距F4.5光圈拍摄,可以看出几乎可以忽略的背景虚化能力)

换上28mm头,1.8光圈全开

随随便便的晚餐,F1.8,1/30s,无闪光灯,ISO 100

再来一次钥匙扣,F1.8,1/30s,无闪光灯,ISO 400

周日拉同事去试镜,同事说还没去过皇居,于是就去了皇居。
家附近的紫阳花,F1.8

皇居外苑的松树,F1.8

这棵树下有个卖乌冬面的,午餐速战速决。F1.8

皇居外苑草坪上的小花,F2

二重桥后面的某桥,F9

二重桥,F7.1

看完无聊的二重桥,同事提议绕一圈走到北之丸
F7.1

立入禁止,F1.8

警察叔叔不知在干啥,F9

梧桐叶子,F2.2

小野花,F2

北之丸公园入口路上的指示牌,F9

某展览馆附近,F8

某展览馆附近,F1.8

青苔,F1.8

青草上的泡泡,F2.2

武道馆,F11

回家晚饭后想骑自行车去拍一下东京天空树,拿上三角架
手持一张,F1.8,1/40s

正寻找着角度,天空树居然关灯了,于是算了
隅田川上随便一张夜景后回家,F11,11s,ISO 640

回家路上正骑着车打着电话呢,遇到了警察蜀黍查车,第一次被查

2,863 次浏览 | 没有评论
2012年7月22日 | 归档于 私语

只用天平称3次,找出13个球中重量与其他球不同的唯一球

题目:13个有编号的球,其中12个重量相同,剩下的一个与其他球重量不同,但不知是轻了还是重了,只用天平称3次,找出这个与众不同的球
twitter上无意从别人的转推看到,开始以为很简单,深入思考后发现细分的情形还是不少的。其实它是一条扫雷题。或许某些招聘也会拿来当面试题

如果知道与众不同的球(下面简称特殊球)是轻了还是重了,就变成了非常简单的题目,即使球数量是27个,都能只称3次就找出特殊球

闲得蛋疼推导出了解法

解法:
将重量都相同的球称之为标准球。
不妨假定球的编号是1,2,3,…,13

将之分成三堆,1,2,3,4为堆A,5,6,7,8为堆B,9,10,11,12,13为堆C

第一次称:
将堆A和堆B放在天平上,如果平衡(分支一),则特殊球在堆C,如果不平衡(分支二),则特殊球在堆A或堆B

分支一:
堆A和堆B的球全部都是标准球。这时候要利用上标准球的一个,不妨加入球1
将球1和球9放在天平一边,球10和球11放在天平另一边,称第二次,如果平衡(分支三),则特殊球在12,13之中,如果不平衡(分支四),则特殊球在9,10,11之中,我们要记下1,9 VS 10,11那边轻那边重

分支三:
很简单,将12和标准球放在天平两边称第三次,平衡则特殊球是13(已经找出但仍然不知道轻了还是重了),不平衡则特殊球是12,并且还能知道轻了还是重了

分支四:
将10和11放在天平两边称第三次,
如果平衡,则特殊球是9,因此10,11都是标准球,依据第二次的结果,还能知道9重了还是轻了
如果不平衡,则特殊球在10,11当中。在第二次的结果中,如果10,11这边重了,说明特殊球是重了,则特殊球是第三次的较重者;如果第二次10,11这边轻了,说明特殊球是轻了,则特殊球是第三次的较轻者

回到分支二
分支二:
回顾一下:特殊球要么在堆A中,要么在堆B中
如果堆A在第一次测量中较轻,分支五
如果堆B在第一次测量中较轻,分支六
分支五和分支六的后续解法是对称的,我们只需要解决分支五即可

分支五:堆A(1,2,3,4)较轻,堆B(5,6,7,8)较重,这个结果很重要,后面的推理要利用到
将1,5,6放在天平一边,2,7,8放在天平另一边,称第二次
如果天平平衡,则1,2,5,6,7,8都是标准球,特殊球在3,4当中,拿标准球的任一个和3称第三次,如果平衡则特殊球是4,如果不平衡,则特殊球是3
如果1,5,6较轻,2,7,8较重,分支七
如果1,5,6较重,2,7,8较轻,分支八

分支七:
我们要筛选出特殊球的备选球,第二次测量的结果也要利用,1,5,6较轻,2,7,8较重
1,7,8都是特殊球的备选,2,5,6都不可能是备选。因为2或5或6如果是特殊球,代入去都能导致矛盾
这种推理过程跟扫雷是一样的
然后将7,8放在天平两边称第三次,如果平衡,则特殊球是1,如果不平衡,则特殊球是较重的那个

分支八(和分支七的推理逻辑是一样的):
第二次测量结果已知是1,5,6较重,2,7,8较轻
可以筛选出2,5,6是特殊球的备选。1,7,8都不可能是。
将5,6放在天平两边称第三次,如果平衡,则特殊球是2,如果不平衡,则特殊球是较重的那个

OVER。

3,841 次浏览 | 2 条评论
2012年7月5日 | 归档于 私语

使用VNC远程连接Amazon EC2的Ubuntu实例

使用AmazonEC2的Ubuntu实例编译Python-Webkit(http://www.gnu.org/software/pythonwebkit/)
需要启动浏览器证实编译成果,唯有安装VNC

使用putty SSH登录Ubuntu

首先安装VNC Server

cd /home/ubuntu
sudo apt-get update
sudo apt-get install vnc4server

启动VNC Server一次,生成配置文件,同时也设置密码

sudo vnc4server

再关掉

sudo vnc4server -kill :1

修改配置文件

cd ./.vnc
sudo vim xstartup

将#去掉

# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc

安装(或更新)桌面

sudo apt-cache search ubuntu-desktop
sudo apt-get install ubuntu-desktop

如果ubuntu开启了防火墙,关掉它

sudo ufw status
sudo ufw disable

给予X11初始化脚本执行权限

sudo chmod 755 /etc/X11/xinit/xinitrc

启动VNC Server,同时可以设置分辨率和色深(24位色深比较耗带宽,自己看网速情况设定)

sudo vnc4server -geometry 1280x1024 -depth 24

到这里ubuntu上的设置完毕

Amazon EC2的Security Groups里,允许5900,5901端口通信

本地:
下载VNC Viewer
(注意仅需要Viewer,不需要服务器端的东东)

启动VNC Viewer,注意地址是 ip:1域名:1
连接,接着会提示输入密码,就能成功进入远程主机的桌面

远程主机桌面的D键被设定为显示桌面的快捷键,需要在keybord shortcuts里禁用这个快捷键
退出远程桌面,重启VNC Server才能生效

sudo vnc4server -kill :1
sudo vnc4server -geometry 1280x1024 -depth 24

10,549 次浏览 | 6 条评论
2012年6月21日 | 归档于 技术

用Google新图形化编程语言Blockly写一类迷宫的通用解

在CB看到新闻:Blockly: Google新推出的图形化编程语言,手一贱点了链接,接着玩起了Demo中的迷宫游戏Maze。

http://blockly-demo.appspot.com/blockly/demos/maze/index.html

Maze是一个自定义类,提供向前向后移动,向左转向右转以及判断前后左右是否有墙等函数。
这里的迷宫是一个很规整的迷宫,墙都是上下左右的直线段,而且是单位长度的整倍,小人每次只能移动单位长度。
(大概从拓扑学上迷宫都可以约化成这样的规整迷宫?)

Demo中的迷宫很简单,顺序结构可以到达终点,但余想写一个通用的解法,可以解所有迷宫。
一个封闭迷宫,沿着墙的一边走,最终可以回到原点,这构成了一个环。如果目标地在这个环上,无论起始方向如何,都能到达目标地。
方向1:

方向2:

方向1中,即使途中有环,都可以避过捕捉。(1和2无本质区别)

余很快发现这个解法还是不够通用,比如下面的简单迷宫,这种解法永远无法找到目标地

因此这个程序只能对付目标和原点在同一个环的情况,或者说,程序需要依赖原点位置才能找到目标,还没足够通用

5,027 次浏览 | 1 条评论
2012年6月1日 | 归档于 程序

Dewplayer播放列表生成器

实在忍受不了用记事本编辑Dewplayer的播放列表,于是用JavaScript写了一个生成器,顺便练一下JavaScript

使用地址:
http://go.kuyur.info/playlist-creator/

将几个文件(index.html/make.js/style.css)下载回本地也可以运行
支持IE6~IE9,Firefox,Chrome,应该也支持Opera
话说,IE8以下真的和别人格格不入啊,IE9在标准JavaScript支持上改善了不少,但还不够

用法简单说明:

必填:
Track titles:歌曲标题列表
Track URLs:
在选中Pattern模式时,请在单行文本框输入URL模式字符串,形如http://example.com/{##}.mp3。{##}会被替换,从1开始,单位递增1,#的位数是生成数字的位数。
{#}.mp3会产生1.mp3,2.mp3,…,9.mp3,10.mp3,…
{##}.mp3会产生01.mp3,02.mp3,…,09.mp3,10.mp3,…
在选中List模式时,请在多行文本框输入歌曲的URL列表,行数必须和曲目标题列表的行数相同

选填:
(Optional) Album Title:专辑名
(Optional) Album Cover URL:专辑封面图片URL

点击Generate按钮就可以生成支持Dewplayer的xml播放列表。
Save按钮现在无效

有时间大概会做成一个wordpress插件

参考资料:
播放列表xml化的命名空间:http://xspf.org/ns/0/
xml文本的格式化: http://www.cnblogs.com/evlon/archive/2009/01/09/1372283.html

5,514 次浏览 | 5 条评论
2012年3月8日 | 归档于 技术, 程序

Google JavaScript Style Guide 中文简要翻译 – Style Rules部分

JavaScript Style Rules

2.1 Naming 命名
通常地,像下面一样命名:

functionNamesLikeThis
variableNamesLikeThis
ClassNamesLikeThis
EnumNamesLikeThis
methodNamesLikeThis
SYMBOLIC_CONSTANTS_LIKE_THIS

函数名、变量名、方法名:首字母小写
类名、枚举名:首字母大写
常量:恒大写,下划线分割

2.1.1 属性和方法
私有属性、变量和方法应该以下划线结尾
受保护属性、变量和方法不应当以下划线结尾(和公有一样)
关于私有、受保护,参考visibility一节

2.1.2 方法和函数的参数
可选的参数以「opt_」开头
如果函数接受不定参数,应当将此参数放在参数列表的最后,并且以var_args命名
但在函数体内不应使用var_args。使用arguments数组来代替。

2.1.3 属性的Getters和Setters
ECMAScript5的Getters和Setters不建议使用。
如果使用了,那么Getters中不应该修改可见的状态
错误的用法:

/**
 * WRONG -- Do NOT do this.
 */
var foo = { get next() { return this.nextId++; } };
};

2.1.4 存取函数
属性的Getters和Setters不是必需的。
但是,如果要写,那么Getters必须写成getFoo()、Setters必须写成setFoo(value)的形式。
布尔型的Getters可以写成isFoo(),并且通常看起来更自然。

2.1.5 命名空间
JavaScript没有原生的包或命名空间机制。
全局的命名冲突很难debug,而且在两个工程合并时会导致棘手的问题。
我们采取约定俗成的方式来防止冲突来实现代码重用。

2.1.5.1 为全局的代码使用命名空间
总是使用和工程名或类库名相同的词作为(伪)命名空间的前缀。
例如,如果你正在写一个叫”Sloth”的工程,合理的(伪)命名空间应该是sloth.* :

var sloth = {};

sloth.sleep = function() {
  ...
};

许多JavaScript类库提供创建命名空间的高级函数,如the Closure LibraryDojo toolkit

goog.provide('sloth');

sloth.sleep = function() {
  ...
};

2.1.5.2 明晰命名空间的所有权
当创建子命名空间时,确保父命名空间的所有人知道你在干什么。
例如,当你开始一个为sloths创建hats的工程时,确保Sloth团队知道你在使用sloth.hats命名空间

2.1.5.3 内部代码不能使用和外部代码相同的命名空间
外部代码是指不在你的代码库中的代码,它们独立地编译(通常是引入的第三方类库,但也有可能是别的团队写的类库)。
内部代码和外部代码的命名空间要严格分离。
比如,一个外部库定义了foo.hats的命名空间,foo.hats.*变得可用,
但你的代码不能在foo.hats下面定义任何符号。
错误的写法:

foo.require('foo.hats');

/**
 * WRONG -- Do NOT do this.
 * @constructor
 * @extend {foo.hats.RoundHat}
 */
foo.hats.BowlerHat = function() {
};

如果需要在外部命名空间中定义新的API时,应当显式的导出API函数(并且仅导出这些函数)
内部代码在调用这些函数时仍然使用内部名称,这可以保持一致并且编译器可以优化它们:

foo.provide('googleyhats.BowlerHat');

foo.require('foo.hats');

/**
 * @constructor
 * @extend {foo.hats.RoundHat}
 */
googleyhats.BowlerHat = function() {
  ...
};

goog.exportSymbol('foo.hats.BowlerHat', googleyhats.BowlerHat);

2.1.5.4 别名以增加可读性
使用本地别名替代全修饰名,以增加可读性。
本地别名必须和全修饰名的最后部分吻合。
例如:

/**
 * @constructor
 */
some.long.namespace.MyClass = function() {
};

/**
 * @param {some.long.namespace.MyClass} a
 */
some.long.namespace.MyClass.staticHelper = function(a) {
  ...
};

myapp.main = function() {
  var MyClass = some.long.namespace.MyClass;
  var staticHelper = some.long.namespace.MyClass.staticHelper;
  staticHelper(new MyClass());
};

不要别名命名空间。
错误的写法:

myapp.main = function() {
  var namespace = some.long.namespace;
  namespace.MyClass.staticHelper(new namespace.MyClass());
};

避免直接使用别名的属性,除非它是一个枚举
正确的写法:

/** @enum {string} */
some.long.namespace.Fruit = {
  APPLE: 'a',
  BANANA: 'b'
};

myapp.main = function() {
  var Fruit = some.long.namespace.Fruit;
  switch (fruit) {
    case Fruit.APPLE:
      ...
    case Fruit.BANANA:
      ...
  }
};

错误的写法:

myapp.main = function() {
  var MyClass = some.long.namespace.MyClass;
  MyClass.staticHelper(null);
};

永远不要在全局环境创建别名。请仅在函数内使用它们。

2.1.6 文件名
文件名保持全小写,以避免在大小写敏感的平台上发生混乱。
文件名的扩展名应该是js,除在分隔时使用「-」或「_」外,不应包括其他的标点符号。
(优选使用「-」)

2.2 自定义的toString()方法
必须总是能成功调用并且没有任何副作用。
自定义toString()是好的,但必须保证:
(1)总是能成功调用
(2)没有任何副作用
如果你的toString没有满足这些要求,很容易就会导致严重问题。
例如,如果toString()里调用了一个做断言的方法,断言失败时可能会尝试输出发生失败的类的名称(跟踪失败发生在哪里),而输出类名当然又要调用到toString()

2.3 延迟初始化
可以。
不总是可能在声明时就初始化变量,所以延迟初始化时可以的。

2.4 Explicit scope
总是使用显式范围
(待译)
Always use explicit scope – doing so increases portability and clarity. For example, don’t rely on window being in the scope chain. You might want to use your function in another application for which window is not the content window.

2.5 代码格式化
基本上使用C++的格式化规则
除了下面几点以外。

2.5.1 大括弧
为了避免解析器隐含的插入分号,总是在入口的那一行开始大括弧。
例如:

if (something) {
  // ...
} else {
  // ...
}

2.5.2 数组和对象的初始化
当一行能写得下时,可以全部写在一行:

var arr = [1, 2, 3];  // 「[」后面和「]」前面没有空格!
var obj = {a: 1, b: 2, c: 3};  // 「{」后面和「}」前面没有空格!

多行数组的初始化则缩进2个空格,和块的方式类似,例如:

// Object initializer.
var inset = {
  top: 10,
  right: 20,
  bottom: 15,
  left: 12
};

// Array initializer.
this.rows_ = [
  '"Slartibartfast" <fjordmaster@magrathea.com>',
  '"Zaphod Beeblebrox" <theprez@universe.gov>',
  '"Ford Prefect" <ford@theguide.com>',
  '"Arthur Dent" <has.no.tea@gmail.com>',
  '"Marvin the Paranoid Android" <marv@googlemail.com>',
  'the.mice@magrathea.com'
];

// Used in a method call.
goog.dom.createDom(goog.dom.TagName.DIV, {
  id: 'foo',
  className: 'some-css-class',
  style: 'display:none'
}, 'Hello, world!');

书写属性的初始化时不要对齐
正确的写法:

CORRECT_Object.prototype = {
  a: 0,
  b: 1,
  lengthyName: 2
};

错误的写法:

WRONG_Object.prototype = {
  a          : 0,
  b          : 1,
  lengthyName: 2
};

2.5.3 函数参数列表
如果可能,全部的参数都写在同一行。
但当一行超过80个字符位,为了更加容易阅读,必须换行。
为了节省空间,可以尽量写满80字符,也可以为了更加易读,每行只放一个参数。
每行缩进4个字符位,或者对齐圆括弧。

一些例子:

// Four-space, wrap at 80.  Works with very long function names, survives
// renaming without reindenting, low on space.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
    veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
  // ...
};

// Four-space, one argument per line.  Works with long function names,
// survives renaming, and emphasizes each argument.
goog.foo.bar.doThingThatIsVeryDifficultToExplain = function(
    veryDescriptiveArgumentNumberOne,
    veryDescriptiveArgumentTwo,
    tableModelEventHandlerProxy,
    artichokeDescriptorAdapterIterator) {
  // ...
};

// Parenthesis-aligned indentation, wrap at 80.  Visually groups arguments,
// low on space.
function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo,
             tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) {
  // ...
}

// Parenthesis-aligned, one argument per line.  Visually groups and
// emphasizes each individual argument.
function bar(veryDescriptiveArgumentNumberOne,
             veryDescriptiveArgumentTwo,
             tableModelEventHandlerProxy,
             artichokeDescriptorAdapterIterator) {
  // ...
}

当调用的函数本身已经缩进时,参数列可以选择再缩进4个字符也可以选择对齐函数
例如,下面的几种写法都是可以的:

if (veryLongFunctionNameA(
        veryLongArgumentName) ||
    veryLongFunctionNameB(
    veryLongArgumentName)) {
  veryLongFunctionNameC(veryLongFunctionNameD(
      veryLongFunctioNameE(
          veryLongFunctionNameF)));
}

2.5.4 匿名函数的传入
当在参数列表中声明匿名函数时,为了使匿名函数更可读(避免整个函数块显示在右半屏幕),
匿名函数体应当从调用语句的位置起再缩进2个字符位,
或者从函数的声明位置起缩进2个字符位。
例如:

// 匿名函数在调用语句的同一行中声明
prefix.something.reallyLongFunctionName('whatever', function(a1, a2) {
  if (a1.equals(a2)) {
    someOtherLongFunctionName(a1);
  } else {
    andNowForSomethingCompletelyDifferent(a2.parrot);
  }
});

// 匿名函数另起一行声明
var names = prefix.something.myExcellentMapFunction(
    verboselyNamedCollectionOfItems,
    function(item) {
      return item.name;
    });

2.5.5 更多的缩进
实际上除了数组/对象的初始化以及传递匿名函数外,所有的换行都应该:
要不左对齐上一行的表达式,
要不缩进4个字符。而不是缩进2个字符。

例如:

someWonderfulHtml = '' +
                    getEvenMoreHtml(someReallyInterestingValues, moreValues,
                                    evenMoreParams, 'a duck', true, 72,
                                    slightlyMoreMonkeys(0xfff)) +
                    '';

thisIsAVeryLongVariableName =
    hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine();

thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() +
    thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore();

someValue = this.foo(
    shortArg,
    'Some really long string arg - this is a pretty common case, actually.',
    shorty2,
    this.bar());

if (searchableCollection(allYourStuff).contains(theStuffYouWant) &&
    !ambientNotification.isActive() && (client.isAmbientSupported() ||
                                        client.alwaysTryAmbientAnyways())) {
  ambientNotification.activate();
}

2.5.6 空白行
使用空白行将逻辑上相关的代码分组。
例如:

doSomethingTo(x);
doSomethingElseTo(x);
andThen(x);

nowDoSomethingWith(y);

andNowWith(z);

2.5.7 二元和三元运算符
总是将运算符集中在一行,这样就不用考虑解析器隐含插入分号的问题。
否则换行和缩进就要遵守上面的规则。

var x = a ? b : c;  // 如果写得下,都写在一行

// 缩进4个字符位也可以
var y = a ?
    longButSimpleOperandB : longButSimpleOperandC;

// 或者缩进时对齐第一个操作数
var z = a ?
        moreComplicatedB :
        moreComplicatedC;

2.6 圆括号
仅在语法或语义需要时候使用。

永远不要为一元运算符例如delete、typeof、void使用圆括号。
也不要在关键字例如return、throw、case、in、new后面使用圆括号。

2.7 字符串
选择使用单引号「’」。
为了保持兼容,推荐选择「’」而不是「”」,在创建包含HTML的字符串时更容易:

var msg = 'This is some HTML';

2.8 可见性(私有域和受保护域)
推荐使用JSDoc的@private和@protected标签,以指示类、函数和属性的可见性。

在使用Closure Compiler编译时,参数「–jscomp_warning=visibility」可以让编译器检测到违反可见性时发出警告。

标记为@private的全局变量和函数仅允许被相同文件中的代码使用。

构造函数如果以@private标记,即意味着只能在同一文件中创建类实例时被调用,
或者在实例化类的静态实例成员时被调用。(防止类在文件外被实例化)
同一文件中的静态属性和instanceof操作符也仍可访问构造函数。

全局的变量/函数/构造函数不应该标记@protected。

// File 1.
// AA_PrivateClass_ and AA_init_ are accessible because they are global
// and in the same file.

/**
 * @private
 * @constructor
 */
AA_PrivateClass_ = function() {
};

/** @private */
function AA_init_() {
  return new AA_PrivateClass_();
}

AA_init_();

@private的属性可被同一文件的任何代码访问,同时也可被拥有这个属性的类的静态方法或实例方法所访问(跨文件时)。
但不能在另外一个文件中被子类所访问或复写。

@protected的属性可被同一文件的任何代码访问,同时也可被派生子类的静态方法或实例方法所访问。

注意JavaScript的这些标签和C++/Java不同。
JavaScript的private和protected允许从同一文件中的任何地方访问,不仅仅是同一个类内。
同样,和C++不一样的是,private的属性不允许被子类复写。

// File 1.

/** @constructor */
  AA_PublicClass = function() {
};

/** @private */
AA_PublicClass.staticPrivateProp_ = 1;

/** @private */
AA_PublicClass.prototype.privateProp_ = 2;

/** @protected */
AA_PublicClass.staticProtectedProp = 31;

/** @protected */
AA_PublicClass.prototype.protectedProp = 4;

// File 2.

/**
 * @return {number} The number of ducks we've arranged in a row.
 */
AA_PublicClass.prototype.method = function() {
  // Legal accesses of these two properties.
  return this.privateProp_ + AA_PublicClass.staticPrivateProp_;
};

// File 3.

/**
 * @constructor
 * @extends {AA_PublicClass}
 */
AA_SubClass = function() {
  // Legal access of a protected static property.
  AA_PublicClass.staticProtectedProp = this.method();
};
goog.inherits(AA_SubClass, AA_PublicClass);

/**
 * @return {number} The number of ducks we've arranged in a row.
 */
AA_SubClass.prototype.method = function() {
  // Legal access of a protected instance property.
  return this.protectedProp;
};

注意在JavaScript中,没法分别一个类型(例如AA_PrivateClass_)和这个类型的构造函数。
也没有方法表明一个类型是公有的,而同时它的构造函数是私有的。
(因为给构造函数起个别名就可以轻易绕过编译器的安全性检查)

注:
JavaScript本身不支持私有、保护、公有机制,如果不是利用闭包来实现,类的成员都是直接可达的。
在注释中添加JSDoc中约定的标签后,一些编译器可以实现安全性检查,以使得代码更加安全稳定。
但事实上浏览器的JS解析器仍然会忽略所有注释。添加约束标签是为了让开发者遵守约定。
这种保护私有域的书写方式和闭包方式是完全不同的。
阅读全文…

3,956 次浏览 | 没有评论
2012年2月27日 | 归档于 程序

修改Flash MP3 Player JW新添加曲目的插入位置

估计有不少人使用Flash MP3 Player JW作为wordpress博客的播放插件。
xml方式配置播放列表(dewplayer同样支持),另外拖曳式编辑播放列表也算是这个插件的一个特点

余希望新曲插入到播放列表头部, 但插件只支持插入到尾部,再通过拖曳调整,当曲目列表太庞大时很烦,拖半天都拖不到头部

于是暴力修改源码

打开插件下的inc/class.playlist_editor.php,搜索“add new song”,会找到相关代码段
再定位到 (2.3版本是539行)

$list_item.appendTo($songs_list);

很简单,将appendTo函数改成prependTo函数。这些函数是jQuery库操纵DOM元素的函数。

不过暴力修改代码之后想直接插入到尾部就不行了,比较好的方法是加多一个新添加曲目插入位置的配置项
余现在只是用Flash MP3 Player JW作为侧边栏的widget,整碟试听的播放列表如果用Flash MP3 Player JW来编辑很痛苦,因此余不需要新添加曲目插入到尾部的功能。本着够用就是好的原则,这次于是就这样了

整碟试听的播放列表现在是用记事本之类的工具编辑,但已经很烦了,准备写个程序为dewplayer自动生成播放列表

3,029 次浏览 | 没有评论
2012年2月27日 | 归档于 技术