顯示具有 實作筆記 標籤的文章。 顯示所有文章
顯示具有 實作筆記 標籤的文章。 顯示所有文章

2013年12月10日 星期二

iOS 開發筆記 - 使用 NSNotification 傳遞與接收事件

通常會設計多個 UIViewController 供使用者觀看與操作,如果 AUIViewController 提供設定參數後,當使用者切換到 BUIViewController 可以馬上就使用到新資料來操作時,這時可以把資料儲存在指定地方,當 BUIViewController 之 viewWillAppear 裡就能夠去重讀資料來用。

然而,有時希望 AUIViewController 改完資料時,可以馬上讓 BUIViewController 也得知,這時做法至少有兩種,第一種就是讓 AUIViewController 可以接觸到 BUIViewController ,例如把 BUIViewController 當作一個 member variable ,也就是 AUIViewController.member = BUIViewController object,只是這招有點破壞架構的美感,但偶爾使用還滿堪用的!第二種,就是採用 Notification 架構,讓 BUIViewController 聆聽專屬於它的事件,而 AUIViewController 在需要的時候送事件出去即可,此架構更加符合 async 。

此例筆記 NSNotification 的用法(此架構也稱 radio station,Android 派則是 broadcast 啦):

BUIViewController.h:

#define kTargetDataUpdate @"TargetDataUpdate"

BUIViewController.m 之註冊聆聽事件:

- (void)setupOptions:(NSNotification *)event
{
        NSLog(@"event:%@, object:%@", event, event.object);
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setupOptions:) name:kTargetDataUpdate object:nil];

}


BUIViewController.m 之移除聆聽事件:

- (void)viewWillDisappear:(BOOL)animated
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name: kTargetDataUpdate object:nil];
    [super viewWillDisappear:animated];
}


AUIViewController (或其他物件亦可) 之發送事件:

NSDictionary *info = @{@"status":@"ok"};
[[NSNotificationCenter defaultCenter] postNotificationName: kTargetDataUpdate object:info userInfo:nil];

2013年11月7日 星期四

[Python] Bottle + Nginx + uWSGI @ Ubuntu 12.04

環境:

$ sudo apt-get install python-bottle nginx uwsgi uwsgi-plugin-python

Python Bottle 範例 (/var/www/bottle_app):

$ vim /var/www/bottle_app/app.py
from bottle import Bottle, route, run, install, template, static_file
#from bottle_sqlite import SQLitePlugin

#import bottle
#application = bottle.default_app()

app = application = Bottle()
#app.install(SQLitePlugin(dbfile='/tmp/test.db'))

@app.route('/')
def index():
        return template('Hello world')

if __name__ == '__main__':
        run(app, host='0.0.0.0', port=1234)


設定 uWSGI:

$ sudo vim /etc/uwsgi/apps-available/bottle_app.ini
[uwsgi]
socket = /run/uwsgi/app/bottle_app/socket
chdir = /var/www/bottle_app
master = true
plugins = python
file = app.py
uid = www-data
gid = www-data
$ sudo ln -s /etc/uwsgi/apps-available/bottle_app.ini /etc/uwsgi/apps-enabled/bottle_app.ini
$ sudo service uwsgi restart


設定 nginx:

$ sudo vim /etc/nginx/sites-available/bottle_app
upstream _bottle {
server unix:/run/uwsgi/app/bottle_app/socket;
}

server {
listen 8080;
root /var/www/bottle_app;

location / {
try_files $uri @uwsgi;
}

location @uwsgi {
include uwsgi_params;
uwsgi_pass _bottle;
}
}

$ sudo ln -s /etc/nginx/sites-available/bottle_app /etc/nginx/sites-enabled/bottle_app
$ sudo service nginx restart


其他部分,對於 /var/www/bottle_app/app.py 留意 application 變數是 uwsgi 尋找的對象,若想要改變可以在 uwsgi 定義 callable (預設 callable = application)。

至於使用 bottle 開發上麻煩的地方是 templates(views) 更新後,必須重新啟動才能生效,就算改用 uwsgi 也沒有改善,不知是不是少設定甚麼,或是預設規劃就是如此?

除此之外,若不用那麼著重效率,還可以替換掉 Bottle 預設的 wsgiref WSGIServer (non-threading HTTP server),而且改法很簡單(switching-the-server-backend):

$ sudo apt-get install python-paste
$ vim app.py
...
if __name__ == '__main__':
import paste
run(
app ,
host='0.0.0.0',
port=1234,
server='paste'
)

2013年11月1日 星期五

使用 mutt 產生 message/rfc822 測資 @ Ubuntu 12.04

最近正在研究 email 格式,其中一個 content-type: message/rfc822 很神秘,好像很久沒看過了?甚至 2010 年 GMail 相關討論串 中,仍看得到抱怨 GMail 不處理 (現況經測試,GMail 都會將 message/rfc822 展開)

接著就是來產生 message/rfc822 了,這似乎比較常從 outlook 那邊看到,但懶得用outlook 產品,直接用 mutt 吧!

使用 mutt 時,可以設定 mime forward 機制後,接著每一封轉信都可以用 message/rfc822 格式寄出。

$ vim ~/.muttrc
set mime_forward=yes
set mime_forward_rest=yes


或是使用 mutt 時,按 : 去設定上述兩個值亦可。

一封寄給自己的信:

Return-Path: <changyy@vm>
X-Original-To: changyy@vm
Delivered-To: changyy@vm
Received: by vm (Postfix, from userid 1000)
        id 03701604E5; Sat, 26 Oct 2013 10:53:47 +0800 (CST)
Date: Sat, 26 Oct 2013 10:53:47 +0800
From: changyy <changyy@vm>
To: changyy <changyy@vm>
Subject: Test message/rfc822
Message-ID: <20131026025347.GA17760@vm>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.21 (2010-09-15)
Status: RO
Content-Length: 9
Lines: 1

as title


把上述那封用 message/rfc822 轉寄給自己:

Return-Path: <changyy@vm>
X-Original-To: changyy@vm
Delivered-To: changyy@vm
Received: by vm (Postfix, from userid 1000)
        id B96A9604E5; Sat, 26 Oct 2013 10:54:13 +0800 (CST)
