2011年1月24日 星期一

Django 與 Ubuntu 10.04 server 使用筆記

記得 2009 年年底,我也曾接觸 Django 一陣子,但久而沒碰,等於砍掉重練。最近被派去管一台機器,順手筆記一下到底安裝了啥東西。大學時期,每個人都搶著管機器,想說要有個經驗,碩班後,大概越來不想管機器,而工作之後呢?管機器根本就是個吃力不討好的事,做得好沒人稱讚也不會列入考績,做差會被抱怨啊 XD 所以啊,工作後能不管機器還是不要管,甚至一些案子都忘了規劃管機器的人力費用,也有可能沒規畫架設機器的時間。說完題外話 XD 來回顧一下以前的筆記:



以前我比較熟 FreeBSD,現在?大概沒有一項熟,工作上大概都用 Ubuntu ,目前是用 Ubuntu 10.04 64-bit server 。


光碟安裝完系統後,系統更新:


$ sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
$ sudo vim /etc/apt/source
:%s/us.archive/tw.archive
$ sudo apt-get update


安裝 denyhosts ,可用來阻擋惡意 try 帳密的 IP


$ sudo apt-get install denyhosts
$ sudo touch /etc/hosts.deny
$ sudo touch /etc/hosts.allow
$ sudo /etc/init.d/denyhosts restart


也可以把自己常用的 IP 寫在 /etc/hosts.allow 以防止不小心擋掉自己


sshd: MyIP1, MyIP2, MyIP3


在新增網卡後的設定,此為 VirtualBox 上安裝 eth1 網卡的例子


查看網卡有沒安裝好


$ dmesg | grep eth


若看不到可以移掉下述檔案並重開機偵測


$ sudo mv /etc/udev/rules.d/70-persistent-et.rules /etc/udev/rules.d/70-persistent-et.rules.old 


若 ifconfig 看不到但 dmesg 可以查看到,那就要設定  /etc/network/interfaces ,此例為設定 DHCP 方式


$ sudo vim /etc/network/interfaces
auto eth1
iface eth1 inet dhcp


$ sudo /etc/init.d/networking restart


安裝 MySQL、Apache Web Server、PHP


$ sudo apt-get install mysql-server
$ sudo apt-get install apache2
$ sudo apt-get install php5


設定 Apache 相關


啟用 https 其及 ssl 模組


$ sudo a2enmod ssl
$ sudo a2ensite default-ssl


啟用某些設定檔,如 /etc/apache2/site-available/default_setting


$ sudo a2ensite default_setting


設定完重新啟動


$ sudo /etc/init.d/apache restart


安裝 X-Window,有些環境一開始只可以本機端登入,有些 Web service 測試就需要先安裝一下 X-Window 啦,不然只能用 telnet localhost port 跟 GET / 測試,還滿辛苦的,也可以安裝 lynx 這種工具來使用


$ sudo apt-get ubuntu-desktop


設定開機預設不要進 X-Window (目前還待測試,有點怪,機器跑起來還必須透過 ALT+ F1, ... 切換到其他的來使用)


$ vim /etc/init/gdm.conf 
stop on runlevel [0216]
$ sudo service gdm stop


簡易 MySQL 語法,建立帳號、設定資料庫權限


$ mysql -u root -p
mysql> CREATE DATABASES mydb;
mysql> CREATE USER 'user'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL ON mydb.* TO 'user'@'localhost';
mysql> FLUSH PRIVILEGES;


$ mysql -u user -p
mysql> USE mydb;
mysql> CREATE TABLE my_table ( int x , int y );
mysql> INSERT INTO my_table (x,y) values( 1, 2 );
mysql> DESC my_table;
mysql> SELECT * FROM my_table;
mysql> DELETE FROM my_table WHERE x=1;
mysql> DROP TABLE my_table;


設定 utf8 相關


$ vim /etc/mysql/my.cnf

[client]
default-character-set = utf8

[mysqld]
default-character-set=utf8
init_connect='SET NAMES utf8'


