Monday, May 18, 2009

UPDATE 20090516 : mingpao.php

Mingpao News has changed their layout, so there is a need to rewrite the parser.

Here is the code :

Code:

<?php

function InitCurl()
{
global $ch;

$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3");
}

function CurlGetContent($url)
{
global $ch;

curl_setopt($ch, CURLOPT_URL, $url);

return curl_exec($ch);
}

function DisplayNewsPage($news)
{
global $base_url;
global $today;

$b = $base_url.$today.'/';
print '<table><tr><td width=200 VALIGN="top">';

if (is_array($news['PicSet']))
{
foreach ($news['PicSet'] as $k => $v)
{
print "<img src=\"".$b.$v['Pic']."\" onmouseover=\"popImg(true, this);\" onmouseout=\"popImg(false);\" width=180><BR>\r\n";
print $v['PicText']."<BR><BR>\r\n";
}
}
print '</td><td VALIGN="top">';
print '<H1>'.$news['Title'].'</H1><BR><BR>';
print $news['Body'];
print '</td></tr>';
print '</table>'."\r\n";
ob_flush();
flush();
}

function GetPic($url)
{
global $base_url;
global $today;


$page = CurlGetContent($base_url.$today.'/'.$url.'?Mode=1');

$pic = $pictext = "";

if (!preg_match_all('|mainphotolink"><img src="([^"]+)"|U', $page, $layer1, PREG_SET_ORDER)) return "";

$pic = $layer1[0][1];

if (preg_match_all("|<td align=\"center\" class=\"caption\">(.*?)</td>|U", $page, $layer2, PREG_SET_ORDER))
{
$pictext = $layer2[0][1];
}

return Array(
'Pic' => $pic,
'PicText' => $pictext,
);

}

function GetNewsContent($url)
{
global $base_url;
global $today;

$page = CurlGetContent($base_url.$today.'/'.$url);

$p = $body = $title = $pictext = $pic = "";

if (preg_match_all("|<h1>(.*?)</h1>|U", $page, $layer1, PREG_SET_ORDER))
{
$title = str_replace('<br>', ' ', $layer1[0][1]);
}

if (preg_match_all('|<div class="[^"]*" id="newscontent[\d]*">(.*?)</div>|is',$page, $layer2, PREG_SET_ORDER))
{
foreach ($layer2 as $v)
{
$body .= $v[1];
}
}

if (preg_match_all("|<a href=\"([^\"]+)\?Mode=1\">|U",$page, $layer3, PREG_SET_ORDER))
{
foreach ($layer3 as $v)
{
$ar = GetPic($v[1]);
if (is_array($ar)) $p[$v[1]] = $ar;
}

}

return Array(
'Title' => $title,
'Body' => $body,
'PicSet' => $p,
);

}

function ProcessAllTitles()
{
global $title_pages_set;
if (!is_array($title_pages_set))
{
echo 'ERR';
return;
}
foreach ($title_pages_set as $key => $val)
{
if (!strstr($val,"main"))
{
ProcessPage($key, $val);
}
}
}

function GetTitle($ming)
{
if (!preg_match_all("|sublink\[0\] = \'(.*?)\'|U",$ming, $layer1, PREG_SET_ORDER)) return "1";

if (!preg_match_all("|<a href=\"([^\"]+)\"[^>]*?>([^<]+)</a>|U",$layer1[0][1], $layer2, PREG_SET_ORDER)) return "2";

$titles = "";
foreach ($layer2 as $v)
{
if (!preg_match_all("|index.htm|U", $v[1], $layer3, PREG_SET_ORDER)) continue;
$title = $v[2];
$link = $v[1];
$titles[] = Array(
'Title' => $title,
'Link' => $link,
);
}
return $titles;

}

function GetHeadlines($url)
{
global $base_url;
global $today;

$page = CurlGetContent($base_url.$today.'/'.$url);
$heads = "";

if (!preg_match_all('#<(h1|li)>(.*?)</\1>#U', $page, $layer1, PREG_SET_ORDER)) return;
foreach ($layer1 as $v)
{
if (!preg_match_all("|<a href=\"([^\"]+)\">(.*?)</a>|U", $v[2], $layer2, PREG_SET_ORDER)) continue;
$title = str_replace('<br>','&nbsp;&nbsp;&nbsp;&nbsp;',trim($layer2[0][2]));
$link = $layer2[0][1];
$heads[$link] = Array(
'Title' => $title,
'Link' => $link,
);
}
return $heads;
}

function DisplayHeader($heads, $name)
{
print '<FORM action='.$_SERVER['PHP_SELF'].' method=POST target=_blank>';
print '<table><tr>';

print '<td width=100 valign=top>'.$name ."<BR>\n";

print '<INPUT type=SUBMIT value=Submit><INPUT type=RESET>';
print '<input type=BUTTON onclick="selectAll(this.form);" value=SelectAll>';

print '<INPUT type=HIDDEN name=ShowNews value=1>';
print "<BR>\r\n</td><td valign=top>";
foreach ($heads as $v)
{
print "<INPUT type=CHECKBOX name=".$v['Link']." value=".$v['Link'].">";
print "<a href=".$_SERVER['PHP_SELF']."?ShowNews=1&".$v['Link']."=".$v['Link']." target=_blank>".$v['Title']."</a><BR>\n";
}
print '</td></tr></table>';
print '</FORM>'."\r\n";
}

function PrintJavaScript()
{
?>

<script type='text/javascript'>
function get(eid)
{
var d = document;
var r = d.getElementById(eid);
return r;
}
function popImg(open, iref)
{
if (open)
{
var top = (iref.offsetParent.offsetParent.offsetTop + iref.offsetTop) + 'px';
var curleft = 0;
var obj = iref;
do {
curleft += obj.offsetLeft;
} while (obj = obj.offsetParent);
var left = (curleft + iref.offsetWidth )+ 'px';
var img = '<img src="' + iref.src + '" />';
var d = document;
if (null == get('popImg'))
{
var pop = d.createElement('DIV');
pop.id = 'popImg';
pop.style.position = 'absolute';
d.body.appendChild(pop);
}
var pop = get('popImg');
pop.innerHTML = img;
pop.style.top = top;
pop.style.left = left;
pop.style.display = 'block';
}
else
{
var pop = get('popImg');
pop.style.display = 'none';
}
}
function selectAll(formObj)
{
for (var i=0;i < formObj.length;i++)
{
fldObj = formObj.elements[i];
if (fldObj.type == 'checkbox')
{
fldObj.checked = true;
}
}
}

</script>

<?PHP

}

