# 應徵《軟體工程師-Web後端網頁》 - Terence
## Q1-1 問題:某位使用者在Google Photos儲存的所有照片。
使用的 API 為 ```mediaItems.search``` 。
因為 library 內的媒體類型不只有照片,還包括其他類型(比如影片),所以要用 filter 過濾出照片:
```json=
{
"filters": {
"mediaTypeFilter": {
"mediaTypes": ["PHOTO"]
}
}
}
```
## Q1-2 問題:某位使用者在Google Photos儲存的相簿名稱,以及各相簿裏面的照片
分成幾個步驟:
1. 取得 album 清單,使用的 API 為 ```albums.list```
2. 以 ```albumId``` 為 filter 條件,呼叫 ```mediaItems.search```
3. 將第 2 步驟取得的 ```mediaItems``` 過濾,只留下 PHOTO 格式的 items (取出在 ```MediaMetadata``` 底下的 ```photo": {}``` 與 ```video": {}``` 裡的內容做判斷)。
:::info
為何不直接將 ```albumId``` 與 ```mediaTypes``` 一起過濾? API 官方網站提到 ```If you set both the album and the filters, the request results in an error.``` 禁止將兩者一起使用。
:::
:::info
以上 API 都會回傳 ```nextPageToken``` 的字串,若不為空字串表示以下還有內容,可以用這個 token 繼續呼叫 API 取回更多值。
實作時應使用 while/for 來重複呼叫 API ;用遞迴的話要注意 stack 容量的可能問題,實務上會多一項問題要考慮,所以不推薦使用遞迴。
:::
:::info
第 3 步驟雖然執行時間為 *O(n)* ,但項目較多的時候仍然可能造成效能衝擊,可能需要在前端的 GUI 安排對應元件,比如進度條、進度圓圈、取消鈕……等。
:::
---
## Q2 問題:影像處理佇列程式虛擬碼
```vb=
CLASS Queue
constructor()
// 初始化 array
_items ← [imagePath]
isEmpty
// 若 array 為 0 表示這是空的 Queue
let isZero ← isEqual(_items.length, 0)
return isZero
addToQueue(imgPath)
// 將路徑加到 array 最後
_items.insertAt(imgPath, _items.length)
dequeue() // 取出第 1 個影像的路徑、移除
let item ← _items[0]
_items.removeFrom(0)
return item
removeFromQueue(imgPath)
// 線性遍歷找出相符的影像路徑並自 queue 內移除
FOR i ← 0 TO _items.length - 1
IF _items[i] == imgPath THEN
let item ← _items[i]
_items.removeFrom(i)
return item
END IF
END FOR
// 在正常情況下,不太可能找不到;以下放置處理錯誤的程式碼
// ...
END CLASS
```
:::info
```removeFromQueue()``` 在正常情況下不會發生找不到 node 的現象,可預期不會出現這種異常。所以應該丟出 error 讓上一層的呼叫者知道這個錯誤。
:::
:::info
這個簡單的 queue 無法提供 concurrency 或是 multi-threading 的功能。
不過題目中提到「程式碼皆在主執行緒上,因此不必考慮 class 中的不同方法對於共同的變數會發生競爭情況 (race condition) 」,情境單純,不必過度設計 concurrency 。
:::
Queue 的操作範例(也是虛擬碼):
```verilog=
processQueue(Q)
WHILE !Q.isEmpty()
let imgPath ← Q.dequeue()
imageProcess(imgPath, function(){})
main
const tasks = [
{"priority": 5, "image_path="user/img_folder/image_776b},
{"priority": 9, "image_path="user/img_folder/image_cefb},
{"priority": 1, "image_path="user/img_folder/image_6f55} ]
// 依照重要程度排序 tasks
tasks.sort( (a, b) => return a.priority - b.priority )
let q ← new Queue();
FOR i ← 0 TO tasks.length - 1
q.addToQueue(tasks[i])
END FOR
// 將 queue 內的工作交給函數 processQueue() 處理
processQueue(q)
```
:::info
題目中提到「先被選擇的照片必須先被處理」,所以依照優先程度 sort() ,把需要提前處理的照片放在 queue 的前面。
:::
:::info
題目中提到「處理照片佇列的過程中,必須要確保同一時間,只能夠對一張照片進行處理」,所以可以將 processQueue 修改成「不可重複進入」,如下:
:::
```diff=
+ let reentryFlag ← false
processQueue(Q)
+ IF reentryFlag THEN return
+ reentryFlag = true
WHILE !Q.isEmpty()
let imgPath ← Q.dequeue()
imageProcess(imgPath, function(){})
+ reentryFlag = false
```
---
## Q3 問題:伺服器溝通
```verilog=
const STATUS_NONSET ← 1
const STATUS_SUCCESS ← 2
const STATUS_FAILURE ← 3
async FUNCTION performUpload()
let params ← {"id": 9999, "image": img_file, "type": 3}
let result ← await request(URL_UPLOAD, params, function(){...}, function(){...}
return (result == 1) ? STATUS_SUCCESS : STATUS_FAILURE
async FUNCTION performPolling()
let params ← {"id": 9999}
let result ← await request(URL_POLLING, params, function(){...}, function(){...}
IF result == 0 THEN return STATUS_NONSET
IF result == 1 THEN return STATUS_SUCCESS
IF result == 2 THEN return STATUS_FAILURE
async FUNCTION main()
// 伺服器上傳照片api若無法順利執行,或者api回覆失敗,則進行重試
let resultUpload ← await performUpload()
IF result != STATUS_SUCCESS THEN
resultUpload ← await performUpload()
IF result != STATUS_SUCCESS THEN return // 重試仍然失敗則返回
END IF
// 最多重試的次數
const MAX_RETRY_COUNT ← 20
let retry_count ← 0
WHILE count < MAX_RETRY_COUNT
// 等待2秒
await (() => new Promise(resolve => setTimeout(resolve, 2000)))();
// 照片狀態的請求
let resultPolling ← performPolling()
IF resultPolling != STATUS_NONSET THEN
break
END IF
retry_count ← retry_count + 1
// 若20次後,照片仍處於處理中的狀態時,則顯示訊息詢問客戶端是否繼續等待
IF count == MAX_RETRY_COUNT THEN
let answer ← await showMessage("Do you want to wait for image processing?", function(){...})
IF answer == "no" THEN return
count ← 0
END IF
END WHILE
PRINT(resultPolling == STATUS_SUCCESS ? "Done!" : "Failed")
CALL main()
```
:::info
上述虛擬碼皆非可直接編譯、執行的程式碼,只是示意的作用;也缺乏 test cases ,所以只是用來做邏輯表達。
:::