2018年3月30日 星期五

關於白內障術後照護

今天早上 10 點請假回鄉下載爸去鎮上的劉眼科割右眼白內障換人工水晶體, 下午一點到診所後先點眼藥水, 一點半上樓手術, 約一小時就好了, 爸說根本沒感覺到痛. 護理師拿了一張術後注意事項, 主要眼睛不能碰到水, 不能碰撞, 不能彎腰, 提重物, 甚至避免打噴嚏與咳嗽等. 參考 :

白內障一定要動手術嗎?
白內障手術及術後居家照顧須知
# 白內障手術病人照護

傍晚阿泉伯與伯母特地拿青菜來, 與他們在龍眼樹下聊了好久. 伯母也有白內障, 故詢問手術細節, 聽說不會痛好像有打算要去做. 明天早上要複診, 故明日的補班我也請假了.

二哥高中畢旅

前天二哥段考一考完, 昨天就出發畢旅, 因校車趕不上 07:00 到校, 所以 06:20 開車載他去鳳山. 行程是中午到台中麗寶, 傍晚入住劍潭, 今天參觀台大. 感覺二哥才上高中, 轉眼就要畢旅, 呵呵, 時間過得真快啊!

2018年3月28日 星期三

Joomla 學習筆記 (二) : 使用 Akeeba 做系統備份

上週在 000a.biz 架好 Joomla 後, 就開始來學習 Joomla 網站的管理與設定. 首先要搞定的是系統備份, 包括程式與資料庫兩部分 : 傳統上程式部分可以在主機後台 (例如我用的 cpanel.000a.biz) 的 File Manager 介面壓縮網站全部檔案後下載; 資料庫部分則進入 PhpMyAdmin 介面匯出 sql 的 zip 檔.

不過 Joomla 有一個 Akeeba 擴充程式 (外掛) 可以一次搞定系統備份, 將程式與資料庫都把包成 .jpa 檔案, 再用 FTP 下載即可. 首先到 Akeeba 官網的 Products 項目下點選 "Akeeba Backup for Joomla!" :

https://www.akeebabackup.com/products/akeeba-backup.html




點 "Akeeba Backup Core" 下載 pkg_akeeba-6.0.1-core.zip 檔 :




登入網站後台 (administrator/index.php), 例如我架在 000a.biz 的 joomla 目錄下, 網址即為  http://xxxxxxx.000a.biz/joomla/administrator/index.php, 點選 "擴充套件/管理/安裝" :




從檔案總管將下載的 Akeeba Backup 壓縮檔拖曳到框框中即上傳並自動安裝 :




安裝完畢跳出 Configuration 視窗, 按 "Configuration Wizard" 鈕自動進行組態調整以增進網站程式執行效能 :




組態調整完畢按 "Backup Now" 鈕, 在 "ANGIE Password" 設定密碼 (我都設與後台管理密碼一樣免得忘記), "Backup comment" 輸入備份原因進行備份, 按 "Backup Now!" 鈕即開始備份, 如果才剛安裝 Joomla, 這會花大約 3 分鐘, 運轉一段時間資料庫增大時間會更久 :







OK, 這樣就完成系統備份了.

2018年3月27日 星期二

Node.js 學習筆記 (三) : 檔案模組 fs 測試

過去這一周把圖書館借來的幾本 Node.js 書籍囫圇吞棗地看了一些, 迫不及待要來測試一番. 其中以核心套件中的 fs 模組最為重要, 因為在建立網頁伺服器時會用到, 而且這是 Javascript 跨出瀏覽器禁錮的最重要功能-存取本機檔案, 此功能以前只能用微軟的 ActiveX 元件 FSO 才辦得到.

Node.js 將檔案讀取與寫入, 刪除檔案, 更改檔名, 查詢目錄或連結等操作封裝在核心模組 fs 中, 使用標準 POSIX 呼叫進行檔案操作. 此模組較特別之處是每個函數都提供了同步 (Sync) 與非同步版本, 例如讀取檔案同步版是呼叫 fs.redFileSync(), 而非同步則是 fs.readFile().

本系列之前的測試紀錄參考 :

關於 Node.js
如何更新樹莓派的 Node.js
Node.js 學習筆記 (一) : 在 Windows 安裝 Node.js

Node.js 8.x 版檔案系統 API 文件參考 :

https://nodejs.org/dist/latest-v8.x/docs/api/fs.html
# w3schools : Node.js Tutorial

以下僅就常用的 fs 模組函數進行測試, 除了官網範例外, 還參考了下面兩本書 :

# 你不能錯過的 Node.js 指南
# 不一樣的 Node.js

使用 fs 模組前需先用 require() 載入 :

var fs=require("fs");

然後就可以呼叫它的函數進行檔案操作. 與其他模組不同之處是, fs 模組的函數有非同步或同步之分, 同步版的函數以 Sync 結尾, 其差異如下 :
  1. 同步 : 無回呼函數 (call back), 執行時程序會阻塞 (blocked) 或停住等待 
  2. 非同步 : 有回呼函數 (call back), 執行時程序不會阻塞 (blocked) 或停住等待
除了同步非同步之分外, fs 模組部分函數如 fs.stat(), fs.chown(), fs.chmod(), fs.utimes() 等還可用 open() 開啟後取得檔案描述表 file descriptor 來操作. 

常見的檔案操作測試如下 :


1. 讀取檔案 :

非同步 : fs.readFile(filename, [options,] callback)
同步 :     fs.readFileSync(filename, [options,])

此函數用來讀取指定之檔案內容. 第一參數 filename 為包含如 "./" 或 "../" 等路徑之檔案名稱, 第二參數則是一個可有可無之選項物件, 例如指定檔案編碼格式則傳入 {encoding : "utf-8"} 或 {encoding : "ascii"}, 也可以傳入字串如 "utf-8" 或 "ascii". 非同步操作須傳入一個回呼函數來處理檔案操作結束後的傳回值, 它預設為最後一個參數. 以讀取檔案為例, fs.readFile() 處理結束後會傳回 err(錯誤訊息) 與 data(讀取到的檔案內容) 兩個參數, 須將其傳入回呼函數中處理, 例如 :

//fs_test_1.js
var fs=require("fs");
fs.readFile("./test.txt", function(err, data) {
  if (err) {console.error(err);}
  else {console.log(data);}
  })
console.log("檔案讀取操作");

上例是讀取程式所在目錄底下的 test.txt 檔案, 若讀取成功則傳回之 err 為 null, 就會執行 else 輸出讀取到的檔案內容, 否則 err 物件不為 null, 表示檔案讀取失敗, 就輸出 err 物件內容, 例如 :

D:\Node.js\test>node fs_test_1.js
{ Error: ENOENT: no such file or directory, open 'D:\Node.js\test\test.txt'
  errno: -4058,
  code: 'ENOENT',
  syscall: 'open',
  path: 'D:\\Node.js\\test\\test.txt' }

如果在程式所在目錄下準備了一個 test.txt 檔, 裡面就只有 Hello 與 World 兩行字 :

Hello
World