Date: Sat, 26 Oct 2013 10:54:13 +0800
From: changyy <changyy@vm>
To: changyy <changyy@vm>
Subject: [changyy@vm: Test message/rfc822]
Message-ID: <20131026025413.GB17760@vm>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="KsGdsel6WgEHnImy"
Content-Disposition: inline
User-Agent: Mutt/1.5.21 (2010-09-15)
Status: RO
Content-Length: 703
Lines: 29


--KsGdsel6WgEHnImy
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

forward test

--KsGdsel6WgEHnImy
Content-Type: message/rfc822
Content-Disposition: inline

Return-Path: <changyy@vm>
X-Original-To: changyy@vm
Delivered-To: changyy@vm
Received: by vm (Postfix, from userid 1000)
        id 03701604E5; Sat, 26 Oct 2013 10:53:47 +0800 (CST)
Date: Sat, 26 Oct 2013 10:53:47 +0800
From: changyy <changyy@vm>
To: changyy <changyy@vm>
Subject: Test message/rfc822
Message-ID: <20131026025347.GA17760@vm>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.21 (2010-09-15)

as title

--KsGdsel6WgEHnImy--

2013年10月30日 星期三

[Linux] 監控記憶體使用量,以 python 為例 @ Ubuntu 12.04

監控方式就...去翻 /proc/pid/status 來看

粗略測試方式:
  • 寫一隻程式,最後一行跑 loop 讓程式不會結束,或是讀 io卡住也行
  • 用 ps 得知此程式 pid,接著翻 /proc/pid/status 出來
例如我想知道 python 記憶體使用上會不會 copy by reference 或 copy by reference,那就重複把某個 string data 設值給別人看看,最後用一個 loop 卡住,讓程式不會結束,接著就用 ps aux | grep 'test.py' 去找出來 PID ,再去 cat /proc/PID/status 出來。

test.py:

import time

data = open('tmp').read()
i = 1000
co = []
while i > 0 :
        #co.append(str(data)+"1")
        #co.append(str(data)+"")
        co.append(str(data))
        i -= 1
print "Done:", len(co)
while True:
        time.sleep(1)


連續動作:

$ ps aux | grep test.py | grep -v "grep\|vim" | awk '{system("cat /proc/"$2"/status");}' | grep -i "pid\|vm\|name"

當 co.append(str(data)) 跟 co.append(str(data)+"") 時,i 數值變大對記憶體增加不多:

Name:   python
Pid:    12180
PPid:   11910
TracerPid:      0
VmPeak:    32532 kB
VmSize:    32532 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:      5676 kB
VmRSS:      5676 kB
VmData:     2700 kB
VmStk:       136 kB
VmExe:      2496 kB
VmLib:      5240 kB
VmPTE:        72 kB
VmSwap:        0 kB


但 co.append(str(data)+" ") 時,i 數值變大對記憶體就開始噴了:

Name:   python
Pid:    12189
PPid:   11910
TracerPid:      0
VmPeak:   340532 kB
VmSize:   340532 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:    313496 kB
VmRSS:    313496 kB
VmData:   310700 kB
VmStk:       136 kB
VmExe:      2496 kB
VmLib:      5240 kB
VmPTE:       680 kB
VmSwap:        0 kB

2013年10月26日 星期六

[Linux] 研究 c-icap modules 與 Squid 相關設定 @ Ubuntu 12.04

前鎮子剛用 pyicap 完成簡單的 ICAP 實作,但 pyicap 還是有一點不順,所以就來試試 c-icap 吧!盡管在 wikipeida 上查閱了幾種 icap server 的實作,但不少已經說不繼續維護,然後就叫大家去看 c-icap 啦,再加上 c-icap 也可以在 apt-get 上方便取得,所以,就來摸摸 so 吧!(據說中國那邊還很盛行,ISP還是會幫忙把網頁加上自家廣告)

先聊一下 c-icap 的部分,它是一隻用 C 實作 ICAP server 的角色,而 Squid 本身就內建 ICAP client 的角色了。使用 c-icap 的理由,則是他提供模組的方式,將你想做的事寫成 module ,設定後可變成 c-icap 的一個 service ,僅需依照 c-icap 的框架實作必要的函式,可省去實作 ICAP 協定。

此篇記錄著重在如何實作 c-icap module ,而網路資料不多,最佳解就是看程式碼 :P

$ apt-get source c-icap libc-icap-mod-urlcheck

此過程會產生兩個目錄:c-icap-0.1.6 和 c-icap-modules-0.1.6,其中 c-icap-modules-0.1.6 環境可以專門拿來編 c-icap modules,但其實最簡單的 c-icap-module 是在 c-icap-0.1.6/services/echo/srv_echo.c,如其名,echo 這只是個範例,但註解非常豐富,看他就夠啦。

其中比較特別的,就是查看 c-icap-module 框架,請參考這段:

CI_DECLARE_MOD_DATA ci_service_module_t service =
{
"echo", /* mod_name, The module name */
"Echo demo service", /* mod_short_descr,  Module short description */
ICAP_RESPMOD | ICAP_REQMOD, /* mod_type, The service type is responce or request modification */
echo_init_service, /* mod_init_service. Service initialization */
NULL, /* post_init_service. Service initialization after c-icap configured. Not used here */                                            
echo_close_service, /* mod_close_service. Called when service shutdowns. */
echo_init_request_data, /* mod_init_request_data */
echo_release_request_data, /* mod_release_request_data */
echo_check_preview_handler, /* mod_check_preview_handler */
echo_end_of_data_handler, /* mod_end_of_data_handler */
echo_io, /* mod_service_io */
NULL,
NULL
};


各項簡介:
  • 此 c-icap module service 啓動與關閉:echo_init_service, echo_close_service
  • 當 request 進來:echo_init_request_data
  • 取得 request preview:echo_check_preview_handler
  • 當 request 處理中:echo_io
  • 當 request 處理完:echo_release_request_data

舉個例來說,如果要用 PCRE 去做處理,一開始要初始化 PCRE 的項目可以擺在 echo_init_service ,而釋放 PCRE 的資源可以擺在 echo_close_service,而每次處理 request 前需要初始化資源則擺在 echo_init_request_data,釋放在 echo_release_request_data。

至於更深的應用就要去 c-icap-modules-0.1.6/service 翻 clamav 和 url_check,這兩項都跟資安相關,前者是掃毒軟體,後者則是可以拿來過濾惡意網站。有興趣可以翻一輪,比較特別的是處理 request 進來的資料,有用到 ci_simple_file_* 和 ci_membuf_* 系列的函式庫,簡言之就是記憶體擺不下就放檔案,或是有些函式只支援掃檔,那就先擺在檔案去進行。這邊便就不多講了。