如果一開始不是 UTF-8 的話,又跑了服務一陣子,那大概就親自去改


$ mysql -u user -p 
mysql> status
--------------
mysql  Ver 14.14 Distrib 5.1.41, for debian-linux-gnu (x86_64) using readline 6.1

Connection id:          252
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         5.1.41-3ubuntu12.9 (Ubuntu)
Protocol version:       10
Connection:             Localhost via UNIX socket
Client characterset:    utf8
Server characterset:    utf8
UNIX socket:            /var/run/mysqld/mysqld.sock
Uptime:                 21 hours 38 min 58 sec


Threads: 3  Questions: 5376  Slow queries: 0  Opens: 267  Flush tables: 1  Open tables: 63  Queries per second avg: 0.68
--------------
mysql> ALTER DATABASE mydb CHARACTER SET utf8 COLLATE utf8_general_ci;
mysql> USE mydb;
mysql> SHOW CREATE TABLE my_table;
可查看最後的編碼是否為 DEFAULT CHARSET=utf8
mysql> ALTER TABLE my_table CHARACTER SET utf8 COLLATE utf8_general_ci ;


安裝 Django 相關


$ sudo apt-get install libapache2-mod-wsgi python-mysqldb python-setuptool python-django


建立一個 Project


$ cd ~/
$ django-admin startproject myproj
$ cd ~/myproj
$ django-admin startapp myapp



設定 myproj 的 settings.py


DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = 'mydb' # Or path to database file if using sqlite3.
DATABASE_USER = 'user' # Not used with sqlite3.
DATABASE_PASSWORD = 'password' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = ''  # Set to empty string for default. Not used with sqlite3.

TIME_ZONE = 'Etc/GMT-8'

INSTALLED_APPS = (
    'myproj.myapp',
)

SYS_TEMP_DIR = '/tmp'


設定 myproj 的 urls.py


from django.conf.urls.defaults import *

urlpatterns = patterns('',
    # Example:
    (r'^myapp', include('myproj.myapp.urls')),
)


設定 myapp 的 model.py


from django.db import models

import datetime

# Create your models here.
class MyTable(models.Model):
        mypath = models.CharField(max_length=255,primary_key=True)
        mydate = models.DateTimeField(default=datetime.date.today,auto_now_add = True)
        myscore = models.IntegerField()


設定 myapp 的 views.py


# -*- coding:utf-8 -*-
from django.shortcuts import render_to_response
from django.template import RequestContext
from django import http

from myproj.myapp.models import MyTable

import simplejson
import sys, os, os.path
from time import gmtime, strftime, localtime

from myproj.settings import SYS_TEMP_DIR

def MyTest(request):
        _result = {}

        _name = request.POST.get('name','anonymous') or request.GET.get('name','anonymous')

        #my_time = gmtime()
        my_time = localtime()
        my_date = strftime( "%Y-%m-%d" , my_time )

        save_dir = SYS_TEMP_DIR
        save_file = ''

        if save_dir is None:
                _result['status']='1'
                _result['message']='Internal Error'
                result=simplejson.dumps(_result)
                return http.HttpResponse(str(result))

        if request.FILES and request.FILES['my_file'] :
                upload_file = request.FILES['my_file']
                save_file = str( _name  ) + str( strftime( "_%y_%m_%d_%H_%M_%S" , my_date ) )
                save_path = os.path.join( save_dir , save_file )

                try:   
                        destination = open( save_path , 'wb+' )
                        for chunk in upload_file.chunks():
                                destination.write(chunk)
                        destination.close()
                except:
                      _result['status']='1'
                      _result['message']='Internal Error'
                      result=simplejson.dumps(_result)
                      return http.HttpResponse(str(result))
        else:  
                return render_to_response('my_upload.html' , { 'test':'hello world' } )

        try:   
                db_object = MyTable(mypath=save_file)
                db_object.mydate = my_time
                db_object.save()
        except:
                _result['status']='1'
                _result['message']='Internal Error'
                result=simplejson.dumps(_result)
                return http.HttpResponse(str(result))

        _result['status']='0'
        result=simplejson.dumps(_result)
        return http.HttpResponse(str(result))