將 test.txt 以 ANSI (ASCII) 格式存檔, 執行結果如下 :

D:\Node.js\test>node fs_test_1.js
<Buffer 48 65 6c 6c 6f 0d 0a 57 6f 72 6c 64>

可見傳回的 data 是存在緩衝區物件 Buffer 內的未解碼 ASCII 碼, 其中 0d 0a 分別為跳行字元 CR LF. 如果將 test.txt 以 UTF-8 格式存檔, 則執行結果如下 :

D:\Node.js\test>node fs_test_1.js
檔案讀取操作
<Buffer ef bb bf 48 65 6c 6c 6f 0d 0a 57 6f 72 6c 64>

可見開頭多了 3 個 byte 的 UTF-8 位元組順序記號 ef bb bf.

如果要得到解碼後的文字, 必須在 fs.readFile() 方法中傳入 encoding 參數 (第二參數), 如果 test.txt 是以 ANSI (ASCII) 存檔, 則 encoding 為 "ASCII" 或 "ascii" :

//fs_test_2.js
var fs=require("fs");
fs.readFile("./test.txt", "ASCII", function(err, data) {
  if (err) {console.error(err);}
  else {console.log(data);}
  })
console.log("檔案讀取操作");

可見執行結果不再是 ASCII 碼, 而是已解碼之可讀文字 :

D:\Node.js\test>node fs_test_1.js
檔案讀取操作
Hello
World

或者呼叫 toString() 函數也可以, 例如 :

//fs_test_2.js
var fs=require("fs");
fs.readFile("./test.txt", function(err, data) {
  if (err) {console.error(err);}
  else {console.log(data.toString());}
  })
console.log("檔案讀取操作");

注意, Node.js 非同步函數設計慣例最後一個參數為 callback 函數, 一個函數只有一個 callback. 回呼函數的第一個參數固定為 error 物件, 其餘參數為回呼函數的其他傳回值.

如果將 test.txt 存成 UTF-8 格式, 但卻用 fs_test_2.js 的 ASCII 格式讀取, 則前面三個位元組順序記號將無法解碼為可讀文字 :

D:\Node.js\test>node fs_test_2.js
檔案讀取操作
o;?Hello
World

這時應該用 UTF-8 格式讀取 :

//fs_test_3.js
var fs=require("fs");
fs.readFile("./test.txt", "UTF-8", function(err, data) {
  if (err) {console.error(err);}
  else {console.log(data);}
  console.log("檔案讀取操作完成");
  })