設定 c-icap:

$ sudo cp output/changyy.so /usr/lib/c_icap/
$ sudo vim /etc/c-icap/c-icap.conf
Service changyy changyy.so
$ sudo /etc/init.d/c-icap restart


設定 squid:

icap_enable on
icap_send_client_ip on
icap_send_client_username on
icap_client_username_encode off
icap_client_username_header X-Authenticated-User
icap_preview_enable on
icap_preview_size 1024

icap_service service_req reqmod_precache bypass=1 icap://localhost:1344/changyy
adaptation_access service_req allow all
icap_service service_resp respmod_precache bypass=1 icap://localhost:1344/changyy
adaptation_access service_resp allow all


其中 icap://localhost:1344/changyy 在此的意義就是 c-icap 中的 Service changyy 啦,最後一提的是 c-icap.conf 有 DebugLevel ,這個數字攸關 debug 訊息,在 c-icap-modules 中許多實作都會用 ci_debug_printf 來印訊息,其中第一個數字若小於等於 DebugLevel 才會被印出來。而輸出的 log 可以在 /var/log/c-icap/server.log 查到。

2013年10月23日 星期三

[OSX] 使用 Command line 啓動 VNC 和設定連線密碼 @ Mac OSX 10.8

如果說,都用 Mac OS X 遠端 Mac OS X,那的確不太需要設定 VNC 密碼,因為會轉換到 Mac OS X 自己的協定,但如果是從 Windows/Linux 連到 Mac OS X 的話,設定密碼還是安全一點點。

啓動 VNC:

$ sudo defaults write /var/db/launchd.db/com.apple.launchd/overrides.plist com.apple.screensharing -dict Disabled -bool false
$ sudo launchctl load /System/Library/LaunchDaemons/com.apple.screensharing.plist


設定 VNC 密碼:

$ sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -configure -access -off -restart -agent -privs -all -allowAccessFor -allUsers -clientopts -setvncpw -vncpw VNC_PASSWORD
Starting...
bind(): Address already in use
com.apple.screensharing: Already loaded
Activated Remote Management.
Stopped ARD Agent.
Stopped VNC Privilege Proxy
buffalo: Set user remote access.
macports: Set user remote access.
ookon: Set user remote access.
Set the client options.
Setting allow all users to YES.
Setting all users privileges to ##########.
Done.

[Linux] 使用 virsh 建立 snapshot (管理KVM) @ Ubuntu 12.04

繼上一篇 [Linux] 安裝 Kernel Virtual Machine (KVM) 與純文字操作 後,接著是想著該如何使用 virsh 指令管理 VM,如建立 snapshot 、恢復等。

$ virsh list --all
 Id Name                 State
----------------------------------
  1 vmlinux               running

$ virsh snapshot-list vmlinux
 Name                 Creation Time             State
------------------------------------------------------------


接著嘗試建立 snapshot:

$ sudo virsh snapshot-create vmlinux
error: Requested operation is not valid: Disk '/var/lib/libvirt/images/vmlinux.img' does not support snapshotting

發現目前的格式不支援,接著登入 vmlinux 把機器關掉,進行格式轉換並且更新原本 VM 的敘述檔:

$ sudo qemu-img convert -f raw -O qcow2 /var/lib/libvirt/images/vmmail.img /var/lib/libvirt/images/vmmail.qcow2

$ virsh dumpxml vmlinux
...
  <devices>
    <emulator>/usr/bin/kvm</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='raw'/>
      <source file='/var/lib/libvirt/images/vmlinux.img'/>
...
$ sudo virsh edit vmlinux
...
  <devices>
    <emulator>/usr/bin/kvm</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/var/lib/libvirt/images/vmlinux.qcow2'/>
...


重新啟動VM:

$ sudo virsh start vmlinux
$ virsh list --all
 Id Name                 State
----------------------------------
  1 vmlinux               running


建立 snapshot:

$ sudo virsh snapshot-create vmlinux
Domain snapshot 1382549415 created

$ virsh snapshot-list vmlinux
 Name                 Creation Time             State
------------------------------------------------------------
 1382549415           2013-10-23 20:30:15 +0800 running


指定回復:

$ sudo virsh snapshot-revert vmlinux
error: --snapshotname or --current is required

$ sudo virsh snapshot-revert vmlinux --snapshotname 1382549415
$ virsh list --all
 Id Name                 State
----------------------------------
  1 vmlinux               running


上述操作過程中,沒有把 VM 關掉,唯一的差別是連到 VM 的 putty 斷線了而已,也算挺方便的。

2013年10月22日 星期二

[Python] 使用 PyICAP 淺玩 ICAP 與 Squid (以 Response Modification / RESPMOD 為例) @ Ubuntu 12.04

最近在研究 Internet Content Adaptation Protocol (ICAP),這東西好玩之處是可以跟 Proxy server 整再一起,當 proxy 幫 client 去要資料前後,可以透過 ICAP 來將資料加工處理一下。此協定主要可分成兩種資料流:Request Modification 和 Response Modification,直接看圖最清楚:

Request Modification:

Request Modification

Response Modification:

Response Modification

在 Ubuntu 中,可以用 apt-cache search icap 可以看到少少的套件可以把玩(用病毒掃描、限制網址瀏覽等),舉例來說,當使用者要瀏覽一個惡意網站前,可以先阻擋下來;當使用者下載完一個檔案時,可以幫忙掃描,若是病毒則可以擋下。至於我想把玩的主因之一是跟人閒聊一些免費 AP 時,很好奇它是怎樣做到幫網頁加料的,也就是用了他們家的 AP 後,瀏覽的網頁都會多個廣告,以此為目標加以進行下,此為 Response Modification 例子。

首先感謝 pyicap 這小又不簡單的 framework (ICAP 說難不難,說簡單也不簡單,詳請請看 RFC 3507),再次感謝作者佛心分享,依照著內附的 example/respmod_copy.py 小改一下,實作方向此:

  • 告訴 ICAP-client (Squid) 不要對 HTML, HTM 做 preview
  • 挑出 content-type 是 HTML 出來
  • 處理 HTTP 資料,將 gzip 的解開
  • 更新 <body> 資料
  • 將資料在壓成 gzip 後丟出去

片段程式碼(完整版):

#!/bin/env python
# -*- coding: utf8 -*-

