在node-webkit中使用sqlite3

这两天在调研使用node-webkit开发Mac应用并且提交到Mac App Store的可能性。这类客户端应用有非常通用的一点就是需要一个本地数据存储功能。根据node-webkit的官方wiki,我觉得最适合的就是所谓的Web SQL Database了,同时文档中也说是使用sqlite3来实现的。因此就需要实现sqlite3与node-webkit的整合。

为了测试,我直接clone了一个windows版的demo。最开始我的尝试是使用node-webkit 0.9.2版(使用最新版的目的是为了保证node-webkit没有使用一些过时的或者private的api导致MAS审核悲剧)。首先我用npm install sqlite3安装了sqlite3 for node,结束之后运行nw node-webkit-sqlite3-windows-demo,结果提示error:

Error: Cannot find module '/Users/voidmain/WorkSpace/NodeJS/node-webkit-sqlite3-windows-demo/node_modules/sqlite3/lib/binding/node-v11-darwin-ia32/node_sqlite3.node'

如果直接google这个错误会有人说明使用nw-gyp rebuild --target=<node-webkit-version> --arch=ia32来rebuild一下(如果没有安装nw-gyp的话需要先npm install一下),但是使用node-webkit 0.9.2的话,编译不会通过,会提示有error,于是继续google,终于在sqlite3项目的issue 265中看到了这个:

node-webkit 0.9.2 with sqlite3

看来是死胡同了,无奈只能使用更早版本的node-webkit,所以我换成了node-webkit 0.8.6版,然后再次用nw-gyp编译,还是出错,错误是Undefined variable module_name in binding.gyp while trying to load binding.gyp,继续google,最后又在wiki中找到了解答:Build native modules with nw gyp,关键的一段是:

For some packages you may need to use node-pre-gyp (e.g. when you get the error “Undefined variable module_name in binding.gyp while trying to load binding.gyp”), which supports building for both node.js and node-webkit by using either node-gyp or nw-gyp.

1
2
$ npm install node-pre-gyp
$ node-pre-gyp build --runtime=node-webkit --target=0.3.3

至此,再次编译就能通过,而且运行无压力了。但是仍然有两点顾虑,首先0.8.6版本的node-webkit是否对Mac App Store友好,其次,在sandboxing环境下数据库文件存储的位置问题。为了解决这两个疑惑可能只能通过一下小应用来测试一下了。有后续进展我会继续更新blog。

Share Comments

NSUserDefaults无法保存?

FML。。花了一上午调了一个bug。。

事情是这样的,我正在写的这个mac应用用到了Core Data,所以就把Core Data的文件放到了~/Library/Container/my.app.container这个目录下。但是在开发的过程中entity的结构总会发生变化,在基本稳定之前我也不想写升级那些,所以就偷懒把container目录给删了

上午在用NSUserDefaults保存用户的选项的时候,当前保存成功,调用[[NSUserDefaults standardUserDefaults] synchronize]也返回YES,但是就是重启应用之后保存的内容就消失了。去~/Library/Preferences目录下找也确实没有对应的文件。

调了一上午,尝试了各种解决方案,也没搞定,最后终于在这个SO问题里面找到了答案

关键是answer下面的第一个comment:

Also if you move the container while testing / debugging to the trash, the cfprefsd (see Activity Monitor) still keeps a link to the .plist. Empty the trash and force quit both cfprefsd (user and root).

用ps一看果然有2个cfprefsd,有一个应该就是之前删除container的时候留下的,把它kill了,然后重试就好了!

感谢 @mahal,真是救了我一命!

Share Comments

关于Leadership

今天被朋友推荐看了一段视频,叫”Leadership From A Dancing Guy”:

youtube地址:

youku地址:

无解说加长版地址:






我的思考

  • 如果想要引发一场潮流的话,首先你要有勇气站出来,表达自己的观点
  • 你的观点(产品)必须容易被其他人接受(所谓easy to follow)
  • 作为最开始的leader,你需要尝试辅导最开始的几个follower,因为他们很重要(if the leader is the flint,the first follower is really the spark)
  • 如果想要引领潮流,不要想着“你”到底有多重要,重要的是这场潮流(movement)
  • 作为leader不要怕失败,从那个加长版本可以看到,他最开始试图教好几个人如何跟他一起跳他的舞蹈,但是都失败了,可是他仍然自娱自乐,没有停下来
  • 换位思考,如果你看到有人在尝试引发一场潮流,而你觉得他的行为能吸引你,不要犹豫,站出来成为first followers,帮助一下leader吧!
Share Comments

How to Use New Android Animation API for Compat Fragment

Why

Since honeycomb (API level 11), Google has introduced a new set of APIs to help developers build better app, including Fragment and property animation.

These new APIs are great, but they are not quite compatible with older devices, thus Google provided a support library to solve this problem.

While building my app, I’m trying to use the compat fragment to support as many devices as possible, meanwhile, I want to take advantage of new set of animation apis to create animation easier.

The FragmentTransaction class has a simple API named setCustomAnimations() to set the animation while switching fragments. But, sadly, when I tried to use the animator xml file (I mean, using property animation elements like <objectAnimator> in the animation xml) on Gingerbread emulator (with API level less than 11), the app simply crashed complaining it does not understand the objectAnimator sh*t, which means you have to give up on the new animation API with compat fragment.

No way!

And finally, I managed to find a way to get this work. Here’s what I’ve tried.

If you are urged to see a demo, please check the readme of this sample on github.

Starting with a simple fragment

Since this is a experimental project, I don’t want to build some complex fragments, that’s why I decided to use RoundedColourFragment from ActionBarSherlock project.

The fragment is quite simple, it will generate a colored rounded rectangle as the view of the fragment, whose color can be specified as a argument of the constructor.

Importing NineOldAndroids

In order to use the new animation api on older versions of android, I found of the NineOldAndroids project.

Even though I didn’t quite get its name (sad), the project is really awesome. It somehow managed to give the developer a way to use the new animation apis even on Android 1.0, amazing. It’s fun, but the only way is you cannot put them in XML files, still need to work out how to use the animation.

Two parts of the animation

Not being able to use the animation in XML files, I have to write some code to make this happen. Here’s a methods I defined to trigger the new animation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.ObjectAnimator;
……
private void flipit() {
ObjectAnimator visToInvis = ObjectAnimator.ofFloat(frontFrag.getView(),
"rotationY", 0f, 90f);
visToInvis.setDuration(300);
visToInvis.setInterpolator(accelerator);
visToInvis.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
FragmentTransaction ft = getSupportFragmentManager()
.beginTransaction();
ft.replace(R.id.root, backFrag);
ft.commit();
}
});
visToInvis.start();
}

This is the first part of the animation, the old fragment fliped to disappear by changing the rotationY property from 0 to 90 degree. Once this animation is finished, I need to replace the old fragment with the new one by calling FragmentTransaction#replace method.

But we cannot simply let the new fragment appear and be there, it will be awkward. So, I’ve designed a callback that will be triggered once the new fragment’s view is ready, the code is listed below.

1
2
3
4
5
6
7
8
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mListener != null) {
mListener.onViewCreated();
}
}

Once the view is created, I’ll kick off the second part of the animation, which changes the rotationY property from -90 degree to 0. Here’s the code.

1
2
3
4
5
6
7
8
9
10
@Override
public void onViewCreated() {
View v = backFrag.getView();
final ObjectAnimator invisToVis = ObjectAnimator.ofFloat(v, "rotationY",
-90f, 0f);
invisToVis.setDuration(300);
invisToVis.setInterpolator(decelerator);
invisToVis.start();
}

And now, the animation is working perfectly.

Show me the code

I’ve created a project on github, called FragmentCompatAnimator, make sure to check it out.

Conclusion

It is always a good idea to use new APIs for better performance and use experience. But compatibilty is a tough problem. Thanks to all opensource framework contributors, it is and will be easier for us developers to take advantage of the new APIs but still be able to make it work on older devices.

Share Comments

