2012/12/30

北海岸一日遊

跨年前的衝動 有如行軍般的旅程
一瓶水 一張不怕晒的臉 我出發了
相當充實的一天



從汐萬路上山
拉瓦那咖啡

風櫃嘴

冷水崛

野柳

金包里老街

朱銘博物館

十八王宮廟

石門風力發電

石門劉家肉粽

老梅綠色礁岩海岸

富貴角燈塔

富基漁港

石門婚紗廣場


白沙灣、淺水灣


漁人碼頭



環了整個台北市,全長約80公里,可以從一早玩到晚上 :)

2012/12/27

宜蘭拜訪神秘海灘

自從用了 Evernote,我的生活變得更輕鬆了,像是旅遊規劃就非常適合,先在電腦上編輯,把火車時刻表,景點、美食搭配 google map 快照,記事本就像旅遊指南,搭配 iPhone 的行動版 Evernote,暢行無阻呀!


宜蘭是我一直想來的地方,這次我試著一個人旅行,從台北搭著慢車,來到外澳車站。
走出車站,跨越一條馬路就來到海邊了,從來不知道有這麼愜意的地方,很久沒看海了整個心都開闊起來

海岸對面就是龜山島


沿著海灘一路往南走,真的蠻漂亮的

映著湖面的蘭陽博物館 是那麼美

沿途還會經過伯朗咖啡服務區,可以停留拍個照

來到頭城車站 從外澳走過來約2公里多
停留了一會 吃個小吃 看看老式的街道


搭上火車來到南澳 準備尋找神秘海灘 是這次來的目的



沒有交通工具 只能靠兩條腿啦
走過海岸大橋 神秘海灘就在出海口右手邊的方向
沿途上還有小黑跟小黃陪伴

就是這邊了吧 佈滿礫岩的海灘
沿著海岸往南走 尋找海蝕洞
陡峭的山壁 上面就是蘇花公路


就是這邊了 海蝕洞
GPS: N24'24'43' E121'47'30''
大浪侵蝕的力量果然果然很驚人

從南澳車站走到海蝕洞要 7 公里,建議還是用開車的
從『旅人Carrie愛漫遊 』得知,最大的海蝕洞有20公尺高,要沿海岸走4.3公里
只能下次再來拜訪了,還有一個湧泉湖這次也沒找到 :P

另外要注意一下季節的潮汐 如果漲潮太高通行就會有困難

2012/10/23

PhoneGap - Cross platform solution for mobile app - Hello world

跨平台的 mobile solution 越來越多種, google 一下就可以找到人家推薦或比較的文章:



其實大部分都是用純網頁的方式來開發:HTML5 + Javascript,好像只有 Titanium 會把 project 轉成 native code 再編譯,而 PhoneGap 則是為每個平台開發了 framework,提供存取硬體資源的界面,而 UI 層一樣用網頁的方式呈現。
效能理論上是 native code 好些,但目前我的需求在於呈現一些靜態的資料,不需要大量的運算或是圖形成像,選擇了 Phonegap + jQuery mobile 來玩玩看。



下列記錄一個專案產生及編譯的過程:

2.1.0 版 (目前最新版)

1. 安裝方式跟以往不同,沒有 dmg 檔,改採 command line 的方式,可以先看一下官網的 Getting Started with iOS,整理步驟如下:
  • 安裝 Xcode Command Line Tools
  • 下載 cordova source,並解壓縮
  • 進到目錄裡執行下列 script,創建 iOS 專案
$ cd ./phonegap-phonegap-26d211b/lib/ios/bin
$ ./create ~/my-phonegap com.phonegap my-phonegap
$ ./update_cordova_subproject ~/my-phonegap/my-phonegap.xcodeproj/

2. 編譯
開啟剛剛產生出來的 xcode project,從圖中可以看到裡面還包含 CordovaLib,先編譯 CordovaLib 再編譯自己的專案

順利的話就會 deploy 到模擬器上了

註:預設是把 ARC 打開的,所以如果 target iphone 上面沒有支援,就會有下面的錯誤發生。Disable ARC 不是一個好方法,如果有需要可以使用舊版的 library。
dyld: lazy symbol binding failed: Symbol not found: _objc_retain
  Referenced from: /var/mobile/Applications/81EB9778-1819-454F-BF09-5D7CA2706A1A/phonegap.app/phonegap
  Expected in: /usr/lib/libobjc.A.dylib