設定 myapp 的 urls.py


from django.conf.urls.defaults import *
urlpatterns = patterns('',
        (r'^upload/', 'myproj.myapp.views.MyTest'),
)


建立 templates


$ cd ~/myproj/myapp
$ mkdir templates
$ vim my_upload.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
        <head>
                <meta http-equiv="content-type" content="text/html; charset=utf-8" />
                <title>GPX Upload</title>
        </head>
        <body>
{% if test %}
                <h1>{{ test }}</h1>
{% end if %}
                <form action="." enctype="multipart/form-data" method="post">
                        <input type="file" name="my_file" />
                        <button type="submit">Upload</button>
                </form>
        </body>
</html>



建立 myproj 之 myapp 相關的 db table


$ cd ~/myproj
$ python manage.py syncdb



測試,用瀏覽器連 http://MyIP:8000/myapp/upload/


$ cd ~/myproj
$ python manage.py runserver MyIP:8000



重置 myapp 的 db table


$ cd ~/myproj
$ python manage.py reset myapp


最後,也可以使用 apache 的 mod-wsgi 模組


$ cd ~/myproj
$ mkdir apache
$ vim django.wsgi

import os, sys
sys.path.append('/home/user/myproj')
os.environ['DJANGO_SETTINGS_MODULE'] = 'myproj.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()


設定 apache 檔


$ vim /etc/apache2/site-available/default

WSGIScriptAlias /
/home/user/myproj/apache/django.wsgi
# WSGIDaemonProcess demo_com user=www-data group=www-data processes=1 threads=10
# WSGIProcessGroup demo_com


$ sudo /etc/init.d/apache restart


2011年1月23日 星期日

買了!Uptech TV7130電視卡

IMG_0109


前幾個禮拜,常常跟博班 Adios 大神閒聊電視的事情,沒想到我也買了電視卡了。宿舍本來就有提供第四台,但住了一年多並沒有吸引我去買電視卡/電視盒的念頭,但最後我還是敗了。買電視卡的主因是因為前陣子申請了壹電視的網樂通,原先以為他可以當電視盒來用,沒想到他只輸出 AV 端子 Orz 盡管東西非常慢才來,在等待的過程中,我也回信說要退掉,但耐心的客服始終說服我,最後也就等到網樂通啦!算是比第一批申請的人,卻慢了快三個禮拜吧!


TV7130 產品應用
來源:TV7130產品型錄


接著來說說 TV7130 電視卡吧!這是我第一次使用電視卡,更早之前算是使用過電視盒。電視卡的缺點是需要插在主機上面,並佔用一個插槽,並且要看電視就必須開機。我買的這張是使用 PCI 插槽,在高階一點的電視卡是用 PCI-E 的。再加上現在主機板大多設計為 M-ATX 的板子,為了縮小面積,所提供的 PCI 插槽就少了不少,像我這張主機板是 GA-MA785GPMT-UD2H 的,就只有兩個 PCI 插槽。一個用在網路卡,另一個就用在這張電視卡,剛好都插滿了。而使用電視卡的優點,那就是可以邊用電腦邊看電視,換句話說,可以把看電視弄成小視窗擺在螢幕上,而不用像電視盒那樣佔了一個螢幕資源,除此之外,也可以錄製電視節目喔。但真正吸引得我的主因,就是可以把邊寫程式邊看電視啦 cc 以前常用廣播當作背景音樂,現在大概可以用電視當背景音樂吧 XD


TV7130 配備
來源:TV7130 繁體中文使用手冊第二版