function GetToday($ming)
{
global $today;
global $base_url;
global $main_url;
if (!preg_match_all('|<base href="http://news.mingpao.com/(\d+)/">|U',$ming, $layer1, PREG_SET_ORDER))
{
return "";
}
return $layer1[0][1];

}

function PrepareGlobals()
{
global $HTTP_HEADER;
global $HTTP_FOOTER;

global $today;
global $base_url;
global $main_url;

InitCurl();

$HTTP_HEADER = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=Big5-HKSCS"><title>My MingPaoNews</title>';
$HTTP_HEADER.= '<style type="text/css"> ';
$HTTP_HEADER.= 'a:link { color: #0000FF; text-decoration: none; } ';
$HTTP_HEADER.= 'a:active { color: #000088; text-decoration: underline; } ';
$HTTP_HEADER.= 'a:visited { color: #000088; text-decoration: none; } ';
$HTTP_HEADER.= 'a:hover { color: #0055FF; text-decoration: underline; } ';
$HTTP_HEADER.= 'table {width: 100% ; border-width:1px; border-collapse: collapse; border-color:#003333; border-style:dashed} ';
$HTTP_HEADER.= 'td {padding: 3px;} ';
$HTTP_HEADER.= '</style>';
$HTTP_HEADER.= '</head><body>'."\r\n";

$HTTP_FOOTER = "\r\n".'</body></html>'."\r\n" ;

$base_url = "http://news.mingpao.com/";
$main_url = $base_url.'index.htm';
$today = GetToday(CurlGetContent($main_url));
}

function DoShowNews()
{
global $HTTP_HEADER;
global $HTTP_FOOTER;

print $HTTP_HEADER;
PrintJavaScript();

foreach($_REQUEST as $k => $v)
{
if ($k == "ShowNews") continue;
$news = GetNewsContent($v);
if (is_array($news)) DisplayNewsPage($news);
}

print $HTTP_FOOTER;
}

function DoIndex()
{
global $HTTP_HEADER;
global $HTTP_FOOTER;
global $main_url;

print $HTTP_HEADER;

PrintJavaScript();

$ming = CurlGetContent($main_url);
$titles = GetTitle($ming);
if (!is_array($titles))
{
print "ERR Titles";
print $HTTP_FOOTER;
return;
}
foreach($titles as $v)
{
$head = GetHeadlines($v['Link']);
if (is_array($head)) DisplayHeader($head, $v['Title']);
}

print $HTTP_FOOTER;

}

// MAIN HERE



PrepareGlobals();

if (isset($_REQUEST['ShowNews']))
{
DoShowNews();
}
else
{
DoIndex();
}

?>

Thursday, April 23, 2009

課外/公餘活動與責任

從小就喜歡參加課外活動,尤其是參與不同的公職,中學時有班會、學生會、Prefect等,大學時搞O Camp,畢業後做Forum管理員。這類活動除了能直接學會很多不同的知識外,最重要是能夠和不同的同學、朋友合作,就算是同一個職份,和不同人合作也有不同的過程,不同的經歷。縱使並不是每次的合作和工作都很順利,但當問題出現時,都能使我們學到更多,在解決問題的過程中,使我們的團隊更緊密、更有效率。

哈哈!世事怎麼會這樣完美?以下就是每一次都出現在我身上的問題:

我參加了一個組織。
我熱心參與組織的活動。
搞手們欣賞我的熱心,把我當作是搞手的一部份。
一班朋友搞活動,一樂也!
滿足感使我更加投入,付出更多精神時間。

這時,往往會出現兩個問題:
問題一:過份的付出使正常生活失調,包括工作及健康等。
問題二:挑起了過多的責任,使得組織的責任分配失衡,影響了組織的穩定性。

兩個問題中,問題一是個人的事,解決不了終歸都是己身的責任,但問題二是團體的事,問題一出現,小則口角動武,大則影響組織的存亡。

問題出現的先決條件,是先要成為組織的要員,甚至重心。這並不是什麼困難的事。沒有利益關係的工作,要做多少有多少,自問辦事能力不低,有心有力總會得人認同。

當你不斷的付出,你心裡總會想,如果大家也付出同等的努力,各人在其位置盡心盡力,成績、效果都會很好,而且團隊的合作經歷能給大家很大的滿足感,隊友間的友誼亦能更上一層樓。

可惜的是,這樣的想法是太天真。每個人的時間空間畢竟有個極限,能付出多少是因人而異。當你越做越多,你希望大家亦能和你一樣,可惜大家的極限已到,在你付出的同時,平衡就會慢慢失去。久而久之,重心就會慢慢集中於一點,付出最多的人,責任就會越來越多。

結果,以下的情況都出現了:

「我今天沒空,你可以幫幫忙嗎?」…事後…「你做得比我更好,以後這個就交給你了!」此後,搞手降格成為會員。

「你做的比我好,我留下來也沒有意思」在失去滿足感下,有搞手慢慢失去了蹤影。

「今天你不在,我們成不了事!」偶爾將所有失敗的責任都放在你的頭上。

說實話,得到大家的認同,滿足感是很大的。但成為領袖並不是我的目的,我只想大家可以一起為相同的目標進發。

遇到以上的各種情況,要是換著是你,你會如何去處理呢?

為改善失衡的情況,減低自己的工作量。結果是,放下的工作沒人願意接受,最少並不自願。強硬別人做不喜歡的事,最終會把人趕走的,尤其是這種自願性興趣工作。而且,用硬的不是我的首選。
為改善失衡的情況,提高對大家的要求。結果是,大家為提高了的工作量感到不滿,更有人因壓力而退出。為自願性興趣工作出一分力,有一分已經很好,何必要求別人出兩分、三分呢?

我的最後選擇是:放棄改善失衡情況,搞高對自己的要求。問題一...又嚴重了...

Sunday, March 29, 2009