dyld: lazy symbol binding failed: Symbol not found: _objc_storeStrong
  Referenced from: /var/mobile/Applications/81EB9778-1819-454F-BF09-5D7CA2706A1A/phonegap.app/phonegap
  Expected in: /usr/lib/libobjc.A.dylib

3. 再來就可以參考 API 去撰寫自己的 app 了,資料放在 www 這 folder 底下即可



1.8.0 版

1. 下載 source 後直接執行 phonegap-phonegap-2b7ffca/lib/ios/Cordova-1.8.0.dmg 

2. 安裝後開啓 xcode 就可以看到 Cordova-based Application 的專案可以新增

3. 編譯並執行

出現了錯誤?
ERROR: Start Page at 'www/index.html' was not found
原來是 www 加進專案的方式有問題,正確的步驟如下:在專案按右建選擇"Add file to (project name)",選擇 www 資料夾,並勾選 "Copy items into destination group's" folder (if needed)",Folders 選擇 "Create folder references for any added folders"

再編譯一次就沒有問題了!

2012/08/26

Nginx load balance

Nginx 內建的 load balance 規則有 round-robin、weight、ip_hash、fair、url_hash
但如果想更精確的把特定 request 導到特定的 server,可以自己來寫 rule。

例如想用 user id 做 despatch,下面的例子是利用 user id 的開頭第一個字元來做決定
server {
    listen 80;
    server_name www.domain.com;

    location ~* /profile/[a-m] {
        proxy_pass http://server0.domain.com;
    }

    location ~* /profile/[n-z] {
        proxy_pass http://server1.domain.com;
    }    
}

如此一來
http://www.domain.com/brian 就會導向 server0
http://www.domain.com/vicky 就會導向 server1



如果 user id 是流水號,那我們就判斷 id 的個位數字
server {
    listen 80;
    server_name www.domain.com;

    location ~* /profile/[0-9]*[02468]$ {
        proxy_read_timeout 10s;
        proxy_pass http://server0.domain.com;
    }

    location ~* /profile/[0-9]*[13579]$ {
        proxy_pass http://server1.domain.com;
    }
}

http://www.domain.com/2000 就會導向 server0
http://www.domain.com/2001 就會導向 server1



如果 request 是 http long polling,可以考慮用301 Moved Permanently 的方式讓 client 的 connection keep 在 target server,減少 nginx 的 connection 數量
server {
    listen 80;
    server_name www.my-domain.com;

    location ~* /profile/[a-m] {
        rewrite ^(.*) http://server0.domain.com$1 permanent;
    }

    location ~* /profile/[n-z] {
        rewrite ^(.*) http://server1.domain.com$1 permanent;
    }    
}

有關 location rule 可以參考:高飞鸟 – Highbird » Blog Archive » Nginx的location匹配规则

2012/08/22

在 iOS 平台加入 Flurry 廣告

Flurry

1. 註冊後,新增 Application,把 API Key 記下來

2. 建立 Ad space


3. 開始寫程式,先下載 SDK,裡面包含教學pdf 與 library
把 Flurry, FlurryAds 拖曳到 project 內

4. 在 app launch 的時候去初始化 flurry
AppDelegate.m
#import "Flurry.h"
#import "FlurryAds.h"

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    [Flurry startSession:@"YOUR_API_KEY"];
    [FlurryAds initialize:rvc];
}

5. 在想呈現廣告的 viewController 加入程式碼
ViewController.m
#import "FlurryAds.h"

- (void)viewWillAppear:(BOOL)animated
{
    [FlurryAds isAdAvailableForSpace:@"FrontPage" view:self.view 
        size:BANNER_TOP timeout:1000];
}

6. 加入額外的 framework
  • MediaPlayer.framework
  • SystemConfiguration.framework

7. Run


參考資料
Publisher/GettingStarted/TechnicalQuickStart - Flurry
iPhoneAgent: FlurryAds Class Reference

2012/08/04

工欲善其事,必先利其器:GDB 基本教學

最基本的編譯時要加"-g",把 debugging information 編譯進去,否則印不出什麼資訊來。再來就是不要做 optimize,不然程式碼行數會對不上。
$ gcc -g -o hello hello.c

啟動 GDB
$ gdb ./hello