安裝這張電視卡並非難事,插進主機板,安裝一下驅動程式重開機後,接上 Cable 線就算搞定。然後我一直搞不懂為啥他多了一條線,也就是上圖左下角的那條 Phone Jack to Phone Jack ,最後,我才發現原來裝了電視卡後,使用時螢幕上雖然可以看到影像(第一次使用別忘了要去電台掃描),但始終沒有聲音,原來這就是那條線的目的,他就是用來把電視卡輸出的聲音導入音效卡的輸入端,當然,也可以另外接喇叭吧。我使用的就是導回音效卡的輸入端,並且在電視卡軟體->[設定]->[裝置]->[播放控制]->[Rear Blue In]


這樣的使用缺點就是電視的聲音大小聲,跟主機上使用的聲音大小聲會綁再一起,例如我想要把電視的聲音關小一點,把 Skype 的聲音放大一點,那這樣是不太行的!整體上但勉強都還堪用囉!


對於電視卡算是多了點了解,最驚訝的是音效的輸出設計,只提供從電視卡輸出,這大概也跟自身產品有關吧,未來可以在找一個喇叭接著用,就可以改善這種困擾吧。


USB 無線網卡 - SAPIDO AU-4912 超迷你無線N

usb網卡


上週無意間發現這款 USB 無線網卡,就在 Plurk 上詢問有無使用經驗,倒是讓我大學同學也敗了這款,他還比我早買 XD 同學買的主因是自己筆電網卡很不穩定,所以需要新購一張網卡,最後則是被這款體積小的給吸引了,只是筆電 USB 孔很少,要留意啊。這是一款跟 10 元硬幣大小差不多大的 USB 無線網卡,我買他的主因有兩個,一個是讓主機可以透過無線網路上網,第二個則是它可當 Soft AP 囉。其中 Soft AP 就是透過軟體讓無線網卡變成無線基地台啦,雖然說原理都差不多,但並不是每張無線網卡的製造商都有提供 Soft AP 的程式。


我在新竹 NOVA 邊逛了會,發現還不少家在賣 USB 無線網卡,體積跟隨身碟差不多大的比較多,跟這張一樣大的比較少,另外,價格也都不便宜,都要 500 上下,我在順發有看到一款體積稍大的,但賣 369 元的無線網卡,但最後還是花多一點點錢試試這款小體積的網卡。可以多留一下 spec ,像這張網卡是 150 Mbps ,我看到有得賣得比較貴是 300 Mbps 囉,只是速度快慢是否影響很大,那就很難說了!我覺得 150 Mbps 在某些使用情境已經非常夠用了。


usage


使用上很簡便,若只是當一般無線網卡,那可能插上主機後,在 Win 7 x64 甚至連驅動程式都不用安裝也可以用。但如果要當 Soft AP 的話,那必須安裝軟體,安裝完後,請將 [模式] 調成 [基地台] ,接著再選擇要分享的網路以及加設定密連線的密碼,拉到此頁面下面,按下 OK 後即可完工!我用 iPod Touch 4 測試可以正常使用網路。


逛了一下內附的 CD ,發現裡頭也有 Linux 跟 Mac 的 driver ,看起來還不錯吧。有興趣的可以再去官網看看,至少短時間我還測不到啥問題。未來應該會把它當成旅行用的隨身 3C 產品 XD


2011年1月18日 星期二

心的方向!Go~








昨天到台北參加聚餐,往高鐵站的路上,被後照鏡上火紅的夕陽閃了許久,讓我哼起了這首歌。有點久沒騎車到高鐵站,除了騎過頭外,沒想到連高鐵前面常常一排排違規停車的機車群,忽然都沒看到,害我一時不知要停在哪。


最近還是有非技術類的心情想要記下來,但每每寫到一半就被我 cancel 掉,實在是三五句提到公司福利不然就政治八卦等,這樣的行為讓我對自己感到失落。去年底開始警惕自己不要談論這類話題,希望 2011 年有好的開始。盡管背負的壓力還沒減輕,但開始比較不會只為錢工作了。今年的工作壓力理當比去年大,計畫趕不上變化,但還好,除了每週唸不熟領域的 paper 外,剩下的應該都還能應付吧?