PostgreSQL, Slony-I, pgadmin, win32, First time config/installation

In one occasion, I have to use Slony-I for Database replication. As a starter, I look for my friend (Gooooogle) for help. She showed me some tutorial: some rather outdated with version or some having broken pic link. After quite a lot of struggle, I have managed to get it working, and would like to provide a better tutorial/notes for the installation.

Here is my software config:
PostgreSQL : 8.3.5-2
Slony-I : 1.2.15
OS: Windows XP, Windows Server 2003

Notice that my Postgres version of 8.3.5-2 is not available to be downloaded in official site, which only contain 8.3.5-1 (and higher version) in the mirror I have tried. (Minor version is not showing anywhere, AND 8.3.5-1 is NOT working for me somehow) I am not sure if the procedure will work on future versions but the general wisdom should able to apply to most situation.

My tutorial will be based on Dave's PostgreSQL Blog with extra information that help any first time comer to setup everything gracefully.

The target of the procedure is to setup a localhost replication between 2 database of your choice.

Initial Installation:

Install PostgreSQL server and run Application Stack Builder to add Replication : Slony-I support.
Start pgAdmin III, goto File -> Options, setup the Slony-I Path. On default case it should be C:\Program Files\PostgreSQL\8.3\share
Use pgAdmin, connect to the localhost server, and remember to save password.

Database Preparation:
If it is your first time playing around Slony-I, do not apply anything on the working database yet, which would probably screw things up.
With pgAdmin, backup with default setting a working database into an *.backup file.
Create 2 new database, which will be acting as Master and Slave, with names of your choice. I will use db_1 and db_2 which represent the master and slave respectively.
Restore db_1 with the backup file obtained above, using default option. (Full restore)
Restore db_2 with the backup file, using "Schema only" option.

Setting up Replication at pgAdmin

All the setting is located in Databases : db_1 / db_2 : Replication

Right click on db_1 : Replication and choose "New Slony-I Cluster"
Join existing cluster : UN-checked
Cluster name : sample
Local Node Left : 1
Local Node Right : Master Node
Admin Node Left : 99
Admin Node Right : Admin Node
Press OK.

Right click on db_2 : Replication and choose "New Slony-I Cluster"
Join existing cluster : Checked
Server : localhost
Database : db_1
Cluster name : sample
Local Node Left : 10
Local Node Right : Slave Node
Admin Node : 99 - Admin Node
Press OK.

Upcoming is path setup, which is not well documented in any reference.

Right click on db_1 : Replication : sample : Nodes : Master Node : Path and choose New path
Server : 10 - Slave Node
Connect Info : host=localhost port=5432 user=postgres dbname=db_2
Conn retry : 10
Press OK.

Right click on db_2 : Replication : sample : Nodes : Slave Node : Path and choose New path
Server : 1 - Master Node
Connect Info : host=localhost port=5432 user=postgres dbname=db_1
Conn retry : 10
Press OK.

Now we define Replication set.

Right click on db_1 : Replication : sample : Replication Sets and choose new Replication Set.
ID : 1
Comment : sample set

Now we choose the table to be replicated.
Right click on Replication Sets : sample set : Tables and choose New tables
Table : Any table of your choice
ID : 1
Index : Auto filled when choosing table.
Press OK.

Repeat the above for all the tables that you want to replicate with increasing IDs. If your Database contain hundreds of tables that need to be replicated, this manual action can takes hours. The following SQL can help this progress.

SELECT COL.table_name as table_name , CON.constraint_name as constraint_name FROM information_schema.columns as COL INNER JOIN information_schema.constraint_table_usage as CON ON CON.table_name = COL.table_name WHERE COL.table_schema='public' GROUP BY COL.table_name , CON.constraint_name ORDER BY COL.table_name;

and use the result to fill the following SQL.

SELECT _<ClusterName>.setaddtable(<MasterID>, <ID>, 'public.<table_name>', '<constraint_name>', '');

In our example,
<ClusterName> : sample
<MasterID> : 1
<ID> : increasing counter from 1
<table_name> : from above query
<constraint_name> : from above query

Notice that the query assume that there is one on one mapping between table_name and constraint_name. Otherwise, multiple matches will exist in result set. In my occasion, Hibernate is used and extra constraint is added to some of the tables, and I have added "AND CON.constraint_name not LIKE 'fk%'" in my WHERE clause to filter those constraints.

Now we create a new Subscription
Right click on db_1 : Replication : sample : Replication Sets : sample set : Subscription and choose New subscription.
Origin : 1
Provider : 1 - Master Node
Receiver : 10 - Slave Node
Can forward : UN-checked
Press OK.

Here ends the Database preparation.

Slon daemon setup

Slon daemon is needed to be running in all server involving the replication. The setting is quite tricky and differ from each other for different version of Windows being used.

To ease the installation, add slon.exe to the PATH environment variable.
Control Panel : System : Advanced : Environment Variables
Search for PATH in system environment and append ";C:\Program Files\PostgreSQL\8.3\bin" or other path if your installation is not on default.
Start command prompt and try "slon" to see if the path is correct. If successful, an help screen will be shown.

Register new service:
slon -regservice Slony-I

Create Engine Configure files:
Create folder C:\Slony
Create C:\Slony\db_1.conf with the following contents (2 lines)
cluster_name='sample'
conn_info='host=localhost port=5432 user=postgres dbname=db_1'

Create C:\Slony\db_2.conf with the following contents (2 lines)
cluster_name='sample'
conn_info='host=localhost port=5432 user=postgres dbname=db_2'

Add engine to the daemon:
Do the following in command prompt :
slon -addengine C:\Slony\db_1.conf
slon -addengine C:\Slony\db_2.conf

Double check the above with :
slon -listengines
You should see the path of the two conf files being listed.

Before starting the daemon, there are extra setting to be done for running the service successfully.
In folder C:\Documents and Settings\<Current User>\Application Data\postgresql,
there should be a postgres password file pgpass.conf. Slon need to access this file to connect to the database for replication. However as Slon is a daemon that do not run as current user, so the owner of Slon service should contain such a file.

Copy the file to C:\Documents and Settings\postgres\Application Data\postgresql
On default, this folder do not exist and have to be created manually (unless you login as postgres and saved password before). Make sure user "postgres" have read/write access to the password file.