必要時加入"-d (dirctory)"的參數,指定 source code 的位置
$ gdb -d /home/brian/test ./hello 

若程式已經在執行了,可以先查詢 pid,然後用 attach 的方式。記得 binary 的要跟正在 run 的 program 一致
$ gdb ./hello 12238

進入GDB後執行程式
(GDB) run
若要帶參數的話
run (init parameter)
Ex:
(GDB) run -h localhost -p 8080

Breakpoint
設定中斷點
b (filename):(line num)
Ex:
(GDB) b hello.c:5

列出目前的中斷點
(GDB) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040049c in main at hello.c:5

移除中斷點
d (breakpoint id)
Ex:
(GDB) d 1

列出當下的狀況
列出 function stack (Back trace)
(GDB) bt
#0  hello () at hello.c:9
#1  0x00000000004004cf in main () at hello.c:14

列出更詳細的資訊,包含 variable 的值
(GDB) bt full
#0  hello () at hello.c:9
        i = 0
#1  0x00000000004004cf in main () at hello.c:14
No locals.

列印變數
p (variable)
Ex:
(GDB) p iValue
(GDB) p *stEmployee

切換 frame。要列印區域變數時,必須要切換到正確的 frame
f (frame num)
EX:
(GDB) f 3

流程控制
Step over (不會進 function)
(GDB) n
Step into (會跳入 function)
(GDB) s
Continue
(GDB) c

Signal Handle
handle (signal) (operation)
Operation 預設為 stop print noignore,也就是遇到 signal 時,GDB 會先攔截,並中斷程式,必要時可以改為 nostop noprint,讓程式本身去處理 signal。若下達 ignore 則是讓程式忽略此 signal。
(GDB) handle SIGUSR nostop noprint

列出目前 signal 設定的狀態
(GDB) i handle
Signal        Stop      Print   Pass to program Description

SIGHUP        Yes       Yes     Yes             Hangup
SIGINT        Yes       Yes     No              Interrupt
SIGQUIT       Yes       Yes     Yes             Quit
SIGILL        Yes       Yes     Yes             Illegal instruction
SIGTRAP       Yes       Yes     No              Trace/breakpoint trap
有關 signal 的部分可以參考 http://sourceware.org/gdb

Thread
查看目前在哪個 thread
(GDB) thread

切換 thread
(GDB) thread 3

列出所有 thread 的 function stack
(GDB) thread apply all bt
(GDB) thread apply all bt full

Ref:
Examining the Symbol Table

在 Mac OS 上使用 Wireshark

Network interface 預設的權限是 root,由於 wireshark 是以 user 的權限來開啓,故無法存取 interface,解決的辦法就是修改權限為大家都可讀。
sudo chmod 644 /dev/bpf*
解決!

2012/08/01

Ignore SIGPIPE in socket programming

通常在寫 socket programming 時,系統對一個 invalid fd (file descriptor) 做寫入時會讓系統丟出 SIGPIPE 的 signal,為了避免程式被中斷,通常都會 ignore 它。

#include <signal.h>
struct sigaction sa;

sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;

sigemptyset(&sa.sa_mask);
sigaction(SIGPIPE, &sa, 0);

2012/07/05

Mac OS X 釋放記憶體

我的 Macbook Pro 已經把 ram 升級到 8GB 了,但長時間使用還是會有不足的現象,打開活動監視器發現大部分的 memory 好像都 cache 住了。

在網路上找到 purge 這個指令,是這麼解釋的:
purge -- force disk cache to be purged (flushed and emptied)
Disk I/O的速度遠小於 Memory,所以 OS 會把常用的資料 cache 一份在 memory,這樣就不用每次都跑去 disk了。而此指令就是把這些 cache 釋放掉摟。


打開終端機執行 purge


Cool! 釋放了大半部的記憶體,終於不用常常重開機了~


參考資料
Using purge to free inactive memory on Mac OS X

2012/06/26

C compile 常見的錯誤

C 語言是我最熟悉的語言,通常我用它的時候都是講求效能,但顧效能的同時也需要花費更多心力(效率降低)。抓 memory leak、segmentation fault 這些都很要命,當然用對工具可以讓你 debug 速度加快很多,gdb、valgrind、oprofile 也都是必學的。由於沒有 OO 概念,常常需要用 structure 跟 point 去模擬物件、封裝,專案寫大的時候就會很難維護。