[更新-教程]如何在Mac上玩儿国服星际2(原生,非port版)

##更新
前一阵重装了一次星际2,发现可以直接通过暴雪登陆器mac版安装,所以之前的方法就作废了,登陆器使用方法请参考官方博客

–原文–

星际争霸2更新虫心之后,以前的通过region.xml在Mac上移植方法好像失效了,好不容易找到了一种解决方案,把我操作的流程写下来,希望能对大家有帮助。话不多说,进入正题。

准备工作

要实现这个流程准备工作的任务还是挺重的,主要分为一下几步:

~~- 在Windows上下载一个星际2的客户端,用网易提供的绿色版就行。

  • 在Mac上下载台湾(美国应该也行,我没测试过)服务器的Mac客户端。在Mac上进行安装,注意,不用全部更新完成,更新至绿色可执行状态即可。
  • 下载Support.dmgSC_II_apps&toolsCN[2.0.6.25180].dmg两个文件,注意,不要用浏览器打开,用迅雷直接下载URL。~~

开始替换

~~以上内容全部下载完成后就要开始操作了:
1.把国服绿色版压缩包解压。
2.把Support.dmg和apps&tools.dmg里面的内容复制到解压路径下,必要时替换对应内容。
3.把Mac上台湾客户端下的StarCraft IIStarCraft II EditorStarCraft II Public TestVersions的内容复制&替换到解压路径下。
4.启动游戏,这个时候游戏的客户端可能会一直停在初始化阶段,可以从菜单中选择修复客户端,然后静静的等着它完成更新。
5.更新结束后,点击执行,这时候发现,客户端的右边多了中国特色的几行字,恭喜你,成功了!
~~

说明

我之前使用了挺多种方法的,可能在某些步骤上由于之前方法遗留的内容导致与各位的流程不太一致,但基本思想应该是通用的。另外,坛子里面有人说不能更新,我倒是没有这个问题。

致谢

这篇博客的方法主要依赖于这个博文,感谢大神提供的方法。最后上图,并祝大家gl hf
在Mac上运行星际2的接图

Share Comments

[教程]如何在Mac上玩儿国服Dota2(非port版)

很多人不买Mac的原因(之一)是Mac上没啥游戏,这一点确实没错,不过仔细想想大家有多少时间玩儿那么多游戏呢?其实平时有一两个长玩的游戏也就不错了。另一方面,很多著名游戏例如WOW,Dota2,SC2,D3等等都有原生的Mac版,本来就没有太大的游戏障碍。

但是这些游戏都没有原生的国服版本,真是悲哀啊。于是Mac党只能自立更生,寻找解决方案,这篇博文就是教大家如何在Mac上玩儿原生的Dota2。接下来还会写一篇关于如何在Mac上玩儿原生国服星际2的教程。

准备工作

言归正传,要想在Mac上玩国服Dota2,首先需要有一个完美世界的帐号,以及Dota2激活码(等公测了这个激活码应该也就不是必须的了)。为了进入游戏,还需要一个Steam帐号。有了这些就万事具备了。

绑定帐号

在Mac上安装Steam平台,并用自己的steam帐号登陆,这个时候直接搜索Dota2应该是无法找到的,好像是对中国地区不开放的原因,所以需要在朋友或者自己的windows电脑上,下载国服的Dota2客户端,然后用Steam帐号登陆,并绑定自己的完美世界帐号以及Dota2激活码。

下载内容

绑定结束后,再次登陆Steam平台,就会看到Dota2和Dota2 Test两个游戏,如下图。其中Dota2 Test是Beta版本的Dota2,一般用于公测,注意:下载这个版本的Dota2是无法登陆国服的,因为国服没有对应的服务。所以咱们直接下载Dota2就可以了。等待下载完成。
下载Dota2客户端内容

设置参数

下载完成后,在Dota2这一项目上点击右键,选择Properties选项,点击Set Launch Options按钮,在弹出框中输入:-perfectworld steam,然后点OK,如下图。这样一来,就指定了Steam运行的是完美世界的服务,也就是国服了。
下载Dota2客户端内容