import random
import SocketServer

from pyicap import *

import StringIO
import gzip
import re

#...

class ICAPHandler(BaseICAPRequestHandler):

def example_OPTIONS(self):
# ...
self.set_icap_header('Transfer-Complete', 'html,htm')
# ...

def example_RESPMOD(self):
# ...
if self.preview:
# ...
elif analysis_flag:
# ...
try:
orig_data = ''
if content_encoding in ('gzip', 'x-gzip', 'deflate'):
if content_encoding == 'deflate':
data = StringIO.StringIO(zlib.decompress(raw))
else:
data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(raw))
orig_data = data.read()

#formated_out = orig_data.replace( "<body>", """
formated_out = re.sub(r"(<body[^>]*>)", """
<body>
<!-- src from: http://zh-yue.wikipedia.org/wiki/%E4%B8%96%E7%95%8C%E4%B8%89%E5%A4%A7%E5%A4%9C%E6%99%AF -->
<center><img src="http://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Victoria_Harbour_around_Chinese_New_Year_Night_with_Fireworks_and_Laser_Show.jpg/1024px-Victoria_Harbour_around_Chinese_New_Year_Night_with_Fireworks_and_Laser_Show.jpg" /></center>
""", orig_data )

data = StringIO.StringIO()
f = gzip.GzipFile(fileobj=data, mode='w')
f.write(formated_out)
f.close()

self.write_chunk( data.getvalue() )

need_output = False

except Exception, e:
print e

if need_output:
self.write_chunk(raw)

# ...


Squid 設定(/etc/squid3/squid.conf):

icap_enable on
icap_send_client_ip on
icap_send_client_username on
icap_client_username_encode off
icap_client_username_header X-Authenticated-User
icap_preview_enable on
icap_preview_size 1024
#icap_service service_req reqmod_precache bypass=1 icap://127.0.0.1:13440/squidclamav
#adaptation_access service_req allow all
icap_service service_resp respmod_precache bypass=1 icap://127.0.0.1:13440/example
adaptation_access service_resp allow all


成果,瀏覽一則 Yahoo 新聞時,最上頭被植入一個張圖檔(紅色框框):

Response Modification example

[Python] 處理 HTTP gzip 的資料 @ Ubuntu 12.04

摸了一下 Python 小東西,過程中會去接收 Web server 收下的資料,其中有的會編碼為 gzip ,這時就用 python 稍微解一下:

import StringIO
import gzip

data = gzip.GzipFile('', 'rb', 9, StringIO.StringIO(raw)) # ('filename', 'read/write mode', compression level)
orig_data = data.read()


反過來,把字串 gzip 一下:

import StringIO
import gzip

data = StringIO.StringIO()
f = gzip.GzipFile(fileobj=data, mode='w')
f.write(orig_data)
f.close()

raw = data.getvalue()

2013年10月18日 星期五

[Linux] 安裝 Kernel Virtual Machine (KVM) 與純文字操作 @ Ubuntu 12.04

kvm install  nographics

最近比較常用 VirtualBox 來工作,但如果有一台好一點的機器,每次工作都要用圖形介面也稍顯麻煩,於是就嘗試將那一台好機器裝成 Ubuntu 12.04 64-Bit,接著再裝 kvm 來用,未來就單純透過 terminal 就能完成工作了(KVM也有圖形介面可以用)。

確認 CPU 是否支援虛擬化:

$ sudo apt-get install cpu-checker
$ kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used


另外,也有人常用以下指令檢查(有吐資料就行):

$ egrep '(vmx|svm)' --color=always /proc/cpuinfo

安裝 kvm:

$ sudo apt-get install kvm

檢查 kvm (此例是用一顆 Intel CPU):

$ lsmod | grep kvm
kvm_intel             137888  0
kvm                   422160  1 kvm_intel

查看一下相關模組:

$ modprobe -l | grep kvm
kernel/arch/x86/kvm/kvm.ko
kernel/arch/x86/kvm/kvm-intel.ko
kernel/arch/x86/kvm/kvm-amd.ko


安裝 kvm 相關常用工具(其中 virt-manager 是圖形化介面,此例可以不安裝):

$ sudo apt-get install libvirt-bin virt-manager virtinst

檢查此台 Host OS 機器狀態:

$ virsh nodeinfo
CPU model:           x86_64
CPU(s):              8
CPU frequency:       1600 MHz
CPU socket(s):       1
Core(s) per socket:  4
Thread(s) per core:  2
NUMA cell(s):        1
Memory size:         8024168 kB


查看網卡設定是否有 virbr 系列:

$ ifconfig | grep virbr
virbr0    Link encap:Ethernet  HWaddr 11:22:33:44:55:66


查看目前虛擬機器狀態:

$ virsh list --all
 Id Name                 State
----------------------------------


建立 VM (Guest OS) 其名為 vmproxy,安裝一台 Ubuntu 12.04 64-bit server 版:

$ sudo virt-install --connect=qemu:///system  --name vmproxy --arch=x86_64 --vcpus=2 --ram=1024 --os-type=linux --hvm --network=bridge:virbr0 --network=network:default --hvm --nographics --accelerate --location http://tw.archive.ubuntu.com/ubuntu/dists/precise/main/installer-amd64/ --disk path=/var/lib/libvirt/images/vmproxy.img,size=20 --extra-args="auto text console=tty1 console=ttyS0,115200"

其中 --location http://tw.archive.ubuntu.com/ubuntu/dists/precise/main/installer-amd64/ 代表要安裝 Ubuntu 12.04 64Bit 系列的,而 --extra-args 的參數是為了解決 KVM 接收 Guest OS console 的問題;--network=bridge:virbr0 --network=network:default 代表配置兩張網卡,一張是 virbr0 ,另一張是預設,而預設網路會走 NAT。

安裝過程須留意的是網路設定,依上述指令,第二張網路卡會是走 NAT 的,可以先採用這張,回頭再去設定 br0 的問題。最後的安裝畫面,記得選一下 Base Ubuntu Server 跟 Openssh server 囉。

kvm install

接著當 Guest OS 重新開機後,可以登入試試,並確認網路狀態(連外是否ok),另外有興趣也可以印一下 cpuinfo 囉。

vmlinux:~$ ifconfig
eth1      Link encap:Ethernet  HWaddr 52:54:00:a7:b7:c5
          inet addr:192.168.122.250  Bcast:192.168.122.255  Mask:255.255.255.0
