2010年8月31日 星期二

Javascript Unzip Testing

前陣子一直在調校 Javascript & Unzip 的事情,找到一個滿貼切工作的 -- Booktorious ,並且開始修改它。只是再怎樣地修改,在 Mobile Device 都不太適用,也被提醒會不會挑到的程式沒有實做很好,這部份我有留意它 unzip 的部份,的確存在不少可以精進的地方,當我準備要改得時候,我又看到了 rePublish 裡頭用的 zip 其實就已經接近我要改善的方式,因此筆記一下比較的過程,之後有空再慢慢增加其他對應的 library。


整個過程,看起來有點線性的成長,隨著檔案大小的增加,解壓縮的時間也會接近倍數成長。而 Booktorious 之 js-unzip 裡頭,有用到大量的資料複製,所以時間上花費會更多,相對於 rePublish 之 zip 的使用,對於 raw data 採用紀錄 offset 的方式,因此比 Booktorious 更加接近線性關係。


函式庫:



測資(純粹看 size 關係而非內容或檔案數目):



@ AMD X4 955, Ubuntu 10.04 i386, DDR3-1333 4GB, Google Chrome 6.0.495.0 dev


91KB


0.034s @ [js-zip & js-inflate]
0.026s @ [zip & inflate]


929KB


0.302s @ [js-zip & js-inflate]
0.139s @ [zip & inflate]


9.2MB


14.945s @ [js-zip & js-inflate]
1.654s @ [zip & inflate]


@ iPad, iOS 3.2.2


91KB


2.182s @ [js-zip & js-inflate]

1.214s @ [zip & inflate]


929KB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]

7.177s @ [zip & inflate]


9.2MB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]
JavaScript execution exceeded timeout @ [zip & inflate]


@ iPhone 3G, iOS 4.0.2


91KB


8.438s @ [js-zip & js-inflate]
5.397s @ [zip & inflate]


929KB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]
JavaScript execution exceeded timeout @ [zip & inflate]


9.2MB


JavaScript execution exceeded timeout @ [js-zip & js-inflate]
JavaScript execution exceeded timeout @ [zip & inflate]


以下是實驗的 Source Code,而測試中如果瀏覽器已經等很久甚至產生 timeout 的訊息時,試著一次只測試一個 library 吧,並且在 iPad 或 iPhone 也有機會碰到直接跳出 Safari 的情況


@index.html


<!DOCTYPE html>
<html xml:lang="utf-8" lang="utf-8" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script type="text/javascript" src="js-unzip.js"></script>
        <script type="text/javascript" src="js-inflate.js"></script>
        <script type="text/javascript" src="zip.js"></script>
        <script type="text/javascript" src="inflate.js"></script>
    </head>
    <body>