Have Fun

准备就绪后就可以启动游戏了,在我的本子上测试效果还可以,稍微有点模糊,另外,因为Steam上下载的内容是英文版的,所以人物的对话和配音都是英文的,这一点应该可以通过将国服对应文件复制过来解决,但是我没有具体检查文件列表,有兴趣的朋友可以研究下,并且欢迎给我留言,我好即时补充。借用Dota蛋疼集锦里面小Y的话吧,Don’t worry, be happy!
进入游戏后的画面

Share Comments

What I have learned from terminal-notifier

The terminal-notifier is a cute command-line tool written by alloy that sends User Notifications for OS X systems running 10.8 or later.

There’s not so much code down there, but still I’ve learned a lot, and here’s what I’ve got:

Architecture Overview

There are two major parts in this project. To deliver NSUserNotification or respond to user click event, we need a Cocoa App even though there will never be a display window or something. According to the builder, it is currently packaged as an application bundle, because NSUserNotification does not work from a ‘Foundation tool’.

To provide a easier way to install and use terminal-notifier, the project also provides a ruby gem, whose source is located at Ruby/ folder of the project.

As for the communication, I’ve managed to draw a small graph below:

Overview

The ruby gem is in charge of providing a command-line interface. Once the arguments are collected, it will call the Cocoa App’s bin file with extra arguments. By then, the Cocoa App will read the arguments and deliver the notification to user.

That’s quite straight-forward, yet the result is amazingly powerful. Let me tell you the technique details I’ve learned below.

Lesson #1 - Are we running this app on OS X 10.8 or later?

Easy, but you need to know two commands, namely uname and sw_vers. You can refer to man to see what exactly they are doing, briefly, uname tells the name of the operating system, which is Darwin for OS X, and sw_vers returns the version info for OS X.

And here’s the code from terminal-notifier:

1
@available = `uname`.strip == 'Darwin' && `sw_vers -productVersion`.strip >= '10.8'

This is ruby code, and it’s easy to understand I believe.

Lession #2 - Don’t forget some useful variables

Just a quick flash back, $0 refers to the name of current running script, $: is the same as $LOAD_PATH and $? is the status code of the last process terminated. For a detailed list, please refer this article.

Lession #3 - NSUserDefaults will help you with the command-line arguments

Parsing command-line arguments is not an unusual job, there are surely a lot of ways to do this in all programming languages. As I’m new to Cocoa Programming, I DID NOT know that one can read command-line arguments using NSUserDefaults. Imagine how I felt lost while reading the following code:

1
2
3
4
5
6
7
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *subtitle = defaults[@"subtitle"];
NSString *message = defaults[@"message"];
NSString *remove = defaults[@"remove"];
NSString *list = defaults[@"list"];
NSString *sound = defaults[@"sound"];

Who the heck fills the standardUserDefaults for us with the command-line arguments? It’s impossible.

But, indeed, it is possible. I’ve found this article that explains the defaults domain and their precedence. As it turns out, it is the NSArgumentDomain that does the heavy-lift job of parsing the arguments and store them into the standardUserDefaults. Note that this is quite a good way, espically overriding the system wide defaults with command line arguments. It is a way of hacking something, I guess.

Lession $4 - Easier API with objectForKeyedSubscript:

What if you want to grab some object using [] operator, by that I mean instead of retrieving object like dict[@"key"] rather than [dict objectForKey: @"key"]. And again, it’s simple, just implement the objectForKeyedSubscript: method for the class or add category with some predefined classes. Here’s the example from terminal-notifier:

1
2
3
4
5
6
7
8
@interface NSUserDefaults (Subscript)
@end
@implementation NSUserDefaults (Subscript)
- (id)objectForKeyedSubscript:(id)key;
{
return [self objectForKey:key];
}

With this code snippet, we add the power of subscription to NSUserDefaults, and it’s fun to use.

Conclusion

I’ve learned the 4 major lessons from terminal-notifier, but there are many other lessions I’ve not mentioned. And to write better code, we should all read more f**king code.

Share Comments