console.log("檔案讀取操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_3.js
檔案讀取操作中 ...
Hello
Tony
檔案讀取操作完成

上面程式均為檔案讀取的非同步操作, 當呼叫 fs.readFile() 之後, 執行緒不會停下來等候檔案讀取動作結束, 而是繼續往下執行, 因此會先印出 "檔案讀取操作", 然後才出現讀取到的檔案內容. 然而 fs.readFile() 的同步版函數 fs.readFileSync() 則非如此, 執行緒會停住等待檔案 I/O 完成. 由於同步版沒有回呼函數, 因此必須用 try catch 來捕捉例外, 例如 :

//fs_test_4.fs
var fs=require("fs");
try {
  var data=fs.readFileSync("./test.txt","UTF-8");
  console.log(data);
  }
catch (err){
  console.error(err);
  }
console.log("檔案讀取操作");

執行結果如下 :

D:\Node.js\test>node fs_test_4.js
Hello
World
檔案讀取操作

可見與上面非同步操作不同的是, 輸出 "檔案讀取操作" 是在檔案讀取完畢之後才會執行. 以下測試將僅針對非同步.


2. 寫入檔案 :

非同步 : fs.writeFile(file, data[, options], callback)
同步 :     fs.writeFileSync(file, data[, options])

第一參數 file 為檔案名稱, 前面可帶 "./" (目前目錄), "../" (上一層目錄) 或 "html/" 等路徑, 不帶時表示目前目錄. 第二參數 data 為欲寫入之資料, 可以是字串或緩衝區物件 buffer. 寫入字串若要跳行在 Linux 中只要用 "\n" 即可, 但在 Windows 中則必須使用 "\r\n". 備選參數 options 可以是物件 (可有 encoding, mode, flag 等三屬性) 或字串 (僅表示 encoding, 預設為 'utf8'), 若寫入資料為 buffer 物件, 則 encoding 會被忽略.

注意, 使用寫入函數前不需要用 fs.exists() 檢查檔案是否已存在, 如果檔案不存在會自動建立一個空檔案; 如果檔案已存在則會覆蓋裡面的內容. 此外, writeFile() 的回呼函數只有 err 一個參數而已, 亦即只在發生錯誤時才會傳回 Error 物件, 否則不傳回任何值. 例如 :

//fs_test_6.js
var fs=require("fs");
var data="Hello\r\nTony";     //在 Windows 中跳行須用 \r\n
fs.writeFile("./test.txt", data, "UTF8", function(err) {
  if (err) throw err;
  console.log("檔案寫入操作完成!");
  })
console.log("檔案寫入操作中 ... ");

開啟 test.txt 內容如下 :

Hello
Tony

可見確實有跳行. 寫入資料也可以使用 Buffer 物件, 例如 :

//fs_test_7.js
var fs=require("fs");
var data=new Buffer("Hello\r\nTony");  //使用 Buffer 物件
fs.writeFile("./test.txt", data, "UTF8", function(err) {
  if (err) throw err;
  console.log("檔案寫入操作完成!");
  })
console.log("檔案寫入操作中 ... ");

上面的 writeFile() 與 writeFileSync() 函數會覆蓋檔案內原來的內容, 如果不希望覆蓋, 而是希望將內容附加在原來內容後面就要使用下面的 appendFile() 與 appendFileSync() 函數.


3. 追加寫入檔案 (Append) :

非同步 : fs.appendFile(file, data[, options], callback)
同步 :     fs.appendFileync(file, data[, options])

參數用法同 writeFile() 與 writeFileSync(). 例如 :

//fs_test_8.js
var fs=require("fs");
var data="Hello\r\nWorld";
fs.appendFile("./test.txt", data, "UTF8", function(err) {   //只有一個參數 err
  if (err) throw err;
  console.log("檔案附加寫入操作完成!");
  })
console.log("檔案附加寫入操作中 ... ");

執行結果  :

D:\Node.js\test>node fs_test_8.js
檔案附加寫入操作中 ...
檔案附加寫入操作完成!

開啟 test.txt 內容如下 :

Hello
TonyHello
World

因為上面寫入 "Hello\r\nTony" 結尾沒跳行, 因此 "Hello\r\nWorld" 就黏在 Tony 後面了.


4. 讀取檔案目錄 :

非同步 : fs.readdir(path[, options], callback)
同步 :     fs.readdirSync(path[, options])

此函數會讀取指定目錄下的所有檔案名稱 (只限檔案, 不包含子目錄名稱), 然後放在 files 陣列中傳回. 第一參數 path 為包含目錄名稱的路徑, 例如程式所在位置的子目錄 "tmp", 根目錄 "/", 上一層目錄 "../" 等. 備選參數 options 可以是物件 (僅 encoding) 或字串 (僅表示 encoding, 預設為 'utf8'). 非同步回呼函數 callback 有兩個傳入參數 : err 與 files, 讀取到的檔案列表會放在 files 陣列中, 例如 :

//fs_test_9.js
var fs=require("fs");
fs.readdir("/Node.js", function(err, files) {  //讀取根目錄下子目錄 Node.js 的檔案列表
  if (err) throw err;
  console.log(files);
  })
console.log("讀取目錄操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_9.js
讀取目錄操作中 ...
[ 'app.nw',
  'arduino 當機事件.txt',
  'duration.js',
  'ebook',
  'hello.js',
  'http_1.js',
  'index.html',
  'node.exe',
  'Node.js.zip',
  'nw.exe',
  'package.json',
  'process_argv.js',
  'serial.js',
  'serialport.js',
  'server.js',
  'test',
  '露天.txt' ]



5. 建立目錄 :

非同步 : fs.mkdir(path[, mode], callback)
同步 :     fs.mkdirSync(path[, mode])

此函數用來建立目錄. 備選參數 mode 為一整數, 用來設定新建目錄之存取權限, 預設為 0o777. 注意, 非同步之回呼函數只有一個參數 err. 例如 :

//fs_test_10.js
var fs=require("fs");
fs.mkdir("./tmp", function(err) {
  if (err) throw err;
  console.log("新建目錄操作完成");
  })
console.log("新建目錄操作中 ... ");

D:\Node.js\test>node fs_test_10.js
新建目錄操作中 ...
新建目錄操作完成

檢查目前目錄下確實多了一個 tmp 子目錄.



6. 更改檔案或目錄名稱 :

非同步 : fs.rename(oldPath, newPath, callback)
同步 :     fs.renameSync(oldPath, newPath)

此函數用來更改檔案或名稱, 前兩個參數為包含路徑之舊與新檔案或目錄名稱. 注意, 非同步之回呼函數只有一個參數 err. 例如 :

//fs_test_11.js
var fs=require("fs");
fs.rename("test.txt", "test_new.txt", function(err) {
  if (err) throw err;
  console.log("檔案更名操作完成");
  })
console.log("檔案更名操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_11.js
檔案更名操作中 ...
檔案更名操作完成

檢查確定 test.txt 已經被改成 test_new.txt 了.

函數 rename() 也可以更改目錄名稱 (但目前程式所在目錄因為被鎖定無法更名), 例如在上面範例中用 fs.mkdir() 所建立的子目錄 tmp 可以利用 rename() 將其改為 tmp_new, 例如 :

//fs_test_12.js
var fs=require("fs");
fs.rename("./tmp", "./tmp_new", function(err) {   //更改目錄名稱
  if (err) throw err;
  console.log("目錄更名操作完成");
  })
console.log("目錄更名操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_12.js
目錄更名操作中 ...
目錄更名操作完成

檢查目前目錄下之子目錄 tmp 確實被改為 tmp_new 了.


7. 刪除目錄 :

非同步 : fs.rmdir(path, callback)
同步 :     fs.rmdirSync(path)

此函數用來建立目錄. 備選參數 mode 為一整數, 用來設定新建目錄之存取權限, 預設為 0o777. 注意, 非同步之回呼函數只有一個參數 err.

在上面範例中我們用 fs.rename() 將子目錄 tmp 更名為 tmp_new, 此處可用 fs.rmdir() 將其刪除, 例如 :

//fs_test_13.js
var fs=require("fs");
fs.rmdir("./tmp_new", function(err) {
  if (err) throw err;
  console.log("刪除目錄操作完成");
  })
console.log("刪除目錄操作中 ... ");

D:\Node.js\test>node fs_test_13.js
刪除目錄操作中 ...
刪除目錄操作完成

檢查目前目錄下確實已無 tmp_new 這個子目錄了.


7. 修改檔案長度 :

非同步 : fs.truncate(path[, len], callback)
同步 :     fs.truncateSync(path[, len])

此函數用來削減檔案長度. 第一參數 path 為包含路徑之檔案名稱, 備選參數 len 為新檔案內容之長度 (bytes), 預設為 0 (即全部內容刪除), 第三參數 callback 為回呼函數, 只有一個 err 參數. 

我先在目前目錄下編輯 test.txt 檔案, 內容為 "你好啊!Tony". 然後執行下列程式 : 

//fs_test_14.js
var fs=require("fs");
fs.truncate("./test.txt", function(err) {
  if (err) throw err;
  console.log("檔案長度修改操作完成!");
  })
console.log("檔案長度修改操作中 ... ");
執行後開啟 test.txt 檔, 果然裡面的內容都沒了, 可見預設 len=0 表示新檔案長度為 0 byte. 現在重新將 test.txt 內容改回 "你好啊!Tony", 進行下面 len 參數測試, 擷取原檔案前 8 個 bytes 內容 :

//fs_test_15.js
var fs=require("fs");
fs.truncate("./test.txt", 8, function(err) {
  if (err) throw err;
  console.log("檔案長度修改操作完成!");
  })
console.log("檔案長度修改操作中 ... ");


執行後開啟 test.txt, 發現裡面內容只剩下 "你好啊!T", 這是因為中文字型佔 2 個 byte, 前面三個中文字  "你好啊" 佔了 6 個 bytes, 後面再取 2 個 bytes "!T" 就成為 "你好啊!T" 了.

另外若檔案內容有跳行, fs.truncate() 還是可以運作, 但要注意, 在 Windows 中跳行字元 "\r\n" 佔 2 個 bytes, 例如將 test.txt 內容改為如下兩行內容的話 :

Hello
World

執行上面的程式擷取前面 8 個 bytes 會得到如下結果 :

Hello
W

這是因為第一行 "Hello\r\n" 佔了 7 個字元, 接著到下一行取 "W" 即 8 個 bytes 了. 將上面的測試寫成自動檢視檔案內容的程式如下, 使用非同步寫法必須將下一個要做的動作放在回呼函數, 因此會形成所謂的 "回呼地獄", 比較不好閱讀 :

//fs_test_16.js
var fs=require("fs");
var data="Hello\r\nWorld";      //檔案初始內容
fs.writeFile("./test.txt", data, "UTF8", function(err) {     //寫入檔案
  if (err) throw err;
  console.log("檔案寫入操作完成!");
  fs.readFile("./test.txt", "UTF8", function(err, data) {    //讀取檔案內容
    if (err) throw err;
    console.log("檔案讀取操作完成!");
    console.log("檔案內容 : \n" + data);
    fs.truncate("./test.txt", 8, function(err) {     //擷取檔案前 8 個 bytes
      if (err) throw err;
      console.log("檔案長度修改操作完成!");
      fs.readFile("./test.txt", "UTF8", function(err, data) {     //讀取檔案內容
        if (err) throw err;
        console.log("檔案讀取操作完成!");
        console.log("檔案內容 : \n" + data);
        });
      console.log("檔案讀取操作中 ... ");
      });
    console.log("檔案長度修改操作中 ... ");
    });
  console.log("檔案讀取操作中 ... ");
  });
console.log("檔案寫入操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_16.js
檔案寫入操作中 ...
檔案寫入操作完成!
檔案讀取操作中 ...
檔案讀取操作完成!
檔案內容 :
Hello
World
檔案長度修改操作中 ...
檔案長度修改操作完成!
檔案讀取操作中 ...
檔案讀取操作完成!
檔案內容 :
Hello
W

上面程式的同步版如下 :

//fs_test_17.js
var fs=require("fs");
var data="Hello\r\nWorld";
try {  //寫入檔案
  fs.writeFileSync("./test.txt", data);
  console.log("檔案寫入操作完成!");
  }
catch (err) {console.error(err);}
try {  //讀取檔案
  var file=fs.readFileSync("./test.txt", 'utf8', data);
  console.log("檔案讀取操作完成!");
  console.log("檔案內容 : \n" + file);
  }
catch (err) {console.error(err);}
try {  //變更檔案長度
  var data=fs.truncateSync("./test.txt", 8);
  console.log("檔案長度修改完成!");
  }
catch (err) {console.error(err);}
try {  //讀取檔案
  var file=fs.readFileSync("./test.txt", 'utf8', data);
  console.log("檔案讀取操作完成!");
  console.log("檔案內容 : \n" + file);
  }
catch (err) {console.error(err);}

執行結果是一樣的 :

D:\Node.js\test>node fs_test_17.js
檔案寫入操作完成!
檔案讀取操作完成!
檔案內容 :
Hello
World
檔案長度修改完成!
檔案讀取操作完成!
檔案內容 :
Hello
W


8. 取得檔案或目錄資訊 :

非同步 : fs.stat(path, callback)
同步 :     fs.statSync(path)

此函數用來取得檔案或目錄之資訊. 第一參數 path 為包含路徑之檔案或目錄名稱, 第二參數 callback 為回呼函數, 有 err 與 ststs 兩個參數, 其中 stats 為查詢所得之檔案或目錄資訊, 為一 fs.Stats 物件, 可用迴圈列舉其 key, value 以檢視物件內容, 例如 :

//fs_test_18.js
var fs=require("fs"); 
fs.stat("./test.txt", function(err, stats) {
  if (err) throw err;
  console.log("檔案資訊取得操作完成!");
  console.log("檔案資訊 :\n");
  for(var k in stats) {
console.log(k + ":" + stats[k]);
    }
  });
console.log("檔案資訊取得操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_18.js
檔案資訊取得操作中 ...
檔案資訊取得操作完成!
檔案資訊 :

dev:2089136718
mode:33206 
nlink:1
uid:0
gid:0
rdev:0
blksize:undefined
ino:562949954052903 
size:138 
blocks:undefined
atimeMs:1522111655025.0005
mtimeMs:1522202247354.802
ctimeMs:1522202247354.802
birthtimeMs:1521426043495.4006
atime:Tue Mar 27 2018 08:47:35 GMT+0800 (台北標準時間)
mtime:Wed Mar 28 2018 09:57:27 GMT+0800 (台北標準時間)
ctime:Wed Mar 28 2018 09:57:27 GMT+0800 (台北標準時間)
birthtime:Mon Mar 19 2018 10:20:43 GMT+0800 (台北標準時間)
_checkModeProperty:function (property) {
  return ((this.mode & S_IFMT) === property);
}
isDirectory:function () {
  return this._checkModeProperty(constants.S_IFDIR);
}
isFile:function () {
  return this._checkModeProperty(S_IFREG);
}
isBlockDevice:function () {
  return this._checkModeProperty(constants.S_IFBLK);
}
isCharacterDevice:function () {
  return this._checkModeProperty(constants.S_IFCHR);
}
isSymbolicLink:function () {
  return this._checkModeProperty(S_IFLNK);
}
isFIFO:function () {
  return this._checkModeProperty(S_IFIFO);
}
isSocket:function () {
  return this._checkModeProperty(S_IFSOCK);
}

這裡面 uid (使用者 ID), gid (群組 ID) 等資訊在 Linux 較常用. fs.stat() 也可以查詢目錄資訊, 例如目前目錄底下的空子目錄 tmp :

//fs_test_19.js
var fs=require("fs"); 
fs.stat("./tmp", function(err, stats) {    //空的子目錄
  if (err) throw err;
  console.log("檔案資訊取得操作完成!");
  console.log("檔案資訊 :\n");
  for(var k in stats) {
console.log(k + ":" + stats[k]);
    }
  });
console.log("檔案資訊取得操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test.js
檔案資訊取得操作中 ...
檔案資訊取得操作完成!
檔案資訊 :

dev:2089136718
mode:16822   
nlink:1
uid:0
gid:0
rdev:0
blksize:undefined
ino:281474977342527   
size:0   
blocks:undefined
atimeMs:1522135068643.0005
mtimeMs:1522135068643.0005
ctimeMs:1522135068643.0005
birthtimeMs:1522135068643.0005
atime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
mtime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
ctime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
birthtime:Tue Mar 27 2018 15:17:48 GMT+0800 (台北標準時間)
_checkModeProperty:function (property) {
  return ((this.mode & S_IFMT) === property);
}
isDirectory:function () {
  return this._checkModeProperty(constants.S_IFDIR);
}
isFile:function () {
  return this._checkModeProperty(S_IFREG);
}
isBlockDevice:function () {
  return this._checkModeProperty(constants.S_IFBLK);
}
isCharacterDevice:function () {
  return this._checkModeProperty(constants.S_IFCHR);
}
isSymbolicLink:function () {
  return this._checkModeProperty(S_IFLNK);
}
isFIFO:function () {
  return this._checkModeProperty(S_IFIFO);
}
isSocket:function () {
  return this._checkModeProperty(S_IFSOCK);
}


9. 取得絕對路徑 :

非同步 : fs.realpath(path[, options], callback)

此函數用來取得檔案或目錄之絕對路徑. 第一參數 path 為包含路徑之檔案或目錄名稱, 備選參數 options 為一字串或物件, 用來指定檔案內容之編碼 encoding, 預設為 utf8. 第三參數 callback 為回呼函數, 只有一個 err 參數. 例如 :

//fs_test_20.js
var fs=require("fs"); 
fs.realpath("./test.txt", function(err, resolvedPath) {
  if (err) throw err;
  console.log("絕對路徑取得操作完成!");
  console.log("相對路徑 : ./test.txt");
  console.log("絕對路徑 : " + resolvedPath);
  });
console.log("絕對路徑取得操作中 ... ");

執行結果 :

D:\Node.js\test>node fs_test_20.js
絕對路徑取得操作中 ...
絕對路徑取得操作完成!
相對路徑 : ./test.txt
絕對路徑 : D:\Node.js\test\test.txt

查詢目錄之絕對路徑 :

//fs_test_21.js
var fs=require("fs"); 
fs.realpath("./tmp", function(err, resolvedPath) {
  if (err) throw err;
  console.log("絕對路徑取得操作完成!");
  console.log("相對路徑 : ./tmp");
  console.log("絕對路徑 : " + resolvedPath);
  });
console.log("絕對路徑取得操作中 ... ");

執行結果 :

D:\Node.js\test>node fs_test_21.js
絕對路徑取得操作中 ...
絕對路徑取得操作完成!
相對路徑 : ./tmp
絕對路徑 : D:\Node.js\test\tmp


10. 開啟與關閉檔案 :

除了上述的高階檔案操作外, 有時需進行低階操作, 這時需要用到 fs.open() 所傳回之檔案描述子 (file descriptor), 這是一個參照或索引, 作業系統內核用它來指向所開啟檔案之檔案紀錄表. Linux 的系統呼叫大都依賴檔案描述子. 使用 fs.open() 操作檔案完畢後, 須使用 fs.close() 予以關閉. 關於檔案描述子參考 :

https://zh.wikipedia.org/wiki/文件描述符

開啟檔案 :
非同步 : fs.open(path, flags[, mode], callback)
同步 :     fs.openSync(path, flags[, mode])

此函數用來開啟檔案, 第一參數 path 為包含路徑之檔案或目錄名稱, 第二參數 flags 旗標字串設定檔案操作模式 :

 flag 說明
 r 讀取, 若檔案不存在則拋出例外
 r+ 讀取 + 寫入, 若檔案不存在則拋出例外
 rs+ 同步模式之讀取 + 寫入
 w 寫入, 若檔案不存在則建立, 否則覆蓋
 wx 寫入, 若檔案不存在則拋出例外
 w+ 讀取 + 寫入, 若檔案不存在則建立, 否則覆蓋
 wx+ 讀取 + 寫入, 若檔案不存在則拋出例外
 a 附加寫入, 若檔案不存在則建立
 ax  附加寫入, 若檔案不存在則拋出例外
 a+ 讀取 + 附加寫入, 若檔案不存在則建立
 ax+  讀取 + 附加寫入, 若檔案不存在則拋出例外

備選參數 mode 為表示權限之整數, 預設為 0o666. 最後一個參數 callback 為回呼函數, 有兩個參數 err 與 fd, 其中 fd 即為傳回之檔案描述子 (整數).

關閉檔案 :
非同步 : fs.close(fd, callback)
同步 :     fs.closeSync(fd)

此函數用來關閉檔案, 第一參數 fd 為檔案描述子 (整數), 參數 callback 為回呼函數, 只有一個參數 err. 例如 :

//fs_test_22.js
var fs=require("fs");
fs.open("./test.txt", 'a', function(err, fd) {     //開啟檔案
  if (err) throw err;
  console.log("檔案開啟操作完成!");
  console.log("檔案描述表 : " + fd);
  fs.close(fd, function(err) {                          //關閉檔案
    if (err) throw err;
    console.log("檔案關閉操作完成!");
    });
  console.log("檔案關閉操作中 ... ");
  })
console.log("檔案開啟操作中 ... ");

在 Windows 執行結果如下 :

D:\Node.js\test>node fs_test.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述表 : 3
檔案關閉操作中 ...
檔案關閉操作完成!

在 Windows 不管是哪個檔案都是傳回 3.

上面提到 fs.stat(), fs.truncate(), fs.chown(), fs.chmod(), fs.utimes() 等函數有 fd 版本之函數, 即函數名稱前面冠 'f' : fs.fstat(), fs.truncate(), fs.fchown(), fs.fchmod(), fs.futimes() 等. 例如可使用檔案描述子與 fs.fstat() 顯示檔案資訊 :

非同步 : fs.fstat(fd, callback)
同步 :     fs.fstatSync(fd)

其中第一參數 fd 為檔案描述子, 參數 callback 為有 err 與 stats 參數之回呼函數. stats 為傳回之 fs.Stats 物件.

將上面 fs.stat() 的範例修改為如下的 fs.fstat() 版 :

//fs_test_23.js
var fs=require("fs");
fs.open("./test.txt", 'a', function(err, fd) {
  if (err) throw err;
  console.log("檔案開啟操作完成!");
  console.log("檔案描述表 : " + fd);
  fs.fstat(fd, function(err, stats) {
    if (err) throw err;
    console.log("檔案資訊取得操作完成!");
    console.log("檔案資訊 :\n");
    for(var k in stats) {
      console.log(k + ":" + stats[k]);
      }
    fs.close(fd, function(err) {
      if (err) throw err;
      console.log("檔案關閉操作完成!");
      });
    console.log("檔案關閉操作中 ... ");
    });
  console.log("檔案資訊取得操作中 ... ");
  })
console.log("檔案開啟操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_23.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述表 : 3
檔案資訊取得操作中 ...
檔案資訊取得操作完成!
檔案資訊 :

dev:2089136718
mode:33206
nlink:1
uid:0
gid:0
rdev:0
blksize:undefined
ino:562949954052903
size:15
blocks:undefined
atimeMs:1522111655025.0005
mtimeMs:1522227853515.802
ctimeMs:1522227853515.802
birthtimeMs:1521426043495.4006
atime:Tue Mar 27 2018 08:47:35 GMT+0800 (台北標準時間)
mtime:Wed Mar 28 2018 17:04:13 GMT+0800 (台北標準時間)
ctime:Wed Mar 28 2018 17:04:13 GMT+0800 (台北標準時間)
birthtime:Mon Mar 19 2018 10:20:43 GMT+0800 (台北標準時間)
_checkModeProperty:function (property) {
  return ((this.mode & S_IFMT) === property);
}
isDirectory:function () {
  return this._checkModeProperty(constants.S_IFDIR);
}
isFile:function () {
  return this._checkModeProperty(S_IFREG);
}
isBlockDevice:function () {
  return this._checkModeProperty(constants.S_IFBLK);
}
isCharacterDevice:function () {
  return this._checkModeProperty(constants.S_IFCHR);
}
isSymbolicLink:function () {
  return this._checkModeProperty(S_IFLNK);
}
isFIFO:function () {
  return this._checkModeProperty(S_IFIFO);
}
isSocket:function () {
  return this._checkModeProperty(S_IFSOCK);
}
檔案關閉操作中 ...
檔案關閉操作完成!


11. 使用 fs.read() 操作低階檔案讀取 :

除了上面的 fs.readFile() 高階檔案讀取操作外, 也可以使用檔案描述子與 Buffer 物件以 fs.read() 進行低階檔案讀取操作, 例如指定讀取位置與讀取多少 bytes.

非同步 : fs.read(fd, buffer, offset, length, position, callback)

第一參數 fd 為 fs.open() 傳回之檔案描述子; 第二參數 buffer 為儲存讀取內容之 Buffer 物件; 第三參數 offset 是讀取後寫入 Buffer 中的偏移位置 (0 起始之整數); 第四參數 length 指定要從檔案中讀取幾個 byte; 第五參數 position 用來指定要從檔案的哪一個位置開始讀取內容, 如果 position=null 表示從檔案指位器目前位置開始讀取; 第六參數 callback 為包含 err, bytesRead, 以及 buffer 三個參數之回呼函數, 其中 bytesRead 為讀取之 Bytes 數, 而 buffer 為 Buffer 物件.

在下面範例中, 我先在檔案 test.txt 中準備好 "Hello\r\nWorld" 這兩行內容供 fs.read() 讀取, 而且以 ASCII 格式存檔 :

//fs_test_24.js
var fs=require("fs");
fs.open("./test.txt", "r", function(err, fd) {   
  if (err) throw err;
  console.log("檔案開啟操作完成!");
  console.log("檔案描述子 : " + fd);
  var buf=new Buffer(8);     //儲存要讀取的 8 個 bytes
  fs.read(fd, buf, 0, 8, 0, function(err, bytesRead, buffer) {    //從檔案頭開始讀 8 個 bytes
    if (err) throw err;
    console.log("檔案讀取操作完成!");
    console.log("讀取 Bytes 數 : " + bytesRead);
    console.log("讀取內容 : " + buffer);
    });
  console.log("檔案讀取操作中 ... ");
  });
console.log("檔案開啟操作中 ... ");

此程式中我們先建立一個長度為 8 的 Buffer 物件來儲存要讀取之 8 個 bytes 資料, 然後將此 Buffer 物件傳入 fs.read() 中, 讀取完成後, 此 Buffer 物件會被傳入回呼函數的參數 buffer, 讀取到的 Bytes 數則傳給 BytesRead 參數, 讀取到的資料為 "Hello\r\nW" :

D:\Node.js\test>node fs_test_24.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案讀取操作中 ...
檔案讀取操作完成!
讀取 Bytes 數 : 8
讀取內容 : Hello
W

如果改為 fs.read(fd, buf, 0, 8, 3, ... 的話, 就會從 test.txt 的第 3 個 byte 開始讀取, 讀到的資料為 "lo\r\nWorld"  共 8 個 Bytes :

D:\Node.js\test>node fs_test_24.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案讀取操作中 ...
檔案讀取操作完成!
讀取 Bytes 數 : 8
讀取內容 : lo
Worl


12. 使用 fs.write() 操作低階檔案寫入 :

除了上面的 fs.writeFile() 高階檔案寫入操作外, 也可以使用檔案描述子與 Buffer 物件以 fs.write() 進行低階檔案寫入操作, 例如指定寫入位置與寫入多少 bytes.

非同步 : fs.write(fd, buffer[, offset[, length[, position]]], callback)
同步 :     fs.writeSync(fd, buffer[, offset[, length[, position]]])

第一參數 fd 為 fs.open() 傳回之檔案描述子; 第二參數 buffer 為儲存欲寫入內容之 Buffer 物件; 第三參數 offset 是要從 Buffer 中的哪一個偏移位置開始寫入 (0 起始之整數); 第四參數 length 指定要從 Buffer 物件中讀取幾個 byte 寫入檔案; 第五參數 position 用來指定要從檔案的哪一個位置開始寫入 Buffer 內容, 如果 position=null 表示從檔案指位器目前位置開始讀取; 第六參數 callback 為包含 err, bytesWritten, 以及 buffer 三個參數之回呼函數, 其中 bytesWritten 為寫入檔案之 Bytes 數, 而 buffer 為 Buffer 物件.

下列範例使用 fs.write() 將儲存於 Buffer 物件中的 "Hello\r\nTony" 字串寫入 test.txt 中 :

//fs_test_25.js
var fs=require("fs");
fs.open("./test.txt", "r+", function(err, fd) {     //以讀取+寫入模式開啟
  if (err) return console.log(err);
  console.log("檔案開啟操作完成!");
  console.log("檔案描述子 : " + fd);
  var buf=new Buffer("Hello\r\nTony");
  fs.write(fd, buf, 0, buf.length, 0, function(err, bytesWritten, buffer) {   
    if (err) return console.log(err);
    console.log("檔案寫入操作完成!");
    console.log("寫入 Bytes 數 : " + bytesWritten);
    console.log("寫入內容 : " + buffer);
    });
  console.log("檔案寫入操作中 ... ");
  });
console.log("檔案開啟操作中 ... ");

執行結果如下 :

D:\Node.js\test>node fs_test_25.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案寫入操作中 ...
檔案寫入操作完成!
寫入 Bytes 數 : 11
寫入內容 : Hello
Tony

注意, 若以 "r" 唯讀模式開啟檔案, 執行時將出現如下 "EPERM: operation not permitted" 錯誤 :

D:\Node.js\test>node fs_test_25.js
檔案開啟操作中 ...
檔案開啟操作完成!
檔案描述子 : 3
檔案寫入操作中 ...
{ Error: EPERM: operation not permitted, write errno: -4048, code: 'EPERM', syscall: 'write' }

fs.write() 還有另一個使用字串而非 Buffer 物件之多型函數 :

非同步 : fs.write(fd, string[, position[, encoding]], callback)
同步 :     fs.writeSync(fd, string[, position[, encoding]])

其中回呼函數 callback 之參數有 3 : err, written (寫入 bytes 數), string (寫入字串).

//fs_test_26.js
var fs=require("fs");
fs.open("./test.txt", "r+", function(err, fd) {   
  if (err) return console.log(err);
  console.log("檔案開啟操作完成!");
  console.log("檔案描述子 : " + fd);
  var str="Hello\r\nTony";
  fs.write(fd, str, "utf8", function(err, written, string) { 
    if (err) return console.log(err);
    console.log("檔案寫入操作完成!");
    console.log("寫入 Bytes 數 : " + written);
    console.log("寫入內容 : " + string);
    });
  console.log("檔案寫入操作中 ... ");
  });
console.log("檔案開啟操作中 ... ");

執行結果同上.


13. 使用 fs.createReadStream() 操作低階檔案讀取 :

除了 fs.read() 外, fs.createReadStream() 也可以操作低階檔案讀取, 此函數沒有回呼函數, 須掛上readable 事件監聽器, 其 API 如下 :

fs.createReadStream(path[, options])

第一參數 path 為包含路徑之檔案或目錄名稱; 備選參數 options 包含如下選項 :

flags <string> : 讀寫旗標
encoding <string> : 編碼
fd <integer> : 檔案描述器
mode <integer> : 模式
autoClose <boolean> : 自動關閉
start <integer> : 讀取開始索引
end <integer> : 讀取結束索引

//fs_test_26.js
var fs=require("fs");
var rs=fs.createReadStream("./test.txt",{encoding:'utf8'});
console.log("檔案讀取操作中 ...");
rs.on("readable", function() {
  var chunk=rs.read();
  console.log(chunk);
  });
rs.on("end", function() {
  console.log("檔案讀取操作完成!");
  });

執行結果如下 :

D:\Node.js\test>node fs_test_stream.js
檔案讀取操作中 ...
Hello
Tony
null
檔案讀取操作完成!

2018年3月26日 星期一

好書 : Data Science on the Google Cloud platform

今天在歐萊里找到一本介紹如何在谷歌雲端平台 (GCP) 佈署機器學習運算的書 :

Data Science on the Google Cloud Platform: Implementing Ent-to-End Real-Time Data Pipelines: From Ingest to Machine Learning


Source : Oreilly


此書亞馬遜售價 52.27 美元左右, 合台幣約 1500 左右 :

# Data-Science-Google-Cloud-Platform

博客來有賣, 但售價高達 2925 元 :

http://www.books.com.tw/products/F014009538

書中原始碼可在 GitHub 下載 :

https://github.com/GoogleCloudPlatform/data-science-on-gcp

關於 Google Cloud Platform 的介紹, 參考 :




在下面關於 GCP 上的深度學習架構 (Page 33) 可知, 上層使用 Keras 建立模型, 底層當然是 Google 自家的 TensorFlow 運算引擎, 參考 :

Deep Learning with Google Cloud




參考 :

Google Cloud Platform 免費版
Machine learning at scale with Google Cloud Platform
# GOOGLE CLOUD CERTIFIED Professional Data Engineer

2018 年第 12 周記事

本周主要在學習 Node.js, 我發覺雖然 Javascript 在 GitHub 關注度最高, 但似乎 Node.js 書籍在市圖的借閱率不高, 我想可能很多人仍然還是對 Apache + PHP + MySQL 較為熟悉, 且 PHP 有 Joomla, WordExpress, 與 Drupal 等成熟架站機助陣, 而 Node.js 卻沒有. 事實上 Node.js 並非只能用在 Web 網站, 也可以像 Python 一樣用在主機管理.

週六掃墓, 今年因改為周六, 菁菁與二哥要補習, 姊姊要月底才回來, 所以今年只有我跟爸去掃墓. 由於早上 7:30 要到墓園, 所以週五晚上我就先回鄉下了. 上週堂哥 Line 我詢問掃墓是否仍為農曆二月第二個周日, 我說周日日子不佳, 提前一天了, 堂哥說改日期應該通知, 他的班一個月之前就排好了, 臨時要調不容易, 感覺似乎有些不悅, 原來是嬸嬸忘了通知他. 說實在的, 自從見叔因每年都要來回穿梭, 電話聯繫各房磋商日期又常搞不定而火大, 決定將掃墓日期固定後, 我就想總不可能每年農曆二月第二個周日適合掃墓吧? 果然今年就是如此. 但家族都沒有人去注意這件事, 是爸要準備掃墓用品發現才主動跟各房聯繫的. 我想未來一年成立 Line 群組將家族各房聯繫在一起, 同時在手機行事曆上設定提醒, 每年元旦時最重要工作是查閱農民曆, 確定日期是否要變更, 然後農曆過年手機拜年同時發布掃墓確認訊息, 掃墓前一周再次提醒.

週六下午小舅掃完墓過來我家採小番茄, 靖宇表弟今年也從台北回來掃墓, 包括表妹伉儷, 還有小安, 大夥坐在曬穀場邊的龍眼樹旁聊天, 度過了難得的午後時光, 但卻忘記用手機拍張照片. 現時龍眼樹開滿了花, 不時有綠繡眼飛來在樹上築巢, 跳上跳下好不熱鬧. 後院種了六年的樹葡萄也開花結果了, 但剛開始產量還不大, 只有零星幾顆還綠皮的果子 :





當初種下這兩棵樹葡萄時, 我說一般大概要 6~8 年才會結果, 媽說不知她能不能吃得到. 明年掃墓時若剛好也結果就帶些去吧.

2018年3月25日 星期日

樹莓派居家警報系統 HomeAlarmPlusPi

今天在下列網站看到作者研發的 Raspberry+Arduino + ATTiny 85 居家警報系統 (Open Source), 基本架構也是使用 Apache 伺服器與 RESTful Web 模式,這可以作為我進行物聯網實驗的參考 :

HomeAlarmPlusPi 





此系統之原始碼可在 GitHub 下載 :

https://github.com/ferraripr/HomeAlarmPlusPi

2018年3月23日 星期五

組裝五斗櫃

上週購買的五斗櫃到貨後清點零配件發現店家少寄了鎖抽屜用的滑軌螺絲, 反映後本周一收到了, 但因為把組裝說明書放在公司沒帶回來, 所以直到前天晚上才有空進行組裝. 說明書中的配件表與組裝程序如下, 觀察其設計可以學習到一些木工的實作法 :













經過兩個晚上按圖施工, 昨晚終於全部完成, 感覺還不賴哩! 歸定位之後, 見箱就鑽的小咪馬上給我跑進去, 還擺出 "我就是要進來" 那種欠揍樣 :




這高度 (109 cm) 剛好把筆電擺上去之後就可以站著工作了, 符合原先的期望. 要不然有時要用筆電做事情卻因為客廳桌面擺滿書籍而打消念頭, 這五斗櫃上頭就權充我的電腦桌啦!




今天中午請一個小時快到期的補休回家 (菁感冒請假在家), 午覺起來就先把整理箱中的衣服全部搬過來, 然後將原先擺這裡的床頭櫃疊到另一邊去, 空出的整理箱就給菁菁囉. 下周要開始來整理房間了.

購買調乳器

購買調乳器並不是我生第四胎了, 而是鄉下那台壞了要買新的. 說來真是耐用, 那台是姊姊 88 年出生時在鼎中路買的, 已經用了快 20 年, 堪稱是調乳器中的大同電鍋-MIT 萬國牌調乳器 (保固三年) :

【萬國牌】【3年保固】"臉書分享免運"BB3S保溫調乳器【台灣製】

本來這電器在菁菁脫離奶瓶後就該收起來的, 但當時爸媽覺得這款調乳器維持恆溫 50 度, 不管是半夜或早上起來喝水, 剛好有溫水可喝不用調很方便, 就這麼繼續用到現在. 但上周發現浴室旁怎會有水漬, 爸檢查後發現是調乳器有隙縫會漏水, 所以決定讓這台功成身退, 買過一台新的吧!

參考 ;

購買調乳器與好關鍵 2 組

2018年3月22日 星期四

逛書店就會想大便?

今天因為看到 "珍妮愛投資" 的臉書, 就順便去看自己的臉書. 我很少在逛臉書, 因為那要耗掉不少時間, 我通常都快速地滑過那些曬恩愛與曬可愛的, 只在好玩新奇, 發人深省或具有啟發性的好文章上停留. 今天就對一則中山大學同學分享的文章頗有同感 (但不是我) :

為什麼一到書店就想大便?——在日本它被稱為「青木まりこ現象」

哈哈! 這就是我以前不太喜歡帶菁菁逛書店的原因, 常常剛到書店沒多久, 就跑過來說 : 爸比, 我想大便! 偏偏明儀書店沒有附設廁所, 要繞到後面去用. 萬一有人在用, 那就只好火速打道回府, 真是掃興.

妙的是, 我同學還特別為便祕者指出順利排便的訣竅 : 邊上大號邊看書, 而且要選直行書, 說甚麼眼睛持續從上看到下可促進排便云云. 這點我無法證實直行書是否有奇效, 因為不管直行或橫行書, 對我而言都是一樣的. 不過, 我倒是非常認同上大號看書的習慣, 坦白說, 我大部分的知識都是來自馬桶上的悠閒時光. 以前有專家說這樣容易得便秘, 我覺得那完全是鬼扯.

5G 不只是 4G 的升級版而已

今天看到財訊下面這篇 :

5G 印鈔機,狠賺 10 倍的機會

看完才搞懂原來 5G 不是像 2G 升 3G, 3G 升 4G 那樣以上網速度與頻寬為主要訴求而已, 5G 是專為物聯網應用而設計的行動網路. 美國國安會報告建議美國政府應該將 5G 提升為等同登月計畫的規模來看待, 因為 5G 技術除了上網更快以外, 背後還牽涉人工智慧, 自駕車, 車聯網, 物聯網等龐大生態系, 它們全都有賴 5G 基礎建設才能發揮. 在物聯網時代, 手機只不過是眾多可連網的嵌入式設備之一而已.

文章中指出, 5G 的商機包含三大塊, 市場規模至少 1.1 兆美元 :
  1. 物聯網  
  2. 行動網路   
  3. Fixed mobile Access (取代固網)
其中行動網路仍占最大份額 (約 9000 億美元), 其次是物聯網 (約 2000 億美元), 尤其是目前非常熱門的 NB-IoT. 不過 NB-IoT 在 5G 中只是低階物聯網服務, 5G 業者看好的是車聯網與遠距醫療等高階物聯網服務 (即時性與高頻寬). 另外, 人臉辨識, 智慧型監視器, 自駕車等高度依賴 AI 演算的應用在 5G 時代也才能得到充分支持, 5G + AI + IoT 是未來經濟成長的碁石.

因應 5G 高速建設, 將推升基地台元件功率放大器需求, 帶動砷化鎵與氮化鎵等半導體市場的成長, 參考 :

5G 革命來臨,暴利台廠是「它」!全球唯一量產品,毛利率達 54.1%

2018年3月20日 星期二

聊天機器人 Replika

今天在 Yahoo 新聞看到關於聊天機器人 Replika 的介紹 :

從思念中誕生的聊天機器人-Replika

Replika 是舊金山一家叫做 Luka 的 AI 公司開發的聊天機器人, 可以從 App StorePlay Store 上免費下載 (約 78MB) 安裝. 為其取名後就可以像在 Line 上一樣開始聊天, 聊得越多 Replika 對我們的了解就越深入, 越聊越有味.

Luka 的創辦人是來自俄羅斯的 Eugenia Kuyda. 因為摯愛的人死於車禍, 在懷念故人之餘想到何不將上千條交談訊息放進人工智慧裡面訓練仿真機器人呢? 於是催生了 Replica.

以下是註冊帳號與設定聊天機器人步驟, 先輸入自己的名字與 Email,  設定 8 個字元以上的密碼, 然後幫聊天機器人取個名字即可開始聊天了 :







原來 Replika 是用 Scala 與 Python 實作的啊!

Joomla 學習筆記 (一) : 系統安裝

去年 11 月在 000a.biz 成功安裝了 WordPress, 但 WordPress 似乎比較適合用來架設部落格, 如果要架綜合型網站, 我覺得 Joomla 或 Drupal 可能是比較好的選擇. 下面這篇文章說 Drupal 架站難度較高, 有時間的話可以試試看, 但順序會排在最後, 我比較屬意 Joomla, 因為它也有擴充功能, 自己寫的外掛應該可以加入 Joomla 中 :

Joomla!、WordPress、Drupal,三個CMS系統的比較與選擇;建立網站要用那一個比較符合我的需求?

Joomla 的優點是操作容易, 介面較美觀, 學習曲線不會很陡, 且社群友善. 但缺點是程式碼較雜亂, 載入時間較長, 且其架構限制了所能建立的分類 (category) 數目.  Drupal 則具有較彈性的基礎架構, 允許建立較多子分類, 提供較多進階之使用者功能, 且原始碼較精簡, 系統載入速度快. 但缺點是安裝過程長, 學習曲線較陡.

安裝 Joomla 基本上有兩種方式, 一是利用安裝軟體如 Fantastico 或 CPanel, 通常用在虛擬主機上; 二是手動安裝. 我選擇手動安裝, 亦即自行下載 Joomla 上傳主機. 我去年底就從官網下載了 Joomla 3.8.0 的 zip 檔 :

https://www.joomla.org.tw/download

直接用 Web 上傳到 000a.biz 主機結果失敗, 因為以 net2ftp 方式上傳的容量限制為 9.54MB, 而 Joonla 的 zip 檔超過此限制. 改用 Filezilla 上傳不知為何也失敗, 只好回到 net2ftp 方式, 先將 Joomla 的 zip 檔解壓縮, 然後分成兩半壓縮成如下兩個 zip 檔, 然後分別上傳後解壓縮即可 :

Joomla_3.8.0-Stable-Full_Package-1.zip
Joomla_3.8.0-Stable-Full_Package-2.zip




然後連線到申請的 000a.biz 網址開始安裝 Joomla :




這裡比較重要的是帳號與密碼, 這是管理 Joomla 的超級管理員帳密, 必須記下來否則安裝完畢後將無法登入管理後台. 按下一步進入 MySQL 設定頁面 :




填這張表單之前須進入 000a.biz 的 MySQL Database 管理頁面的新增一個資料庫 :




建立資料庫時會要求設定 MySQL 密碼, 這也須記下來填入上面的密碼欄裡. 帳號就是登入 000a.biz 的 cPanel 的帳號. 資料庫建好後, 底下的 Current Database 會顯示目前已有之資料庫, 以及 MySQL 的 hostname 與 username. 也可以在 cPanel 首頁看到 MySQL 連線要用的 hostname 與 username :




按下一步做最終確認 :




這裡主要是勾選 "學習 Joola 範例資料", 這樣安裝完畢後才會馬上有範例網頁. 按下一步進入安裝前檢查 :




按下一步即開始安裝, 完畢後顯示多國語言包安裝頁面, 如果不想安裝語言包的話就按下 "移除 Installation 資料夾" 即完成安裝作業, 按底下的 "網站" 即進入安裝好的 Joomla 網站, 按 "管理" 即進入 Joomla 管理後臺 :




我按下 "額外步驟 : 安裝語言 " 鈕, 挑了繁體中文, 日語, 簡體中文等語系下載安裝 :





按下一步即完成安裝 :




按 "移除 Installation 資料夾" 再按底下的 "網站" 即可看到架好的 Joomla 網站了 :





Joomla 架站看起來很簡單, 但接下來的設定可就項目繁多了. 按 "管理區" 登入即可進入 Joomla 網站後台 :




注意, 這裡的帳密是上面第一步所設定的帳密, 不是 000a.biz 的帳密 .




可見 Joomla 已有新版, 可按 "立即更新" 來升版.




OK, 已經升到最新版了.


參考 :

Jumi – 置入自訂程式碼的Joomla套件
Joomla系統的快速擴充套件Jumi

2018-03-20 補充 :

今天用手機瀏覽網頁效果如下 :



可見 Joomla 有 RWD (Responsive Web Design) 功能.