<script language="Javascript">
    function report( s, clear )
    {
        var report = document.getElementById( 'report' );
        if( clear )
            while ( report.hasChildNodes() && report.childNodes.length >= 1 )
                report.removeChild( report.firstChild );
        if( s )
        {
            report.appendChild( document.createTextNode( s ) );
            report.appendChild( document.createElement( 'br' ) );
        }
    }
    function doZip( epub , choose )
    {
        var path = epub || "epub/91kb.epub";
        var ajReq = new XMLHttpRequest();
 
        try{
            ajReq.open( 'GET' , path , false );
            ajReq.overrideMimeType( 'text/plain; charset=x-user-defined' );
            ajReq.send(null);
            ajReq.overrideMimeType( 'text/plain; charset=UTF-8' );
 
            var out = ajReq.responseText || '' ;
            var out_array = [];
            for( var i=0, len=out.length, scc=String.fromCharCode ; i<len ; ++i )
                out_array[i] = scc( out.charCodeAt(i) & 0xff );
            var out_binary = out_array.join( '' );
 
            if( out_binary !== '' )
            {
                var cost ;
                var unzipper;
 
                report( null, true );
 
                //
                // js-unzip & js-inflate
                //
                if( !choose || choose === 1 )
                {
                    unzipper = null;
                    cost = new Date();
                    unzipper = new JSUnzip( out_binary );
                    if( unzipper.isZipFile() )
                    {
                        unzipper.readEntries();
                        for (var i = 0 , len = unzipper.entries.length; i < len ; i++)
                        {
                            if ( unzipper.entries[i].compressionMethod === 0)
                                ; // unzipper.entries[i].data
                            else if ( unzipper.entries[i].compressionMethod === 8)
                                JSInflate.inflate( unzipper.entries[i].data );
                        }
                    }
                    cost = new Date() - cost ;
                    report( (cost / 1000.0) + 's' + ' @ [js-zip & js-inflate]' );
                }
 
                //
                // zip & inflate
                //
                if( !choose || choose === 2 )
                {
                    unzipper = null;
                    cost = new Date();
                    unzipper = Zip;
                    unzipper.Archive( out_binary );
                    for (var i = 0 , len = unzipper.entries.length; i < len ; i++)
                        unzipper.entries[i].content();
                    cost = new Date() - cost ;
                    report( (cost / 1000.0) + 's' + ' @ [zip & inflate]' );
                }
            }            
 
        }catch( err ){
 
            alert( 'Error:' + err );
 
        }
    }
</script>
        <dl>
            <dt>Do all</dt>
            <dd><button onclick="doZip( 'epub/91kb.epub' );">Unzip 91KB</button></dd>
            <dd><button onclick="doZip( 'epub/929kb.epub' );">Unzip 929kB</button></dd>
            <dd><button onclick="doZip( 'epub/9.2mb.epub' );">Unzip 9.2MB</button></dd>
        </dl>
        <dl>
            <dt>Use Booktorious</dt>
            <dd><button onclick="doZip( 'epub/91kb.epub' , 1 );">Unzip 91KB</button></dd>
            <dd><button onclick="doZip( 'epub/929kb.epub' , 1 );">Unzip 929kB</button></dd>
            <dd><button onclick="doZip( 'epub/9.2mb.epub' , 1 );">Unzip 9.2MB</button></dd>
        </dl>
                    
        <dl>
            <dt>Use rePublish</dt>
            <dd><button onclick="doZip( 'epub/91kb.epub' , 2 );">Unzip 91KB</button></dd>
            <dd><button onclick="doZip( 'epub/929kb.epub' , 2 );">Unzip 929kB</button></dd>
            <dd><button onclick="doZip( 'epub/9.2mb.epub' , 2 );">Unzip 9.2MB</button></dd>
        </dl>
        <p id="report"></p>
    </body>
</html>


2010年8月30日 星期一

追風








昨天睡前想了一會兒,在思考要如何過好生活。記得幾天前還跟同事閒聊,要不要拿進入 G社 當作目標?那種感覺並不是非要進去不可,是拿個目標要求自己,至於現在?完全找不到別人要聘我的理由。


除此之外,也想了一些可能可以列入考慮的事物:



  • 買一台冰箱

  • 買幾個鍋子

  • 買一台電鍋

  • 買一張氣墊床


理由是我的房門前就是宿舍的交誼廳,有電磁爐跟微波爐。過去三餐正常,導致我連煮東西的想法也都沒有,但現在仔細想想,不希望兩年這樣過了,我還是僅學會寫程式賺錢而已。


距離公司宿舍外頭約三分鐘的車程,有一間超商,好像叫五聯社,有點像全聯,印象中有蔬果類的,似乎很適合補貨;前陣子也買了公司健身房的年費,希望一星期可以運動三次啦。


以上是目前暫時想到的生活規劃


2010年8月29日 星期日

PassionRepublic Taiwan Trip








很讚的台灣之旅!這大概是我看過的旅遊影片中的佼佼者吧!從學長 Facebook 的分享來看,這是「馬來西亞動畫設計公司(Passion Republic)員工旅遊」之五天旅程,僅用Canon EOS 7D單眼攝影相機及GoPro Hero防水運動攝影機!而最近 PTT 也有一系列討論馬來西亞的文化等等的,台灣真的挺讚的!