下面把我常遇到的 compile error 列出來:

No such file or directory
$ gcc -o test test.c
test.c:2:23: error: json/json.h: No such file or directory
test.c: In function 'main':
test.c:8: warning: assignment makes pointer from integer without a cast
test.c:12: warning: assignment makes pointer from integer without a cast
表示 include 的 header file 找不到,預設會去找 /usr/include、/usr/local/include,若 library 安裝在其他目錄,可以在 compile 時加上 -I/opt/json-c-0.9/include。


Undefined reference
$ gcc -o test test.c -I/opt/json-c-0.9/include
/tmp/ccMCrNFo.o: In function `main':
test.c:(.text+0x9): undefined reference to `json_object_new_object'
test.c:(.text+0x17): undefined reference to `json_object_new_string
...
test.c:(.text+0xc1): undefined reference to `json_object_put'
collect2: ld returned 1 exit status
表示 link library 失敗,compile 時要加上 -ljson


Linking fail
$ gcc -o test test.c -I/opt/json-c-0.9/include -ljson
/usr/bin/ld: cannot find -ljson
collect2: ld returned 1 exit status
表示找不到 libjson.so 的 library 檔案,預設的搜尋路徑 /lib、/lib64、/usr/lib、/usr/local/lib, 若 library 安裝在其他地方,在 compile 時要加上 -L/opt/json-c-0.9/lib 來增加搜尋的路徑。


Implicit declaration of function
$ gcc -Wall -o test test.c test2.o -I/opt/json-c-0.9/include -L/opt/json-c-0.9/lib -ljson
test.c: In function 'main':
test.c:23: warning: implicit declaration of function 'sub_func'
出現此 warning,表示你用的 function 在 .c 檔中有定義,但是没有在對應的 header 檔中宣告。
上例中我在 test.c 去 call sub_func() 這個 function,這個 function 是在 test2.c 中 implement,但卻沒有在 test2.h 宣告。


Error while loading shared libraries
$./test
./test: error while loading shared libraries: libjson.so.0: cannot open shared object file: No such file or directory
表示 runtime 時 load 不到指定的 library,環境變數 $LD_LIBRARY_PATH 要加入 share library 路徑,LD_LIBRARY_PATH=/opt/json-c-0.9/lib

2012/06/25

JSON parser in C language - json-c

C 語言存取 JSON 格式的資料實在不是很方便,不像 python、javascript 原生的物件表示法就跟 JSON 一樣。C 的 implementation 在官網列了好幾套,我自己是選用 json-c

API 使用方式可以參考這裡

這邊就列出基本的操作,以這樣為例:
{
  "name": "Brian",
  "sex": 0,
  "data": {
    "education": "master",
    "profession": "engineer"
  }
}

組成 JSON object,並輸出成 string
struct json_object *root, *data;

root = json_object_new_object();
json_object_object_add(root, "name", json_object_new_string("Brian"));
json_object_object_add(root, "sex", json_object_new_int(0));

data = json_object_new_object();
json_object_object_add(data, "education", json_object_new_string("master"));
json_object_object_add(data, "profession", json_object_new_string("engineer"));
json_object_object_add(root, "data", data);

// Output to string
printf("%s", json_object_to_json_string(root));

// Decrease counter and free object
json_object_put(data);
json_object_put(root);

解開 JSON object
struct json_object *root, *name, *sex, *data, *edu, *prof;

root = json_tokener_parse(json_string);
// Use is_error() to check the result, don't use "j_root == NULL".
if (is_error(j_root)) {
  printf("parse failed.");
  exit(-1);
}

name = json_object_object_get(root, "name");
sex = json_object_object_get(root, "sex");
data = json_object_object_get(root, "data");
// If parse fail, object is NULL
if (data != NULL) {
  edu = json_object_object_get(data, "education");
  prof= json_object_object_get(data, "profession");
}

if (!name || !sex|| !edu || !prof) {
  printf("parse failed.");
  json_object_put(root);
  exit(-1);
}

// Fetch value
printf("name=%s", json_object_get_string(name));
printf("sex=%d", json_object_get_int(sex));
printf("education=%s", json_object_get_string(edu));
printf("profession=%s", json_object_get_string(prof));

// Free
json_object_put(root);

