2016年12月29日 星期四

[PHP] 使用 str_getcsv 處理 CSV 格式 (Google Docs export CSV format)

這議題已經很久,之前一直沒踩到什麼,直到處理 Google doc export the CSV format ...

大部分的分析都用:

foreach ( @explode("\n", file_get_content('https://docs.google.com/spreadsheets/d/.../export?format=csv...')) as $line ) {
$field = str_getcsv($line);
}


但是,若有 enclosure 的方式(例如 "...\n....\n") 就會出包啦,這在 http://php.net/manual/en/function.str-getcsv.php 網站上也有人提到,解法就是一樣用 str_getcsv 去切 $row:

$row = str_getcsv( file_get_content('https://docs.google.com/spreadsheets/d/.../export?format=csv...'), "\n");
foreach( $row as $line ) {
$field = str_getcsv($line);
}


但是,在處理 Google Docs 又發現切不對,最後發現改用 "\r\n" 即可 :P

$row = str_getcsv( file_get_content('https://docs.google.com/spreadsheets/d/.../export?format=csv...'), "\r\n");
foreach( $row as $line ) {
$field = str_getcsv($line);
}


Updated @ 2017/05/04
可以改用 tsv 輸出: $csv = explode("\n", @file_get_contents('https://docs.google.com/spreadsheets/d/.../export?format=tsv...'));
array_shift($csv); // remove header


foreach($csv as $line) {
$fields = str_getcsv($line, "\t");
print_r($fields);
}

2016年12月3日 星期六

[C] 使用 SQLite - sqlite3_bind_text 之 value 一直配對(綁定)錯誤

最近整理兩年前的程式,一直有空時會重構,由於時間拉太長了,我已不記得改過什麼,又懶得看 git log,就這樣東摸摸西摸摸,最後發現 bug 了。

有一張表:

CREATE TABLE IF NOT EXISTS test (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data TEXT NOT NULL
);


接著,不斷新增資料,並且透過 sqlite3_bind_text 維護:

INSERT OR IGNORE INTO test (data) VALUES (?),(?),(?)

然而,透過 sqlite3_bind_text 綁定時,一直出錯,起初以為是 UTF-8 問題,但早期程式也沒問題,透過觀察,發現記憶體不如預期,追了好一陣子,發現是 sqlite3_bind_text 的參數問題 Orz

https://www.sqlite.org/c3ref/c_static.html
Constants Defining Special Destructor Behavior

typedef void (*sqlite3_destructor_type)(void*);
#define SQLITE_STATIC      ((sqlite3_destructor_type)0)
#define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)


原因在於我一直用 SQLITE_STATIC 啦,但我的結構已經是 std::vector<std::unordered_map<std::string, std::string>> values 這種稍微複雜的,並且透過 iterator 不斷變換資料,因此,必須改用 SQLITE_TRANSIENT 才對。

std::vector<std::unordered_map<std::string, std::string>> values;
std::vector<std::string> fields ({ "data" });
for (auto item : values) {
for (auto field: fields) {
if (item.find(field) != item.end()) {
if (SQLITE_OK != sqlite3_bind_text(stmt, i, item[field].c_str(), -1, SQLITE_TRANSIENT)) {
}
} else {
if (SQLITE_OK != sqlite3_bind_null(stmt, i)) {
}
}
}
}


最後還是埋了個 gtest 定期測試了。

2016年11月6日 星期日

新夥伴 - MacBook Pro (Retina, 13-inch, Early 2015)

MBPR

在 2012 年夏天買的 MBP 在今年漸漸顯出疲態 XD 除了 Android Studio 編的很慢外,最重要的是螢幕開始出現漸白的事件 Orz 恰逢 Apple 發布最新 MBP 後,我選擇添購前一代的 MacBook Pro (Retina, 13 英吋, 2015 年初) (MF839TA/A),也順勢把 MBP 2011 Late 送修,可能是上蓋總成的問題,但維修人員也不確定主機板是否也有問題,若是主機板就不能修了,我個人是一直覺得只是單純的線材老化 XD 總之,還是該迎見新朋友了。


買筆電真是個掙扎,實用度只剩下 coding 吧?其他的都靠手機、平板搞定。

好好把他當個起點,把時間再規劃好一點吧!真的覺得時間越來越貴了,貴到讓人無法把腦中的想法執行出來、貴到大家的機會成本不斷提高。