...

vmlinux:~$ cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 2
model name      : QEMU Virtual CPU version 1.0
stepping        : 3
microcode       : 0x1
cpu MHz         : 3392.292
cache size      : 4096 KB
fpu             : yes
fpu_exception   : yes
cpuid level     : 4
wp              : yes
flags           : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pse36 clflush mmx fxsr sse sse2 syscall nx lm up rep_good nopl pni cx16 popcnt hypervisor lahf_lm
bogomips        : 6784.58
clflush size    : 64
cache_alignment : 64
address sizes   : 40 bits physical, 48 bits virtual
power management:

接著回到原先的 Host OS 可以用 virsh list 查看:

$ virsh list --all
 Id Name                 State
----------------------------------
  1 vmproxy              running


如果要強制停掉:

$ virsh destroy vmproxy

若要刪掉它的資訊:

$ virsh undefine vmproxy

做完 undefine 後,下次 virsh-install 就可以再使用此名字,但若要完整刪掉,還須清除 disk 位置(此例為 /var/lib/libvirt/images/vmproxy.img),也可用圖形界面一次處理好。

若透過 Host OS 的 port forwarding 讓外頭可以直接連入 Guest OS,則可以用用 redir 這個指令:

$ sudo apt-get install redir
$ /usr/bin/redir --lport=20022 --caddr=192.168.122.250 --cport=22 &


最後,雖然 Host OS 預設不會關機,但還是設定一下自動將 Guest OS 啟動的方式:

$ sudo vim /etc/init.d/vm-init.sh
#!/bin/sh
virsh start vmproxy
/usr/bin/redir --lport=20022 --caddr=192.168.122.250 --cport=22 &

$ sudo chmod 755 /etc/init.d/vm-init.sh
$ sudo update-rc.d -f vm-init.sh defaults

[Linux] 修正 wget 下載檔案名字 @ Ubuntu 12.04

常用 wget 下載檔案,但碰到一些會做重導的網頁或是檔名是在 HTTP HEAD 時,用瀏覽器下載可以正確取得檔名,但用 wget 只會看網址。修正方式只是叫 wget 去相信 server 回傳的檔名而已。某個角度來說,wget 預設不這樣做或許有安全性的考量?

例如下載 Ubuntu 64-bit Server LTS ISO 檔:

$ wget 'http://www.ubuntu.com/start-download?distro=server&bits=64&release=lts'
...
HTTP request sent, awaiting response... 200 OK
Length: 697303040 (665M) [application/octet-stream]
Saving to: `start-download?distro=server&bits=64&release=lts'
...


加上 --trust-server-names:

$ wget --trust-server-names 'http://www.ubuntu.com/start-download?distro=server&bits=64&release=lts'
...
HTTP request sent, awaiting response... 200 OK
Length: 697303040 (665M) [application/octet-stream]
Saving to: `ubuntu-12.04.3-server-amd64.iso'
...

2013年10月15日 星期二

[Python] urlib2 設定 User-Agent (偶爾可解掉 urllib2.HTTPError: HTTP Error 403: Forbidden )

雖然造成 403: Forbidden 的因素有很多,但有一種是網站不想讓 crawler 抓資料,但 User-agent 設定完就能解掉,所以此例拿來記錄 urllib2 設定 User-Agent 的過程(盡量先不請 pycurl 出來 XD)。

原本用法:

obj = urllib2.urlopen('http://www.google.com/')

加入 User-Agent 用法:

obj = urllib2.urlopen( urllib2.Request( 'http://www.google.com/' , None , { 'User-Agent' : 'Mozilla/5.0' } ) )

另外紀錄一下 wget 用法:

$ wget -U 'Mozilla/5.0' http://www.google.com

2013年10月11日 星期五

[Linux] 使用 redir 做 port forwarding 與設定開機啓動 @ Ubuntu 12.04

最近又在一台擁有 public ip 的強悍機器上架 VM 了,暫時是用 Ubuntu 12.04 desktop 跑 VirtualBox 來用,所以就碰到如何從外頭連進去的需求,就裝裝 redir 來用吧!
$ sudo apt-get install redir
舉例來說,將 Host OS 的 20022 port 導向到 Guest OS 的 22 port,讓人可以從外頭來進去:
$ /usr/bin/redir --lport=20022 --caddr=192.168.56.101 --cport=22
設成開機啓動:
$ sudo vim /etc/init.d/vbox-port-forwarding.sh
#!/bin/sh

# port forwarding
/usr/bin/redir --lport=20022 --caddr=192.168.56.101 --cport=22 &

$ sudo chmod 755 /etc/init.d/vbox-port-forwarding.sh
$ sudo update-rc.d -f vbox-port-forwarding.sh defaults

2013年10月8日 星期二

安裝 Eclipse 與 Eclipse CDT 支援 C/C++ Project 開發與偵錯 @ Ubuntu 12.04 Desktop

前輩詢問在 Ubuntu 上頭有沒合適的 GUI Debug 方式,我想了很久,實在是我連 gdb 都不常用,學生時代土法煉鋼的 printf 就夠我解決九成問題,此不便的 debug 方式,會讓人謹記教訓,也有訓練記憶之功用? 只能說我接觸的 Project 還不夠大吧 :P 我有印象第一次真的去用 gdb 是因為我寫了類似很多層 struct 的情況。

開始還在想要不要請前輩用 gdb -tui ,所幸還有想到 Eclipse 啦。廢話不多說,來個圖文教學吧。

安裝 Eclipse CDT:

由於 Ubuntu 12.04 裝完的 Eclipse 是 INDIGO 3.7.2 版本,所以在 Eclipse -> Help -> Install New Sotfware 那邊,把 Work with 填入 http://download.eclipse.org/tools/cdt/releases/indigo  (更多選擇請參考 http://www.eclipse.org/cdt/downloads.php)

Ubuntu 12.04 Eclipse install CDT 1

接著偷懶全選,然後就會出現問題 Orz 接著去掉重複項目的舊版、去掉仍有問題的項目,最後就可以安裝了。

Ubuntu 12.04 Eclipse install CDT 2

理當來個 Hello World 範例:

Ubuntu 12.04 Eclipse New C++ Project

Ubuntu 12.04 Eclipse New C++ Project 2

Ubuntu 12.04 Eclipse New C++ Project 3

Ubuntu 12.04 Eclipse New C++ Project 4