Go to Control Panel : Administrative Tools: Services and look for "Slony-I"
Enter Slony-I's settings and access "Log On" tab.
Log on as "This account" and enter "postgres".

Now we are ready to start up the service. Either right click on Slony-I in services and choose Enable, or do "net start Slony-I" in command prompt.

Replication should takes place immediately if every setting above is correct.

Diagnose

In most cases if you are working on it for the first time, there would be some errors in the setting and the replication is not working. You can do one of the following to check up errors.

Using pgAdmin, access db_1 : Replication : sample : Nodes : Master Node, on the right panel, access Statistics tab. If replication is working fine, the numbers keep changing. Otherwise multiple "0"s will be shown. Statistics is also available for Slave nodes.

Using Event viewer (Control Panel : Administrative Tools : Event viewer, Slon's activities will be shown in Application session.

If Slon running as Service didn't show its log nicely to read, we can run Slon in command prompt in Debug mode, as follows:
slon -d 4 sample "host=localhost port=5432 user=postgres dbname=db_1"
slon -d 4 sample "host=localhost port=5432 user=postgres dbname=db_2"

Common Mistake

FATAL main: Cannot connect to local database - fe_sendauth: no password

This indicated that pgpass.conf is not found or not accessible.
Make sure pgpass.conf file exist for the user running the Slony-I service but not the current user. And make sure the user running the service should have full access the the password file. Also make sure that Service owner is correctly set up.

Conclusion

Setting up Slony-I for the first time really takes time and effort. I hope this guild can help whoever that want to start using Slony-I for replication on Windows.

Clearly, more in-depth knowledge in replication is needed for creating a real working Replication solution. One can find more information in the reference session below.

Good luck for your Slony-I setup.

References
http://www.slony.info/documentation/
http://www.pgadmin.org/docs/1.8/slony.html
http://pgsnake.blogspot.com/2007/09/setting-up-slony-i-with-pgadmin.html
http://www.nabble.com/No-replication-on-Windows-Vista-td19488860.html

Friday, February 20, 2009

How to build a Web Host on your computer that is accessible from the outside world?

I assumed that your followed my previous post for your webhost (http://ccstone.blogspot.com/2008/09/how-to-build-web-host-on-your-computer.html) and it is now the time make it accessible from outside world.

1) Change Default Port of your WebHost

1a) Open \mowes\apache2\conf\httpd.conf
1b) Goto line 127 (or somewhere around there
1c) Search for "Listen 80"
1d) Change 80 into any number between 2000 to 8000 (as you like), e.g. 2337
1e) Save and exit
1f) Restart mowes
1g) Give your homepage a test: originally you access a page like http://localhost/mingpao.php , now you have to access the same page with http://localhost:2337/mingpao.php

Notes: the reason behind changing default port is that most ISP blocked port 80, and it is not an easy (noob) task to redirect traffic

2) Setup Port forward in your router (if you have one)

There is no detailed instruction for this setting, as everyone use different brand and model of router. But in general you can follow procedure

2a) Open Command Prompt ("Run..." then enter "cmd")
2b) Type ipconfig and press enter. This is an example of result:

Windows IP Configuration

Ethernet adapter Local Area Connection:

Connection-specific DNS Suffix . :
IP Address. . . . . . . . . . . . : 192.168.1.222
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.1.1

2c) Start browser and browse the page indicated by "Default Gateway". E.g. http://192.168.1.1/ . You are now in router setting.
2d) Enter password as needed. Those login information can be retrieved from your router manual.
2e) Look for Firewall setting / Port Forward / DMZ / etc.. Every brand and model differ from each other, so you have to navigate yourself to the destination.
2f) Setup a Port Forward to your computer. E.g. map port "2337" to "192.168.1.222" as IP Address in your ipconfig result.
2g) Save setting and retrieve your EXTERNAL IP address from your router setting. It can usually be found in "Connection Status" page or "Internet Setting" page. E.g. "202.169.200.234"
2h) Test if your setting is correct. Instead of using http://localhost:2337/mingpao.php, now try http://202.169.200.234:2337/mingpao.php

3) Setup a free domain name for your webserver

Some new router include this function internally. If so, follow the instruction in router manual. For those routers without such setting, or if you do not use a router at all, follow this instruction for easy domain setup.

3a) Visit http://www.no-ip.com/services/managed_dns/free_dynamic_dns.html
3b) Sign up the FREE DNS redirect service
3c) Register a Domain of your choice. E.g. helloworld.myvnc.com
3d) Download and install the No-IP DUC client.
3e) Add in the Hosts your newly registered domain
3f) Test your new host. Instead of http://202.169.200.234:2337/mingpao.php, try http://helloworld.myvnc.com:2337/mingpao.php
3g) (Optional) Setup the "Port 80 redirect" function at NO-IP DUC, so that you don't have to enter the port 2337 (I didn't do it personally)

-------------

After all those setup, you can now access your webhost from anywhere in the world, including your Laptop, iPhone, PDA, etc.

Enjoy

Sunday, September 14, 2008

How to build a Web Host on your computer

Here is the procedure for setting up a web host at home and have PHP/MySQL ready with php_curl library support.

Step 1) Download MoWeS

Goto http://www.chsoftware.net/en/useware/mowes/mowes.htm
Choose "Download" from the left menu.
Click "No thanks, take me to the download"
Click "I Do not have a MoWeS Protable II... "
When choosing the component, check
Apache2
MySQL5
PHP5
PHPMyAdmin
and press "Download now".

Notes: I do not know why people keep asking if they should download the SE version of the modules. Just follow exactly the component named above.

Step 2) Extract & start up
Extract the content to C:\mowes. At this moment you should have mowes.exe as C:\mowes\mowes.exe
Run mowes.exe, it prompt you for setup those components you have chosen.
Start the installation and press "OK" several times.
The webhost will start automatically and your browser will start and goto the hosted website.

Step 3) Get ready with CURL library
In the MoWes Portable II windows, press options.
ON: Start Apache and MySQL automatically when MoWeS starts
ON: Hide the Apache window
OFF: Delete xx logfiles after stooping
OFF: Start Webbrowser
OFF: Start External App
other setting left as default.
Press Accept and OK
Press "End" in your Mowes window. The webhost will be down.