參考資料
json-c-0.9库的json_object_object_get()引发崩溃问题

2012/06/22

Webapp2: Hello world

最近又在摸索 Google app engine,webapp2 基本上就是 GAE 內建的 web framework 啦,提供基本的 URL route、request handler、template、database 存取,相容於 WSGI 架構。

這是官方的 Hello world
helloworld.py
import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp2 World!')

app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

app.yaml
application: helloworld
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: helloworld.py

若使用 WSGI 來啓動 server,預設的 configuration file 就是 app.yaml,裡面會指明要執行哪個 python code,像上面就是 helloworld.py 這個檔案。執行後會去尋找"app"這個 object,所以物件的命名不能有錯,不然就會出現錯誤:
ERROR    2012-06-20 07:37:47,561 wsgi.py:189] 
Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 187, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 239, in _LoadHandler
    raise ImportError('%s has no attribute %s' % (handler, name))
ImportError:  has no attribute app


如果不想透過 google app launcher 來啓動 project,請參考這一篇 Quick start (to use webapp2 outside of App Engine),需要額外安裝三個 package。
程式碼如下,要自己另外帶起 http server:
import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp2 World!')
app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

def main():
    from paste import httpserver
    httpserver.serve(app, host='127.0.0.1', port='8080')

if __name__ == '__main__':
    main()

再從 command line 下達指令就可以了
$ python main.py
serving on http://127.0.0.1:8080

2012/06/20

Python 基本開發環境建置

紀錄 python 開發環境建置過程:

1. 安装 Python-2.7.2
$ tar zxvf Python-2.7.2.tgz
$ cd Python-2.7.2
$ ./configure && make && make install

2. 安装 distribute 0.6.27
$ tar zxvf distribute-0.6.27.tar.gz
$ cd distribute-0.6.27
$ python setup.py build
$ python setup.py install

3. 安裝 pip-1.1
$ tar zxvf pip-1.1.tar.gz
$ cd pip-1.1
$ python setup.py build
$ python setup.py install

4. 安裝 virtualenv 並建制 virtual environment
$ pip install virtualenv
$ virtualenv /opt/env

5. 指定 virtual environment 為預設開發環境
$ source /opt/env/bin/activate
可以看到command line提示改變 (pyenv)brian@localhost:~$
之後用 pip 安裝套件就會放在 /opt/env/lib 之下。


建立獨立的 virtual environment 好處就是比較"乾淨",如果你的系統有多個使用者,大家都裝到系統預設目錄則容易搞混。虛擬環境也可以建立多個,若同時跑多個專案或想切換不同 library 版本也很方便。另一方面也可以釐清開發這個專案"最小需求 library"是哪些,將來要 deploy 時也會比較順利。

2012/06/19

ActiveMQ - 訊息傳遞的仲介

ActiveMQ 是一種 Message-oriented middleware,主要是當作不同 process 或不同主機間的溝通媒介。

會用到 queue 的時機點有很多,就我個人工作上的範疇,通常是
1. Web server 短時間內遇到大量的 request,需要先接起來再由後端子系統處理,達到分散式架構
2. 這件事情本來就沒有很急,丟在 queue 裡面慢慢處理就好


類似 ActiveMQ 的 product 還有 RabbitMQZeroMq這邊列出了更多種的選擇。我的需求很基本,通常會考量幾個點:
1. 夠不夠 robust
2. Client端有沒有支援熟悉的語言 (C/C++, Java, .Net, Python, Php, Ruby, ...)
3. 文件寫得夠不夠清楚
4. 有在持續維護中

本來想用 C 來連接 ActiveMQ,但發現所有的 C library都沒在維護了,而且也沒 API 文件可以看,是有 C++ 的 ActiveMQ-CPP 可以用,但我不熟lol,最後選擇了 Python 的,pyactivemq也是一段時間沒更新了,重點是編譯不過,stomppy 或許是個不錯的選擇:)

訊息交換方式可以參考 Messaging pattern 裡面定義的,而我常用的也就下面兩種:
1. Push - Pull
2. Publish - Subscribe
這也是大多 product 都支援的

上述講得是 application 層的 ,至於 ActiveMQ 底層的傳輸 protocol 有二
1. STOMP (Streaming Text Oriented Messaging Protocol)
2. OpenWire protocol
差別在哪不是很清楚...只知道 stomp 是個大家熟知的標準,follow就對了!