Ubuntu 12.04 Eclipse New C++ Project 5

Ubuntu 12.04 Eclipse New C++ Project 6

Ubuntu 12.04 Eclipse New C++ Project 7

Ubuntu 12.04 Eclipse New C++ Project 8

Ubuntu 12.04 Eclipse New C++ Project 9

Ubuntu 12.04 Eclipse New C++ Project 10

再補一個 Debug 流程:

Ubuntu 12.04 Eclipse C++ Project Debug 1

Ubuntu 12.04 Eclipse C++ Project Debug 2

Ubuntu 12.04 Eclipse C++ Project Debug 3

Ubuntu 12.04 Eclipse C++ Project Debug 4

Ubuntu 12.04 Eclipse C++ Project Debug 5

Ubuntu 12.04 Eclipse C++ Project Debug 6

Ubuntu 12.04 Eclipse C++ Project Debug 7

2013年10月5日 星期六

Mac mini (Late 2009) 更換硬碟

macmini2009late_hd_upgrade_01

幾個月前,某台 Windows 筆電開機一直會找不到硬碟,那時擔心硬碟快壞了,所以買了一顆新硬碟來用,卻發現沒有解決現象,只能猜測單純房間溫度過高吧 XD 於是,多了一顆 500GB 硬碟,就替 2010 年買的 Mac mini 替換一下,拆機後才發現這台才 160GB 而耶,真股奇妙的感覺:Mac mini(2009 年末)- 技術規格

翻翻三年前替它更換記憶體的文章(Mac mini 拆殼 + 更換記憶體),不再像以前那般小心翼翼,單純用一隻鐵尺從後面面板那拆機,不用幾分鐘就拆好外殼了 XD

macmini2009late_hd_upgrade_02

我記得以前查看資料時,發現換硬碟會比較麻煩,但仔細看一下機構,卻也不見得要拆很多東西,算起來是偷吃步的拆法,比換記憶體多拆了 4 棵螺絲而已 :P 拆完鎖硬碟的螺絲後,再透過摩擦力貼著硬碟輕輕往後拉,就可以拆下硬碟,隨機構的設計退出來,再更換硬碟後,傾斜機構讓硬碟插槽可以對好,就可以輕推裝好了。

macmini2009late_hd_upgrade_03

macmini2009late_hd_upgrade_04

Mac mini 強制退出光碟片:開機時,按住滑鼠左鍵

在處理 Mac mini 2009 late 機器時,先擺入了一張舊的 Mac OSX Install 光碟,想說處理後更換新版 OSX 來安裝系統,卻想不到該怎樣退片 XDD

問問 Google 的結果:
開機時,按著滑鼠左鍵,這樣就會退片了 XD
真是神秘的招數。

2013年10月4日 星期五

iOS Developer Program 續約流程

既然去年有寫購買教學(申請 iOS Developer Program 之 Company 版),那就順便記一下續約的好了 XD 簡單的說,離過期一個月前,時會收到 Apple 提醒信件,接著就隨著 Email 給的網址去購買吧!(這種從 Email 點選購買流程要小心病毒信啊 XD)


iOS Developer Promgram 01

iOS Developer Promgram 02

iOS Developer Promgram 03

iOS Developer Promgram 04

iOS Developer Promgram 05

iOS Developer Promgram 06

iOS Developer Promgram 07

iOS Developer Promgram 08

接著,應該是24小時內又會收到啓用通知信,就在網頁上填寫啓用碼即可啓用。

iOS Developer Promgram 09

iOS Developer Promgram 10

西聯匯款 - 使用大眾銀行西聯匯入匯款單領取 Google Adsense 廣告費

大眾銀行西聯匯入匯款單

在國外時剛好收到領款通知,還真是生日禮物咧。先到 西聯匯款- 全球匯款服務 官網查看目前服務的銀行清單,已經變成一排大眾銀行處理了!

帶著忐忑不安的心情,到現場發現要填的資料比網路上找到的國泰世華的範例要少很多!真的挺方便的,但領款一樣要記得帶身份證!

簡單筆記:
  • 收款人就是自己,免不了就 First Name、Last Name 跟 Mobile no. 這三欄就好
  • 領款人就是 Google Inc.,就是 First Name 填 Google,Last Name 填 Inc.
  • 預期匯款金額就是 Google Adsense 發的金額
  • 匯款來自 USA
  • 匯款控制碼就是 Google Adsense 的那10位數字啦!
我甚至連 Google 他們家地址都沒填 XD 真是太便利了。另外上網查的時候,才發現京城銀行有提供線上辦理喔,只要開戶過即可線上處理,但文章敘述只能轉台幣存進戶頭,若京城有提供外幣帳戶,應該又更威了。有機會再去京城開戶吧!

最後會提供一張收據,當然就跟網路上流傳的一開始要填寫的那張很像啦。小小地希望哪天可以像 Admob 一樣,直接用 Paypal 就好 XD

插曲:在等大眾銀行辦理時,隨口聊了幾句,發現外幣換台幣不收手續費耶!就順手把手上幾元美金現鈔換掉,但發現西聯匯款轉台幣時,卻少了 0.1 美金 XD 或許,可以直接先領美金後(若不收手續費),再拿著美金現鈔換台幣 XD 就可以省掉 0.1 美金的匯差了。

2013年9月11日 星期三

Google Doc - Excel 存取其他 sheets 欄位資料

Google Doc - excel

最近用 Google doc 製作表單給大家填寫,當別人回送表單時,系統會用 Google Excel 進行一筆筆的紀錄。

例如表單的目的是要統計活動參與人數,以及攜家帶眷或是搭車人數的統計等。由於原本回應表單 sheet 1 是一筆筆的進來,雖可以更改數值,但系統不允許自己修改欄位,這時就必須另外建立一個 sheet 2 來處理,就碰到跨 sheets 存取的問題。

所幸這個還是有解,工程師只要面對可以程式化的東西,就會不小心玩過頭 XD

總結一下,存取格式: '表單名字'!欄位代號
更多資訊請參考:Reference data from other sheets

其他常用 function:
  • 計算吃素人數 =COUNTIF('表單回應'!H:H, "素食"),其中 "表單回應" 之 H 欄位只有 "葷食" 跟 "素食" 選項
  • 計算累積人數 =SUM('表單回應'!C:C),其中 C 欄位是數字
  • 計算填表人數 =COUNTA('表單回應'!A:A)-1,代表計算共有幾筆,減一是減去第一列的標題列