Goto C:\mowes\php5, copy the 2 files:
libeay32.dll
ssleay32.dll
to your windows system32 folder ( usually c:\windows\system32).

Open C:\mowes\php5\php.ini in text editor. Modify the following lines:
205:
implicit_flush = On
321:
max_execution_time = 120
674:
extension=php_curl.dll
696:
extension=php_openssl.dll
727:
date.timezone = Asia/Hong_Kong
Save and exit text editor.

Notes: Some of the changes include removal of a ';' (semi-colon). Be really really careful and the line should be completely like the one written above. No exception for any of the above lines, each line contain changes to be made.

Notes 2: for line 727, it specifies the default time zone so that any time displayed will be under that time zone. If you are not from Hong Kong, take a look at HERE for more cities of your choice.

Step 4) Testing
Start mowes.exe. The webhost will be ready.
Put any file C:\mowes\www , for example a.zip
Open your web browser, URL = http://localhost/a.zip
If successful, you will be prompted to download a.zip
Put mingpao.php to C:\mowes\www
If successful, you will be reading today's mingpao

Notes: mingpao.php is the ultimate testing script for your configuration. It can be obtained here.

Friday, May 23, 2008

BlackBerry Bold 9000 Hands on

As some of you may know, I work for a company that concentrate on developing Input Method for various platform. I am the one responsible for development on Blackberry and today I received the newest product from RIM, the Bold 9000.

If you have ever played with any of the 8xxx series and now holding a brandnew 9000, you may be impressed by its new interface, new functionality and new plastic/metal case.

However, to make things short, I conclude with one sentence: It looks well, it plays badly.

If you are looking for tech details for Bold, read CNET news, or if you want some screenshot, visit crackberry. The one on my hands looks exactly the same. And it's awesome. (Don't compare it with iPhone though)

As a Chinese Input Method developer, what I concern most, as any Chinese would do, is the ability to display and enter Chinese characters. When comparing with the Font on Pearl, which looks like Chinese Char in CWin31 or just eTen, the ones on Bold are clean and clear, and are as good as those on newest Windows Mobile. Bold support HKSCS natively, so there should be no problem reading Hong Kong News. Moreover, Bold has built-in support for multi-lingual User Interface. One can choose its favorite Interface language right from SetupWizard.

The true power of BB handheld relied on its ability on messaging. Actually I didn't see many Hong Kong Blackberry users typing in Chinese (I see users on MTR mostly). Maybe it's because its poor support on Chinese Char Typing. (Visit here for details on Chinese Support).

Bold supports quite a number of Chinese Input Method (natively, like UI) , like 粵拼, 注音 and 倉頡. However, after the "first taste" of using the input method, I lost my chance to test further... THE INPUT METHOD BROKE MY BLACKBERRY!!!

I started up my Blackberry in English Interface with 粵拼 input method. I went to "message" and compose my first email. Everything is the same as in 8xxx machines, nothing surprising. As I have no real experience using 粵拼, I tried some common strokes like "lee" "sin" "sang" and something like that. The control is really bad as if it is programmed by a guy without any experience on Chinese typing. I tried to type a little faster, but it seems to be extremely slow (on searching output?). The speed is unacceptable and I started losing temper. I tried to hit the keyboard in random and finally it showed me "Input Method Error, restarting..." (not exact phrase). That message popup since then whenever I typed fast enough, which is like 3 key per second (not 3 char per second), which is common for any "normal" Chinese typer. I lost my interest and go on testing something else, like Web Browser.

About half an hour later I switch the machine into Chinese Trad Interface, hoping a native Chinese Interface would gain better performance working in Chinese. I start the message editor again and try typing like before. I sense strangely as i switched between input method. The BB gave me 注音 chars when the active input method is 倉頡, wtf. I keep typing until the proper 倉頡 show up, then after like 2 sec... my BB hanged up. There were no response whatever I do, including pressing and holding the POWER button. It showed me the BB is shutting down and is actually hanged.

There's nothing I can do, and I remove the battery and put it back again (a normal hard reset for BB). I think it will be fine since I do reset like 50-60 times a day when i worked on it (install/uninstall require restarts). But no luck, it gave me "SYSTEM PROCESS ERROR" and force me to reset. And of course, no luck after reset. And i am now holding a bricked Blackberry Bold.

To be lucky is that the machine is a sample from the network provider and I can wait for an exchange a few day later. I should not have great hop as it's just a demo machine anyway. I hope that the stability can be improved before its release. The release date is not announced, and they should have lots of things to do. (and Thx to those sloppy programmers, my state in my company is as firm as it can be)

Sunday, May 4, 2008

Charles Wilson's War

昨天看完DVD版的Charles Wilson's War,很失望。

失望的不是Tom Hans的表現、不是Julia Roberts的表現、不是故事、不是整個制作的任何一個部份。反之,在這幾個方面我也很喜歡,很欣賞。

使我失望的唯一一個地方,是香港方面的宣傳。

Charles Wilson's War是人物傳記,講述的是一名美國的Congressman,利用他的影響力,以協助阿富汗於八十年代被蘇聯入侵時作出反擊,說白一點,是反戰電影。

此片是2007的制作,香港於2008年3月左右上映。當初第一次接觸到此片,是在RoadShow上看到的宣傳片,中文名好像叫做「韋氏風雲」。宣傳片上最令我留意的,當然是Tom Hans。(自Forrest Gump開始,只要是Tom Hans我也會很留意)也就是說,我看此片的主因,就是看角色,怎樣看此片也是「大卡士」之作吧。

因那是RoadShow的宣傳片,聲音差點聽不到,所以只能用眼去判斷那是什麼的片。宣傳片的內容除了各人的對手戲外,最特出的就是Julia Roberts從水裡走出來的一段,占時間肯定是最長的。看了不只一次,就是看不出這是什麼主題的作品,沒有所謂,反正都會看。

看罷,不能說是非常出色,但也找不到大問題(除了歷史的部份,有關蘇聯撤兵原因的一段過份簡單)。可能因為此片太「美國」,有些背景未能理解,這是自身認識不足的問題,說不上是什麼缺點。