意外發現一篇好文章,是在 promote ZeroMQ 的:
新世紀通訊函式庫 – ZeroMQ | 程式設計 遇上 小提琴

--

發現 ActiveMQ-CPP 裡面也有提供 C 的 interface,終於不用被 C++ 所擾,在 tar 檔裡面找不到,在 github 上才有,連結在此

2012/06/11

MySQL 如何避免 SQL injection

遇到這個問題,大部分的人應該會說:用 ORM (Object-relational mapping) 呀!但我就沒有打算多學一套 >< 一方面也是不清楚它欄位的屬性、資料形態是否完全支援 mysql,加上我執行的 query 也蠻複雜的。

先前有找了幾套:
  • SQLAlchemy - 大家比較推薦的
  • Quick ORM - fully compatible with the newest SQLAlchemy

ORM 的好處是你只要設定一次 mapping,之後 select、update、insert 都很方便,但我實在是沒有必要為了一個 injection protect 就用了這麼強大的東西。


除了 ORM 以外...
Search 了一下發現 MySQLdb 本身就有支援以參數的方式來執行 query:
cursor.execute('SELECT * FROM `user` WHERE user=%s AND password=%s;', usr, pwd)
所有的參數都會根據其資料型態來取代 format string,特殊字元自然就會被跳脫了。

要注意的是 statement 內的引號 " 要去掉,format string 通通使用 %s,像下面兩個語法就會有問題:
cursor.execute('SELECT * FROM `user` WHERE username="%s";', username)
cursor.execute('SELECT * FROM `user` WHERE id=%d;', my_id)

另外,也不要把 format string 串起來才傳過去,這樣就會真的發生 SQL injection了:
cursor.execute('SELECT * FROM `user` WHERE user=%s AND password=%s;' % (usr, pwd))

至於其他的 database 可以找看看有沒有 prepared statement 可以使用。


參考資料

Python: 頑固的 variable length argument

這邊卡了很久,做個筆記...

可變長度的參數傳遞
def p(fmt, *arg):
    print fmt % arg

# 傳多個參數
s1 = "hello"
s2 = "world"
p("%s %s", s1, s2)

# 把 list 的 element 當作參數
l = ["Allen", "Billy", "Elmer"]
p("%s %s %s", *tuple(l))

# 混合模式
l = ["Allen", "Billy", "Elmer"]
s = "hello"
p("%s, %s %s %s", *((s, ) + tuple(l)))
Output:
hello world
Allen Billy Elmer
hello, Allen Billy Elmer

2012/06/07

社交網路設計:在地圖中找尋附近的人

很多社交網路App都會用到地圖,以自己為中心,找尋附近的店家、朋友等等。
用程式的邏輯來表達就是:
以 (x, y) 座標為中心,找尋方圓 n 公里以內的點

資料庫不外乎就是儲存經緯度 (longitude, latitude)
-----------------------------
 id | lat       | lng
----+-----------+------------
 1  | 25.151000 | 121.549000
 2  | 25.010000 | 121.574000
 3  | 25.070000 | 121.589000


今天我想找 User1 附近 10km 以內的人
方法一
來看"距離"跟"經緯度"的關係是什麼,從 Decimal degrees - Wikipedia 知道,其實他們並非線性的關係,而是一堆三角函數算出來的,因為隨著緯度越高,單位經緯度所對應的距離就越短。從 別搗蛋 歸納出的對應表:
台灣地區:
兩地經緯度相差 0.5度:距離相差約 50公里
兩地經緯度相差 0.1度:距離相差約 10公里
兩地經緯度相差 0.05度:距離相差約 5公里
兩地經緯度相差 0.01度:距離相差約 1公里

2012/06/01

Mac OS 內建抓圖功能 (Print Screen)

抓圖指令
把抓圖儲存到桌面 (Command 就是蘋果鍵"⌘")
  • Command-Shift-3: 抓全螢幕
  • Command-Shift-4: 可用滑鼠自己選取範圍
  • Command-Shift-4, 再按Space: 抓取視窗元件

和上面一樣,只是儲存到剪貼簿
  • Command-Control-Shift-3
  • Command-Control-Shift-4
  • Command-Control-Shift-4, 再按Space

參考資料
Taking Screenshots in Mac OS X - Mac Guides