2013年8月31日 星期六

SQLite Full-Text Searches 與 Custom Tokenizers 筆記 (FTS3/FTS4)

雖然敝公司的強項就是做 search engines 跟資料儲存管理,但研究其他家的 search 機制也是很基本的功夫。SQLite 小歸小,卻也五臟俱全,應該稱得上市面用戶最廣的資料庫系統吧?光 browser 或 mobile phone 就打趴一大片。

SQLITE Full-text Search 官方介紹網址: SQLite FTS3 and FTS4 Extensions

簡言之 FTS4 比 FTS3 更有效率,但也更吃資料空間。要留意的版本資狀態:
FTS4 is an enhancement to FTS3. FTS3 has been available since SQLite version 3.5.0 in 2007-09-04. The enhancements for FTS4 were added with SQLite version 3.7.4 on 2010-12-08.
至於 FTS3/FTS4 使用時機:
FTS4 is sometimes significantly faster than FTS3, even orders of magnitude faster depending on the query, though in the common case the performance of the two modules is similar. FTS4 also offers the enhanced matchinfo() outputs which can be useful in ranking the results of a MATCH operation. On the other hand, in the absence of a matchinfo=fts3 directive FTS4 requires a little more disk space than FTS3, though only a percent of two in most cases.
建立 FTS3/FTS4 Tables:

-- Create an FTS table named "pages" with three columns:
CREATE VIRTUAL TABLE pages USING fts4(title, keywords, body);

-- Create an FTS table named "mail" with two columns. Datatypes
-- and column constraints are specified along with each column. These
-- are completely ignored by FTS and SQLite.
CREATE VIRTUAL TABLE mail USING fts3(
  subject VARCHAR(256) NOT NULL,
  body TEXT CHECK(length(body)<10240)
);


至於 FTS Queries 範例:

-- The examples in this block assume the following FTS table:
CREATE VIRTUAL TABLE mail USING fts3(subject, body);

SELECT * FROM mail WHERE rowid = 15;                -- Fast. Rowid lookup.
SELECT * FROM mail WHERE body MATCH 'sqlite';       -- Fast. Full-text query.
SELECT * FROM mail WHERE mail MATCH 'search';       -- Fast. Full-text query.
SELECT * FROM mail WHERE rowid BETWEEN 15 AND 20;   -- Slow. Linear scan.
SELECT * FROM mail WHERE subject = 'database';      -- Slow. Linear scan.
SELECT * FROM mail WHERE subject MATCH 'database';  -- Fast. Full-text query.


以及這段:

-- Example schema
CREATE VIRTUAL TABLE mail USING fts3(subject, body);

-- Example table population
INSERT INTO mail(docid, subject, body) VALUES(1, 'software feedback', 'found it too slow');
INSERT INTO mail(docid, subject, body) VALUES(2, 'software feedback', 'no feedback');
INSERT INTO mail(docid, subject, body) VALUES(3, 'slow lunch order',  'was a software problem');

-- Example queries
SELECT * FROM mail WHERE subject MATCH 'software';    -- Selects rows 1 and 2
SELECT * FROM mail WHERE body    MATCH 'feedback';    -- Selects row 2
SELECT * FROM mail WHERE mail    MATCH 'software';    -- Selects rows 1, 2 and 3
SELECT * FROM mail WHERE mail    MATCH 'slow';        -- Selects rows 1 and 3


接著,提提要使用 FTS3 / FTS4 的用法,須要在 compile sqlite3 加上設定才行,預設沒有啟用,而啟用 FTS3 也會啟用 FTS4:

-DSQLITE_ENABLE_FTS3
-DSQLITE_ENABLE_FTS3_PARENTHESIS




CPPFLAGS="-DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS" ./configure <configure options>

此外,切 token 預設可選的機制有 "simple", "porter" (Porter Stemming algorithm), "icu" (需要SQLITE_ENABLE_ICU, 要有C版ICU Library), 和 "unicode61" (SQLite version 3.7.13之後支援)。

-- Create an FTS table named "papers" with two columns that uses
-- the tokenizer "porter".
CREATE VIRTUAL TABLE papers USING fts3(author, document, tokenize=porter);

-- Create an FTS table with a single column - "content" - that uses
-- the "simple" tokenizer.
CREATE VIRTUAL TABLE data USING fts4(tokenize=simple);

-- Create an FTS table with two columns that uses the "icu" tokenizer.
-- The qualifier "en_AU" is passed to the tokenizer implementation
CREATE VIRTUAL TABLE names USING fts3(a, b, tokenize=icu en_AU);


其中 simple 就是會把資料都先轉成小寫,查詢時也會將 keyword 轉小寫;porter 則是支援相關字詞的搜尋,例如資料內是存 "frustrated" 字,但用 "Frustrated" 和 "Frustration” 都可以查到;icu library 全名則為 "International Components for Unicode" library;Unicode61 則跟 simple 很像,但支援 unicode,如 unicode 空白與鏢體符號(切 token 用的 delimiter)等,而 simple 只認得 ASCII 部分。

接著,進入正題 SQLite FTS 這迷人的架構就在於可自定 tokenizers 啦,也是這篇筆記的重點,Custom (User Implemented) Tokenizers,不過網站上只有片段程式碼,問了同事才知道接下來又要翻 code 才行 :P 於是建議我挑 simple_tokenizer 來看,在 sqlite3.c 中,可以搜尋 tokenizers 的描述:
The fts3 built-in tokenizers - "simple", "porter" and "icu"- are implemented in files fts3_tokenizer1.c, fts3_porter.c and fts3_icu.c respectively. The following three forward declarations are for functions declared in these files used to retrieve the respective implementations.
Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed to by the argument to point to the "simple" tokenizer implementation.
And so on.
故搜尋 fts3_tokenizer1.c 可得知以下的定義:

/*
** 2006 Oct 10
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
******************************************************************************
**
** Implementation of the "simple" full-text-search tokenizer.
*/

typedef struct simple_tokenizer {
  sqlite3_tokenizer base;
  char delim[128];             /* flag ASCII delimiters */
} simple_tokenizer;

typedef struct simple_tokenizer_cursor {
  sqlite3_tokenizer_cursor base;
  const char *pInput;          /* input we are tokenizing */
  int nBytes;                  /* size of the input */
  int iOffset;                 /* current position in pInput */
  int iToken;                  /* index of next token to be returned */
  char *pToken;                /* storage for current token */
  int nTokenAllocated;         /* space allocated to zToken buffer */
} simple_tokenizer_cursor;