2016年10月9日 星期日

iOS 開發筆記 - 建立/更新 Apple Push Notification Service (APNs) 憑證、P12轉PEM、驗證 PEM、PHP 範例程式

APNs_export_key

以前寫過幾篇 APNs 筆記,但好像都沒很完整,結果每年要更新憑證時,都要再找資料複習一次 Orz 所以就把它筆記下來吧!
首先,在 iOS Developer 網站上可以維護 APNs 憑證,然而,一旦過期,其實會自動消失 Orz 而 APNs 這服務滿像開發一次後就一直擺在那,沒用行事曆紀錄肯定忘掉。

如果,當初的 Certificate Signing Request (CSR) 以及 private key(.p12) 還留著的話,可以繼續拿來用,若沒有的話,就重新建立。整個就像第一次建立一樣 :P

Step 1:

iOS Developer Website -> Certificate -> All -> [+] -> Production -> Apple Push Notification service SSL (Sandbox & Production) -> App ID: -> Your App ID -> About Creating a Certificate Signing Request (CSR) -> Upload CRS file

若之前的 CSR 跟 p12 都還在,就可以省去製作 CSR,直接拿舊的上傳;若要新建立就從 Keychain -> 憑證輔助程式 -> 從憑證授權要求憑證 -> 輸入 email -> 預設將產生 CertificateSigningRequest.certSigningRequest 檔案,把他拿去上傳吧。另外,記得要把下載到的 aps.cer 點擊匯入至 keychain 中。

Step 2:

在 keychain -> 類別 -> 我的憑證 -> 可以看到 Apple Push Service 資訊,展開後,點擊專用密鑰,在點擊上方的憑證後,可以以憑證為單位輸出 p12 資料,這是跟 APN server 溝通的通行證。

Step 3:

使用 openssl 將通行證轉檔,從 p12 成 pem 檔

$ openssl pkcs12 -in App.p12 -out App.pem -nodes

轉換後,可以用文字編輯器打開 App.pem 檔案,會看到兩段:

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----


而 CERTIFICATE 上頭也會標記是哪個 App ID 的,未來忘記都可以翻來看

Step 4:

做簡單的 pem 驗證即可。

Production:

$ openssl s_client -connect gateway.push.apple.com:2195 -cert App.pem

Sandbox:

$ openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert App.pem

若 pem 是正常的,應該連線完就停在那邊:

...
---
SSL handshake has read 3385 bytes and written 2515 bytes
---
New, TLSv1/SSLv3, Cipher is AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : AES256-SHA
    Session-ID:
    Session-ID-ctx:
    Master-Key: #######
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1476026971
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
^C


Step 5:

可以拿 pem 跟 APNs 互動了!以下是片段 PHP 程式碼,有興趣可再試試 changyy/codeigniter-library-notification 包好的 library。

<?php

$ssl_ctx = stream_context_create();
stream_context_set_option($ssl_ctx, 'ssl', 'local_cert', $pem_file_path);

if( is_resource( $fp = stream_socket_client(
$use_sansbox ? 'ssl://gateway.sandbox.push.apple.com:2195' : 'ssl://gateway.push.apple.com:2195',
$err,
$errstr,
60,
STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT,
$ssl_ctx
) ) ) {

//$payload = array(
// 'aps' => array(
// 'alert' => array(
// 'title' => $title,
// 'body' => $message,
// )
// )
//);

$packed_data =
// Command (Simple Notification Format)
chr(0)
// device token
. pack('n', 32) . pack('H*', $notification_token)
// payload
//. pack('n', strlen($raw_cmd)) . $raw_cmd;
. $raw_data;

if( @fwrite($fp, $packed_data, strlen($packed_data)) !== false ) {
echo "SUCCESS";
} else {
echo "FAILURE";
}

fclose($fp);
}

2016年10月4日 星期二

查看 APK 裡頭的 Package Name @ Mac OS 10.11.6

透過 aapt 工具,以 Mac OS 來說,大概落於 ~/Library/Android/sdk/build-tools/xxxx/aapt,用法:

$ ~/Library/Android/sdk/build-tools/23.0.2/aapt dump badging app-release.apk | grep package:\ name
package: name='aa.bb.cc' versionCode='###' versionName='######' platformBuildVersionName='#.#.#.####'