改變抓圖儲存的位置
打開終端機,下達指令:
$ defaults write com.apple.screencapture location ~/Desktop/Snapshot
$ killall SystemUIServer

若要改回來原本位置:
$ defaults write com.apple.screencapture location ~/Desktop/
$ killall SystemUIServer

參考資料
Change the Screen Shot Save File Location in Mac OS X


改變抓圖的預設檔名
預設的名字是"螢幕抓圖 (datetime)"
$ defaults write com.apple.screencapture name "Snapshot"
$ killall SystemUIServer

參考資料
Change The File Name Of Screenshot In Mac OS X - YouTube

2012/05/31

MySQL INSERT 與 UPDATE 的一些特殊情況用法

最近剛好在設計 social network 的 database schema,遇到很多 special query 的需求,在此一一記錄下來。

如果不存在才 INSERT,存在就 skip
舉例來說,如果想記錄某 user 是否去過某國家
-----------------
 user  | country
-------+---------
 tony  | US
 tony  | HK
 kelly | US
Query:
INSERT INTO `trip` (`user`, `country`) 
SELECT 'tony', 'US' FROM DUAL
WHERE NOT EXISTS (
  SELECT 1 FROM `trip` WHERE `user` = "tony" AND `country` = "US" LIMIT 1
);
* 想 INSERT 的 table 可以跟檢查存在性的 table 不同個。

參考資料
mysql - Insert Where Not Exists-Without Primary Key - Stack Overflow



如果不存在才 UPDATE,否則 skip
舉例來說,你想讓 user 更新 email 欄位(unique),但 email 又不能跟其他人一樣:
------------------------------
 user      | email
-----------+------------------
 olivia    | olivia@email.com
 andy_wang | andy@email.com
 andy_lin  | (NULL)
Query:
UPDATE `profile` SET `email` = "andy@email.com" WHERE `user` = "andy_lin" AND NOT EXISTS
(SELECT 1 FROM `profile` WHERE `email` = "andy@email.com");
但你會發現 mysql execute query 會有下面錯誤:
You can't specify target table 'profile' for update in FROM clause
應該改成:
UPDATE `profile` SET `email` = "andy@email.com" 
WHERE `user` = "andy_lin" AND NOT EXISTS
  (SELECT * FROM (SELECT 1 FROM `profile` WHERE `email` = "andy@email.com") temp);

參考資料
MySQL can’t specify target table for update in FROM clause | VerySimple


2012/05/29

Python 串列(List)、字典(Dict) 的排序

List
直接對list本身排序
>>> l = [3, 2, 5, 4, 1]
>>> l.sort()
>>> l
[1, 2, 3, 4, 5]
>>> l.sort(reverse=True)
>>> l
[5, 4, 3, 2, 1]

不動到原始順序
>>> l = [3, 5, 4]
>>> sorted(l)
[3, 4, 5]
>>> l
[3, 5, 4]
根據element的運算結果
>>> l = ["tom", "Michael", "jenny"]
>>> l.sort(key=str.lower)
>>> l
['jenny', 'Michael', 'tom']


Dictionary
Dict本身是無序的,所以只能在輸出時做排序
>>> d = {"b": 1, "a": 3, "c": 2}
>>> d
{'a': 3, 'c': 2, 'b': 1}
>>> sorted(d)
['a', 'b', 'c']
>>> for key in sorted(d):
...     "%s: %d" % (key, d[key])
... 
'a: 3'
'b: 1'
'c: 2'


Dict in List
List.sort() 內的 key 是排序的依據,可以是 element 的屬性或是 function 運算後的結果
By function
>>> l = [{"id": 3, "name": "john"}, {"id": 2, "name": "brandon"}, {"id": 1, "name": "susan"}]
>>> l
[{'id': 3, 'name': 'john'}, {'id': 2, 'name': 'brandon'}, {'id': 1, 'name': 'susan'}]
>>> def my_func(d):
...     return d['id']
... 
>>> l.sort(key=my_func) 
>>> l
[{'id': 1, 'name': 'susan'}, {'id': 2, 'name': 'brandon'}, {'id': 3, 'name': 'john'}]