static int simpleDelim(simple_tokenizer *t, unsigned char c){
  return c<0x80 && t->delim[c];
}
static int fts3_isalnum(int x){
  return (x>='0' && x<='9') || (x>='A' && x<='Z') || (x>='a' && x<='z');
}

/*
** Create a new tokenizer instance.
*/
static int simpleCreate(
  int argc, const char * const *argv,
  sqlite3_tokenizer **ppTokenizer
){
// ...
}

/*
** Destroy a tokenizer
*/
static int simpleDestroy(sqlite3_tokenizer *pTokenizer){
  sqlite3_free(pTokenizer);
  return SQLITE_OK;
}

/*
** Prepare to begin tokenizing a particular string.  The input
** string to be tokenized is pInput[0..nBytes-1].  A cursor
** used to incrementally tokenize this string is returned in
** *ppCursor.
*/
static int simpleOpen(
  sqlite3_tokenizer *pTokenizer,         /* The tokenizer */
  const char *pInput, int nBytes,        /* String to be tokenized */
  sqlite3_tokenizer_cursor **ppCursor    /* OUT: Tokenization cursor */
){
// ...
}

/*
** Close a tokenization cursor previously opened by a call to
** simpleOpen() above.
*/
static int simpleClose(sqlite3_tokenizer_cursor *pCursor){
  simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor;
  sqlite3_free(c->pToken);
  sqlite3_free(c);
  return SQLITE_OK;
}

/*
** Extract the next token from a tokenization cursor.  The cursor must
** have been opened by a prior call to simpleOpen().
*/
static int simpleNext(
  sqlite3_tokenizer_cursor *pCursor,  /* Cursor returned by simpleOpen */
  const char **ppToken,               /* OUT: *ppToken is the token text */
  int *pnBytes,                       /* OUT: Number of bytes in token */
  int *piStartOffset,                 /* OUT: Starting offset of token */
  int *piEndOffset,                   /* OUT: Ending offset of token */
  int *piPosition                     /* OUT: Position integer of token */
){
// ...
}

/*
** The set of routines that implement the simple tokenizer
*/
static const sqlite3_tokenizer_module simpleTokenizerModule = {
  0,
  simpleCreate,
  simpleDestroy,
  simpleOpen,
  simpleClose,
  simpleNext,
  0,
};

/*
** Allocate a new simple tokenizer.  Return a pointer to the new
** tokenizer in *ppModule
*/
SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(
  sqlite3_tokenizer_module const**ppModule
){
  *ppModule = &simpleTokenizerModule;
}


從底部反過來往上看,至少要實作 Create, Destroy, Open, Close 和 Next,除了初始化跟資源釋放外,最重要的就是 Next 這個部分,簡單的說,就是把一串 byes 交給 Next,而 Next 每次被呼叫時,就從指定的位置往後找出一個 token 出來回傳,以此完成架構。

來做一個簡單的 changyy tokenizer 好了,只會把文章內有 changyy 開頭的 token 都取出來,其餘都 skip 掉:

/*
** Extract the next token from a tokenization cursor.  The cursor must
** have been opened by a prior call to simpleOpen().
*/
static int changyyNext(
  sqlite3_tokenizer_cursor *pCursor,  /* Cursor returned by simpleOpen */
  const char **ppToken,               /* OUT: *ppToken is the token text */
  int *pnBytes,                       /* OUT: Number of bytes in token */
  int *piStartOffset,                 /* OUT: Starting offset of token */
  int *piEndOffset,                   /* OUT: Ending offset of token */
  int *piPosition                     /* OUT: Position integer of token */
){
  changyy_tokenizer_cursor *c = (changyy_tokenizer_cursor *) pCursor;
  changyy_tokenizer *t = (changyy_tokenizer *) pCursor->pTokenizer;
  unsigned char *p = (unsigned char *)c->pInput;

  while( c->iOffset<c->nBytes ){
    int iStartOffset;

    /* Scan past delimiter characters */
    while( c->iOffset<c->nBytes && simpleDelim(t, p[c->iOffset]) ){
      c->iOffset++;
    }

    /* Count non-delimiter characters. */
    iStartOffset = c->iOffset;
    while( c->iOffset<c->nBytes && !simpleDelim(t, p[c->iOffset]) ){
      c->iOffset++;
    }

    if( c->iOffset>iStartOffset ){
      int i, n = c->iOffset-iStartOffset;
      if( n>c->nTokenAllocated ){
        char *pNew;
        c->nTokenAllocated = n+20;
        pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated);
        if( !pNew ) return SQLITE_NOMEM;
        c->pToken = pNew;
      }
      for(i=0; i<n; i++){
        /* TODO(shess) This needs expansion to handle UTF-8
        ** case-insensitivity.
        */
        unsigned char ch = p[iStartOffset+i];
        c->pToken[i] = (char)((ch>='A' && ch<='Z') ? ch-'A'+'a' : ch);
      }
      *ppToken = c->pToken;
      *pnBytes = n;
      *piStartOffset = iStartOffset;
      *piEndOffset = c->iOffset;
      *piPosition = c->iToken++;
      if( !strncmp(c->pToken,"changyy", n > 7 ? 7 : n ) )
      return SQLITE_OK;
    }
  }
  return SQLITE_DONE;
}


上述程式取於 simple_tokenizer 架構,只在做後的 token 加上判斷而已 :) 如此一來,假設測資為 "changyy changes changyys world",那搜尋 changyy 跟 changyys 都可以找到,但搜尋 changes 和 world 都會找不到(因為不符合 token 規定 :P)

$ ./sqlite3
sqlite> .load /path/changyy.so
sqlite> CREATE VIRTUAL TABLE data USING fts3(tokenize=changyy);
sqlite> INSERT INTO data ('content') VALUES ('changyy changes changyys world');
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changyy';
<b>changyy</b> changes changyys world
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changes';
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'changyys';
changyy changes <b>changyys</b> world
sqlite> SELECT SNIPPET(data) FROM data WHERE data MATCH 'world';
sqlite>


可以看到上述只有當 token 有被索引過的才能找出來,如此一來,就算完成偷懶的 custom tokenizer 學習吧 XD 更多操作請參考 https://github.com/changyy/sqlite-study