對於 2010 的感觸,除了提不得的暗黑八卦外,大概是對資源管控有所感觸。例如一天 24 小時,某件事做多了,另一件事就必然做少了。有時也不得不跟別人搶資源,例如宿舍樓下的烘乾機啊,或是火車坐位上永遠只有 3 個扶手。也曾跟大神吃飯,當時探問他對 Android Market 或 iOS App store 有無興趣投入,得到的回應當然是有,但有更重要的事要擺在前頭 -- 陪孩子成長,這時我也才比較會回過頭思考一些事情,投入熱門的產業追求的目標是什麼?有沒有比這些更重要的目標呢?很佩服一些人,像青蛙學弟,可以大膽地嘗試一些新東西,不要只為錢工作,也讓我反省一下,該抽空弄弄點更有意義的東西。


最近對未來有些新的方向了,願能持之以恆,離目標越來越近。


2011年1月6日 星期四

Android 開發筆記 - 模擬與繪製 GPS 移動路線

mygpspathwithmarker
上頭紅色的是繪製的路徑,藍色點則是模擬時,顯示的 GPS 座標位置,將隨著時間變動。這是根據此篇 Android 開發教學筆記 - 透過 Google Maps API 畫出 GPS 路徑Android 開發教學筆記 - 調整 GPS 座標個數,以提昇路徑繪圖效率 的筆記所做的延伸練習。


給定一段 GPS 紀錄的路徑,經過個數的縮減壓縮並在地圖上完成路徑的繪製,接著想要了解原始 GPS 移動與繪製的路徑差異有多大,所以就有了這個練習。依照原始 GPS 座標資訊,畫製一個 marker 隨著時間擺放到指定的 GPS 位置,這樣的效果除了能確認原始 GPS 路線與壓縮後的路徑位置的差異,也可以了解實際收集到的 GPS 座標是否出現在 Google Maps 繪製的地圖道路。


實做上在 class MyGPSPath 裡,新增並且改變一些 member 宣告方式:


Handler jobs;
final double srcLogs[]={ 48.138050017878413, 16.481179967522621 /* ,  ... */ };
double logs[] = {};
int gps_at;


其中 jobs 用來更新畫面,srcLogs用來紀錄原始的不重複的 GPS 資訊,而 logs 會是壓縮過後的 GPS 個數,而 gps_at 則是等會模擬時,用來紀錄走到那個 GPS 點。


接著在 onCreate 裡,初始化 gps_at, jobs, logs 變數,以及調整 overlay :


gps_at = 0;
jobs = new Handler();

ArrayList<Double> data = new ArrayList<Double>();
for( int i=0; i<srcLogs.length ; ++i )
    data.add( new Double( srcLogs[i] ) );
data = rebuildGSPLogsByAngleCheck( data , 0 );
        
logs = new double[data.size()];
for( int i=0; i<data.size() ; ++i)
    logs[i] = data.get(i).floatValue();

List<com.google.android.maps.Overlay> ol = mapView.getOverlays();
ol.clear();
ol.add(new MyPathOverlay());

MapController mapController = mapView.getController();
if( mapController != null )
{
    mapController.setCenter(new GeoPoint( (int) (srcLogs[0]* 1000000) , (int)(srcLogs[1]* 1000000) ) );
    mapController.setZoom(15);
}

new Thread( new Runnable(){
    public void run()
    {
        gps_at = 2;
        while( gps_at < srcLogs.length )
        {
            try{
                jobs.post( new Runnable(){
                    public void run(){
                        List<com.google.android.maps.Overlay> ol = mapView.getOverlays();
                        while( ol.size() > 1 )
                            ol.remove(1);
                        ol.add(new MyMarkerOverlay( srcLogs[gps_at] , srcLogs[gps_at+1]));
                        
                        GeoPoint in = new GeoPoint((int) (srcLogs[gps_at] * 1000000) , (int) (srcLogs[gps_at+1] * 1000000) );
                        Point out = new Point();
                        mapView.getProjection().toPixels(in, out);
                        int diff = 100;
                        if( out.x < diff || out.y < diff || out.x > mapView.getWidth() - diff || out.y > mapView.getHeight() - diff )
                        {
                            MapController mc = mapView.getController();
                            mc.animateTo(new GeoPoint( (int)(srcLogs[gps_at]*1000000) , (int)(srcLogs[gps_at+1]*1000000 )) );
                        }
                    }
                });
                Thread.sleep(500);
            }
            catch(Exception e)
            {
            }
            gps_at +=2 ;
        }
    }
}).start();