很佩服這影片以及這行動!旅行的途中又可以將所學的應用上,我想這是最難的可貴的。記得大五那年我也想試著用所學的嘗試改善生活,例如使用簡單的物理常識,讓家裡更通風等等的,我覺得這真的比賺錢還實在。希望未來也能行之有餘,用所學的幫助別人。


2010年8月28日 星期六

iPad 出包記! Jailbreak 後無法正常啟動

週五下午接到主管的指令,要我把手上那台 iPad 給他 jailbreak 一下,小弟我當然就給他 try 一下。由於那台是 iOS 3.2.1 (最初是 iOS 3.2) ,所以就馬上試試 www.jailbreakme.com 這個網站的高招方式!它是利用 PDF 的漏洞(iOS 3.2.2 已修正),只要 iPad 透過 Safari 瀏覽器,就可以進行 jailbreak ,超方便的。


只是越獄後的 iPad ,還是少了主管要我做的相關資料,並且操作上有點怪怪的,主管就叫我重弄看看,我一時耍小聰明,直接用 General > Reset 的方式把資料清空,當下是因為沒有存 iPad iOS 3.2.1 ,直接用 iTunes 進行恢復大概也只能弄成 iOS 3.2.2 版,所以一時就想試看看 Reset 的方式,結果,試完重開機後,整個 iPad 就無法正常操作。


通常越獄的機器,開機後除了第一張最初的 iOS 的開機畫面,接著會顯示第二張圖片用來標記越獄成功,而我那台 iPad 就是一直停在第二張圖,直到天荒地老 XD 並且無法正常關機,而使用 Home + Power 也頂多是重開機而已,並且因為還沒正常啟動,因此 iTunes 不會辨認出它也無法進行回復。週五晚上六點多,大概覺得沒救了要送修,所以就寄了信回報,就把它擺在旁邊,過了一晚他還是卡在那邊 XD


後來,周六早上想起來,可以利用長壓 Home + Power 鍵讓他進入回復/開發/初始狀態?也就是會看到一條 USB 線與 iTunes 的那個圖。於是,先透過 Home + Power 鍵令他重開機,接著仍舊不放手,不久後就切換到 UBS 線與 iTunes 的圖啦,而 iTunes 就可以偵測到那台 iPad ,雖然顯示有點不正常,但至少可以做回復的動作了!我二話不說,馬上升到 iOS 3.2.2 啦,因為我也懶得去找 iOS 3.2.1 啦,能夠救回 iPad 就好,能不能 jailbreak 以後再說 XDD


就這樣,iPad 驚魂記終於落幕了。由於這台是美國買的吧?送修應該會很麻煩吧 orz 只能慶幸去年十月自己也有把一台 iPhone 3G 越獄過,那台是僅限 AT&T 的 sim 卡,所以我不得不把它越獄啦,那時的越獄方式複雜多了!沒想到那時的經驗還是有幫助到 :D 而這次 iPad 碰到的問題,我猜可能是因為 firmware 越獄後,啟動時可能還要從 disk 讀取一些資訊吧,然而資料卻被我清空,導致讀不到資料卡在那邊吧!