但這港版的宣傳片,也實在是太爛了。其實片中有很多出色的鏡頭,例如在巴基斯坦難民的一段,又或者是Soviet Copters等,都值得加入宣傳片中。至少,從宣傳片中我看不出阿富汗,甚至看不出戰爭。

想深一層,宣傳片的爛,責任何在?是不是發行商的錯?我想也未必。

爛的不是宣傳,而是香港觀眾。如果打正旗號是反戰片,有多少港人會去看?關心國際事務的人不斷減少,炒股的人數則不斷提升,「關心國際大事」就即是「關心會影響股價的事」的意思。縱使Julia Roberts的角色有如「夏蕙姨」,不打緊,找一段載黑超的就無問題。沒有「美色」的西片,怎樣騙你們入去看?

Tuesday, April 29, 2008

php_curl + cookies

curl_setopt($ch, CURLOPT_COOKIEJAR, "d:\Web\PHP\cookieFileName");

很多script會使用cookies,但cookies path最好是full path

Monday, April 28, 2008

PHP5 + Apache2.2 + cURL + windows XP

我用的是PHP Win32 installer,已選好php_curl的設定,但當行script時發現

Fatal error: Call to undefined function: curl_init() in xxxx.php

於是到phpinfo看,curl module沒有load

再看apache的error log

Unable to load dynamic library 'd:\\PHP\\ext\\php_openssl.dll' - The specified module could not be found.\r\n in Unknown on line 0

file明明存在,但就是load不到,所有file都在其位,path variable, php.ini,httd-conf都ok,但就是不行

最後將ssleay32.dll及libeay32.dll放到system32... 就行了

原因不明,path 明明是正確的... 算了,能行就好

Sunday, April 27, 2008

My MingPaoNews

記得大學Year 3時曾上過一個course,名為Information Retrieval,學會編寫網上蜘蛛,因忙著做Final Year Project而沒有放心血下這course的project上,回必一想真是感到有點可惜。

兩三年前,當我還在玩PDA的時候,有網友編了個exe,功能是download 即日Apple Daily同明報,再用ebook工具做成電子書,加上定時功能放到PDA上,那麼每天上班也有新聞可讀。可惜這種program要時常update,網上的報紙要是一改版,program即時報廢。當時心想,不如自己寫一個... 幾年也沒有做出來...

今日,不知為什麼一起床就想去做這個東西,寫了一整天,完成了整個php script。

工作:
到www.mingpaonews.com取得所有headline,
用家選擇若干篇興趣的文章後,再submit即可一次過閱讀所選的文章。

功能:
自動設charset為Big5-HKSCS,不用每頁也要手動轉;
文章的附圖也會貼上,onMouseOver可放大。

限制:
娛樂、財經及賽馬的排版是獨立的,沒有處理它們。

Code:


<?php

function DisplayNewsPage($title,$body,$pic,$pictext)
{
global $base_url;

print '<table border=1><tr><td width=200 VALIGN="top">';
if (is_array($pic))
{
foreach ($pic as $key => $val)
{
print "<img src=\"".$base_url.$val."\" onmouseover=\"popImg(true, this);\" onmouseout=\"popImg(false);\" width=180>"."<BR>\r\n";
print $pictext[$key]."<BR><BR>\r\n";
}
}
print '</td><td VALIGN="top">';
print '<H1>'.$title.'</H1><BR><BR>';
print $body;
print '</td></tr>';
print '</table>';

}

function ProcessNewsPage($url)
{
global $base_url;

$main_content = file_get_contents($base_url.$url);
$pic = "";
$pictext = "";
preg_match_all('|<font size="4" class="txt150" color="#006666">(.*)</font>|U',$main_content, $layer1, PREG_SET_ORDER);

$title = $layer1[0][1];

if (preg_match_all('|<input type="hidden" name="content" value="([^"]*)"|is',$main_content, $layer2, PREG_SET_ORDER))
{
$body = $layer2[0][1];
}

if (preg_match_all("|<font size=2 class='txt150'>(.*)\n.*;javascript:viewlargephoto\('([^']*)'\);|U",$main_content, $layer3, PREG_SET_ORDER))
{
foreach ($layer3 as $val)
{
$pic[] = $val[2];
$pictext[] = $val[1];
}
}

DisplayNewsPage($title,$body,$pic,$pictext);
}

function ProcessPage($name,$url)
{
global $title_keys;
global $base_url;

$main_content = file_get_contents($url);
preg_match_all("|<a[^>]*>(.*)</a>|U",$main_content, $layer1, PREG_SET_ORDER);
foreach($layer1 as $val)
{
if (strstr($val[0],'http')) continue;
if (strstr($val[0],'mailto')) continue;
if (strstr($val[0],'href="#')) continue;

$found = 0;
foreach ($title_keys as $k)
{
if (strstr($val[0],$k))
{
$found = 1;
break;
}
}
if ($found == 1) continue;
if (preg_match_all('|href="([^"]*)"|U',$val[0], $layer2, PREG_SET_ORDER))
{
$a = $layer2[0][1];
if (strstr($val[1],"..."))
{
@$pages[$a]="";
}
else if (@strstr($pages[$a],trim($val[1])))
{
}
else
{
@$pages[$a].=$val[1];
}
}
}

print '<FORM action='.$_SERVER['PHP_SELF'].' method=POST target=_new>';
print '<table border=1><tr>';

print '<td width=100 valign=top>'.$name ."<BR>\n";

print '<INPUT type=SUBMIT value=Submit><INPUT type=RESET>';
print '<INPUT type=HIDDEN name=ShowNews value=1>';
print "<BR>\r\n</td><td valign=top>";
foreach ($pages as $key => $val)
{
print "<INPUT type=CHECKBOX name=$key value=$key>";
print "<a href=".$_SERVER['PHP_SELF']."?ShowNews=1&$key=$key target=_new>$val</a><BR>\n";
}
print '</td></tr></table>';
print '</FORM>';

}

function ProcessAllTitles()
{
global $title_pages_set;
if (!is_array($title_pages_set))
{
echo 'pass 12:00';
return;
}
foreach ($title_pages_set as $key => $val)
{
if (!strstr($val,"main"))
{
ProcessPage($key, $val);
}
}
}