上頭的 MyPathOverlay 則是之前例子中的 MyMapOverlay,在此僅改變成較有意義的名稱,而 onCreate 最後面使用到的 Thread,就是用來模擬 GPS 變化的過程,每 0.5 秒更動 gps_at 時,則重新擺放 MyMarkerOverlay 的位置,並且稍微計算一下,當 Marker 太靠近可視地圖的邊框時,自動調整地圖位置。


另外,還需實做 MyMarkerOverlay,用來標記 GPS 移動:


class MyMarkerOverlay extends com.google.android.maps.Overlay
{
    double at_lat,at_lng;
    MyMarkerOverlay( double _lat, double _lng )
    {
        super();
        at_lat = _lat;
        at_lng = _lng;
    }
    @Override
    public boolean draw(Canvas canvas, MapView mapView,boolean shadow, long when)
    {
        super.draw(canvas, mapView, shadow);
            
        if( !shadow )
        {
            GeoPoint in = new GeoPoint((int) (at_lat * 1000000) , (int) (at_lng * 1000000) );
            Point out = new Point();
            mapView.getProjection().toPixels(in, out);
                
            int r = 6;
            Paint p = new Paint();
                
            p.setColor(Color.BLUE);
            p.setAntiAlias(true);
            RectF oval=new RectF( out.x - r, out.y - r, out.x + r, out.y + r);

            canvas.drawOval(oval, p);
        }
        return true;
    }
}


也可以實做 ItemizedOverlay<OverlayItem> 的方式,好處是用法接近 Web API 的使用,把你想要新增的點,加到下面的 list 即可:


class MyItemMarkerOverlay extends com.google.android.maps.ItemizedOverlay<OverlayItem>
{
    private ArrayList<OverlayItem> myOverlays = new ArrayList<OverlayItem>();
    private Context myContext;
    public MyItemMarkerOverlay(Drawable defaultMarker, Context context)
    {
        super(boundCenterBottom(defaultMarker));
        myContext = context;
    }
    @Override
    protected OverlayItem createItem(int i)
    {
        return myOverlays.get(i);
    }
    @Override
    public int size()
    {
        return myOverlays.size();
    }
    @Override
    protected boolean onTap(int index)
    {
        OverlayItem item = myOverlays.get(index);
        //AlertDialog dialog = new AlertDialog.Builder(MyGPSPath.this).create();
        AlertDialog dialog = new AlertDialog.Builder(myContext).create();
        dialog.setTitle(item.getTitle());
        dialog.setMessage(item.getSnippet());
        dialog.setButton("OK", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which)
            {
                dialog.cancel();
            }
        } );
        dialog.show();
        return true;
    }
    public void addOverlay(OverlayItem overlay)
    {
        myOverlays.add(overlay);
        populate();
    }
}


用法:


List<com.google.android.maps.Overlay> olay = mapView.getOverlays();
Drawable drawable = this.getResources().getDrawable(R.drawable.icon);
MyItemMarkerOverlay markersOverlay = new MyItemMarkerOverlay( drawable, this ); // new MyItemMarkerOverlay( drawable, MyPGSPath.this );

markersOverlay.addOverlay(
    new OverlayItem(
        new GeoPoint((int) (srcLogs[gps_at] * 1000000) , (int) (srcLogs[gps_at+1] * 1000000)),
        "Title",
        "Snippet"
    )
);

olay.add(markersOverlay);


此練習的 GPS 變化,也可以透過 DDMS 來處理,例如定時 send 一些 GPS 座標等,但我還沒嘗試,看來下個練習方向可以用用看 DDMS 囉。