簡寫,use in-line function
>>> l.sort(key=lambda d:d['id'])   
>>> l
[{'id': 1, 'name': 'susan'}, {'id': 2, 'name': 'brandon'}, {'id': 3, 'name': 'john'}]
>>> l.sort(key=lambda d:d['name'])
>>> l
[{'id': 2, 'name': 'brandon'}, {'id': 3, 'name': 'john'}, {'id': 1, 'name': 'susan'}]


參考資料
HowTo/Sorting - PythonInfo Wiki

2012/05/25

在 OS X 安裝 GraphicsMagick 和 Pgmagick

有時候在 Mac OS X 想自己抓 source code 回來裝都會很頭大,所以有人就會建議用套件管理程式,像 MacPortsHomeBrewFink,但說真的也不是什麼套件都在這地方找得到。


這次想在 OS X 安裝 GraphicsMagick 和 Pgmagick,GraphicsMagick 是一套圖形處理的 library,比 ImageMagick 更快更好。其實我也沒有要處理太複雜的事情,就 圖形的裁切、縮放、套用一些濾鏡而已,但速度是我考量的。而 Pgmagick 則是 for python 的 wrapper。


直接抓 GraphicsMagick 原始碼下來編譯
遇到問題:
Undefined symbols for architecture x86_64:
  "___builtin_object_size", referenced from:
      _BlurImageScanlines.omp_fn.1 in magick_libGraphicsMagick_la-effect.o
      _XShearImage.omp_fn.0 in magick_libGraphicsMagick_la-shear.o
      _YShearImage.omp_fn.1 in magick_libGraphicsMagick_la-shear.o
ld: symbol(s) not found for architecture x86_64
collect2: ld returned 1 exit status
make[1]: *** [magick/libGraphicsMagick.la] Error 1
make: *** [all] Error 2

遇到這種問題都很沒轍,上次遇到時好像是 dependency 的library configure 時沒有加 --enable-shared,但這次就不知道怎麼找了。

加了一些有的沒的環境變數 LDFLAGS="-arch x86_64" CXXFLAGS="-arch x86_64" CFLAGS="-m64" ... 沒用。

2012/05/21

Form validation in python web development

表單驗證

User 要傳資料到 server 不外乎就是透過 HTML 表單搭配 HTTP method GET/POST,為了避免 user 輸入奇怪的資料造成 server 異常,通常我們都會去限定各欄位值域、資料形態、格式...這些東西,如果自己用一堆 if 去寫一定會寫到瘋掉,好在網路上已經有現成的 library 可以用。

表單驗證有可以分別在 client 端或跟 server 端去做:
  • Client 端驗證
    好處是可以減少 server loading,少了傳輸的 latency,反應速度也比較快,但這只能防君子不能防小人,client 端驗證是用 javascript,有心人只要把 script 修改一下就可以避過。
    Library 可以參考:10 Useful jQuery Form Validation Techniques and Tutorials
  • Server 端驗證
    無論 client 端有沒有做,server 端做檢查是必然的事情啦!這也是我今天想介紹的,在 python 上我只用過一套:WTForms,一用就上癮,不只可以做到驗證,表單也可以自動產生。
    如果你 web framework 是用 flask,已經有現成的 extension: Flask-WTF 可以用,我自己是用 tornado,必須要另外疊一個 wrapper,幸好已經有人幫忙寫好了: flying with tornado on appengine

2012/05/13

讓 SimpleDB 支援 paging (offset)

在 implement page 的時候通常會用到 SQL 語法的"offset"
取第一頁:
SELECT * FORM `article` OFFSET LIMIT 10
取第二頁:
SELECT * FORM `article` OFFSET LIMIT 10 OFFSET 10
取第三頁:
SELECT * FORM `article` OFFSET LIMIT 10 OFFSET 20

但 AWS SimpleDB 並不支援 offset 這個與法,必須要用 next_token 方式來取
以下用python boto library做個示例:
sdb_conn = SDBConnection(...)

# Skip first 20 items
query = "SELECT COUNT(*) FORM `article` OFFSET LIMIT 20"
rs = sdb_conn.select(sdb_domain, query=query)

# Get target page
query = "SELECT * FORM `article` OFFSET LIMIT 10"
rs2 = sdb_conn.select(sdb_domain, query=query, next_token=rs.next_token)
for item in rs2:
    ...

"SELECT COUNT(*)" 是最有效率的略過方法,如果用"SELECT * "則會有 return 2500 rows 的限制!


 參考資料