function PrepareTitles()
{
global $title_keys;
global $title_pages_set;
global $today;
global $base_url;
global $main_url;

$title_tag = '<font color="FFFFFF">';
$main_content = file_get_contents($main_url);
$title_pages_set = "";

preg_match_all("|<a[^>]*>(.*)</a>|U",$main_content, $layer1, PREG_SET_ORDER);
foreach($layer1 as $val)
{
if (preg_match_all("|".$title_tag."|U",$val[0], $layer2, PREG_SET_ORDER))
{
$title_pages[$val[0]]=$val[1];
}
}

$removal = array('<font color="FFFFFF">', '</font>');
foreach ($title_pages as $key => $val)
{
$key = preg_replace('| target="[^"]*"|U','',$key);
$new_key = str_replace($removal, "", $key);
$new_val = str_replace($removal, "", $val);

preg_match_all('|href="([^"]*)"|U',$new_key, $layer3, PREG_SET_ORDER);
if (!strstr($layer3[0][1],"http"))
{
$title_keys[] = $layer3[0][1];
$title_pages_set[$new_val] = $base_url.$layer3[0][1];
}
}
}



function PrintJavaScript()
{
print '<script type=\'text/javascript\'>';
print 'function get(eid)';
print '{';
print ' var d = document;';
print ' var r = d.getElementById(eid);';
print ' return r;';
print '}';
print 'function popImg(open, iref)';
print '{';
print ' if (open)';
print ' {';
print ' var top = (iref.offsetParent.offsetParent.offsetTop + iref.offsetTop) + \'px\';';
print ' var curleft = 0;';
print ' var obj = iref;';
print ' do {';
print ' curleft += obj.offsetLeft;';
print ' } while (obj = obj.offsetParent);';
print ' var left = (curleft + iref.offsetWidth )+ \'px\';';
print ' var img = \'<img src="\' + iref.src + \'" />\';';
print ' var d = document;';
print ' if (null == get(\'popImg\'))';
print ' {';
print ' var pop = d.createElement(\'DIV\');';
print ' pop.id = \'popImg\';';
print ' pop.style.position = \'absolute\';';
print ' d.body.appendChild(pop);';
print ' }';
print ' var pop = get(\'popImg\');';
print ' pop.innerHTML = img;';
print ' pop.style.top = top;';
print ' pop.style.left = left;';
print ' pop.style.display = \'block\';';
print ' }';
print ' else';
print ' {';
print ' var pop = get(\'popImg\');';
print ' pop.style.display = \'none\';';
print ' }';
print '}';
print '</script>';
}

function PrepareGlobals()
{
global $HTTP_HEADER;
global $HTTP_FOOTER;

global $today;
global $base_url;
global $main_url;

$HTTP_HEADER = '<html><header><meta http-equiv="Content-Type" content="text/html; charset=Big5-HKSCS"><title>My MingPaoNews</title></header><body>'."\r\n";
$HTTP_FOOTER = "\r\n".'</body></html>'."\r\n" ;

$today = date("Ymd");
$base_url = "http://www.mingpaonews.com/".$today."/";
$main_url = $base_url."main.htm";
}

function DoShowNews()
{
global $HTTP_HEADER;
global $HTTP_FOOTER;

print $HTTP_HEADER;
PrintJavaScript();

foreach($_REQUEST as $key => $value)
{
if ($key == "ShowNews") continue;
ProcessNewsPage($value);
}

print $HTTP_FOOTER;
}

function DoIndex()
{
global $HTTP_HEADER;
global $HTTP_FOOTER;

print $HTTP_HEADER;

PrepareTitles();

ProcessAllTitles();

print $HTTP_FOOTER;

}

// MAIN HERE



PrepareGlobals();

if (isset($_REQUEST['ShowNews']))
{
DoShowNews();
}
else
{
DoIndex();
}


?>

Tuesday, March 25, 2008

我的編程歷史

8歲:父親買了我第一部真正接觸的電腦,為386SX25(以往還有AT/XT機、Apple II等,都沒有真真正正地玩過)
10歲:暑假到元朗大會堂學Logo,就是Turle走來走去的那一個Logo。一般同學畢業前學懂repeat,我學畢後自修學會recusion,還編寫了簡單的作曲function,記得有生日歌、聖誕歌等。
11歲:任天堂末代推出過「家用電腦」軟件連Keyboard,內有Basic的,由於不太出名,reference不足,摸不上手。
12歲:在MSDOS 5.0內玩MS Quick Basic,Programming還是在電腦上做比較好。主要為了修改裡面的猩猩+香蕉遊戲而學的。
14歲:得知上會考班要讀電腦,在暑假自修Pascal,中四上課前寫好了Mastermind,學會了linked list,也就是大學一年級的Introduction to programming的程度吧,因為我是在圖書館借了一本Pascal的教本學的,我記得上面寫住給College First Year收讀,當年以為College就是高中的意思...
15歲:在數學科上學會了Calculus,寫了一些Polynomial四則、D、In的function。同年,在Win 3.1上試學Delphi,失敗了... Event-driven model 還是太新了。
16歲:當大家還沒有在用56k modem的時候,我已經在hkstar上有自己的homepage,是手寫的HTML,對應的是Internet Explorer 3.0及Netvigator Communicator 3.5。由於hkstar上不能行script,只好用外國的message board,不然已經給自己編了一個Blog...
17歲:DHTML時代,接觸java、ActiveX等,成立Blog型式的homepage,但還要手動加post的,所以一直都沒有加新文章入內。當年的筆記上,還寫著「public static void main <--???????????????」。
18歲:升大學的暑假,除了做暑期工外就沒其他事做,學會了Visual C,更寫了第一隻DirectX Game,是一個scriptable的plane shooting遊戲,對應的是VC6+Dx7。
19歲:偶然見到同學拿著一本「手機鈴聲大全」,裡面全是鈴聲code... 而且Nokia、Ericsson和Motor等的code都不同的,突發奇想,寫了第一個MFC的program,就是Mobile Phone Song Player,除了能翻譯各機的code外,還可midi發聲及output midi file的。不知為何會有人知道有這個作品的存在,有當時的大鈴聲網站link到我的作品處,介紹說:「這可是香港人的作品!」,下載次數高達5位數... 當年還只是一個year 1的大學生...
21歲:畢業作品為波子棋的AI研究,寫了一個Windows(Doc/View Framework)的client,除了可以和AI對戰,還可以網絡IP對戰,而且有chat room功能呢。
22歲:為給女朋友做生日禮物,學會了Flash,寫了一張Interactive的生日卡,用的還是ActionScript 1.0,還沒有class的概念。
23歲:參加了ACM Online Programming Contest,有興趣可以去看看 Valladolid Online Judge,活躍了一兩年,解決近150題。其中有一題有關 15-puzzle的題目,論速度、論內存,當年還是世界第一呢!
24歲:從事數學教育工作,為工作寫了很多教學上的小幫手,除了offline的「出題機」外,還有一些帶到堂上使用的PocketPC工具,成為我上課不可缺少的一部份。代表作為「24計算」及「火柴遊戲」。
26歲:真真正正成為一名全職的Software Engineer,接觸到的可多了... C/C++/Java/PHP/Blackberry Java/Symbian C++/Mac Cocoa (Objective C) ... 未能盡錄...