@ 2010/08/29 : 那個 USB + iTunes 圖是 DFU Mode ;而 iPad 部分,據說只要原先是 3.2 版,雖然更新到 3.2.2 但透過修改 hosts ,還是可以在用 iTunes 時可以按住 Shift + 回復,就可以使用 iPad iOS 3.2.1 囉,這部分我還沒測試。至於 ipsw 下載位置可以找尋 Google 囉!以下是找到的 Apple 官方(http://appldnld.apple.com/*) 下載位置:



2010年8月25日 星期三

[Python] BBS Crawler 筆記

遙想台灣 BBS 的興起,大概是 1997 前後,至少我是那時候開始接觸的?記得那時可還用著 33.6Kb 的數據機,後來又升級到 56 Kb ,當年好像是我姐說想要玩 BBS ,然後家裡就多牽了一條電話線,就這樣開啟玩網路的年代。除了 BBS 外,還有 ICQ 等聊天軟體,另外我則是跟隨同學的腳步,常常去"史萊姆的第一個家"找些新奇的軟體,當然,還有 MIDI 音樂等等的。但最後,還是跌入 BBS 裡頭,在那個青澀的年代裡。


前陣子想到撈一下 BBS 裡頭的資料,於是挑選了 Python 這個語言,它有 telnetlib 跟 expect 可以用,就等於解決撈 BBS 的最大困難之處。


範例一,使用帳號密碼連到指定的站台:


import telnetlib

tn = telnetlib.Telnet( SITE_IP )
q = tn.expect(['使用者帳號:'] , 10)
tn.write( USER_ID + '\r' )
tn.expect(['密碼:'] , 10 )
tn.write( PASSWORD + '\r' )


其中 "使用者帳號:" 跟 "密碼:" 這是兩個很重要的 pattern ,並且依各家 BBS 的情況都不一樣。例如 PTT 就是"以 new 註冊:" 和 "請輸入您的密碼:" 等。上述的程式碼依序是連到 SITE_IP 這個位置的 BBS ,並且收集封包,等碰到第一個 pattern 時,才將 USER_ID + '\r' 的資料丟出去,然後繼續收集資料直到第二個 pattern 出現,在做對應的動作,此處是丟出密碼。


透過上述例子,我想應該就十分清楚了!由於 telnet 等同不間斷地丟資料過來,因此在判斷執行下一個動作前,要去等待某個關鍵 pattern ,以 BBS 之 24X80 的頁面,大部分就是挑換頁完後最後一個 pattern ,如最右下角等。


其實,這個筆記就只有一個範例就收工了 XD 老狗變不出新把戲啦!說說其它的技巧


def skip():
        q = tn.expect( ['pattern1', 'pattern2', 'pattern3'] , 10 )
        if q[0] == 0:
                tn.write('Key1' + "\r")
                skip()
        elif q[0] == 1:
                tn.write('Key2' + "\r")

                skip()

        elif q[0] == 2:

                return True


這個就是直到看到 pattern3 才離開,並且看到 pattern1 或 pattern2 則分別丟出指定的關鍵字出去。這種用法有時會需要。而為啥 expect 後面有接一個數字,那是因為避免收集不到 pattern 而一直卡在那邊,也就是 timeout 的使用,我覺得可以用在特殊情況,像是碰到 Server 掛掉沒丟完資料,或是自己耍蠢沒弄好全部的 pattern ,導致整個連線一直卡在那邊等到天荒地老啦。


其他常用的就是 Regular Expression 吧


import re

raw = re.sub( re_pattern_replace_space , '' , rawData )

out = re.findall( re_pattern_get_items,  raw )
for sub_item in out:
        pass

check = re.search( re_pattern_check , raw )
if check <> None :
        raw_pattern = check.group(0)


另外,還有丟Key 給 BBS Server 的部分,要去找一下對應表,例如上下左右是對應哪個 ASCII code ,我後來偷懶都用英文而已,例如離開可以用左鍵也可以按 q 鍵,如此等等


最後,提醒一下,有時候 BBS 為了效能的關係,每一次並不是吐一整個完整 24X80 的頁面,也就是並非每次都把整個頁面更新,有時候只更新部分資料,例如這次的頁面跟上次的只差一點點,因此 Server 就可能只丟出要更新的資料並利用控制碼去更新,所以,要判斷頁面是否已收集完畢,不一定能靠最右下角那個 pattern 喔!這是我實作上碰到一個很關鍵的地方。如此一來,整個 telnet crawler 就能完工囉!


其他資料請參考 Python - telnetlibPython - re,別忘了留意使用的 Python 版本是否合用喔。