Friday, March 21, 2008

論壇管理

進入Web 2.0時代,特徵之一是資訊的發佈不再只有個別的組織或團體,用家的全面參與使Web的內容更充實及生活化。用家最主要的參與,最要是以Blog Post,或者是在論壇上進行討論。Blog與論壇的最大分別,在於前者比較個人為主,用家想表達什麼就寫什麼,主題自主性很大;後者則面向公眾 ,用家就一定論題提出意見,互動性較強。

數年前,因迷上一遊戲而加入了一個專門的論壇,更因積極討論而被邀為版主,一做就三年,期間論壇發展迅速。最近重遊舊地,論壇的光榮看來已經不復再,看到這樣的情景,不禁有點心傷。

一個論壇的興衰由無數因素決定:資訊夠不夠豐富、題材夠不夠新鮮、目標用家的選擇......但當中最決定性的,是一個論壇的管理質素。

論壇由三個主要部分組成,分別為管理員(administrator/admin)、版主(moderator/mod)及用家(users),三個組別成員之間的互動,形成活生生的社群。管理員為論壇的當家,人數最少,一般由二至三人組成。版主負責版務,一般要檢視所有文章/回復,人數視乎論壇的主題性質及數量而定,一般每種類的版由不同的人處理,每版人數一至三人。用家則不用太多說明吧,就是參與的人,一般分為「會回覆」及「不會回覆」(CD-ROM,取其Read-Only)兩類。

下表顯示各組別的互動關係:





\AMU
AAtoAAtoMAtoU
MMtoAMtoMMtoU
UUtoAUtoMUtoU
所謂論壇管理,應該是指政策的制定(AtoA)、指令的傳達(AtoM)、版務的處理(MtoU)、用家的互動(UtoU)等全部九個範疇的大共和,進一步改良,粗略總結出下表:





\AMU
A政策的制定指令的傳達規則的執行
M定期的報告工作的協調版務的處理
U用家的意見身份的提升用家的互動

由於曾經作為中層的管理者,後期升級至較高級的身份,同時亦是論壇中最活躍的會員之一,我對以上的種種都有切身的感受,往後一一細談。

Wednesday, March 12, 2008

New Project with C# 2008 + .Net 3.5

學習Visual C,以VS6開始,都差不多十年。陪著我走的有VS6, VS.Net, VS 2003, VS 2005,雖然沒有什麼大作,但過程中包括了Game, Midi, MFC, Network, AI, Mobile等多方面的知識。

去年 1 1月,因工作關係接觸了C#,寫了有關Office plug-in的tech demo,進一步理解什麼是「與時並進」,從前的基礎很重要,新的語言也要好好學習,不然就會好像公司近來interview的求職者一樣,不能達到現今的要求。

所以今天開始,要好好學習,最有效的方法就是切實地去完成一個Product。一直都想更新以前的ChineseCheckers2,今次就以Visual C# 2008,編寫我的ChineseCheckers3。

什麼是「母校」?

「母校」就是你就讀過的學校,或者是你畢業的地方。

但為什麼不就稱為「就讀過的學校」而稱為「母校」呢?重點在於「母」。

母親除了給你Give Birth外,還和你有數之不盡的關係:無條件的包容、無條件的照顧、無條件的愛... 最少,一個母親在沒有病痛、沒有受傷的情況下,都不會忘記自己的兒女。

和你享有相同的母親的人,我們稱他們為兄弟姐妹。

這三個月來,我到過我畢業於的中學、大學,見過不少同學、朋友和老師, 令我再次對「 母校」的定義反思,令我從新再一次判斷那裡是「我的母校」。

學校沒有生命,它不會有記憶,能把你「記憶」下來的,最多是你比賽、成績的記錄,又或者是你破壞的永久傷痕。但對很多人來說,這些紀錄都不存在,除非你是top或bottom students... 所以這種「 記憶」不完全成立。

「記憶」還是生物的專利,也就是說,我們存在的證明,在於「人」。就算你沒有豐功偉蹟,也沒有永久破壞,只要你曾經存在過,總會有人會記得。這些人不一定還存在於過往的物理位置,「笑回客從何處來」都是見怪不怪, 我所指的人,是曾經和你一起渡過多年歲月的「兄弟姐妹」。你記得的不止是你認識的人 ,他們記得的不止是你一個人,這是一種集體回憶,就像皇后碼頭的乘客一樣,每人都有不同的故事,你曾出現於不同人的故事中。

回到學校、參加組聚,見到很多人,亦被很多人見到,聽到的是「呀!多年不見 ,你近來如何?」、「你是... 忘記了,但我認得你」等等,既陌生又親切。不 要害怕說:「我已經忘記了你的名字。」這會使你永久失去珍貴的回憶。反之,說一句:「你以前是鄰班/住在二樓的/上過xxx課的同學吧!」這可更新兩人的回憶。

愈說愈遠了,什麼是母校?能給我以上那些感覺、感想的那些「就讀過的學校」,都是我的母校。

Tuesday, January 8, 2008

歡迎光臨

測試的第一文