init
This commit is contained in:
182
02. PARA/01. Project(專案)/001. Kong.md
Normal file
182
02. PARA/01. Project(專案)/001. Kong.md
Normal file
@@ -0,0 +1,182 @@
|
||||
### Build
|
||||
- [Kong Build Instructions (CI Team)](https://spaces.logitech.com/pages/viewpage.action?pageId=69534226)
|
||||
|
||||
### Setup
|
||||
#### Quickly setup
|
||||
```bash
|
||||
adb root ;\
|
||||
adb shell setenforce 0 ;\
|
||||
adb shell "setprop persist.logi.lss.no-start-at-boot false" ;\
|
||||
adb shell "setprop persist.logi.aicv.disable-engine true" ;\
|
||||
adb reboot
|
||||
```
|
||||
|
||||
After reboot...
|
||||
```bash
|
||||
adb root ;\
|
||||
adb shell setenforce 0 ;\
|
||||
adb shell setprop persist.logitech.platform.security.app_whitelist_enable false ;\
|
||||
adb shell settings put global frogger-dev:developer_mode 1 ;\
|
||||
adb shell settings put global frogger-dev:frogger-nav:show-other 1 ;\
|
||||
adb shell settings put system screen_off_timeout 0 ;\
|
||||
adb shell svc power stayon true ;\
|
||||
adb shell dumpsys battery set ac 1
|
||||
```
|
||||
|
||||
#### Disable whitelist
|
||||
```bash
|
||||
adb root ;\
|
||||
adb shell setenforce 0 ;\
|
||||
adb shell setprop persist.logitech.platform.security.app_whitelist_enable false
|
||||
```
|
||||
|
||||
#### Keep screen on
|
||||
```bash
|
||||
adb shell settings put system screen_off_timeout 0 ;\
|
||||
adb shell svc power stayon true ;\
|
||||
adb shell dumpsys battery set ac 1
|
||||
```
|
||||
|
||||
#### AICV
|
||||
##### Disable
|
||||
```bash
|
||||
adb root ;\
|
||||
adb shell setenforce 0 ;\
|
||||
adb shell "setprop persist.logi.lss.no-start-at-boot false" ;\
|
||||
adb shell "setprop persist.logi.aicv.disable-engine true" ;\
|
||||
adb reboot
|
||||
```
|
||||
|
||||
##### Enable
|
||||
```bash
|
||||
adb shell setprop persist.logi.lss.no-start-at-boot false
|
||||
```
|
||||
|
||||
#### Disable SELinux
|
||||
```bash
|
||||
adb root ; adb shell "setenforce 0"
|
||||
```
|
||||
|
||||
#### Show frogbar
|
||||
```bash
|
||||
adb shell settings put global frogger-dev:frogger-nav:show-other 1
|
||||
```
|
||||
|
||||
#### Developer mode
|
||||
- Turn on
|
||||
```bash
|
||||
adb shell settings put global frogger-dev:developer_mode 1
|
||||
```
|
||||
|
||||
- Turn off
|
||||
```bash
|
||||
adb shell settings put global frogger-dev:developer_mode 0
|
||||
```
|
||||
|
||||
#### Update firmware
|
||||
```bash
|
||||
adb reboot-bootloader ; fastboot wait-device ; fastboot devices ;\
|
||||
./fastboot_script.sh
|
||||
```
|
||||
參考:[[Device Firmware Update](https://spaces.logitech.com/display/VCE/Device+Firmware+Update)
|
||||
|
||||
#### BER Recording
|
||||
Version: 1.231(`G:\\My Drive\\Cowork\\Juiwen Hsu\\Cowork(Awin)\\Kong-FIH\\TMP_KAD_Share\\Daily_Build\\V1.231\\KAD-V1.231-0UWW\\0UWW`)
|
||||
|
||||
##### Turn On RAW data record
|
||||
```bash
|
||||
adb shell "setprop persist.vendor.camera.autoImageDump TRUE" ;\
|
||||
adb shell "setprop persist.vendor.camera.autoImageDumpMask 0x1" ;\
|
||||
adb shell "setprop persist.vendor.camera.autoImageDumpIFEoutputPortMask 0xF00" ;\
|
||||
adb shell "ps -A | grep camera | awk '{if (\$9 ~ /cameraserver/ || \$9 ~ /android.hardware.camera.provider/) {print \$2 \"\t\" \$9}}'" ;\
|
||||
adb shell "ps -A | grep camera | awk '{if (\$9 ~ /cameraserver/ || \$9 ~ /android.hardware.camera.provider/) {print \$2}}' | xargs kill" ;\
|
||||
sleep 2 ;\
|
||||
adb shell "ls -l /data/vendor/camera"
|
||||
```
|
||||
|
||||
##### Turn Off RAW data record
|
||||
```bash
|
||||
adb shell "setprop persist.vendor.camera.autoImageDump FALSE" ;\
|
||||
adb shell "setprop persist.vendor.camera.autoImageDumpMask 0x0" ;\
|
||||
adb shell "setprop persist.vendor.camera.autoImageDumpIFEoutputPortMask 0x0" ;\
|
||||
adb shell "ps -A | grep camera | awk '{if (\$9 ~ /cameraserver/ || \$9 ~ /android.hardware.camera.provider/) {print \$2 \"\t\" \$9}}'" ;\
|
||||
adb shell "ps -A | grep camera | awk '{if (\$9 ~ /cameraserver/ || \$9 ~ /android.hardware.camera.provider/) {print \$2}}' | xargs kill" ;\
|
||||
sleep 2 ;\
|
||||
adb shell "ls -l /data/vendor/camera"
|
||||
```
|
||||
|
||||
##### Record screen
|
||||
```bash
|
||||
adb shell "screenrecord /sdcard/screen_recod.mp4" ;\
|
||||
adb pull /sdcard/screen_recod.mp4 C:\RobotRun\Output\
|
||||
```
|
||||
|
||||
#### Fixed UVC pattern
|
||||
- bash
|
||||
```bash
|
||||
adb root ;\
|
||||
adb disable-verity ;\
|
||||
adb reboot ;\
|
||||
adb wait-for-device ;\
|
||||
sleeep 15 ;\
|
||||
adb root ;\
|
||||
adb remount ;\
|
||||
adb push vendor.fih.hardware.logi_uvc@1.0-service //vendor/bin/hw ;\
|
||||
adb shell mkdir //data/uvc ;\
|
||||
adb push 2160p.jpg //data/uvc ;\
|
||||
adb push 1440p.jpg //data/uvc ;\
|
||||
adb push 1080p.jpg //data/uvc ;\
|
||||
adb shell "chmod 777 /vendor/bin/hw/vendor.fih.hardware.logi_uvc@1.0-service"
|
||||
```
|
||||
|
||||
- Windows cmd
|
||||
```
|
||||
adb root
|
||||
adb disable-verity
|
||||
adb reboot
|
||||
|
||||
## Wait device reboot finish
|
||||
adb root
|
||||
adb remount
|
||||
adb push vendor.fih.hardware.logi_uvc@1.0-service /vendor/bin/hw
|
||||
adb shell mkdir /data/uvc
|
||||
adb push 2160p.jpg /data/uvc
|
||||
adb push 1440p.jpg /data/uvc
|
||||
adb push 1080p.jpg /data/uvc
|
||||
adb shell "chmod 777 /vendor/bin/hw/vendor.fih.hardware.logi_uvc@1.0-service"
|
||||
adb reboot
|
||||
```
|
||||
|
||||
|
||||
### Data
|
||||
- https://sites.google.com/a/logitech.com/lfb_fw/project/product/kong-fogger/kong_pantilt_fw
|
||||
|
||||
### KongQA TODO
|
||||
- [ ] Add version informations on main screen (2021/01/06-)
|
||||
- [x] Rename to "VCDroidTester" (2021/01/30-) ^561a6a
|
||||
- [x] 把VCDroidTest加到RobotRunSite裡面 (2021/02/22-) ^cc0bf4
|
||||
- [x] 更新disable whitelist的command (2021/02/22-) ^0754e4
|
||||
- [x] 給Corleone可以直接叫起VCDroidTest的command (2021/02/22-) ^698615
|
||||
- `adb shell am start -n com.logitech.kongqa/.MainActivity`
|
||||
- [x] 請Juiwen把refresh device的時間變成一個config option (2021/02/22-2021/02/26) ^4a5512
|
||||
- [ ] 研究APK如何display外接camera (2021/02/22-) ^b6e6c8
|
||||
- [ ] 研究APK如何播放audio到外接USB speaker (2021/02/22-) ^a83b91
|
||||
- [ ] 研究APK如何用外接USB microphone收音 (2021/02/22-) ^b85fbe
|
||||
- [x] 做VCDroidTester的簡報 (2021/02/22-2021/03/02)
|
||||
- [x] Purpose
|
||||
- [x] funtions
|
||||
- [x] Where to download
|
||||
- [ ] 建立一個可以由ADB控制的service (2021/03/16-) ^cd73f7
|
||||
- [ ] 經由service開啟指定的camera preview (2021/03/16-)
|
||||
- [ ] 經由service拍照 (2021/03/16-)
|
||||
- [ ] 經由service控制PTZ (2021/03/16-)
|
||||
|
||||
### MISC
|
||||
- [x] UVC class讀取pan/tilt的方法? (2021/02/23-2021/02/24)
|
||||
|
||||
#### Archives
|
||||
- [x] Fix WIFI name & ETH status ^9a787b
|
||||
- [x] Add PTZ control in full screen activity ^d9bdf7
|
||||
- [x] Microphone & speaker 加上label
|
||||
- [x] Present KongQA code to Juiwen(2021/01/07-2021/01/08)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- [x] 確定Group firmware update所需的時間
|
||||
- 86400秒仍然timeout。(2021/0202)
|
||||
- [ ] 測試Group放音
|
||||
- [ ] 測試Group錄音
|
||||
- [ ] 跟Avon要另一個更新firmware或是另一台機器 (2021/02/02-)
|
||||
12
02. PARA/01. Project(專案)/005. TestCam.md
Normal file
12
02. PARA/01. Project(專案)/005. TestCam.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Build
|
||||
1. `Mic\stdafx.h`與`Micdll\StdAfx.h`,版本改為0x0600
|
||||
![[Pasted image 20210412164733.png]]
|
||||
1. `\mic\wavedev.cpp`修正line182與line489的i變數問題。
|
||||
![[Pasted image 20210412165017.png]]
|
||||
1. Project `micdll`、`MTF`、`TSTFRAME`:Configuration Properties -> General -> Platform Toolset要改成`Visual Studio 2010 (v100)`
|
||||
![[Pasted image 20210412170608.png]]
|
||||
1. Project `micdll`、`TSTFRAME`:
|
||||
1. Configuration Properties -> C/C++ -> General -> Additonal Include Directories要加入`..\MeasurementStudioVS2010\VCNET\Include`。
|
||||
![[Pasted image 20210412170837.png]]
|
||||
1. Configuration Properties -> Linker -> General -> Additonal Library Directories要加入`..\MeasurementStudioVS2010\VCNET\Lib`。
|
||||
![[Pasted image 20210412170844.png]]
|
||||
18
02. PARA/01. Project(專案)/006. Sega.md
Normal file
18
02. PARA/01. Project(專案)/006. Sega.md
Normal file
@@ -0,0 +1,18 @@
|
||||
### Setup
|
||||
#### Add camera to whitelist
|
||||
Syntax:
|
||||
```
|
||||
adb shell setprop persist.vendor.logitech.ext.cam.allowedlist <VID>:<PID>
|
||||
```
|
||||
|
||||
Example, Add c930e:
|
||||
```
|
||||
adb root ;\
|
||||
adb shell setprop persist.vendor.logitech.ext.cam.allowedlist 046D:0843
|
||||
```
|
||||
|
||||
For c910
|
||||
```
|
||||
adb root ;\
|
||||
adb shell setprop persist.vendor.logitech.ext.cam.allowedlist 046D:0821
|
||||
```
|
||||
68
02. PARA/01. Project(專案)/007. AutoStation.md
Normal file
68
02. PARA/01. Project(專案)/007. AutoStation.md
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
# Flask
|
||||
## Install packages
|
||||
```
|
||||
pip install Flask-Bootstrap flask-bs4 Flask-Login Flask-Mail Flask-Migrate Flask-Moment Flask-SQLAlchemy Flask-WTF WTForms mysql-connector-python email_validator
|
||||
```
|
||||
|
||||
## 環境變數
|
||||
- DB_URI
|
||||
- DB_NAME
|
||||
- DB_USER
|
||||
- DB_PASSWORD
|
||||
- FLASK_CONFIG = prodution
|
||||
|
||||
# MySQL
|
||||
## Initial
|
||||
1. Create `autostation` schema.
|
||||
2. Create `station_state` table.
|
||||
```sql
|
||||
use autostation;
|
||||
CREATE TABLE station_state(
|
||||
id CHAR(255) NOT NULL,
|
||||
ip CHAR(255) NOT NULL,
|
||||
dhcp_ip CHAR(255) NOT NULL,
|
||||
name CHAR(255) NOT NULL,
|
||||
setupfilemd5 CHAR(255) NOT NULL,
|
||||
setupfileversion CHAR(255) NOT NULL,
|
||||
status CHAR(255) NOT NULL,
|
||||
update_time DATETIME NOT NULL,
|
||||
PRIMARY KEY ( id )
|
||||
);
|
||||
```
|
||||
3. Create `station_report` table.
|
||||
```
|
||||
CREATE TABLE `autostation`.`station_report` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`AutoStationName` VARCHAR(255) NOT NULL,
|
||||
`DeviceName` VARCHAR(255) NOT NULL,
|
||||
`Vid` INT NOT NULL,
|
||||
`Pid` INT NOT NULL,
|
||||
`FirmwareVersion` VARCHAR(255) NOT NULL,
|
||||
`TestCategory` VARCHAR(255) NOT NULL,
|
||||
`VerificationMode` VARCHAR(255) NOT NULL,
|
||||
`TestID` VARCHAR(255) NOT NULL,
|
||||
`TestResult` VARCHAR(255) NOT NULL,
|
||||
`reportjson` LONGTEXT NULL,
|
||||
`reporttxt` LONGTEXT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `id_UNIQUE` (`id` ASC));
|
||||
```
|
||||
|
||||
# TrayIcon
|
||||
- Configuration file: `C:\EasyAVEngine\RobotRunAutoStation\ServerList.json`
|
||||
|
||||
## Notify server
|
||||
### LogPushDone
|
||||
![[LogPushDone_server.png]]
|
||||
![[LogPushDone_client01.png]]
|
||||
![[LogPushDone_client02.png]]
|
||||
```python
|
||||
cc = CAutoStationService() # status="Running"
|
||||
cc.LogPush() # push log to server, it runs in another process.
|
||||
## or CAutostationService解構,也會自己呼叫一次cc.LogPush()
|
||||
```
|
||||
|
||||
## APIs
|
||||
- Trigger a parser for test result folder
|
||||
- ![[trigger a parser for test result folder.png]]
|
||||
99
02. PARA/01. Project(專案)/008. Sentinel.md
Normal file
99
02. PARA/01. Project(專案)/008. Sentinel.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Setup enviroment
|
||||
- Setup Gerrit first: [HA (Highly Available) Production Gerrit Infrastructure](https://spaces.logitech.com/display/VCE/HA+%28Highly+Available%29+Production+Gerrit+Infrastructure)
|
||||
- [Kong Build Instructions (CI Team)](https://spaces.logitech.com/pages/viewpage.action?pageId=69534226)
|
||||
|
||||
# Build Code
|
||||
## Fetch code
|
||||
### From vc.gerrit.logitech.com
|
||||
- `repo init -u ssh://ahuang11@vc.gerrit.logitech.com:29418/manifest -b pacman`
|
||||
- Gerrit HTTP password: IMlLvOUW5NCZcvwq84YrdRs6eL89Dy3AOGokmPOREw
|
||||
|
||||
### From Lesley's server
|
||||
- IP: 172.23.163.201 Port: 22
|
||||
- username/passwd: lhsieh1/lhsieh1
|
||||
- Path
|
||||
- **/homeMTK/awin/mt-8195**: 更新 MTK code 到這版:`pacman-t-alps-release-r0.mp6.tc41sp-V1.19.1.xml`
|
||||
- **/homeMTK/awin/mt-8195_sentinel-dev**: 更新 FIH code 到這版:`sentinel-dev_manifest-0.000.16.xml`
|
||||
- 要抓 MTK apk Sample code 建議從 `/homeMTK/awin/mt-8195` 來抓,因為不確定 FIH 現在的 code 是對照到 MTK 哪個版本
|
||||
- mt-8195: Pacman branch
|
||||
- mt-8195_sentinel-dev: sentinel-dev branch
|
||||
- mt-8195_sentinel-logi: sentinel-logi branch
|
||||
|
||||
## Sync Code
|
||||
Sync code commands:
|
||||
1. Sync the manifests
|
||||
```
|
||||
repo init -u https://sg1-mirror.gerrit.logitech.com/manifest.git -b pacman
|
||||
```
|
||||
`sg1-mirror.gerrit.logitech.com` can be changed to the following servers:
|
||||
* us1-mirror.gerrit.logitech.com (US West)
|
||||
* eu1-mirror.gerrit.logitech.com (EU East)
|
||||
* sg1-mirror.gerrit.logitech.com (Singapore)
|
||||
* in1-mirror.gerrit.logitech.com (India)
|
||||
|
||||
2. Sync the code with the specific version
|
||||
1. Set the manifest by the repo init, for example:
|
||||
```
|
||||
repo init -m pacman-t-alps-release-r0.mp6.tc41sp-V1.19.1.xml
|
||||
```
|
||||
`pacman-t-alps-release-r0.mp6.tc41sp-V1.19.1.xml` can be replaced by the other .xml in `.repo/manifests`.
|
||||
|
||||
2. Sync up the codes
|
||||
```
|
||||
time repo sync -j16 -c --no-tags
|
||||
```
|
||||
Note: `-j16` is used for 8 cores
|
||||
|
||||
## Reference:
|
||||
1. https://spaces.logitech.com/display/VCE/HA+%28Highly+Available%29+Production+Gerrit+Infrastructure
|
||||
2. https://spaces.logitech.com/pages/viewpage.action?pageId=69534226
|
||||
|
||||
# ADB command
|
||||
## Get version
|
||||
```
|
||||
adb shell getprop ro.build.fingerprint
|
||||
|
||||
or
|
||||
|
||||
adb shell getprop ro.vendor.mediatek.version.release
|
||||
```
|
||||
|
||||
## Enable the UVC mode
|
||||
```
|
||||
adb root ;\
|
||||
adb shell setprop vendor.debug.camera.custzone.lt 1 ;\
|
||||
adb shell setprop vendor.debug.camera.lt.fence.enable 1 ;\
|
||||
adb shell setprop vendor.debug.camera.lt.inline.wpe 1 ;\
|
||||
adb shell setenforce 0 ;\
|
||||
adb shell setprop sys.usb.config uvc
|
||||
```
|
||||
|
||||
## Switch camera ID
|
||||
```
|
||||
adb shell setprop debug.webcam.cam.id 1
|
||||
```
|
||||
|
||||
## Disable/Enable Sleep mode
|
||||
- Prevent to sleep: `adb shell "echo test > /sys/power/wake_lock"`
|
||||
- Allow to sleep: `adb shell "echo test > /sys/power/wake_unlock"`
|
||||
|
||||
# MTK demo code
|
||||
- `vendor/mediatek/proprietary/packages/apps/DPTZDemo`
|
||||
|
||||
# Firmware
|
||||
- [PACMAN Firmware build](https://drive.google.com/drive/folders/1wycKlDQv-CHEd_5BZFLwn9ibhTTphOAN)
|
||||
- [FIH Firmware build](https://drive.google.com/drive/u/0/folders/1-Pgfttm-tFUdMetCK2WHZH4cXrryydz9)
|
||||
|
||||
# Docs
|
||||
- [MTK參考文件](https://docs.google.com/document/d/12TbyjaSt2emCZu9NvD4cptN4pc0fkVxj1H3wOoN7X0U/edit#heading=h.gl7sjp6n8i2o)
|
||||
- [Branch意義解釋文件](https://docs.google.com/presentation/d/1iHnoIcCqpXh2v7qpmepuUxciawdDsbhJN5H6zYYEMpg/edit#slide=id.g109895f3f92_0_224)
|
||||
- [Sentinel EVT/DVT Sample Requirements](https://docs.google.com/spreadsheets/d/1uOLk1UU7T-ay-1TZfN1CLIhYqdwqbgGGZwfjRmMMcj0/edit#gid=0)
|
||||
- [Firmware comparison](https://spaces.logitech.com/pages/viewpage.action?pageId=135939412)
|
||||
|
||||
# Jira
|
||||
- [[Sentinel][Tiny] HDR Requirements and System Design](https://jira.logitech.com/browse/VC-67445)
|
||||
- [[Sentinel] APP for testing the image quality](https://jira.logitech.com/browse/VC-63616)
|
||||
- [MTK ISP Pipeline: Data path implementation with single camera](https://jira.logitech.com/browse/VC-67422)
|
||||
- [MTK ISP Pipeline: Data path implementation with dual camera](https://jira.logitech.com/browse/VC-67420)
|
||||
- [[Sentinel][FIH] adb port connection hang](https://jira.logitech.com/browse/VC-64910)
|
||||
- 解決device會hang的問題
|
||||
10
02. PARA/02. Area(領域)/00000000 - 投資.md
Normal file
10
02. PARA/02. Area(領域)/00000000 - 投資.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# ETF
|
||||
## 追蹤美國標普500
|
||||
- VOO(Vaguard 500 Index Fund ETF)
|
||||
- IVV(iShares Core S&P 500)
|
||||
- SPY(S&P 500ETF TRUST ETF)
|
||||
|
||||
## 全球
|
||||
- VTI(Vanguard Total Stock Market)
|
||||
- VXUS/VEU,投資美國以外的全球已開發新興市場。兩者投資四大地斯的比重也相近。兩者的差別在於兩隻ETF對於中小型類股的涵蓋程度。
|
||||
- VT(Vanguard Total World Stock Index Fund ETF)
|
||||
912
02. PARA/02. Area(領域)/002. Study RxKotlin.md
Normal file
912
02. PARA/02. Area(領域)/002. Study RxKotlin.md
Normal file
@@ -0,0 +1,912 @@
|
||||
# 剛開始
|
||||
## 先說說Rx
|
||||
|
||||
Rx最早是Microsoft的某個實驗室為了解決asychronous、scalable還有一些app問題而提出來的libraray。大概在2009年的時候提出,叫做Reactive Extension for .NET(Rx). 一開始是以add-on的方式安裝在.NET 3.5上,到了.NET 4.0就變成了內建的library。也因為它open source的關係,讓其他語言得以將這套概念也移植過去,所以現在有RxJS, RxSwift, RxNET, RxScale, RxJava。這些library都致力於在它們的語言來實作出相同的「行為」,所以理論上iOS工程師可以和Web工程師用Rx來討論app的邏輯是沒有問題的。
|
||||
Rx的官網:[http://reactivex.io/](http://reactivex.io/),它的logo是一支電鰻(Electric eel):
|
||||
![[Rx_Logo_S.png]]
|
||||
|
||||
Rx Community
|
||||
- http://android-united.community/
|
||||
- https://kotlinlang.slack.com/
|
||||
|
||||
什麼是RxJava?
|
||||
> RxJava is a library for composing asynchronous and event-based code using observable sequences and functional style operations, allowing for parameterized execution via schedulers.
|
||||
|
||||
> RxJava, in its essence, simplifies developing asynchronous programs by allowing your code to react to new data and process it in a sequential, isolated manner. In other words, RxJava lets you observe sequence of asychronous events in an app and respond to each event accordingly. Examples are taps by a user on the screen and listening for results if asynchronous network calls.
|
||||
|
||||
## 再說RxJava
|
||||
RxJava是一個實作Rx的framework。
|
||||
RxJava與其他的Rx library提供了asynchronous與event-based的解決辦法
|
||||
|
||||
而Asychronous code跟Sychronous code的差異:
|
||||
Sychronous code按照字面上的意思執行,每一次的結果都相同。
|
||||
Asychronous code則是在必要的時候才被使用,每一次執行的「狀態」不盡相同。也就是沒辦法控制其順序與時間。
|
||||
### Asychronous programming的詞彙
|
||||
#### 1. State
|
||||
- State指的是我們程式所儲存的資料與程式自身行為互動所產生的狀態。
|
||||
-
|
||||
#### 2. Imperative programming
|
||||
- Imperative programming(指令式程式設計)是用一連串的命令或描述來改變程式的狀態。如下面的code:
|
||||
```
|
||||
setupUI()
|
||||
bindClickListeners()
|
||||
createAdapter()
|
||||
listenForChanges()
|
||||
```
|
||||
這些code可能有一些相關的邏輯,但是字面上看不出來,即使互相調換可能會造成錯誤,但也可能不會。
|
||||
|
||||
#### 3. Side effect
|
||||
- Side effect指的是「一段程式修改了它本身區域外的狀態」,譬如說,一個處理event的function它除了處理event之外,也改變的UI上所顯示的文字。
|
||||
- Side effect並不總是不好的,我們的程式就是要對某些東西做出改變,完全無法改變任何東西的程式是沒有用的。
|
||||
|
||||
RxJava試著用接下來的2個概念來解決剛剛提到的3個概念上的問題
|
||||
#### 4. Declarative code
|
||||
- 又叫Fucntional programming,Fucntional programming不產生任何side effect。
|
||||
- Declarative code定義的是行為。
|
||||
- RxJava試著在Declarative code和Imperative programming取一個平衡點,它定義行為,然後依順序執行。
|
||||
|
||||
#### 5. Reactive systems
|
||||
Reactive systems通常有幾個特性:
|
||||
- Reponseive:保持UI在最新狀態
|
||||
- Resilient:每個行為都是獨立定義的,而且有辦法靈活的處理錯誤。
|
||||
- Elastic:程式的十座可以處理不同的工作量
|
||||
- Message driven:每個元件使用Message driven(訊息驅動)的方式來互相溝通,並改進可用性與獨立性,解開(decouple)生命週期與實作的關聯。
|
||||
|
||||
### Rx的三大組成
|
||||
#### 1. Observables
|
||||
`Observable<T>`是Rx的基礎之一,Observable允許觀察者觀察它,並接收它發出來的資料。
|
||||
##### Observables 的基礎:event
|
||||
Observable會以3種事件(event)來發出資料:
|
||||
1. **next**:**next** event會伴隨著一筆資料,這也是觀察者用來接收資料的event。
|
||||
2. **complete**:**complete** event表示Observable已經「成功的」結束了它的生命週期,在**complete** event之後,觀察者不會再收到任何**next** event。
|
||||
3. **error**:**error** event表示Observable在發生錯誤的情況下結束它的生命週期。跟**complete** event依樣,後續不會再有任何**next** event。
|
||||
|
||||
一個Observable用next所發出來的一連串資料我們稱為"sequence"。sequence可以分為兩種:
|
||||
1. Finite sequnece:
|
||||
想像你要下載一個檔案,我們的code大概是這個樣子:
|
||||
```kotlin
|
||||
API.download(file = "http://www...")
|
||||
.subscribeBy(
|
||||
onNext = {
|
||||
// Handle downloading here
|
||||
},
|
||||
onComplete = {
|
||||
// Handle download finish here
|
||||
},
|
||||
onError = {
|
||||
// Handle error here
|
||||
},
|
||||
)
|
||||
```
|
||||
`API.download()`會產生一個Obervable,然後我們藉由`subscribeBy`來訂閱他,並加入我們的處理程序,我們在`onNext`裡面處理接收到的檔案buffer,在`onComplete`裡面了解到檔案已經完成下載,可以做一些後續的處理,`onError`則是發生了某些錯誤,需要重來或是通知使用者之類。
|
||||
|
||||
2. Infinite sequence:
|
||||
Switch button就是一個例子,我們要處理switch button的code會是這樣:
|
||||
```kotlin
|
||||
switch.checkdChanges()
|
||||
.subscribeBy(
|
||||
onNext = { isOn ->
|
||||
if (isOn) {
|
||||
// Handle on here
|
||||
} else {
|
||||
// Handle off here
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
可以看到這一段`subscribeBy()`裡面並沒有`onComplete`跟`onError`,因為switch button根本就不會產生這兩種event。
|
||||
|
||||
#### 2. Operators
|
||||
Operators用來處理Observable所發出來的資料,可能是過濾或者做一些轉換,或其他操作。再以switch button做例子,下面的code可以把switch button的狀態做幾個改變:
|
||||
1. 我們只想收到on的狀態。
|
||||
2. 把on的狀態轉為一個字串"We've been toggled on!"。
|
||||
```kotlin
|
||||
switch.checkdChanges()
|
||||
.filter { it == true }
|
||||
.map { "We've been toggled on!" }
|
||||
.subscribeBy(
|
||||
onNext = { message ->
|
||||
updateTextView(message)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### 3. Schedulers
|
||||
Scheduler可以想像成thread,RxJava已經內建了好幾種scheduler,而且應該可以適用於大部分的情形。
|
||||
例如IO scheduler可以讓你的檔案下載在背景執行,`TeampolineScheduler`可以讓你的程式同時執行, `ComputationScheduler`可以讓你將程式分配給不同的thread來處理需要大量運算的資料。
|
||||
|
||||
RxJava是一個很獨立的library,所以有2個library可以跟RxJava一起合作:
|
||||
1. RxAndroid:提供Android Looper class跟RxJava的scheduler之間的橋接管道。
|
||||
2. RxBinding:用來把UI的click listen之類的callback轉變為Observable的`subscribeBy`。
|
||||
|
||||
# 安裝
|
||||
在`build.gradle`裡的`depedencies`區域加入:
|
||||
```
|
||||
implementation "io.reactivex.rxjava3:rxjava:3.0.2"
|
||||
implementation "io.reactivex.rxjava3:rxkotlin:3.0.0"
|
||||
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
|
||||
```
|
||||
|
||||
# Observable
|
||||
Standard Observable has three types of event:
|
||||
1. next
|
||||
2. complete
|
||||
3. error
|
||||
|
||||
Obervable很適合用marble diagram來表示:
|
||||
![[Pasted image 20210120150947.png]]
|
||||
|
||||
3個event解釋如下:
|
||||
1. `onNext()`:`onNext()` event會伴隨著一筆資料,這也是觀察者用來接收資料的event。
|
||||
2. `onComplete()`:`onComplete()` event表示Observable已經「成功的」結束了它的生命週期,在`onComplete()` event之後,觀察者不會再收到任何`onNext()` event。
|
||||
3. `onError()`:`onError()` event表示Observable在發生錯誤的情況下結束它的生命週期。跟`onComplete()` event依樣,後續不會再有任何`onNext()` event。
|
||||
|
||||
另外,要注意:一個Observable在沒有被訂閱的情況下,「**是不會發送任何event的**」。
|
||||
|
||||
A example of usage of standard Observable:
|
||||
```kotlin
|
||||
API.download("http://...")
|
||||
.subscribeBy(
|
||||
onNext = { /* do something */ },
|
||||
onComplete = { /* do something */ },
|
||||
onError = { /* do something */ },
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## 建立`Observable`的方法
|
||||
### 1. `just`
|
||||
```kotlin
|
||||
val observable = Observable.just(1, 2, 3)
|
||||
```
|
||||
變數observable的內容設為1個"1、2、3"三個數,型別會是`Observable<Int!>!`。
|
||||
如果使用了onNext來發送event的話,將會依序發送1、2、3。
|
||||
但如果是:
|
||||
```kotlin
|
||||
val observable = Observable.just(listOf(1, 2, 3))
|
||||
```
|
||||
變數observable的內容會是一個list,這個list的內容是"1、2、3"。型別是`Observable<List<Int>!>!`。
|
||||
如果使用了onNext來發送event的話,將發送一個包含1、2、3的list。
|
||||
|
||||
### 2. `fromIterable`
|
||||
用來將list的內容轉變為一個一個單獨的element給Observable。
|
||||
```kotlin
|
||||
val observable = Observable.fromIterable(listOf(2, 3, 1))
|
||||
```
|
||||
變數observable的型別會是`Observable<Int!>!`,而不是`Observable<List<Int>!>!`。
|
||||
|
||||
### 3. `empty`
|
||||
建立一個「空的」Observable,可以用來表示一個馬上就會結束的事情,或是不包含任何東西的情況。
|
||||
```kotlin
|
||||
val observable = Observable.empty<Unit>()
|
||||
observable.subscribeBy(
|
||||
onNext = { println(it) },
|
||||
onComplete = { println("Completed") }
|
||||
)
|
||||
```
|
||||
用`empty()`所建立的observable只會發出`onComplete()` event,所以上面的`onNext()` event永遠不會發生。
|
||||
還有,Observable所包含的element一定要有一個型別,而且不可以是null,所以上面的`empty()`必須明白的寫出`Unit`型別:`empty<Unit>() `。
|
||||
|
||||
### 4. `never`
|
||||
建立一個不會發出任何event的observable。
|
||||
|
||||
### 5. `range`
|
||||
產生一個範圍的數列,參數型別必須是整數(`Int`)。
|
||||
```kotlin
|
||||
val observable = Observable.range(1, 10)
|
||||
```
|
||||
上例的`onNext()`會依序發送1~10的數字出來。
|
||||
|
||||
### 6. `create`
|
||||
用來定義自己的event發送方法。
|
||||
範例:
|
||||
```kotlin
|
||||
val observable = Observable.create<String> { emitter ->
|
||||
emitter.onNext("A")
|
||||
emitter.onNext("C")
|
||||
emitter.onNext("B")
|
||||
emitter.onComplete()
|
||||
}
|
||||
val subscription = observable.subscribeBy(
|
||||
onNext = { println("Received: $it") },
|
||||
onComplete = { println("Completed") },
|
||||
onError = { println("Completed") }
|
||||
)
|
||||
```
|
||||
`create`必須帶入要發送的型別,例如`Int`、`String`或是任何class,此例中是`create<String>`,表示會送出的element是`String`型別。
|
||||
然後`create`則是發送的實作,範例是會發送"A" -> "C" -> "B",然後用`onComplete`來結束。
|
||||
注意:要是observable沒有`onComplete`或是`onError`,然後`Disposable`(也就是訂閱者)也沒有呼叫`dispose()`,則會造成memory leak。
|
||||
|
||||
### 7. `defer`
|
||||
`defer`會建立一個Observable factory,每一次呼叫這個factory都會產生一個新的Observable。`defer`只有一個參數,就是我們要「製造」Observable的方法:
|
||||
```kotlin
|
||||
var flip = false
|
||||
|
||||
val factory: Observable<Int> = Observable.defer {
|
||||
flip = !flip
|
||||
if (flip) {
|
||||
Observable.just(1, 2, 3)
|
||||
} else {
|
||||
Observable.just(4, 5, 6)
|
||||
}
|
||||
}
|
||||
```
|
||||
`defer`後面的lambda就是我們要「製造」Observable的方法。當`flip`是`true`的時候,我們產生`Observable.just(1, 2, 3)`,反之則產生`Observable.just(4, 5, 6)`。Observable裡面所帶的element都是整數,這也是為什麼factory的型別是`Observable<Int>`。
|
||||
接下來訂閱這個factory:
|
||||
```kotlin
|
||||
for (i in 0..3) {
|
||||
val subscription = factory.subscribe {
|
||||
println("Factory out: $it")
|
||||
}
|
||||
disposables.add(subscription)
|
||||
}
|
||||
disposables.dispose()
|
||||
```
|
||||
上面的例子產生了4個Observable。依照flip的值來產生不一樣內容的Observable。
|
||||
|
||||
How to subscrible a Observable
|
||||
## 訂閱`Observable`的方法
|
||||
1. `observable.subscrible()`
|
||||
2. `observable.subscribleBy()`
|
||||
|
||||
Remember to release the resource. Call `disposable()` if you don't need Observable anymore. Or use `CompositeDisposable()` to collect all Disposable and release them.
|
||||
|
||||
## 特殊的Observable
|
||||
### 1. `Single`
|
||||
`Single`只有`onSuccess`跟`onError`兩種event。在發出`onSuccess`或是`onError`之後,`Single`就結束了。
|
||||
譬如說讀取檔案,只會有讀取成功跟讀取失敗兩種情況,下面的範例讀取一個檔案,要是檔案不存在就發送`onError()`,反之就發送`onSuccess()`。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
fun loadText(filename: String): Single<String> {
|
||||
return Single.create create@{ emitter ->
|
||||
val file = File(filename)
|
||||
|
||||
if (!file.exists()) {
|
||||
emitter.onError(FileNotFoundException("Can't find $filename"))
|
||||
return@create
|
||||
}
|
||||
|
||||
val contents = file.readText(Charsets.UTF_8)
|
||||
emitter.onSuccess(contents)
|
||||
}
|
||||
}
|
||||
|
||||
// Use the single observable
|
||||
val subscription = loadText("Copyright.txt")
|
||||
.subscribeBy(
|
||||
onSuccess = { println("Success read: $it") },
|
||||
onError = { println("Error: $it") }
|
||||
)
|
||||
subscriptions.add(subscription)
|
||||
```
|
||||
`loadText()`這個function會返回`Single<String>`物件,要是讀取檔案成功,就把檔案內容用`onSuccess()`發送出來:
|
||||
```kotlin
|
||||
val contents = file.readText(Charsets.UTF_8)
|
||||
emitter.onSuccess(contents)
|
||||
```
|
||||
要是檔案不存在,就發出`onError()`:
|
||||
```kotlin
|
||||
emitter.onError(FileNotFoundException("Can't find $filename"))
|
||||
```
|
||||
|
||||
### 2. `Completable`
|
||||
`Completable`只有`onCompleted`跟`onError`兩種event。跟`Single`一樣,在發出`onCompleted`或是`onError`之後,`Completable`就結束了。
|
||||
|
||||
### 3. `Maybe`
|
||||
`Maybe`是`Single`跟`Completable`的混合,他有`onSuccess(value)`、`onCompleted`跟`onError`三種event。`Maybe`只會發出這三種的其中一種event,然後就結束了。
|
||||
|
||||
## 停止訂閱或是結束一個`Observable`
|
||||
### 使用`Disposable.dispose()`
|
||||
每一次呼叫`observable.subscrible()`或是`observable.subscribleBy()`都會回傳一個`Disposable`物件,當我們不再需要訂閱一個Observable的時候,我們必須呼叫`dispose()`停止訂閱:
|
||||
```kotlin
|
||||
val alphaSequnce = Observable.just("A", "B", "C")
|
||||
val subscription = alphaSequece.subscribe {
|
||||
println(it)
|
||||
}
|
||||
|
||||
subscription.dispose()
|
||||
```
|
||||
|
||||
### 使用`CompositeDisposable.dispose()`
|
||||
對每一個`Disposable`物件在停止訂閱之後都要呼叫一次`dispose()`是很煩人的,RxJava提供了一個`CompositeDisposable` class。它可以收納所有的`Disposable`物件,然後一次停止:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val subscriptionNumbers = Observable.just(1, 2, 3).subscribe {
|
||||
println("Numbers: $it")
|
||||
}
|
||||
val subscriptionAlphabets = Observable.just("A", "B", "C").subscribe {
|
||||
println("Alphabets: $it")
|
||||
}
|
||||
|
||||
subscriptions.add(subscriptionNumbers)
|
||||
subscriptions.add(subscriptionAlphabets)
|
||||
subscriptions.dispose() <-- subscriptionNumbers 與 subscriptionAlphabets 都會一起呼叫dispose()
|
||||
```
|
||||
忘記呼叫`dispose()`可能會造成memory leak。
|
||||
|
||||
# Subjects
|
||||
Observable必須在建立的時候就指定好資料,之後沒辦法再新增資料。而Subject可以在建立資料之後,再新增資料,Subject也會將新增的資料再馬上轉發給它的訂閱者。
|
||||
## 1. `PublishSubject`
|
||||
`PublishSubject`剛開始是沒有任何資料的,它也只會將最新的資料發送給它的訂閱者。另外,要是`PublishSubject`本身結束了(已經送出了`onComplete` event),那麼新的訂閱者將不會收到任何資料,但是會收到`onComplete` event。
|
||||
```kotlin
|
||||
val publishSubject = PublishSubject.create<Int>()
|
||||
|
||||
publishSubject.onNext(0)
|
||||
|
||||
val subscriptionOne = publishSubject.subscribe {
|
||||
println(it)
|
||||
}
|
||||
|
||||
publishSubject.onNext(1)
|
||||
publishSubject.onNext(2)
|
||||
|
||||
val subscriptionTwo = publishSubject.subscribe {
|
||||
println("2: $it")
|
||||
}
|
||||
|
||||
publishSubject.onNext(3)
|
||||
subscriptionOne.dispose()
|
||||
publishSubject.onNext(4)
|
||||
publishSubject.onComplete()
|
||||
publishSubject.onNext(5)
|
||||
subscriptionTwo.dispose()
|
||||
|
||||
val subscriptionThree = publishSubject.subscribeBy(
|
||||
onNext = { println("3: $it") },
|
||||
onComplete = { println("3: Completed") }
|
||||
)
|
||||
```
|
||||
上例中的`subscriptionThree`只會收到`onComplete` event,也就是只會印出`"3: Completed"`。
|
||||
|
||||
## 2. `BehaviorSubject`
|
||||
行為跟`PublishSubject`類似,但是`BehaviorSubject`會發送「最後一筆資料」給新的訂閱者。如果`BehaviorSubject`最後的event是`onError`,那麼新的訂閱者也會收到`onError` event。例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val behaviorSubject = BehaviorSubject.createDefault("Initial value")
|
||||
|
||||
behaviorSubject.onNext("X")
|
||||
|
||||
val subscriptionOne = behaviorSubject.subscribeBy(
|
||||
onNext = { println("1: $it") },
|
||||
onError = { println("1: ERROR, $it") }
|
||||
)
|
||||
|
||||
behaviorSubject.onError(RuntimeException("Error!"))
|
||||
behaviorSubject.onNext("Y") // <-- 不會有人收到,因為已經被onError給terminate了
|
||||
|
||||
subscriptions.add(behaviorSubject.subscribeBy(
|
||||
onNext = { println("2: $it") },
|
||||
onError = { println("2: $it") }
|
||||
))
|
||||
```
|
||||
另外,可以直接取得`BehaviorSubject`目前的值,以上例來說,只要用`behaviorSubject.value`就可以,這方法可以很方便的在Rx與非Rx的程式中交換資料。
|
||||
例子中是用static method `BehaviorSubject.createDefault()`來建立一個有初始值的`BehaviorSubject`,當然也可以跟`PublishSubject`一樣,用`BehaviorSubject.create()`來建立。
|
||||
|
||||
## 3. `ReplaySubject`:
|
||||
`BehaviorSubject`會發送會後一筆資料,`ReplaySubject`就是發送最後n筆資料。我們可以用`ReplaySubject.createWithSize()`這個static method來建立一個`ReplaySubject`。例:
|
||||
```kotlin
|
||||
val replaySubject = ReplaySubject.createWithSize<String>(2)
|
||||
```
|
||||
變數`replaySubject`的buffer容量是2,型別是`String`。
|
||||
|
||||
## 4. `AsyncSubject`
|
||||
`AsyncSubject`的行為比較特別,`AsyncSubject`只會結束的時候,同時發出最後一筆資料。也就是說,即便一直提供資料給`AsyncSubject`,它也不會發出任何`onNext` event給它的訂閱者,直到它收到`onComplete`的時候,它才會同時發出最後一筆`onNext`與`onComplete`給它的訂閱者。
|
||||
|
||||
## 5. `RxRelay`
|
||||
`RxRelay`永遠不會發出`onComplete`或是`onError`。下面例子建立了一個`PublishRelay`:
|
||||
```
|
||||
val publishRelay = PublishRelay.create<Int>()
|
||||
```
|
||||
|
||||
要使用`RxRelay` library,必須在build.gradle裡面加入:
|
||||
```
|
||||
implementation "com.jakewharton.rxrelay3:rxrelay:3.0.0"
|
||||
```
|
||||
|
||||
# Operators
|
||||
## 1. Filtering Operators
|
||||
### `ignoreElement`
|
||||
`ignoreElement()`會忽略掉由[[002. Study RxKotlin#Subjects]]丟出來的**next** event,訂閱者只會收到`onCompleted`跟`onError`這兩種event,也就是讓Subject退化成[[002. Study RxKotlin#2 Completable]]。
|
||||
例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val strikes = PublishSubject.create<String>()
|
||||
|
||||
subscriptions.add(
|
||||
strikes.ignoreElements()
|
||||
.subscribeBy {
|
||||
println("Done") <-- 只會收到onComplete跟onError
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### `elementAt`
|
||||
`elementAt()`只會處理「第n個」**next** event,n之前跟n之後的都會被忽略。例如:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val strikes = PublishSubject.create<String>()
|
||||
|
||||
subscriptions.add(
|
||||
strikes.elementAt(2) <-- 只要收第2個
|
||||
.subscribeBy {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
|
||||
strikes.onNext("A") <-- 第0個
|
||||
strikes.onNext("B") <-- 第1個
|
||||
strikes.onNext("C") <-- 第2個
|
||||
strikes.onNext("D") <-- 第3個
|
||||
```
|
||||
上面例子只要收「第2個next event」,所以只會收到"**C**"。這也是一個`onSuccess` event。
|
||||
`elementAt()`也等於是把Subject退化成[[002. Study RxKotlin#3 Maybe]]。
|
||||
要是Subject的「第n個」還沒收到就結束了,那就是收到`onComplete` event。
|
||||
|
||||
### `filter`
|
||||
`filter()`接收一個lambda函數,每一個next event所帶的element都必須經過這個函數的「驗證」,只有驗證結果為`true`的時候,才會pass給訂閱者。例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.fromIterable(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||
.filter {
|
||||
it > 5 <-- 數值必須大於5才可以pass給訂閱者
|
||||
}
|
||||
.subscribe {
|
||||
print("Get number: $it\n")
|
||||
}
|
||||
)
|
||||
```
|
||||
所以上面的結果會收到6、7、8、9、10。
|
||||
|
||||
### `skip`
|
||||
忽略「前n個」next event。
|
||||
```kotlin
|
||||
Observer.just(1, 2, 3, 4 ,5)
|
||||
.skip(3) <-- 忽略前3個
|
||||
.subscribe {
|
||||
println("it")
|
||||
}
|
||||
// Output 4, 5
|
||||
```
|
||||
|
||||
### `skipWhile`
|
||||
`skipWhile`也是用一個lambda來當作通過條件,跟[[002. Study RxKotlin#filter]]類似。但不像`filter`會去檢查「每一個」進來的element,`skipWhile`是「當lambda檢查到第一個`false`的時候,後面全部通過」。
|
||||
另一個跟`filter`不同的是,`skipWhile`是在檢查為`true`把next event忽略掉,檢查到`false`的時候開始放行。
|
||||
例如,我們要收集字串,但我們要當字串是"start"的時候才開始收集字串,例:
|
||||
```kotlin
|
||||
val subscriptions \= CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just("1", "2", "3", "Start", "1", "3", "2")
|
||||
.skipWhile { it != "Start" } <-- 比對為false開始放行
|
||||
.subscribe {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
|
||||
// Output
|
||||
Get Start
|
||||
Get 1
|
||||
Get 3
|
||||
Get 2
|
||||
```
|
||||
|
||||
### `skipUntil`
|
||||
與前面的skip operator不同,`skipUntil`不是用lambda來決定skip的條件,而是依賴於「另一個subject」,`skipUntil`會一直忽略,直到「另一個subject」發出`onNext` event。例:
|
||||
```kotlin
|
||||
val subject = PublishSubject.create<String>()
|
||||
val trigger = PublishSubject.create<String>()
|
||||
|
||||
subject
|
||||
.skipUntil(trigger)
|
||||
.subscribe {
|
||||
println("it")
|
||||
}
|
||||
```
|
||||
In this code you'll get nothing, until `trigger` sent an `onNext()` event.
|
||||
Example:
|
||||
```kotlin
|
||||
subject.onNext("A") // Ignored
|
||||
subject.onNext("B") // Ignored
|
||||
trigger.onNext("1") // TRIGGER!
|
||||
subject.onNext("C") // send out
|
||||
```
|
||||
![[Pasted image 20210202155003.png]]
|
||||
|
||||
### `take`
|
||||
`take`跟[[002. Study RxKotlin#skip]]相反,`take`是接收「前n個」訊息,之後全部忽略。
|
||||
|
||||
### `takeWhile`
|
||||
`takeWhile`是用lambda當判斷條件,當判斷為`true`的時候放行,一旦判斷為`false`,之後的所有訊息都會被忽略。
|
||||
```kotlin
|
||||
exampleOf("takeWhile") {
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.fromIterable(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1))
|
||||
.takeWhile {
|
||||
it < 5
|
||||
}
|
||||
.subscribe {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
上例中,判斷到5的時候,`it < 5`為`false`,所以之後的都會忽略,即使最後那個1也是一樣被忽略。
|
||||
`takeWhile`跟[[002. Study RxKotlin#skipWhile]]一樣,但是行為相反。
|
||||
|
||||
### `takeUntil`
|
||||
跟[[002. Study RxKotlin#skipUntil]]相反的行為,會一直接收訊息,直到他依賴的subject發出訊息後停止。
|
||||
|
||||
### `distinctUntilChanged`
|
||||
`distinctUntilChanged`用來過濾「連續且相同」的訊息,例如連續的"Hi",那麼就只會收到第1個"Hi",之後的都不會收到。但是一旦收到的訊息改變了,再次收到以前發過的訊息,只要它沒有跟前一筆相同,那麼就還是會接收。例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just(5, 5, 3, 3, 1)
|
||||
.distinctUntilChanged()
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
|
||||
// Output
|
||||
5
|
||||
3
|
||||
1
|
||||
```
|
||||
|
||||
`distinctUntilChanged`預設用class的`equal()` method來判斷,當然我們也可以給它一個lambda來當客製我們的條件,被lambda判斷為`true`的話,該訊息就會被忽略:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just("ABC", "CCD", "FAG")
|
||||
.distinctUntilChanged { first, second ->
|
||||
first[second.length - 1] == second[0]
|
||||
}
|
||||
.subscribe {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
```
|
||||
上例中,我們希望「第2個字串的開頭字母要是跟第1個字串的結尾字母一樣的話,那麼就不要顯示」。第1筆"ABC"一定會接收,第2筆"CCD"則會被忽略,第3筆"FAG"會被接收。
|
||||
|
||||
## 2. Transforming Operators
|
||||
### 1. toList
|
||||
`toList`可以把每一個單獨從Observalbe發出來的元素變成一個list,如:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val items = Observable.just("A", "B", "C")
|
||||
|
||||
subscriptions.add(
|
||||
items.toList()
|
||||
.subscribeBy {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
```
|
||||
原本會單獨發出的"A", "B", "C",現在變成只翠發出一個List,其內容是`["A", "B", "C"]`。
|
||||
|
||||
### 2. map
|
||||
`map`跟Kotlin的`map`行為上差不多,只是Kotlin的`map`是作用在List上,而RxJava的`map`是作用在Observable上。`map`根據你提供的lambda函式來對每一個element做轉換,如下例,將每一個羅馬數字轉換為阿拉伯數字:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just("M", "C", "V", "I")
|
||||
.map {
|
||||
it.romanNumeralIntValue()
|
||||
}
|
||||
.subscribeBy {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
```
|
||||
注意到了嗎?`map`用來轉換Observable所包含的item型別,上例中,Observable的item本來是一個字串(`String`),被`map`轉換為數字(`Int`)。
|
||||
|
||||
### 3. flatMap
|
||||
`flatMap`用來處理「Observable發出來的Observable」,並且「記錄每一個變化」。用例子比較好說明:
|
||||
```kotlin
|
||||
class Student(val score: BehaviorSubject<Int>)
|
||||
|
||||
val subscriptions = CompositeDisposable()
|
||||
val ryan = Student(BehaviorSubject.createDefault(80))
|
||||
val charlotte = Student(BehaviorSubject.createDefault(90))
|
||||
val student = PublishSubject.create<Student>()
|
||||
|
||||
student
|
||||
.flatMap {
|
||||
it.score
|
||||
}
|
||||
.subscribeBy {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
|
||||
student.onNext(ryan) <-- 1
|
||||
ryan.score.onNext(85) <-- 2
|
||||
|
||||
student.onNext(charlotte) <-- 3
|
||||
ryan.score.onNext(95) <-- 4
|
||||
charlotte.score.onNext(100) <-- 5
|
||||
```
|
||||
|
||||
我們有一個叫做student的`PublishSubject`,另外有兩個Student class(分別是ryan與charlotte),而這個Student class有一個member叫做score,score的類別是`BehaviorSubject<Int>`。我們的`flatMap` lambda不做任何轉換,直接bypass分數。
|
||||
1. student先選擇ryan來發出第一個onNext event,ryan原本的分數是80,所以我們會收到80。
|
||||
2. ryan變更為85,所以我們會收到85。
|
||||
3. student選擇了charlotte並發出一個onNext event,charlotte原本的分數是90,所以我們會收到90。
|
||||
4. ryan變更為95,所以我們會收到95。
|
||||
5. charlotte變更為100,所以我們會收到100。
|
||||
結果會收到80、85、90、95、100。
|
||||
|
||||
ryan跟charlotte都是獨立的Observable,但透過`flatMap`我們可以把它們的值(以及後續的變化)變成一連串的數值,這就是`flat`的意思。
|
||||
![[rxJava_flatMap.png]]
|
||||
|
||||
### 4. switchMap
|
||||
`switchMap`跟`flatMap`類似,也是處理「Observable發出來的Observable」,但是差別在於`switchMap`一但切換到新的Observable之後,上一個Observale的訊息就部會收到了,以`flatMap`的例子來說,在`student.onNext(charlotte)`這一行之後,ryan的改變就不會收到了。例:
|
||||
```kotlin
|
||||
val ryan = Student(BehaviorSubject.createDefault(80))
|
||||
val charlotte = Student(BehaviorSubject.createDefault(90))
|
||||
val student = PublishSubject.create<Student>()
|
||||
|
||||
student
|
||||
.switchMap {
|
||||
it.score
|
||||
}
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
|
||||
student.onNext(ryan)
|
||||
ryan.score.onNext(85)
|
||||
|
||||
student.onNext(charlotte)
|
||||
ryan.score.onNext(95)
|
||||
charlotte.score.onNext(100)
|
||||
```
|
||||
結果會收到80、85、95、100。`ryan.score.onNext(95)`這一行的95不會收到。
|
||||
|
||||
`switchMap`適合用在會「改變興趣」的場景,例如說原本是要持續收到台北氣溫的改變,接著使用者把地點改到高雄,那我們就會變成持續收到高雄的溫度變化而不是台北的,又或者說,你會隨著使用的的輸入持續的搜尋結果,例如使用者依序輸入k、o、t、l、i、n,每輸入一個字母我們就搜尋一次,但我們只關注最後一個字串搜尋,不在意之前的搜尋結果。
|
||||
|
||||
### 5. materialize
|
||||
`materialize`能將Observable的值包裝成一個`Notification`,回到[[002. Study RxKotlin#4 switchMap]]的例子,如果任何一個學生發出了`onError`的訊息,那麼連`student`本身都會因為這個Exception而中斷,所以即使切到了charlotte,我們也收不到charlotte的訊息了。`materialize`可以將`onError`包裝成一個`Notification`,讓exception留在ryan本身而不會影響到上面的student。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val ryan = Student(BehaviorSubject.createDefault(80))
|
||||
val charlotte = Student(BehaviorSubject.createDefault(90))
|
||||
val student = BehaviorSubject.createDefault<Student>(ryan)
|
||||
|
||||
// 1
|
||||
val studentScore = student.switchMap { it.score.materialize() } <-- HERE!
|
||||
// 2
|
||||
subscriptions.add(
|
||||
studentScore
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
// 3
|
||||
ryan.score.onNext(85)
|
||||
ryan.score.onError(RuntimeException("Error!"))
|
||||
ryan.score.onNext(90)
|
||||
// 4
|
||||
student.onNext(charlotte)
|
||||
```
|
||||
![[rxkotlin_materialize.png]]
|
||||
|
||||
### 6. dematerialize
|
||||
`dematerialize`用來反解`materialize`所包裝的東西,例如上例中,會將`materialize`所包裝出來的`Observable<Notification<Int!>!>!`反解為`Observable<Int>!`,例:
|
||||
```kotlin
|
||||
subscriptions.add(
|
||||
studentScore
|
||||
.filter {
|
||||
if (it.error != null) {
|
||||
println("Got error: ${it.error}")
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
.dematerialize { it }
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
```
|
||||
要注意的是如果發生Exception的話,直接`println`還是會產生Exception,所以需要用`filter`來把error給濾掉。
|
||||
![[rxkotlin_dematerialize.png]]
|
||||
|
||||
在Subject所發出的element仍然是Subject的時候,如果element發生error(Exception),會導致上層的Subject也跟著停止,`materialize`/`dematerialize`可以用來包裝element,讓element所發出的東西都變成`Notification`,這樣就部會影響上層的Subject了。
|
||||
|
||||
## 3. Combining Operators
|
||||
### startWith
|
||||
用來在Observable本身所帶的item前面再加上其他item。實際的有`startWithIterable()`與`startWithItem()`。
|
||||
例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val numbers = Observable.just(3, 4, 5)
|
||||
val startWith = numbers.startWithIterable(listOf(1, 2))
|
||||
|
||||
startWith.subscribe {
|
||||
println(it)
|
||||
}.addTo(subscriptions)
|
||||
```
|
||||
|
||||
### concat
|
||||
`concat`是一個static method,用來合併2個Observable。`concat`會先等第一個Observable結束,然後再等待第二個,之後把它們合併起來。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val first = Observable.just(1, 2, 3)
|
||||
val second = Observable.just(3, 4, 5)
|
||||
|
||||
Observable.concat(first, second)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
```
|
||||
|
||||
### concatWith
|
||||
跟[[002. Study RxKotlin#concat]]一樣,但是`concatWith`是一個member function,而不是一個static method。`concatWith`一樣會先等自己結束,然後再等第二個Obervable(當參數的那一個)結束,之後再合併。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val first = Observable.just(1, 2, 3)
|
||||
val second = Observable.just(3, 4, 5)
|
||||
|
||||
first.concatWith(second)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
```
|
||||
|
||||
> ## 注意
|
||||
> 要被合併的兩個Observable類型必須要一樣,不可以一個是`Obsrvable<String>`而另一個是`Observable<Int>`,compiler會報錯喔。
|
||||
|
||||
### concatMap
|
||||
`concatMap`接受一個lambda函示,並回傳另一個Observable序列,`concatMap`會保證Observable的順序。
|
||||
Given multiple Observable, and map each Observable to a lambda function. And make sure the sequence of given Observable list.
|
||||
|
||||
### merge
|
||||
`merge`是一個static function。
|
||||
`merge`會按照接收的順序把element合併起來,例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val odd = PublishSubject.create<Int\>()
|
||||
val even = PublishSubject.create<Int\>()
|
||||
|
||||
Observable.merge(odd, even)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
|
||||
odd.onNext(1)
|
||||
even.onNext(2)
|
||||
odd.onNext(3)
|
||||
even.onNext(4)
|
||||
odd.onNext(5)
|
||||
even.onNext(6)
|
||||
```
|
||||
odd與even兩個交互發出elemet,merge依順序接收,而不是像[[002. Study RxKotlin#concat]]是依照Observable的順序。
|
||||
`merge` complete的時機點定義如下:
|
||||
- 當來源的Observable與內部的Observable都complete的時候,merge本身也會發出complete。
|
||||
- 內部Observable結束的順序跟接收的順序沒有關係。(一律按照接收點)
|
||||
- 如果有任何Observable發生error,`merge`會轉發這個error,然後自己發生terminate。
|
||||
|
||||
Q:
|
||||
- What's the different between `flatMap()`? -> 很大的差別,`flatMap()` 有map的功能。
|
||||
|
||||
### mergeWith
|
||||
就像`concatWith()`與`concat()`的關係,`mergeWith()`跟`merge`也是一樣的關係。
|
||||
`mergeWith()`是一個member function,必須由某個Observable instance來呼叫。
|
||||
|
||||
### combineLatest
|
||||
combineLatest是`Observables`的static funtion(注意不是`Observable`)。
|
||||
combineLatest只會接收2個Observalbe的「最後一個」elements,然後交由你所提供的lambda來處置,例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val left = PublishSubject.create<String>()
|
||||
val right = PublishSubject.create<String>()
|
||||
|
||||
Observables.combineLatest(left, right) { leftString, rightString ->
|
||||
"$leftString, $rightString"
|
||||
}.subscribe {
|
||||
println(it)
|
||||
}.addTo(subscriptions)
|
||||
|
||||
left.onNext("Hello")
|
||||
right.onNext("World")
|
||||
left.onNext("It's nice to")
|
||||
right.onNext("be here")
|
||||
left.onNext("Actually, it's super great to")
|
||||
```
|
||||
|
||||
重點:
|
||||
1. 在上例中是直接結合2個字串,但是其實可以是任何用途。
|
||||
2. 在實務中,`combineLatest`可以用來結合2個不同型別的Observable,然後再回傳另一個不同型別的Observable。`combineLatest`回傳的Observable型別由lambda決定。
|
||||
3. `combineLatest`必須在「每一個」Observable都發出element之後才會動作。如果不確定Observable是否會發出element,可以使用[[002. Study RxKotlin#startWith]]來讓Observable有一個初始值,這樣可以避免`combineLatest`永遠不會發生的情況。
|
||||
4. 如果有某個Observable已經complete,`combineLatest`會保留它的最後一個element,然後繼續結合更新的element。
|
||||
5. 直到最後一個Observable complete,`combineLatest`才會complete。
|
||||
![[combineLatest.png]]
|
||||
|
||||
### zip
|
||||
- `zip` wait until each if the inner Ovservables emits a new value.
|
||||
|
||||
### Triggers
|
||||
#### withLastestFrom
|
||||
- `withLatestFrom` is useful in all situations where you want the current(latest) value emittted frim an Observable, but only when a particular trigger occurs.
|
||||
|
||||
#### sample
|
||||
- Just like `withLastFrom`. But each time the trygger Ivsercable emits a value, `sample` emits the latest value from the "other" Obervable, but only if it arrived since the last "tick". You can combine `withLastFrom` and `distinctUntilChanged` to do the same behavior of `sample`.
|
||||
```
|
||||
exampleOf("sample") {
|
||||
val subscriptions = CompositeDisposable()
|
||||
val button = PublishSubject.create<Unit>()
|
||||
val editText = PublishSubject.create<String>()
|
||||
|
||||
editText.sample(button)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}.addTo(subscriptions)
|
||||
|
||||
editText.onNext("Par")
|
||||
editText.onNext("Pari")
|
||||
editText.onNext("Paris")
|
||||
button.onNext(Unit)
|
||||
button.onNext(Unit) <- button emits twice, but editText only emit last value
|
||||
}
|
||||
|
||||
// Output
|
||||
--- Example of: sample ---
|
||||
Paris
|
||||
```
|
||||
|
||||
### Switchs
|
||||
- ambWith
|
||||
- Think of `amb` as in ambiguous.
|
||||
- `ambWith` connect to two Observables. And wait any of them who emit element first. If any Observable emit element, another one will be unsubscribed.
|
||||
### reduce
|
||||
- `reduce` accumulates a summary value.
|
||||
|
||||
### scan
|
||||
- Like `reduce`, but emit per input value.
|
||||
|
||||
## 4. Time-Based Operators
|
||||
### Buffering
|
||||
#### replay
|
||||
- This operator creates a new sequence that records the last N elements emitted by the source Observable.
|
||||
|
||||
#### replayAll
|
||||
|
||||
#### window
|
||||
- Difference is that it emits an Observable of the buffered items, instead of emitting an array.
|
||||
|
||||
### Time-Shifting
|
||||
#### delaySubscription
|
||||
- Delay the time a subscriber starts receiving elements from its subscription.
|
||||
|
||||
#### delay
|
||||
- This operator subscribes immediateley to the source observable, but delays every emitted element by the specified amount of time.
|
||||
|
||||
### Timer
|
||||
#### Observable.interval
|
||||
- Produce an infinite Observable sequence of Int values.
|
||||
|
||||
#### Observable.timer
|
||||
- Specify a "due time" as the time that elapsed between the point of subscription and the first emitted value.
|
||||
- If the "repeat period" is not assigned, the timer Observable will emit once, the complete.
|
||||
|
||||
#### timeout
|
||||
- Emit an TimeoutException error event. If not caught, it terminates the sequence.
|
||||
|
||||
## 5. Explore Operators
|
||||
|
||||
|
||||
# 參考資料:
|
||||
- [RxMarbles: Interactive diagrams of Rx Observables](https://rxmarbles.com/#delayWhen)
|
||||
1
02. PARA/02. Area(領域)/003. LBRY.md
Normal file
1
02. PARA/02. Area(領域)/003. LBRY.md
Normal file
@@ -0,0 +1 @@
|
||||
- [Contributor's Guide - lbry.tech](https://lbry.tech/contribute)
|
||||
0
02. PARA/02. Area(領域)/004. IPFS.md
Normal file
0
02. PARA/02. Area(領域)/004. IPFS.md
Normal file
10
02. PARA/02. Area(領域)/005. 投資.md
Normal file
10
02. PARA/02. Area(領域)/005. 投資.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# ETF
|
||||
## 追蹤美國標普500
|
||||
- VOO(Vaguard 500 Index Fund ETF)
|
||||
- IVV(iShares Core S&P 500)
|
||||
- SPY(S&P 500ETF TRUST ETF)
|
||||
|
||||
## 全球
|
||||
- VTI(Vanguard Total Stock Market)
|
||||
- VXUS/VEU,投資美國以外的全球已開發新興市場。兩者投資四大地斯的比重也相近。兩者的差別在於兩隻ETF對於中小型類股的涵蓋程度。
|
||||
- VT(Vanguard Total World Stock Index Fund ETF)
|
||||
59
02. PARA/02. Area(領域)/20150803 - Android/ADB.md
Normal file
59
02. PARA/02. Area(領域)/20150803 - Android/ADB.md
Normal file
@@ -0,0 +1,59 @@
|
||||
## am
|
||||
### start
|
||||
用`am start`來打開一個activity,例:
|
||||
```
|
||||
adb shell am start -S com.logitech.sentineliq/.MainActivity --es cameraId 0
|
||||
```
|
||||
其中`-S`是指先停止app再打開app。還有其他的命令如下:
|
||||
```
|
||||
-D: enable debugging
|
||||
-W: wait for launch to complete
|
||||
--start-profiler <FILE>: start profiler and send results to <FILE>
|
||||
-P <FILE>: like above, but profiling stops when app goes idle
|
||||
-R: repeat the activity launch <COUNT> times. Prior to each repeat,
|
||||
the top activity will be finished.
|
||||
-S: force stop the target app before starting the activity
|
||||
--opengl-trace: enable tracing of OpenGL functions
|
||||
```
|
||||
|
||||
### 參考
|
||||
- [Android activity manager "am" command help](https://gist.github.com/tsohr/5711945)
|
||||
|
||||
## pm
|
||||
### list packages
|
||||
- `adb shell pm list packages`:可以列出所有安裝的apk
|
||||
|
||||
## Forward
|
||||
ADB forward用來把PC端收到的TCP轉到Android去,這樣就可以透過USB ADB達到網路的功能。
|
||||
例:
|
||||
```
|
||||
adb forward tcp:6100 tcp:7100
|
||||
```
|
||||
|
||||
上述等於:
|
||||
```
|
||||
PC Android
|
||||
http://127.0.0.1:6100 APP(port: 8080)
|
||||
| |
|
||||
| |
|
||||
V----------> ADB(USB)----------->
|
||||
|
||||
```
|
||||
|
||||
Android端所回應的HTTP封包也會經由原路回來,但是如果Android端要發一個request的話,PC端就收不到了,必須經由[[ADB#Reverse]]才行。
|
||||
|
||||
### 參考
|
||||
* [Android Debug Bridge (adb) | Android Developers](https://developer.android.com/studio/command-line/adb#forwardports)
|
||||
|
||||
## Reverse
|
||||
ADB reverse用來監聽Android端收到的request,假設今天在Android APP上寫一個`http://127.0.0.1:8080/say/message`,並希望PC端可以收到的話,就可以用reverse來操作,例:
|
||||
```
|
||||
adb reverse tcp:8081 tcp:8080
|
||||
```
|
||||
也就是說Android那邊發過來的封包會送交給PC這邊的port 8081。因此,PC端還需要寫一個Server來監聽8081才行。
|
||||
|
||||
### 參考
|
||||
* [adb命令-adb reverse的研究-有解無憂](https://www.uj5u.com/qita/277742.html)
|
||||
|
||||
## Logcat
|
||||
- Enlarge logcat buffer to 16M: `adb logcat -G 16M`
|
||||
2
02. PARA/02. Area(領域)/20150803 - Android/AOSP.md
Normal file
2
02. PARA/02. Area(領域)/20150803 - Android/AOSP.md
Normal file
@@ -0,0 +1,2 @@
|
||||
## AOSP framework jar
|
||||
To use AOSP jar file in Android Studio: [Import AOSP framework jar file](https://medium.com/@chauyan/import-aosp-framework-jar-file-f0c2ac979a8a)
|
||||
55
02. PARA/02. Area(領域)/20150803 - Android/Ktor.md
Normal file
55
02. PARA/02. Area(領域)/20150803 - Android/Ktor.md
Normal file
@@ -0,0 +1,55 @@
|
||||
Ktor是由Kotlin提供的一個framwork。
|
||||
要在Android使用Ktor,需要在build.gradle加入以下的dependency:
|
||||
```
|
||||
implementation "io.ktor:ktor:1.2.5"
|
||||
implementation "io.ktor:ktor-server-netty:1.2.5"
|
||||
implementation "io.ktor:ktor-gson:1.2.5"
|
||||
```
|
||||
|
||||
在`packagingOptions`裡,也需要加入以下的設定來必面編譯問題:
|
||||
```
|
||||
packagingOptions {
|
||||
exclude 'META-INF/*'
|
||||
}
|
||||
```
|
||||
|
||||
在`AndroidManifest.xml`中,記得加入internet的權限:
|
||||
```
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
```
|
||||
|
||||
然後就是HTTP Server的code了:
|
||||
```
|
||||
embeddedServer(Netty, 8080) {
|
||||
install(ContentNegotiation) {
|
||||
gson {}
|
||||
}
|
||||
routing {
|
||||
get("/") {
|
||||
call.respond(mapOf("message" to "Hello world"))
|
||||
}
|
||||
}
|
||||
}.start(wait=true)
|
||||
```
|
||||
|
||||
但是這段code會block,所以需要一個thread把它包起來:
|
||||
```kotlin
|
||||
Thread {
|
||||
httpEngine = embeddedServer(Netty, 8080) {
|
||||
install(ContentNegotiation) {
|
||||
gson {}
|
||||
}
|
||||
routing {
|
||||
get("/") {
|
||||
call.respond(mapOf("message" to "Hello world"))
|
||||
}
|
||||
|
||||
get("/say/{something}") {
|
||||
call.respond(mapOf("message" to "You say: " + call.parameters["something"]))
|
||||
|
||||
activity?.findViewById<TextView>(R.id.textView)?.text = "You say: " + call.parameters["something"]
|
||||
}
|
||||
}
|
||||
}.start(wait = false)
|
||||
}.start()
|
||||
```
|
||||
205
02. PARA/02. Area(領域)/20150803 - Android/Service.md
Normal file
205
02. PARA/02. Area(領域)/20150803 - Android/Service.md
Normal file
@@ -0,0 +1,205 @@
|
||||
## Service的生命週期
|
||||
![[Pasted image 20220307103552.png]]
|
||||
|
||||
## Service的啟動方式
|
||||
Service由`startService()`啟動之後,便獨立動作,啟動者(例如某個activity)無法取得Service的intance,也無法呼叫Service的API。Service可以被多次呼叫`startService()`,但是只要一旦`stopService()`被呼叫了,Service就會結束。所以需要確保Service的管理者是誰,統一管理者來呼叫`startService()`與`stopService()`才不會造成混亂。
|
||||
|
||||
`bindService()`則是像是典型的Server-Client架構,第一次`bindService()`的時候會建立Service的instance,然後可以多次`bindService()`與`unbindService()`,當Service沒也任何人跟它"Bind"的時候,Service才會結束。
|
||||
|
||||
### startService
|
||||
要建立自己的Service,需要繼承`Service()`類別,然後複寫4個成員函式:
|
||||
```kotlin
|
||||
class MyService : Service() {
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i("Awin","onCreate - Thread ID = " + Thread.currentThread().id)
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i("Awin", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
Log.i("Awin", "onBind - Thread ID = " + Thread.currentThread().id)
|
||||
return null // 因為目前沒有要支援bindService(),所以這裡直接return null
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("Awin", "onDestroy - Thread ID = " + Thread.currentThread().id)
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
假設現在由MainActivity呼叫`startService()`,因為是第一次呼叫,所以MyService的callback被呼叫的順序是:`onCreate()` -> `onStartCommand()`。
|
||||
第二次之後的呼叫就只會執行`onStartCommand()`。
|
||||
當MainActivity呼叫`stopService()`時,則會執行MyService的`onDestroy()`。
|
||||
|
||||
所以,必須將「長期任務」開個 Thread 並且執行在 `onStartCommand()` 方法內。
|
||||
|
||||
Client端的code:
|
||||
```kotlin
|
||||
val intent = Intent(this, ServiceDemo.class)
|
||||
startService(intent)
|
||||
```
|
||||
|
||||
### bindService
|
||||
使用`bindService()`來建立Service的一個好處就是可以取得Service的instance,然後藉由這個instance來使用Service的API。
|
||||
要使用`bindService()`的話,Service類別必須實做`onBind()`與`onUnbind()`至兩個override function。
|
||||
也必須提供一個Binder class(繼承自`Binder()`),來讓client呼叫。例:
|
||||
```kotlin
|
||||
class MyService : Service() {
|
||||
|
||||
//client 可以通过Binder获取Service实例
|
||||
inner class MyBinder : Binder() {
|
||||
val service: MyService
|
||||
get() = this@MyService
|
||||
}
|
||||
|
||||
//通过binder实现调用者client与Service之间的通信
|
||||
private val binder = MyBinder()
|
||||
|
||||
private val generator: Random = Random()
|
||||
|
||||
override fun onCreate() {
|
||||
Log.i("xiao", "MyService - onCreate - Thread = " + Thread.currentThread().name)
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intent 啟動時,啟動組件傳遞過來的Intent,如Activity可利用Intent封裝所需要的參數並傳遞給Service
|
||||
* @param flags 表示啟動請求時是否有額外數據,可選值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY
|
||||
* 0: 在正常創建Service的情況下,onStartCommand傳入的flags為0。
|
||||
*
|
||||
* START_FLAG_REDELIVERY:
|
||||
* 這個值代表了onStartCommand()方法的返回值為 START_REDELIVER_INTENT,
|
||||
* 而且在上一次服務被殺死前會去調用stopSelf()方法停止服務。
|
||||
* 其中START_REDELIVER_INTENT意味著當Service因記憶體不足而被系統kill後,
|
||||
* 則會重建服務,並透過傳遞給服務的最後一個 Intent調用 onStartCommand(),此時Intent時有值的。
|
||||
*
|
||||
* START_FLAG_RETRY
|
||||
* 該flag代表當onStartCommand()調用後一直沒有返回值時,會嘗試重新去調用onStartCommand()。
|
||||
*
|
||||
* @param startId 指明當前服務的唯一ID,與stopSelfResult(int startId)配合使用,stopSelfResult()可以更安全地根據ID停止服務。
|
||||
*
|
||||
* @return
|
||||
* START_STICKY:
|
||||
* 當Service因記憶體不足而被系統kill後,一段時間後記憶體再次空閒時,
|
||||
* 系統將會嘗試重新創建此Service,一旦創建成功後將回調onStartCommand方法,
|
||||
* 但其中的Intent將是null,除非有掛起的Intent,如pendingintent,
|
||||
* 這個狀態下比較適用於不執行命令、但無限期運行並等待作業的媒體播放器或類似服務
|
||||
*
|
||||
*
|
||||
* START_NOT_STICKY:
|
||||
* 當Service因記憶體不足而被系統kill後,即使系統記憶體再次空閒時,
|
||||
* 系統也不會嘗試重新創建此Service。除非程序中再次調用startService啟動此Service,
|
||||
* 這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時運行服務。
|
||||
*
|
||||
* START_REDELIVER_INTENT:
|
||||
* 當Service因記憶體不足而被系統kill後,則會重建服務,
|
||||
* 並透過傳遞給服務的最後一個 Intent 調用 onStartCommand(),任何掛起 Intent均依次傳遞。
|
||||
* 與START_STICKY不同的是,其中的傳遞的Intent將是非空,是最後一次調用startService中的intent。
|
||||
* 這個值適用於主動執行應該立即恢復的作業(例如下載文件)的服務。
|
||||
*/
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i("xiao", "MyService - onStartCommand - startId = $startId, Thread = " + Thread.currentThread().name)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder{
|
||||
Log.i("xiao", "MyService - onBind - Thread = " + Thread.currentThread().name)
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent): Boolean {
|
||||
Log.i("xiao", "MyService - onUnbind - from = " + intent.getStringExtra("from"))
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i("xiao", "MyService - onDestroy - Thread = " + Thread.currentThread().name)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
//getRandomNumber是Service暴露出去供client调用的公共方法
|
||||
fun getRandomNumber(): Int {
|
||||
return generator.nextInt()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Client端要做的事:
|
||||
1. 建立ServiceConnection類型實例,並覆載`onServiceConnected()`方法和`onServiceDisconnected()`方法。
|
||||
2. 當執行到onServiceConnected callback時,可通過IBinder instance得到Service的instance,這樣可實現client與Service的連接。
|
||||
3. onServiceDisconnected callback被執行時,表示client與Service已經斷開,在此可以寫一些斷開後需要做的處理。
|
||||
```kotlin
|
||||
class AActivity : AppCompatActivity() {
|
||||
|
||||
private var service: MyService? = null
|
||||
private var isBind = false
|
||||
|
||||
private var conn = object : ServiceConnection{
|
||||
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
|
||||
isBind = true
|
||||
val myBinder = p1 as MyService.MyBinder
|
||||
service = myBinder.service
|
||||
Log.i("xiao", "ActivityA - onServiceConnected")
|
||||
val num = service!!.getRandomNumber()
|
||||
Log.i("xiao", "ActivityA - getRandomNumber = $num");
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(p0: ComponentName?) {
|
||||
isBind = false
|
||||
Log.i("xiao", "ActivityA - onServiceDisconnected")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_a)
|
||||
|
||||
Log.i("xiao", "ActivityA - onCreate - Thread = " + Thread.currentThread().name)
|
||||
|
||||
btn_bind_service_a.setOnClickListener {
|
||||
val intent = Intent(this,MyService::class.java)
|
||||
intent.putExtra("from","ActivityA")
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 bindService");
|
||||
bindService(intent, conn, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
btn_unbind_service_a.setOnClickListener {
|
||||
if(isBind){
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 unbindService");
|
||||
unbindService(conn)
|
||||
}
|
||||
}
|
||||
btn_a_start_b.setOnClickListener {
|
||||
val intent = Intent(this,BActivity::class.java)
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 启动 ActivityB");
|
||||
startActivity(intent)
|
||||
}
|
||||
btn_finish_a.setOnClickListener {
|
||||
Log.i("xiao", "----------------------------------------")
|
||||
Log.i("xiao", "ActivityA 执行 finish");
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.i("xiao", "ActivityA - onDestroy")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 參考
|
||||
1. [如何使用Service(kotlin)](https://givemepass.blogspot.com/2015/10/service.html)
|
||||
2. [Android kotlin service使用简析 - 簡書](https://www.jianshu.com/p/f9712b470b42)
|
||||
3. [《Android》『Service』- 背景執行服務的基本用法 - 賽肥膩膩の娛樂生活誌](https://xnfood.com.tw/android-service/#skill_02)
|
||||
17
02. PARA/02. Area(領域)/20150803 - Android/Tools.md
Normal file
17
02. PARA/02. Area(領域)/20150803 - Android/Tools.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# screenrecord
|
||||
## 螢幕錄影
|
||||
用`screenrecord `可以把目前的螢幕錄製下來,一個簡單的例子:
|
||||
```
|
||||
adb shell screenrecord --size 1200x1920 /storage/sdcard0/demo.mp4
|
||||
```
|
||||
|
||||
### 指定錄製時間
|
||||
```
|
||||
adb shell screenrecord --time-limit 10 /storage/sdcard0/demo.mp4
|
||||
```
|
||||
|
||||
### 指定bit rate
|
||||
```
|
||||
adb shell screenrecord --bit-rate 6000000 /storage/sdcard0/demo.mp4
|
||||
```
|
||||
沒有指定時間的話,就必須手動`Ctrl+c`來中止錄影。
|
||||
8
02. PARA/02. Area(領域)/20150803 - Android/UI.md
Normal file
8
02. PARA/02. Area(領域)/20150803 - Android/UI.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## 在thread更新UI
|
||||
Android framework只能在main thread裡更新UI,若需要在其他的thread更新UI的話,需要呼叫activity的`runOnUiThread()`。
|
||||
例:
|
||||
```kotlin
|
||||
activity?.runOnUiThread {
|
||||
activity?.findViewById<TextView>(R.id.textView)?.text = "You say: " + call.parameters["something"]
|
||||
}
|
||||
```
|
||||
912
02. PARA/02. Area(領域)/20200207 - Study RxKotlin.md
Normal file
912
02. PARA/02. Area(領域)/20200207 - Study RxKotlin.md
Normal file
@@ -0,0 +1,912 @@
|
||||
# 剛開始
|
||||
## 先說說Rx
|
||||
|
||||
Rx最早是Microsoft的某個實驗室為了解決asychronous、scalable還有一些app問題而提出來的libraray。大概在2009年的時候提出,叫做Reactive Extension for .NET(Rx). 一開始是以add-on的方式安裝在.NET 3.5上,到了.NET 4.0就變成了內建的library。也因為它open source的關係,讓其他語言得以將這套概念也移植過去,所以現在有RxJS, RxSwift, RxNET, RxScale, RxJava。這些library都致力於在它們的語言來實作出相同的「行為」,所以理論上iOS工程師可以和Web工程師用Rx來討論app的邏輯是沒有問題的。
|
||||
Rx的官網:[http://reactivex.io/](http://reactivex.io/),它的logo是一支電鰻(Electric eel):
|
||||
![[Rx_Logo_S.png]]
|
||||
|
||||
Rx Community
|
||||
- http://android-united.community/
|
||||
- https://kotlinlang.slack.com/
|
||||
|
||||
什麼是RxJava?
|
||||
> RxJava is a library for composing asynchronous and event-based code using observable sequences and functional style operations, allowing for parameterized execution via schedulers.
|
||||
|
||||
> RxJava, in its essence, simplifies developing asynchronous programs by allowing your code to react to new data and process it in a sequential, isolated manner. In other words, RxJava lets you observe sequence of asychronous events in an app and respond to each event accordingly. Examples are taps by a user on the screen and listening for results if asynchronous network calls.
|
||||
|
||||
## 再說RxJava
|
||||
RxJava是一個實作Rx的framework。
|
||||
RxJava與其他的Rx library提供了asynchronous與event-based的解決辦法
|
||||
|
||||
而Asychronous code跟Sychronous code的差異:
|
||||
Sychronous code按照字面上的意思執行,每一次的結果都相同。
|
||||
Asychronous code則是在必要的時候才被使用,每一次執行的「狀態」不盡相同。也就是沒辦法控制其順序與時間。
|
||||
### Asychronous programming的詞彙
|
||||
#### 1. State
|
||||
- State指的是我們程式所儲存的資料與程式自身行為互動所產生的狀態。
|
||||
-
|
||||
#### 2. Imperative programming
|
||||
- Imperative programming(指令式程式設計)是用一連串的命令或描述來改變程式的狀態。如下面的code:
|
||||
```
|
||||
setupUI()
|
||||
bindClickListeners()
|
||||
createAdapter()
|
||||
listenForChanges()
|
||||
```
|
||||
這些code可能有一些相關的邏輯,但是字面上看不出來,即使互相調換可能會造成錯誤,但也可能不會。
|
||||
|
||||
#### 3. Side effect
|
||||
- Side effect指的是「一段程式修改了它本身區域外的狀態」,譬如說,一個處理event的function它除了處理event之外,也改變的UI上所顯示的文字。
|
||||
- Side effect並不總是不好的,我們的程式就是要對某些東西做出改變,完全無法改變任何東西的程式是沒有用的。
|
||||
|
||||
RxJava試著用接下來的2個概念來解決剛剛提到的3個概念上的問題
|
||||
#### 4. Declarative code
|
||||
- 又叫Fucntional programming,Fucntional programming不產生任何side effect。
|
||||
- Declarative code定義的是行為。
|
||||
- RxJava試著在Declarative code和Imperative programming取一個平衡點,它定義行為,然後依順序執行。
|
||||
|
||||
#### 5. Reactive systems
|
||||
Reactive systems通常有幾個特性:
|
||||
- Reponseive:保持UI在最新狀態
|
||||
- Resilient:每個行為都是獨立定義的,而且有辦法靈活的處理錯誤。
|
||||
- Elastic:程式的十座可以處理不同的工作量
|
||||
- Message driven:每個元件使用Message driven(訊息驅動)的方式來互相溝通,並改進可用性與獨立性,解開(decouple)生命週期與實作的關聯。
|
||||
|
||||
### Rx的三大組成
|
||||
#### 1. Observables
|
||||
`Observable<T>`是Rx的基礎之一,Observable允許觀察者觀察它,並接收它發出來的資料。
|
||||
##### Observables 的基礎:event
|
||||
Observable會以3種事件(event)來發出資料:
|
||||
1. **next**:**next** event會伴隨著一筆資料,這也是觀察者用來接收資料的event。
|
||||
2. **complete**:**complete** event表示Observable已經「成功的」結束了它的生命週期,在**complete** event之後,觀察者不會再收到任何**next** event。
|
||||
3. **error**:**error** event表示Observable在發生錯誤的情況下結束它的生命週期。跟**complete** event依樣,後續不會再有任何**next** event。
|
||||
|
||||
一個Observable用next所發出來的一連串資料我們稱為"sequence"。sequence可以分為兩種:
|
||||
1. Finite sequnece:
|
||||
想像你要下載一個檔案,我們的code大概是這個樣子:
|
||||
```kotlin
|
||||
API.download(file = "http://www...")
|
||||
.subscribeBy(
|
||||
onNext = {
|
||||
// Handle downloading here
|
||||
},
|
||||
onComplete = {
|
||||
// Handle download finish here
|
||||
},
|
||||
onError = {
|
||||
// Handle error here
|
||||
},
|
||||
)
|
||||
```
|
||||
`API.download()`會產生一個Obervable,然後我們藉由`subscribeBy`來訂閱他,並加入我們的處理程序,我們在`onNext`裡面處理接收到的檔案buffer,在`onComplete`裡面了解到檔案已經完成下載,可以做一些後續的處理,`onError`則是發生了某些錯誤,需要重來或是通知使用者之類。
|
||||
|
||||
2. Infinite sequence:
|
||||
Switch button就是一個例子,我們要處理switch button的code會是這樣:
|
||||
```kotlin
|
||||
switch.checkdChanges()
|
||||
.subscribeBy(
|
||||
onNext = { isOn ->
|
||||
if (isOn) {
|
||||
// Handle on here
|
||||
} else {
|
||||
// Handle off here
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
可以看到這一段`subscribeBy()`裡面並沒有`onComplete`跟`onError`,因為switch button根本就不會產生這兩種event。
|
||||
|
||||
#### 2. Operators
|
||||
Operators用來處理Observable所發出來的資料,可能是過濾或者做一些轉換,或其他操作。再以switch button做例子,下面的code可以把switch button的狀態做幾個改變:
|
||||
1. 我們只想收到on的狀態。
|
||||
2. 把on的狀態轉為一個字串"We've been toggled on!"。
|
||||
```kotlin
|
||||
switch.checkdChanges()
|
||||
.filter { it == true }
|
||||
.map { "We've been toggled on!" }
|
||||
.subscribeBy(
|
||||
onNext = { message ->
|
||||
updateTextView(message)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### 3. Schedulers
|
||||
Scheduler可以想像成thread,RxJava已經內建了好幾種scheduler,而且應該可以適用於大部分的情形。
|
||||
例如IO scheduler可以讓你的檔案下載在背景執行,`TeampolineScheduler`可以讓你的程式同時執行, `ComputationScheduler`可以讓你將程式分配給不同的thread來處理需要大量運算的資料。
|
||||
|
||||
RxJava是一個很獨立的library,所以有2個library可以跟RxJava一起合作:
|
||||
1. RxAndroid:提供Android Looper class跟RxJava的scheduler之間的橋接管道。
|
||||
2. RxBinding:用來把UI的click listen之類的callback轉變為Observable的`subscribeBy`。
|
||||
|
||||
# 安裝
|
||||
在`build.gradle`裡的`depedencies`區域加入:
|
||||
```
|
||||
implementation "io.reactivex.rxjava3:rxjava:3.0.2"
|
||||
implementation "io.reactivex.rxjava3:rxkotlin:3.0.0"
|
||||
implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
|
||||
```
|
||||
|
||||
# Observable
|
||||
Standard Observable has three types of event:
|
||||
1. next
|
||||
2. complete
|
||||
3. error
|
||||
|
||||
Obervable很適合用marble diagram來表示:
|
||||
![[Pasted image 20210120150947.png]]
|
||||
|
||||
3個event解釋如下:
|
||||
1. `onNext()`:`onNext()` event會伴隨著一筆資料,這也是觀察者用來接收資料的event。
|
||||
2. `onComplete()`:`onComplete()` event表示Observable已經「成功的」結束了它的生命週期,在`onComplete()` event之後,觀察者不會再收到任何`onNext()` event。
|
||||
3. `onError()`:`onError()` event表示Observable在發生錯誤的情況下結束它的生命週期。跟`onComplete()` event依樣,後續不會再有任何`onNext()` event。
|
||||
|
||||
另外,要注意:一個Observable在沒有被訂閱的情況下,「**是不會發送任何event的**」。
|
||||
|
||||
A example of usage of standard Observable:
|
||||
```kotlin
|
||||
API.download("http://...")
|
||||
.subscribeBy(
|
||||
onNext = { /* do something */ },
|
||||
onComplete = { /* do something */ },
|
||||
onError = { /* do something */ },
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## 建立`Observable`的方法
|
||||
### 1. `just`
|
||||
```kotlin
|
||||
val observable = Observable.just(1, 2, 3)
|
||||
```
|
||||
變數observable的內容設為1個"1、2、3"三個數,型別會是`Observable<Int!>!`。
|
||||
如果使用了onNext來發送event的話,將會依序發送1、2、3。
|
||||
但如果是:
|
||||
```kotlin
|
||||
val observable = Observable.just(listOf(1, 2, 3))
|
||||
```
|
||||
變數observable的內容會是一個list,這個list的內容是"1、2、3"。型別是`Observable<List<Int>!>!`。
|
||||
如果使用了onNext來發送event的話,將發送一個包含1、2、3的list。
|
||||
|
||||
### 2. `fromIterable`
|
||||
用來將list的內容轉變為一個一個單獨的element給Observable。
|
||||
```kotlin
|
||||
val observable = Observable.fromIterable(listOf(2, 3, 1))
|
||||
```
|
||||
變數observable的型別會是`Observable<Int!>!`,而不是`Observable<List<Int>!>!`。
|
||||
|
||||
### 3. `empty`
|
||||
建立一個「空的」Observable,可以用來表示一個馬上就會結束的事情,或是不包含任何東西的情況。
|
||||
```kotlin
|
||||
val observable = Observable.empty<Unit>()
|
||||
observable.subscribeBy(
|
||||
onNext = { println(it) },
|
||||
onComplete = { println("Completed") }
|
||||
)
|
||||
```
|
||||
用`empty()`所建立的observable只會發出`onComplete()` event,所以上面的`onNext()` event永遠不會發生。
|
||||
還有,Observable所包含的element一定要有一個型別,而且不可以是null,所以上面的`empty()`必須明白的寫出`Unit`型別:`empty<Unit>() `。
|
||||
|
||||
### 4. `never`
|
||||
建立一個不會發出任何event的observable。
|
||||
|
||||
### 5. `range`
|
||||
產生一個範圍的數列,參數型別必須是整數(`Int`)。
|
||||
```kotlin
|
||||
val observable = Observable.range(1, 10)
|
||||
```
|
||||
上例的`onNext()`會依序發送1~10的數字出來。
|
||||
|
||||
### 6. `create`
|
||||
用來定義自己的event發送方法。
|
||||
範例:
|
||||
```kotlin
|
||||
val observable = Observable.create<String> { emitter ->
|
||||
emitter.onNext("A")
|
||||
emitter.onNext("C")
|
||||
emitter.onNext("B")
|
||||
emitter.onComplete()
|
||||
}
|
||||
val subscription = observable.subscribeBy(
|
||||
onNext = { println("Received: $it") },
|
||||
onComplete = { println("Completed") },
|
||||
onError = { println("Completed") }
|
||||
)
|
||||
```
|
||||
`create`必須帶入要發送的型別,例如`Int`、`String`或是任何class,此例中是`create<String>`,表示會送出的element是`String`型別。
|
||||
然後`create`則是發送的實作,範例是會發送"A" -> "C" -> "B",然後用`onComplete`來結束。
|
||||
注意:要是observable沒有`onComplete`或是`onError`,然後`Disposable`(也就是訂閱者)也沒有呼叫`dispose()`,則會造成memory leak。
|
||||
|
||||
### 7. `defer`
|
||||
`defer`會建立一個Observable factory,每一次呼叫這個factory都會產生一個新的Observable。`defer`只有一個參數,就是我們要「製造」Observable的方法:
|
||||
```kotlin
|
||||
var flip = false
|
||||
|
||||
val factory: Observable<Int> = Observable.defer {
|
||||
flip = !flip
|
||||
if (flip) {
|
||||
Observable.just(1, 2, 3)
|
||||
} else {
|
||||
Observable.just(4, 5, 6)
|
||||
}
|
||||
}
|
||||
```
|
||||
`defer`後面的lambda就是我們要「製造」Observable的方法。當`flip`是`true`的時候,我們產生`Observable.just(1, 2, 3)`,反之則產生`Observable.just(4, 5, 6)`。Observable裡面所帶的element都是整數,這也是為什麼factory的型別是`Observable<Int>`。
|
||||
接下來訂閱這個factory:
|
||||
```kotlin
|
||||
for (i in 0..3) {
|
||||
val subscription = factory.subscribe {
|
||||
println("Factory out: $it")
|
||||
}
|
||||
disposables.add(subscription)
|
||||
}
|
||||
disposables.dispose()
|
||||
```
|
||||
上面的例子產生了4個Observable。依照flip的值來產生不一樣內容的Observable。
|
||||
|
||||
How to subscrible a Observable
|
||||
## 訂閱`Observable`的方法
|
||||
1. `observable.subscrible()`
|
||||
2. `observable.subscribleBy()`
|
||||
|
||||
Remember to release the resource. Call `disposable()` if you don't need Observable anymore. Or use `CompositeDisposable()` to collect all Disposable and release them.
|
||||
|
||||
## 特殊的Observable
|
||||
### 1. `Single`
|
||||
`Single`只有`onSuccess`跟`onError`兩種event。在發出`onSuccess`或是`onError`之後,`Single`就結束了。
|
||||
譬如說讀取檔案,只會有讀取成功跟讀取失敗兩種情況,下面的範例讀取一個檔案,要是檔案不存在就發送`onError()`,反之就發送`onSuccess()`。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
fun loadText(filename: String): Single<String> {
|
||||
return Single.create create@{ emitter ->
|
||||
val file = File(filename)
|
||||
|
||||
if (!file.exists()) {
|
||||
emitter.onError(FileNotFoundException("Can't find $filename"))
|
||||
return@create
|
||||
}
|
||||
|
||||
val contents = file.readText(Charsets.UTF_8)
|
||||
emitter.onSuccess(contents)
|
||||
}
|
||||
}
|
||||
|
||||
// Use the single observable
|
||||
val subscription = loadText("Copyright.txt")
|
||||
.subscribeBy(
|
||||
onSuccess = { println("Success read: $it") },
|
||||
onError = { println("Error: $it") }
|
||||
)
|
||||
subscriptions.add(subscription)
|
||||
```
|
||||
`loadText()`這個function會返回`Single<String>`物件,要是讀取檔案成功,就把檔案內容用`onSuccess()`發送出來:
|
||||
```kotlin
|
||||
val contents = file.readText(Charsets.UTF_8)
|
||||
emitter.onSuccess(contents)
|
||||
```
|
||||
要是檔案不存在,就發出`onError()`:
|
||||
```kotlin
|
||||
emitter.onError(FileNotFoundException("Can't find $filename"))
|
||||
```
|
||||
|
||||
### 2. `Completable`
|
||||
`Completable`只有`onCompleted`跟`onError`兩種event。跟`Single`一樣,在發出`onCompleted`或是`onError`之後,`Completable`就結束了。
|
||||
|
||||
### 3. `Maybe`
|
||||
`Maybe`是`Single`跟`Completable`的混合,他有`onSuccess(value)`、`onCompleted`跟`onError`三種event。`Maybe`只會發出這三種的其中一種event,然後就結束了。
|
||||
|
||||
## 停止訂閱或是結束一個`Observable`
|
||||
### 使用`Disposable.dispose()`
|
||||
每一次呼叫`observable.subscrible()`或是`observable.subscribleBy()`都會回傳一個`Disposable`物件,當我們不再需要訂閱一個Observable的時候,我們必須呼叫`dispose()`停止訂閱:
|
||||
```kotlin
|
||||
val alphaSequnce = Observable.just("A", "B", "C")
|
||||
val subscription = alphaSequece.subscribe {
|
||||
println(it)
|
||||
}
|
||||
|
||||
subscription.dispose()
|
||||
```
|
||||
|
||||
### 使用`CompositeDisposable.dispose()`
|
||||
對每一個`Disposable`物件在停止訂閱之後都要呼叫一次`dispose()`是很煩人的,RxJava提供了一個`CompositeDisposable` class。它可以收納所有的`Disposable`物件,然後一次停止:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val subscriptionNumbers = Observable.just(1, 2, 3).subscribe {
|
||||
println("Numbers: $it")
|
||||
}
|
||||
val subscriptionAlphabets = Observable.just("A", "B", "C").subscribe {
|
||||
println("Alphabets: $it")
|
||||
}
|
||||
|
||||
subscriptions.add(subscriptionNumbers)
|
||||
subscriptions.add(subscriptionAlphabets)
|
||||
subscriptions.dispose() <-- subscriptionNumbers 與 subscriptionAlphabets 都會一起呼叫dispose()
|
||||
```
|
||||
忘記呼叫`dispose()`可能會造成memory leak。
|
||||
|
||||
# Subjects
|
||||
Observable必須在建立的時候就指定好資料,之後沒辦法再新增資料。而Subject可以在建立資料之後,再新增資料,Subject也會將新增的資料再馬上轉發給它的訂閱者。
|
||||
## 1. `PublishSubject`
|
||||
`PublishSubject`剛開始是沒有任何資料的,它也只會將最新的資料發送給它的訂閱者。另外,要是`PublishSubject`本身結束了(已經送出了`onComplete` event),那麼新的訂閱者將不會收到任何資料,但是會收到`onComplete` event。
|
||||
```kotlin
|
||||
val publishSubject = PublishSubject.create<Int>()
|
||||
|
||||
publishSubject.onNext(0)
|
||||
|
||||
val subscriptionOne = publishSubject.subscribe {
|
||||
println(it)
|
||||
}
|
||||
|
||||
publishSubject.onNext(1)
|
||||
publishSubject.onNext(2)
|
||||
|
||||
val subscriptionTwo = publishSubject.subscribe {
|
||||
println("2: $it")
|
||||
}
|
||||
|
||||
publishSubject.onNext(3)
|
||||
subscriptionOne.dispose()
|
||||
publishSubject.onNext(4)
|
||||
publishSubject.onComplete()
|
||||
publishSubject.onNext(5)
|
||||
subscriptionTwo.dispose()
|
||||
|
||||
val subscriptionThree = publishSubject.subscribeBy(
|
||||
onNext = { println("3: $it") },
|
||||
onComplete = { println("3: Completed") }
|
||||
)
|
||||
```
|
||||
上例中的`subscriptionThree`只會收到`onComplete` event,也就是只會印出`"3: Completed"`。
|
||||
|
||||
## 2. `BehaviorSubject`
|
||||
行為跟`PublishSubject`類似,但是`BehaviorSubject`會發送「最後一筆資料」給新的訂閱者。如果`BehaviorSubject`最後的event是`onError`,那麼新的訂閱者也會收到`onError` event。例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val behaviorSubject = BehaviorSubject.createDefault("Initial value")
|
||||
|
||||
behaviorSubject.onNext("X")
|
||||
|
||||
val subscriptionOne = behaviorSubject.subscribeBy(
|
||||
onNext = { println("1: $it") },
|
||||
onError = { println("1: ERROR, $it") }
|
||||
)
|
||||
|
||||
behaviorSubject.onError(RuntimeException("Error!"))
|
||||
behaviorSubject.onNext("Y") // <-- 不會有人收到,因為已經被onError給terminate了
|
||||
|
||||
subscriptions.add(behaviorSubject.subscribeBy(
|
||||
onNext = { println("2: $it") },
|
||||
onError = { println("2: $it") }
|
||||
))
|
||||
```
|
||||
另外,可以直接取得`BehaviorSubject`目前的值,以上例來說,只要用`behaviorSubject.value`就可以,這方法可以很方便的在Rx與非Rx的程式中交換資料。
|
||||
例子中是用static method `BehaviorSubject.createDefault()`來建立一個有初始值的`BehaviorSubject`,當然也可以跟`PublishSubject`一樣,用`BehaviorSubject.create()`來建立。
|
||||
|
||||
## 3. `ReplaySubject`:
|
||||
`BehaviorSubject`會發送會後一筆資料,`ReplaySubject`就是發送最後n筆資料。我們可以用`ReplaySubject.createWithSize()`這個static method來建立一個`ReplaySubject`。例:
|
||||
```kotlin
|
||||
val replaySubject = ReplaySubject.createWithSize<String>(2)
|
||||
```
|
||||
變數`replaySubject`的buffer容量是2,型別是`String`。
|
||||
|
||||
## 4. `AsyncSubject`
|
||||
`AsyncSubject`的行為比較特別,`AsyncSubject`只會結束的時候,同時發出最後一筆資料。也就是說,即便一直提供資料給`AsyncSubject`,它也不會發出任何`onNext` event給它的訂閱者,直到它收到`onComplete`的時候,它才會同時發出最後一筆`onNext`與`onComplete`給它的訂閱者。
|
||||
|
||||
## 5. `RxRelay`
|
||||
`RxRelay`永遠不會發出`onComplete`或是`onError`。下面例子建立了一個`PublishRelay`:
|
||||
```
|
||||
val publishRelay = PublishRelay.create<Int>()
|
||||
```
|
||||
|
||||
要使用`RxRelay` library,必須在build.gradle裡面加入:
|
||||
```
|
||||
implementation "com.jakewharton.rxrelay3:rxrelay:3.0.0"
|
||||
```
|
||||
|
||||
# Operators
|
||||
## 1. Filtering Operators
|
||||
### `ignoreElement`
|
||||
`ignoreElement()`會忽略掉由[[20200207 - Study RxKotlin#Subjects]]丟出來的**next** event,訂閱者只會收到`onCompleted`跟`onError`這兩種event,也就是讓Subject退化成[[20200207 - Study RxKotlin#2 Completable]]。
|
||||
例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val strikes = PublishSubject.create<String>()
|
||||
|
||||
subscriptions.add(
|
||||
strikes.ignoreElements()
|
||||
.subscribeBy {
|
||||
println("Done") <-- 只會收到onComplete跟onError
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### `elementAt`
|
||||
`elementAt()`只會處理「第n個」**next** event,n之前跟n之後的都會被忽略。例如:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val strikes = PublishSubject.create<String>()
|
||||
|
||||
subscriptions.add(
|
||||
strikes.elementAt(2) <-- 只要收第2個
|
||||
.subscribeBy {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
|
||||
strikes.onNext("A") <-- 第0個
|
||||
strikes.onNext("B") <-- 第1個
|
||||
strikes.onNext("C") <-- 第2個
|
||||
strikes.onNext("D") <-- 第3個
|
||||
```
|
||||
上面例子只要收「第2個next event」,所以只會收到"**C**"。這也是一個`onSuccess` event。
|
||||
`elementAt()`也等於是把Subject退化成[[20200207 - Study RxKotlin#3 Maybe]]。
|
||||
要是Subject的「第n個」還沒收到就結束了,那就是收到`onComplete` event。
|
||||
|
||||
### `filter`
|
||||
`filter()`接收一個lambda函數,每一個next event所帶的element都必須經過這個函數的「驗證」,只有驗證結果為`true`的時候,才會pass給訂閱者。例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.fromIterable(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||
.filter {
|
||||
it > 5 <-- 數值必須大於5才可以pass給訂閱者
|
||||
}
|
||||
.subscribe {
|
||||
print("Get number: $it\n")
|
||||
}
|
||||
)
|
||||
```
|
||||
所以上面的結果會收到6、7、8、9、10。
|
||||
|
||||
### `skip`
|
||||
忽略「前n個」next event。
|
||||
```kotlin
|
||||
Observer.just(1, 2, 3, 4 ,5)
|
||||
.skip(3) <-- 忽略前3個
|
||||
.subscribe {
|
||||
println("it")
|
||||
}
|
||||
// Output 4, 5
|
||||
```
|
||||
|
||||
### `skipWhile`
|
||||
`skipWhile`也是用一個lambda來當作通過條件,跟[[20200207 - Study RxKotlin#filter]]類似。但不像`filter`會去檢查「每一個」進來的element,`skipWhile`是「當lambda檢查到第一個`false`的時候,後面全部通過」。
|
||||
另一個跟`filter`不同的是,`skipWhile`是在檢查為`true`把next event忽略掉,檢查到`false`的時候開始放行。
|
||||
例如,我們要收集字串,但我們要當字串是"start"的時候才開始收集字串,例:
|
||||
```kotlin
|
||||
val subscriptions \= CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just("1", "2", "3", "Start", "1", "3", "2")
|
||||
.skipWhile { it != "Start" } <-- 比對為false開始放行
|
||||
.subscribe {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
|
||||
// Output
|
||||
Get Start
|
||||
Get 1
|
||||
Get 3
|
||||
Get 2
|
||||
```
|
||||
|
||||
### `skipUntil`
|
||||
與前面的skip operator不同,`skipUntil`不是用lambda來決定skip的條件,而是依賴於「另一個subject」,`skipUntil`會一直忽略,直到「另一個subject」發出`onNext` event。例:
|
||||
```kotlin
|
||||
val subject = PublishSubject.create<String>()
|
||||
val trigger = PublishSubject.create<String>()
|
||||
|
||||
subject
|
||||
.skipUntil(trigger)
|
||||
.subscribe {
|
||||
println("it")
|
||||
}
|
||||
```
|
||||
In this code you'll get nothing, until `trigger` sent an `onNext()` event.
|
||||
Example:
|
||||
```kotlin
|
||||
subject.onNext("A") // Ignored
|
||||
subject.onNext("B") // Ignored
|
||||
trigger.onNext("1") // TRIGGER!
|
||||
subject.onNext("C") // send out
|
||||
```
|
||||
![[Pasted image 20210202155003.png]]
|
||||
|
||||
### `take`
|
||||
`take`跟[[20200207 - Study RxKotlin#skip]]相反,`take`是接收「前n個」訊息,之後全部忽略。
|
||||
|
||||
### `takeWhile`
|
||||
`takeWhile`是用lambda當判斷條件,當判斷為`true`的時候放行,一旦判斷為`false`,之後的所有訊息都會被忽略。
|
||||
```kotlin
|
||||
exampleOf("takeWhile") {
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.fromIterable(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1))
|
||||
.takeWhile {
|
||||
it < 5
|
||||
}
|
||||
.subscribe {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
上例中,判斷到5的時候,`it < 5`為`false`,所以之後的都會忽略,即使最後那個1也是一樣被忽略。
|
||||
`takeWhile`跟[[20200207 - Study RxKotlin#skipWhile]]一樣,但是行為相反。
|
||||
|
||||
### `takeUntil`
|
||||
跟[[20200207 - Study RxKotlin#skipUntil]]相反的行為,會一直接收訊息,直到他依賴的subject發出訊息後停止。
|
||||
|
||||
### `distinctUntilChanged`
|
||||
`distinctUntilChanged`用來過濾「連續且相同」的訊息,例如連續的"Hi",那麼就只會收到第1個"Hi",之後的都不會收到。但是一旦收到的訊息改變了,再次收到以前發過的訊息,只要它沒有跟前一筆相同,那麼就還是會接收。例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just(5, 5, 3, 3, 1)
|
||||
.distinctUntilChanged()
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
|
||||
// Output
|
||||
5
|
||||
3
|
||||
1
|
||||
```
|
||||
|
||||
`distinctUntilChanged`預設用class的`equal()` method來判斷,當然我們也可以給它一個lambda來當客製我們的條件,被lambda判斷為`true`的話,該訊息就會被忽略:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just("ABC", "CCD", "FAG")
|
||||
.distinctUntilChanged { first, second ->
|
||||
first[second.length - 1] == second[0]
|
||||
}
|
||||
.subscribe {
|
||||
println("Get $it")
|
||||
}
|
||||
)
|
||||
```
|
||||
上例中,我們希望「第2個字串的開頭字母要是跟第1個字串的結尾字母一樣的話,那麼就不要顯示」。第1筆"ABC"一定會接收,第2筆"CCD"則會被忽略,第3筆"FAG"會被接收。
|
||||
|
||||
## 2. Transforming Operators
|
||||
### 1. toList
|
||||
`toList`可以把每一個單獨從Observalbe發出來的元素變成一個list,如:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val items = Observable.just("A", "B", "C")
|
||||
|
||||
subscriptions.add(
|
||||
items.toList()
|
||||
.subscribeBy {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
```
|
||||
原本會單獨發出的"A", "B", "C",現在變成只翠發出一個List,其內容是`["A", "B", "C"]`。
|
||||
|
||||
### 2. map
|
||||
`map`跟Kotlin的`map`行為上差不多,只是Kotlin的`map`是作用在List上,而RxJava的`map`是作用在Observable上。`map`根據你提供的lambda函式來對每一個element做轉換,如下例,將每一個羅馬數字轉換為阿拉伯數字:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
|
||||
subscriptions.add(
|
||||
Observable.just("M", "C", "V", "I")
|
||||
.map {
|
||||
it.romanNumeralIntValue()
|
||||
}
|
||||
.subscribeBy {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
```
|
||||
注意到了嗎?`map`用來轉換Observable所包含的item型別,上例中,Observable的item本來是一個字串(`String`),被`map`轉換為數字(`Int`)。
|
||||
|
||||
### 3. flatMap
|
||||
`flatMap`用來處理「Observable發出來的Observable」,並且「記錄每一個變化」。用例子比較好說明:
|
||||
```kotlin
|
||||
class Student(val score: BehaviorSubject<Int>)
|
||||
|
||||
val subscriptions = CompositeDisposable()
|
||||
val ryan = Student(BehaviorSubject.createDefault(80))
|
||||
val charlotte = Student(BehaviorSubject.createDefault(90))
|
||||
val student = PublishSubject.create<Student>()
|
||||
|
||||
student
|
||||
.flatMap {
|
||||
it.score
|
||||
}
|
||||
.subscribeBy {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
|
||||
student.onNext(ryan) <-- 1
|
||||
ryan.score.onNext(85) <-- 2
|
||||
|
||||
student.onNext(charlotte) <-- 3
|
||||
ryan.score.onNext(95) <-- 4
|
||||
charlotte.score.onNext(100) <-- 5
|
||||
```
|
||||
|
||||
我們有一個叫做student的`PublishSubject`,另外有兩個Student class(分別是ryan與charlotte),而這個Student class有一個member叫做score,score的類別是`BehaviorSubject<Int>`。我們的`flatMap` lambda不做任何轉換,直接bypass分數。
|
||||
1. student先選擇ryan來發出第一個onNext event,ryan原本的分數是80,所以我們會收到80。
|
||||
2. ryan變更為85,所以我們會收到85。
|
||||
3. student選擇了charlotte並發出一個onNext event,charlotte原本的分數是90,所以我們會收到90。
|
||||
4. ryan變更為95,所以我們會收到95。
|
||||
5. charlotte變更為100,所以我們會收到100。
|
||||
結果會收到80、85、90、95、100。
|
||||
|
||||
ryan跟charlotte都是獨立的Observable,但透過`flatMap`我們可以把它們的值(以及後續的變化)變成一連串的數值,這就是`flat`的意思。
|
||||
![[rxJava_flatMap.png]]
|
||||
|
||||
### 4. switchMap
|
||||
`switchMap`跟`flatMap`類似,也是處理「Observable發出來的Observable」,但是差別在於`switchMap`一但切換到新的Observable之後,上一個Observale的訊息就部會收到了,以`flatMap`的例子來說,在`student.onNext(charlotte)`這一行之後,ryan的改變就不會收到了。例:
|
||||
```kotlin
|
||||
val ryan = Student(BehaviorSubject.createDefault(80))
|
||||
val charlotte = Student(BehaviorSubject.createDefault(90))
|
||||
val student = PublishSubject.create<Student>()
|
||||
|
||||
student
|
||||
.switchMap {
|
||||
it.score
|
||||
}
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
|
||||
student.onNext(ryan)
|
||||
ryan.score.onNext(85)
|
||||
|
||||
student.onNext(charlotte)
|
||||
ryan.score.onNext(95)
|
||||
charlotte.score.onNext(100)
|
||||
```
|
||||
結果會收到80、85、95、100。`ryan.score.onNext(95)`這一行的95不會收到。
|
||||
|
||||
`switchMap`適合用在會「改變興趣」的場景,例如說原本是要持續收到台北氣溫的改變,接著使用者把地點改到高雄,那我們就會變成持續收到高雄的溫度變化而不是台北的,又或者說,你會隨著使用的的輸入持續的搜尋結果,例如使用者依序輸入k、o、t、l、i、n,每輸入一個字母我們就搜尋一次,但我們只關注最後一個字串搜尋,不在意之前的搜尋結果。
|
||||
|
||||
### 5. materialize
|
||||
`materialize`能將Observable的值包裝成一個`Notification`,回到[[20200207 - Study RxKotlin#4 switchMap]]的例子,如果任何一個學生發出了`onError`的訊息,那麼連`student`本身都會因為這個Exception而中斷,所以即使切到了charlotte,我們也收不到charlotte的訊息了。`materialize`可以將`onError`包裝成一個`Notification`,讓exception留在ryan本身而不會影響到上面的student。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val ryan = Student(BehaviorSubject.createDefault(80))
|
||||
val charlotte = Student(BehaviorSubject.createDefault(90))
|
||||
val student = BehaviorSubject.createDefault<Student>(ryan)
|
||||
|
||||
// 1
|
||||
val studentScore = student.switchMap { it.score.materialize() } <-- HERE!
|
||||
// 2
|
||||
subscriptions.add(
|
||||
studentScore
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
// 3
|
||||
ryan.score.onNext(85)
|
||||
ryan.score.onError(RuntimeException("Error!"))
|
||||
ryan.score.onNext(90)
|
||||
// 4
|
||||
student.onNext(charlotte)
|
||||
```
|
||||
![[rxkotlin_materialize.png]]
|
||||
|
||||
### 6. dematerialize
|
||||
`dematerialize`用來反解`materialize`所包裝的東西,例如上例中,會將`materialize`所包裝出來的`Observable<Notification<Int!>!>!`反解為`Observable<Int>!`,例:
|
||||
```kotlin
|
||||
subscriptions.add(
|
||||
studentScore
|
||||
.filter {
|
||||
if (it.error != null) {
|
||||
println("Got error: ${it.error}")
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
.dematerialize { it }
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
)
|
||||
```
|
||||
要注意的是如果發生Exception的話,直接`println`還是會產生Exception,所以需要用`filter`來把error給濾掉。
|
||||
![[rxkotlin_dematerialize.png]]
|
||||
|
||||
在Subject所發出的element仍然是Subject的時候,如果element發生error(Exception),會導致上層的Subject也跟著停止,`materialize`/`dematerialize`可以用來包裝element,讓element所發出的東西都變成`Notification`,這樣就部會影響上層的Subject了。
|
||||
|
||||
## 3. Combining Operators
|
||||
### startWith
|
||||
用來在Observable本身所帶的item前面再加上其他item。實際的有`startWithIterable()`與`startWithItem()`。
|
||||
例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val numbers = Observable.just(3, 4, 5)
|
||||
val startWith = numbers.startWithIterable(listOf(1, 2))
|
||||
|
||||
startWith.subscribe {
|
||||
println(it)
|
||||
}.addTo(subscriptions)
|
||||
```
|
||||
|
||||
### concat
|
||||
`concat`是一個static method,用來合併2個Observable。`concat`會先等第一個Observable結束,然後再等待第二個,之後把它們合併起來。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val first = Observable.just(1, 2, 3)
|
||||
val second = Observable.just(3, 4, 5)
|
||||
|
||||
Observable.concat(first, second)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
```
|
||||
|
||||
### concatWith
|
||||
跟[[20200207 - Study RxKotlin#concat]]一樣,但是`concatWith`是一個member function,而不是一個static method。`concatWith`一樣會先等自己結束,然後再等第二個Obervable(當參數的那一個)結束,之後再合併。
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val first = Observable.just(1, 2, 3)
|
||||
val second = Observable.just(3, 4, 5)
|
||||
|
||||
first.concatWith(second)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
```
|
||||
|
||||
> ## 注意
|
||||
> 要被合併的兩個Observable類型必須要一樣,不可以一個是`Obsrvable<String>`而另一個是`Observable<Int>`,compiler會報錯喔。
|
||||
|
||||
### concatMap
|
||||
`concatMap`接受一個lambda函示,並回傳另一個Observable序列,`concatMap`會保證Observable的順序。
|
||||
Given multiple Observable, and map each Observable to a lambda function. And make sure the sequence of given Observable list.
|
||||
|
||||
### merge
|
||||
`merge`是一個static function。
|
||||
`merge`會按照接收的順序把element合併起來,例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val odd = PublishSubject.create<Int\>()
|
||||
val even = PublishSubject.create<Int\>()
|
||||
|
||||
Observable.merge(odd, even)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}
|
||||
.addTo(subscriptions)
|
||||
|
||||
odd.onNext(1)
|
||||
even.onNext(2)
|
||||
odd.onNext(3)
|
||||
even.onNext(4)
|
||||
odd.onNext(5)
|
||||
even.onNext(6)
|
||||
```
|
||||
odd與even兩個交互發出elemet,merge依順序接收,而不是像[[20200207 - Study RxKotlin#concat]]是依照Observable的順序。
|
||||
`merge` complete的時機點定義如下:
|
||||
- 當來源的Observable與內部的Observable都complete的時候,merge本身也會發出complete。
|
||||
- 內部Observable結束的順序跟接收的順序沒有關係。(一律按照接收點)
|
||||
- 如果有任何Observable發生error,`merge`會轉發這個error,然後自己發生terminate。
|
||||
|
||||
Q:
|
||||
- What's the different between `flatMap()`? -> 很大的差別,`flatMap()` 有map的功能。
|
||||
|
||||
### mergeWith
|
||||
就像`concatWith()`與`concat()`的關係,`mergeWith()`跟`merge`也是一樣的關係。
|
||||
`mergeWith()`是一個member function,必須由某個Observable instance來呼叫。
|
||||
|
||||
### combineLatest
|
||||
combineLatest是`Observables`的static funtion(注意不是`Observable`)。
|
||||
combineLatest只會接收2個Observalbe的「最後一個」elements,然後交由你所提供的lambda來處置,例:
|
||||
```kotlin
|
||||
val subscriptions = CompositeDisposable()
|
||||
val left = PublishSubject.create<String>()
|
||||
val right = PublishSubject.create<String>()
|
||||
|
||||
Observables.combineLatest(left, right) { leftString, rightString ->
|
||||
"$leftString, $rightString"
|
||||
}.subscribe {
|
||||
println(it)
|
||||
}.addTo(subscriptions)
|
||||
|
||||
left.onNext("Hello")
|
||||
right.onNext("World")
|
||||
left.onNext("It's nice to")
|
||||
right.onNext("be here")
|
||||
left.onNext("Actually, it's super great to")
|
||||
```
|
||||
|
||||
重點:
|
||||
1. 在上例中是直接結合2個字串,但是其實可以是任何用途。
|
||||
2. 在實務中,`combineLatest`可以用來結合2個不同型別的Observable,然後再回傳另一個不同型別的Observable。`combineLatest`回傳的Observable型別由lambda決定。
|
||||
3. `combineLatest`必須在「每一個」Observable都發出element之後才會動作。如果不確定Observable是否會發出element,可以使用[[20200207 - Study RxKotlin#startWith]]來讓Observable有一個初始值,這樣可以避免`combineLatest`永遠不會發生的情況。
|
||||
4. 如果有某個Observable已經complete,`combineLatest`會保留它的最後一個element,然後繼續結合更新的element。
|
||||
5. 直到最後一個Observable complete,`combineLatest`才會complete。
|
||||
![[combineLatest.png]]
|
||||
|
||||
### zip
|
||||
- `zip` wait until each if the inner Ovservables emits a new value.
|
||||
|
||||
### Triggers
|
||||
#### withLastestFrom
|
||||
- `withLatestFrom` is useful in all situations where you want the current(latest) value emittted frim an Observable, but only when a particular trigger occurs.
|
||||
|
||||
#### sample
|
||||
- Just like `withLastFrom`. But each time the trygger Ivsercable emits a value, `sample` emits the latest value from the "other" Obervable, but only if it arrived since the last "tick". You can combine `withLastFrom` and `distinctUntilChanged` to do the same behavior of `sample`.
|
||||
```
|
||||
exampleOf("sample") {
|
||||
val subscriptions = CompositeDisposable()
|
||||
val button = PublishSubject.create<Unit>()
|
||||
val editText = PublishSubject.create<String>()
|
||||
|
||||
editText.sample(button)
|
||||
.subscribe {
|
||||
println(it)
|
||||
}.addTo(subscriptions)
|
||||
|
||||
editText.onNext("Par")
|
||||
editText.onNext("Pari")
|
||||
editText.onNext("Paris")
|
||||
button.onNext(Unit)
|
||||
button.onNext(Unit) <- button emits twice, but editText only emit last value
|
||||
}
|
||||
|
||||
// Output
|
||||
--- Example of: sample ---
|
||||
Paris
|
||||
```
|
||||
|
||||
### Switchs
|
||||
- ambWith
|
||||
- Think of `amb` as in ambiguous.
|
||||
- `ambWith` connect to two Observables. And wait any of them who emit element first. If any Observable emit element, another one will be unsubscribed.
|
||||
### reduce
|
||||
- `reduce` accumulates a summary value.
|
||||
|
||||
### scan
|
||||
- Like `reduce`, but emit per input value.
|
||||
|
||||
## 4. Time-Based Operators
|
||||
### Buffering
|
||||
#### replay
|
||||
- This operator creates a new sequence that records the last N elements emitted by the source Observable.
|
||||
|
||||
#### replayAll
|
||||
|
||||
#### window
|
||||
- Difference is that it emits an Observable of the buffered items, instead of emitting an array.
|
||||
|
||||
### Time-Shifting
|
||||
#### delaySubscription
|
||||
- Delay the time a subscriber starts receiving elements from its subscription.
|
||||
|
||||
#### delay
|
||||
- This operator subscribes immediateley to the source observable, but delays every emitted element by the specified amount of time.
|
||||
|
||||
### Timer
|
||||
#### Observable.interval
|
||||
- Produce an infinite Observable sequence of Int values.
|
||||
|
||||
#### Observable.timer
|
||||
- Specify a "due time" as the time that elapsed between the point of subscription and the first emitted value.
|
||||
- If the "repeat period" is not assigned, the timer Observable will emit once, the complete.
|
||||
|
||||
#### timeout
|
||||
- Emit an TimeoutException error event. If not caught, it terminates the sequence.
|
||||
|
||||
## 5. Explore Operators
|
||||
|
||||
|
||||
# 參考資料:
|
||||
- [RxMarbles: Interactive diagrams of Rx Observables](https://rxmarbles.com/#delayWhen)
|
||||
228
02. PARA/02. Area(領域)/20210604 - Windows media foundation.md
Normal file
228
02. PARA/02. Area(領域)/20210604 - Windows media foundation.md
Normal file
@@ -0,0 +1,228 @@
|
||||
- [媒體基礎程式設計指南 - Win32 apps | Microsoft Docs](https://docs.microsoft.com/zh-tw/windows/win32/medfound/media-foundation-programming-guide)
|
||||
|
||||
## 研究MFVideoEVRWebcam.cpp
|
||||
- IMFActivate: 代表實際的device,但是實際的device object是在`IMFActivate->ActivateObject()`才真的建立。
|
||||
- IID_PPV_ARGS:取得interface的IID value,並取得其pointer。詳細參考:[[20210726 - COM Interface#IID_PPV_ARGS Marco]]。
|
||||
-
|
||||
```
|
||||
CoInitializeEx
|
||||
MFStartup
|
||||
↳ ListVideoDevicesWithBriefFormat
|
||||
MFCreateAttributes(&pDeviceAttributes, 1)
|
||||
pDeviceAttributes->SetGUID()
|
||||
MFEnumDeviceSources(pDeviceAttributes, &ppDevices, &deviceCount)
|
||||
ppDevices[i]->GetAllocatedString
|
||||
ppDevices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &friendlyNameLength)
|
||||
ppDevices[i]->ActivateObject(IID_PPV_ARGS(&pMediaSource))
|
||||
MFCreateSourceReaderFromMediaSource(...)
|
||||
↳ ListModes
|
||||
pReader->GetNativeMediaType(0, dwMediaTypeIndex, &pType)
|
||||
↳ GetMediaTypeDescription
|
||||
pMediaType->GetMajorType(&MajorType)
|
||||
pMediaType->GetCount(&cAttrCount)
|
||||
MediaType->GetItemByIndex
|
||||
pMediaType->GetItemType
|
||||
↪ GetVideoTypeDescriptionBrief(...)
|
||||
pMediaType->GetGUID(MF_MT_SUBTYPE, &subType)
|
||||
MFGetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, &width, &height)
|
||||
MFGetAttributeRatio(pMediaType, MF_MT_FRAME_RATE, &fpsNum, &fpsDen)
|
||||
↳ MFCreateVideoRendererActivate(_hwnd, &pActive)
|
||||
pActive->ActivateObject(IID_IMFMediaSink, (void**)&pVideoSink)
|
||||
pVideoSink->QueryInterface(__uuidof(IMFVideoRenderer), (void**)&pVideoRenderer)
|
||||
pVideoRenderer->InitializeRenderer(NULL, NULL)
|
||||
pVideoSink->QueryInterface(__uuidof(IMFGetService), (void**)&pService)
|
||||
pService->GetService(MR_VIDEO_RENDER_SERVICE, __uuidof(IMFVideoDisplayControl), (void**)&pVideoDisplayControl)
|
||||
pVideoDisplayControl->SetVideoWindow(_hwnd)
|
||||
pVideoDisplayControl->SetVideoPosition(NULL, &rc)
|
||||
pVideoSink->GetStreamSinkByIndex(0, &pStreamSink)
|
||||
pStreamSink->GetMediaTypeHandler(&pSinkMediaTypeHandler)
|
||||
pSinkMediaTypeHandler->GetMediaTypeCount(&sinkMediaTypeCount)
|
||||
GetVideoSourceFromDevice(WEBCAM_DEVICE_INDEX, &pVideoSource, &pVideoReader)
|
||||
↳
|
||||
pVideoReader->SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE)
|
||||
pVideoSource->CreatePresentationDescriptor(&pSourcePresentationDescriptor)
|
||||
pSourcePresentationDescriptor->GetStreamDescriptorByIndex(0, &fSelected, &pSourceStreamDescriptor)
|
||||
pSourceStreamDescriptor->GetMediaTypeHandler(&pSourceMediaTypeHandler)
|
||||
pSourceMediaTypeHandler->GetMediaTypeCount(&srcMediaTypeCount)
|
||||
MFCreateMediaType(&pWebcamSourceType)
|
||||
FindMatchingVideoType(pSourceMediaTypeHandler, ...)
|
||||
↳
|
||||
pSourceMediaTypeHandler->IsMediaTypeSupported(pWebcamSourceType, &pWebCamMatchingType)
|
||||
pSourceMediaTypeHandler->SetCurrentMediaType(pWebCamMatchingType)
|
||||
pSourceMediaTypeHandler->SetCurrentMediaType(pWebcamSourceType)
|
||||
pSourceMediaTypeHandler->GetCurrentMediaType(&pVideoSourceOutputType)
|
||||
|
||||
```
|
||||
|
||||
|
||||
## 建立Video capture device
|
||||
### 1. 取得video capture的數量
|
||||
```cpp
|
||||
IMFAttributes* pDeviceAttributes = NULL;
|
||||
IMFActivate** ppDevices = NULL;
|
||||
UINT32 deviceCount = 0;
|
||||
|
||||
CHECK_HR(MFCreateAttributes(&pDeviceAttributes, 1),
|
||||
"Error creating device attributes.");
|
||||
|
||||
// Request video capture devices.
|
||||
CHECK_HR(pDeviceAttributes->SetGUID(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID),
|
||||
"Error initialising video configuration object.");
|
||||
|
||||
CHECK_HR(MFEnumDeviceSources(pDeviceAttributes, &ppDevices, &deviceCount),
|
||||
"Error enumerating devices.");
|
||||
```
|
||||
|
||||
### 2. 用Symbolic來建立IMFActivate
|
||||
`MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK`可以用來取得一個獨一無二的ID,相對的,也可以用來建立IMFActivate。
|
||||
```cpp
|
||||
IMFAttributes* pDeviceAttributes = NULL;
|
||||
IMFActivate* pDevice = NULL;
|
||||
UINT32 deviceCount = 0;
|
||||
bool result = false;
|
||||
|
||||
CHECK_HR(MFCreateAttributes(&pDeviceAttributes, 1),
|
||||
"Error creating device attributes.");
|
||||
|
||||
CHECK_HR(pDeviceAttributes->SetGUID(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID),
|
||||
"Error initialising video configuration object.");
|
||||
|
||||
CHECK_HR(pDeviceAttributes->SetString(
|
||||
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
|
||||
deviceSymbolic.c_str()),
|
||||
"Error setting video capture symbolic link");
|
||||
|
||||
CHECK_HR(MFCreateDeviceSourceActivate(pDeviceAttributes, &pDevice),
|
||||
"Error create activate device");
|
||||
```
|
||||
API:
|
||||
- [MFCreateDeviceSourceActivate function (mfidl.h)](https://docs.microsof.com/en-us/windows/win32/api/mfidl/nf-mfidl-mfcreatedevicesourceactivate)
|
||||
- [IMFAttributes::SetGUID (mfobjects.h)](https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfattributes-setguid)
|
||||
- [IMFAttributes::SetString (mfobjects.h)](https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfattributes-setstring)
|
||||
- [IMFAttributes::GetString (mfobjects.h)](https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfattributes-getstring)
|
||||
|
||||
## 建立Audio capture device
|
||||
相對於video capture是用`MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK`來建立IMFActivate,audio capture是用`MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID`來建立獨一無二的ID
|
||||
|
||||
## IAMCameraControl interface
|
||||
使用`IAMCameraControl`來控制webcam的行為,例如曝光與[[20210604 - Windows media foundation#Pan Tilt Relative Control]]。
|
||||
|
||||
### Pan/Tilt Relative Control
|
||||
1. 需要先用`IMFMediaSource->QueryInterface()`取得`IID_IAMCameraControl`。
|
||||
```cpp
|
||||
hret = mfMediaSource->QueryInterface(
|
||||
IID_IAMCameraControl,
|
||||
(void**)&pCameraControl
|
||||
);
|
||||
```
|
||||
2. 接下來就可以用`IAMCameraControl->GetRange()`、`Set()`、`Get()`來取得property,根據[CameraControlProperty](https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-cameracontrolproperty)可以看到可以用的property有:
|
||||
1. `CameraControl_Pan`
|
||||
2. `CameraControl_Tilt`
|
||||
3. `CameraControl_Roll`
|
||||
4. `CameraControl_Zoom`
|
||||
5. `CameraControl_Exposure`
|
||||
6. `CameraControl_Iris`
|
||||
7. `CameraControl_Focus`
|
||||
3. 但裡面並沒有關於PanRelative或是TiltRlative的控制,在google之後,在Stackoverflow上發現這篇:[Media Foundation Exposure in milliseconds](https://stackoverflow.com/questions/63124813/media-foundation-exposure-in-milliseconds),裡面有人提到可以用ksmedia的property:`KSPROPERTY_CAMERACONTROL_EXTENDED_EXPOSUREMODE`(要使用這個property必須include `<ks.h>`與`<ksmedia.h>`),你會發現這個property定義在`<ksmedia.h>`裡面的`KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY`這個enum裡面,然後順藤摸瓜的可以找到`KSPROPERTY_VIDCAP_CAMERACONTROL`這個enum,裡面就有定義`KSPROPERTY_CAMERACONTROL_PAN_RELATIVE`、`KSPROPERTY_CAMERACONTROL_TILT_RELATIVE`這些relative相關的東西了。
|
||||
4. 現在我們可以用`IAMCameraControl->GetRange()`來把`KSPROPERTY_CAMERACONTROL_PAN_RELATIVE`的值讀出來。
|
||||
```cpp
|
||||
hret = pCameraControl->GetRange(
|
||||
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
|
||||
&min,
|
||||
&max,
|
||||
&steppingDelta,
|
||||
&default,
|
||||
&capsFlags
|
||||
);
|
||||
printf("KSPROPERTY_CAMERACONTROL_PAN_RELATIVE hret = 0x%08X\n", hret);
|
||||
printf("min = %d\n", min); // get 1
|
||||
printf("max = %d\n", max); // get 1
|
||||
printf("steppingDelta = %d\n", steppingDelta); // get 1
|
||||
printf("default = %d\n", default); // get 1
|
||||
printf("capsFlags = %d\n", capsFlags); // get 0
|
||||
```
|
||||
但你會發現min、max的值都一樣。
|
||||
但是`IAMCameraControl->Set()`還是能用,用1跟-1來表示左轉與右轉。
|
||||
```cpp
|
||||
hret = pCameraControl->Set(
|
||||
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
|
||||
-1, // or 1
|
||||
flag
|
||||
);
|
||||
```
|
||||
5. `KSPROPERTY_CAMERACONTROL_TILT_RELATIVE`的控制方法跟Pan一樣,只是用1跟-1是表示上與下。
|
||||
6. Pan relative test code
|
||||
```cpp
|
||||
HRESULT hret;
|
||||
IAMCameraControl* pCameraControl = NULL;
|
||||
|
||||
hret = mfMediaSource->QueryInterface(
|
||||
IID_IAMCameraControl,
|
||||
(void**)&pCameraControl
|
||||
);
|
||||
printf("hret = 0x%08X\n", hret);
|
||||
|
||||
long min;
|
||||
long max;
|
||||
long steppingDelta;
|
||||
long default;
|
||||
long capsFlags;
|
||||
long value;
|
||||
KSPROPERTY_CAMERACONTROL_EXTENDED_EXPOSUREMODE;
|
||||
hret = pCameraControl->GetRange(
|
||||
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
|
||||
&min,
|
||||
&max,
|
||||
&steppingDelta,
|
||||
&default,
|
||||
&capsFlags
|
||||
);
|
||||
printf("KSPROPERTY_CAMERACONTROL_PAN_RELATIVE hret = 0x%08X\n", hret);
|
||||
printf("min = %d\n", min);
|
||||
printf("max = %d\n", max);
|
||||
printf("steppingDelta = %d\n", steppingDelta);
|
||||
printf("default = %d\n", default);
|
||||
printf("capsFlags = %d\n", capsFlags);
|
||||
|
||||
hret = pCameraControl->Get(
|
||||
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
|
||||
&value,
|
||||
&capsFlags
|
||||
);
|
||||
printf("CameraControl_Pan hret = 0x%08X\n", hret);
|
||||
printf("value = %d\n", value);
|
||||
printf("capsFlags = 0x%08X\n", capsFlags);
|
||||
|
||||
long flag = CameraControl_Flags_Manual;
|
||||
hret = pCameraControl->Set(
|
||||
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
|
||||
-1,
|
||||
flag
|
||||
);
|
||||
printf("pCameraControl->Set(KSPROPERTY_CAMERACONTROL_PAN_RELATIVE, -1) hret = 0x%08X\n", hret);
|
||||
Sleep(500);
|
||||
hret = pCameraControl->Set(
|
||||
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
|
||||
1,
|
||||
flag
|
||||
);
|
||||
printf("pCameraControl->Set(KSPROPERTY_CAMERACONTROL_PAN_RELATIVE, 1) hret = 0x%08X\n", hret);
|
||||
Sleep(500);
|
||||
```
|
||||
|
||||
## IAMVideoProcAmp interface
|
||||
使用`IAMVideoProcAmp`來控制webcam的影像相關部份。可以從[VideoProcAmpProperty enumeration](https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-videoprocampproperty)這個頁面看到詳細定義。節錄如下:
|
||||
- `VideoProcAmp_Brightness`
|
||||
- `VideoProcAmp_Contrast`
|
||||
- `VideoProcAmp_Hue`
|
||||
- `VideoProcAmp_Saturation`
|
||||
- `VideoProcAmp_Sharpness`
|
||||
- `VideoProcAmp_Gamma`
|
||||
- `VideoProcAmp_ColorEnable`
|
||||
- `VideoProcAmp_WhiteBalance`
|
||||
- `VideoProcAmp_BacklightCompensation`
|
||||
- `VideoProcAmp_Gain`
|
||||
52
02. PARA/02. Area(領域)/20210726 - COM Interface.md
Normal file
52
02. PARA/02. Area(領域)/20210726 - COM Interface.md
Normal file
@@ -0,0 +1,52 @@
|
||||
## IID_PPV_ARGS Marco
|
||||
我们已经知道 CoCreateInstance 和 QueryInterface 函数的最后一个参数都是 void** 类型,这就会带来潜在的类型不匹配的安全问题,考虑下面的代码段:
|
||||
```cpp
|
||||
// Wrong!
|
||||
|
||||
IFileOpenDialog *pFileOpen;
|
||||
|
||||
hr = CoCreateInstance(
|
||||
__uuidof(FileOpenDialog),
|
||||
NULL,
|
||||
CLSCTX_ALL,
|
||||
__uuidof(IFileDialogCustomize), // The IID does not match the pointer type!
|
||||
reinterpret_cast<void**>(&pFileOpen) // Coerce to void**.
|
||||
);
|
||||
```
|
||||
|
||||
在这段代码中,想要获取的接口类型是 IFileDialogCustomize,但是传入的却是一个 IFileOpenDialog 类型的指针变量。reinterpret_cast 表达式会绕过 C++ 类型系统的检测,所以编译器并不会主动抛出错误。
|
||||
|
||||
如果这段代码被运行,好的结果是函数返回失败,无法找到该接口;而坏的结果是,函数会返回成功,而你将获得一个类型结果不匹配的指针。换句话说,这个指针类型没有匹配到内存中真实的虚函数表,如果你所想不会有啥好事发生。
|
||||
|
||||
> 一个虚函数表(virtual method table)是一个函数指针表,它被用来实现 COM 组件运行期间的动态绑定。这种方式也是大多数 C++ 编译器用来实现动态绑定(多态)的方法。
|
||||
|
||||
IID_PPV_ARGS 宏是一个帮助你避免类型错误的方法,使用方法如下:
|
||||
|
||||
```cpp
|
||||
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
|
||||
```
|
||||
|
||||
替换为
|
||||
|
||||
```cpp
|
||||
IID_PPV_ARGS(&pFileOpen)
|
||||
```
|
||||
|
||||
这个宏会自动将 `__uuidof(IFileOpenDialog)` 参数插入。它可以保证你返回的接口指针类型的正确性。这是修改后的代码:
|
||||
|
||||
```cpp
|
||||
// Right.
|
||||
IFileOpenDialog *pFileOpen;
|
||||
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
|
||||
IID_PPV_ARGS(&pFileOpen));
|
||||
```
|
||||
|
||||
你可以在 QueryInterface 函数中使用一样的宏:
|
||||
|
||||
```cpp
|
||||
IFileDialogCustomize *pCustom;
|
||||
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
|
||||
```
|
||||
|
||||
### 參考資料
|
||||
- [COM 编码实践 - 没事造轮子](https://wangzhechao.com/com-bian-ma-shi-jian/)
|
||||
31
02. PARA/03. Resources(資源)/00. Informations/Datas.md
Normal file
31
02. PARA/03. Resources(資源)/00. Informations/Datas.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Family
|
||||
## Wife
|
||||
- 帳戶
|
||||
- 中國信託新竹分行
|
||||
- SWIFTCODE:CTCBTWTP299
|
||||
- 台幣帳戶:299511057829
|
||||
- 外幣帳戶:0000554131046751
|
||||
|
||||
## Ray
|
||||
- ID:O100875958
|
||||
|
||||
## Denny
|
||||
- ID:O100968172
|
||||
|
||||
## Me
|
||||
- Logitech
|
||||
- Empolyee ID:239344
|
||||
- ZOOM Direct number:03-6122437
|
||||
- ZOOM Extension No.:862437
|
||||
- [Qualcomm ID](https://myaccount.qualcomm.com/login)
|
||||
- Account: [jteng2@logitech.com](mailto:jteng2@logitech.com)
|
||||
- Password: Vn571jim@
|
||||
- Software tool
|
||||
- Office Professional Plus 2019 Key: `X88NP-YKCMM-DKVWJ-QYPWF-7H8J6`
|
||||
- VS2013 key: `YDJWM-3WVQ7-JXMKW-DTHV3-2KXPG`
|
||||
- Crypto coin
|
||||
- ERC20: `0x9Ce80345355Ad8C17991620E13d8423900CEDcd0`
|
||||
- ERC20: `0x9Ce80345355Ad8C17991620E13d8423900CEDcd0`
|
||||
- 國泰證券
|
||||
- 銀行交割帳號:`(013)699510287987`
|
||||
- 證券交易帳號:`8888-3839353`
|
||||
1
02. PARA/03. Resources(資源)/00. Informations/Logitech.md
Normal file
1
02. PARA/03. Resources(資源)/00. Informations/Logitech.md
Normal file
@@ -0,0 +1 @@
|
||||
- [產品spec](https://docs.google.com/spreadsheets/d/1iie7TIg7EFnC16Vic9Q7_iVELgtV2AphOkNuQ236M78/edit#gid=1851180669)
|
||||
34
02. PARA/03. Resources(資源)/00. Informations/核芯達.md
Normal file
34
02. PARA/03. Resources(資源)/00. Informations/核芯達.md
Normal file
@@ -0,0 +1,34 @@
|
||||
北京核芯達科技有限公司
|
||||
註冊資金5000萬元
|
||||
CEO李慎威
|
||||
李慎威在集成电路领域具有近27年资深行业经验,曾先后担任PALM、联发科(MediaTek)智能手机产品部副总经理、高通(Qualcomm)中国区芯片产品线研发负责人、紫光展锐(展讯通信)副总裁、Imagination高级副总裁等职务。
|
||||
|
||||
由半導體產業資深老兵李慎威與北汽產投、Imagination、翠微股份聯合發起
|
||||
特別是在定位技術上,核芯達偏愛SLAM技術。
|
||||
核芯達開發的基於智慧駕艙和麵向L2-L4多級別環境感知方案,預計將分別於2021和2022年實現成功流片
|
||||
![[Pasted image 20210728112504.png]]
|
||||
|
||||
北汽產投
|
||||
|
||||
|
||||
SLAM——自動駕駛導航裡的熱門技術
|
||||
|
||||
SLAM 是同步定位與地圖構建 (Simultaneous Localization And Mapping) 的縮寫,最早是由 Hugh Durrant-Whyte 和 John J.Leonard 在1988年提出的。SLAM與其說是一個演算法不如說它是一個概念更為貼切,它被定義為解決“機器人從未知環境的未知地點出發,在運動過程中通過重複觀測到的地圖特徵(比如,牆角,柱子等)定位自身位置和姿態,再根據自身位置增量式的構建地圖,從而達到同時定位和地圖構建的目”的問題方法的統稱。
|
||||
|
||||
SLAM技術的核心步驟,大體上而言,SLAM包含了:感知、定位、建圖這三個過程。
|
||||
|
||||
感知——機器人能夠通過感測器獲取周圍的環境資訊。
|
||||
|
||||
定位——通過感測器獲取的當前和歷史資訊,推測出自身的位置和姿態。
|
||||
|
||||
建圖——根據自身的位姿以及感測器獲取的資訊,描繪出自身所處環境的樣貌。
|
||||
|
||||
雙目立體視覺,強光下變現更優異
|
||||
|
||||
![[Pasted image 20210728112028.png]]
|
||||
|
||||
TOF:簡單來講,通過光的飛行時間來計算距離。
|
||||
|
||||
結構光:通過紅外鐳射器,將具有一定結構特徵的光線投射到被拍攝物體上,再由專門的紅外攝像頭進行採集反射的結構光圖案,根據三角測量原理進行深度資訊的計算。
|
||||
|
||||
雙目立體視覺:原理類似於萊卡相機,對近距離和遠距離有一些視差的補償。核芯達對這個技術很是看好,原因就在於不容易受到強光干擾,成本較低也沒有什麼專利困擾。李慎威解釋,人對亮到全黑環境會有一個適應過程,人在這方面的過程非常慢,如果從暗到亮,這個適應是比較快的。對於TOF和結構光則會有一些短板,近期特斯拉與一輛側躺的白色卡車相撞事件就是一個很好的例子。機器視覺並沒有計算出來前方有物體存在。
|
||||
@@ -0,0 +1,190 @@
|
||||
## docker-compose.yml
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
nginx_reverseproxy_l4:
|
||||
container_name: nginx
|
||||
restart: always
|
||||
image: nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./data:/etc/nginx
|
||||
```
|
||||
|
||||
## 準備
|
||||
目錄架構如下:
|
||||
```
|
||||
.
|
||||
├── data
|
||||
│ ├── mime.types
|
||||
│ └── nginx.conf
|
||||
└── docker-compose.yaml
|
||||
```
|
||||
|
||||
### nginx.conf
|
||||
其中`nginx.conf`的內容如下:
|
||||
```
|
||||
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
stream {
|
||||
map $ssl_preread_server_name $backend_name {
|
||||
tjn.awin.one trojan;
|
||||
storj.awin.one swag;
|
||||
blog.awin.one swag;
|
||||
gitea.awin.one swag;
|
||||
}
|
||||
|
||||
# trojan
|
||||
upstream trojan {
|
||||
server 192.168.1.31:443;
|
||||
}
|
||||
|
||||
# swag
|
||||
upstream swag {
|
||||
server 192.168.1.20:44320;
|
||||
}
|
||||
|
||||
|
||||
# 监听 443 并开启 ssl_preread
|
||||
server {
|
||||
listen 80 reuseport;
|
||||
listen 443 reuseport;
|
||||
listen [::]:443 reuseport;
|
||||
proxy_pass $backend_name;
|
||||
ssl_preread on;
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
```
|
||||
|
||||
### mime.types
|
||||
其中`mime.types`的內容如下:
|
||||
```
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
```
|
||||
247
02. PARA/03. Resources(資源)/01. 架站/02. SWAG Reverse proxy.md
Normal file
247
02. PARA/03. Resources(資源)/01. 架站/02. SWAG Reverse proxy.md
Normal file
@@ -0,0 +1,247 @@
|
||||
[SWAG](https://docs.linuxserver.io/general/swag)可以幫我們反向代理,並且有https。是由linuxserver.io包裝的Nginx webserver和reverse proxy的container。
|
||||
|
||||
#### 建立docker-compose
|
||||
1. 建立folder
|
||||
```bash
|
||||
mkdir -p ~/dockers/linuxserverswag ; cd ~/dockers/linuxserverswag
|
||||
```
|
||||
|
||||
2. 建立`docker-compose.yaml`:
|
||||
```
|
||||
vim docker-compose.yaml
|
||||
```
|
||||
|
||||
填入內容如下:
|
||||
```yaml
|
||||
version: "2.1"
|
||||
services:
|
||||
swag:
|
||||
image: ghcr.io/linuxserver/swag
|
||||
container_name: swag
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Asia/Taipei
|
||||
- URL=awin.one
|
||||
- SUBDOMAINS=wildcard
|
||||
- VALIDATION=dns
|
||||
- CERTPROVIDER= #optional
|
||||
- DNSPLUGIN=cloudflare #optional
|
||||
- PROPAGATION= #optional
|
||||
- DUCKDNSTOKEN= #optional
|
||||
- EMAIL=awinhuang@gmail.com
|
||||
- ONLY_SUBDOMAINS=false #optional
|
||||
- EXTRA_DOMAINS= #optional
|
||||
- STAGING=false #optional
|
||||
- MAXMINDDB_LICENSE_KEY= #optional
|
||||
volumes:
|
||||
- ./config:/config
|
||||
ports:
|
||||
- 44320:443
|
||||
- 8020:80 #optional
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
3. 先跑一次:
|
||||
```bash
|
||||
sudo docker-compose up
|
||||
```
|
||||
會發現有錯誤,這是正常的,錯誤訊息像這樣:
|
||||
```
|
||||
swag | Unsafe permissions on credentials configuration file: /config/dns-conf/cloudflare.ini
|
||||
swag | Cleaning up challenges
|
||||
swag | Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address and Global key?)
|
||||
swag | ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.
|
||||
```
|
||||
按`ctrl + c`退出。這時候`config`目錄也會有swag所mapping出來的相關檔案。修改`config/dns-conf/cloudflare.ini`:
|
||||
```bash
|
||||
vim config/dns-conf/cloudflare.ini
|
||||
```
|
||||
把`config/dns-conf/cloudflare.ini`改為:
|
||||
```
|
||||
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
|
||||
# Replace with your values
|
||||
|
||||
With global api key:
|
||||
dns_cloudflare_email = awinhuang@gmail.com
|
||||
dns_cloudflare_api_key = <YOUR_API_KEY_FROM_CLOUDFLARE>
|
||||
|
||||
# With token (comment out both lines above and uncomment below):
|
||||
#dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
|
||||
```
|
||||
|
||||
4. 再跑一次:
|
||||
```bash
|
||||
sudo docker-compose up
|
||||
```
|
||||
這一次就可以順利取得認證了,訊息像這樣:
|
||||
```
|
||||
swag | IMPORTANT NOTES:
|
||||
swag | - Congratulations! Your certificate and chain have been saved at:
|
||||
swag | /etc/letsencrypt/live/awin.one/fullchain.pem
|
||||
swag | Your key file has been saved at:
|
||||
swag | /etc/letsencrypt/live/awin.one/privkey.pem
|
||||
swag | Your certificate will expire on 2021-04-26. To obtain a new or
|
||||
swag | tweaked version of this certificate in the future, simply run
|
||||
swag | certbot again. To non-interactively renew *all* of your
|
||||
swag | certificates, run "certbot renew"
|
||||
swag | - If you like Certbot, please consider supporting our work by:
|
||||
swag |
|
||||
swag | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
|
||||
swag | Donating to EFF: https://eff.org/donate-le
|
||||
swag |
|
||||
swag | New certificate generated; starting nginx
|
||||
swag | Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind,
|
||||
swag | and add a new env variable "MAXMINDDB_LICENSE_KEY", set to your license key.
|
||||
swag | [cont-init.d] 50-config: exited 0.
|
||||
swag | [cont-init.d] 60-renew: executing...
|
||||
swag | The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am).
|
||||
swag | [cont-init.d] 60-renew: exited 0.
|
||||
swag | [cont-init.d] 70-templates: executing...
|
||||
swag | [cont-init.d] 70-templates: exited 0.
|
||||
swag | [cont-init.d] 99-custom-files: executing...
|
||||
swag | [custom-init] no custom files found exiting...
|
||||
swag | [cont-init.d] 99-custom-files: exited 0.
|
||||
swag | [cont-init.d] done.
|
||||
swag | [services.d] starting services
|
||||
swag | [services.d] done.
|
||||
swag | nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
|
||||
swag | Server ready
|
||||
```
|
||||
最後一行的`swag | Server ready`表示server已經跑起來了。先按下`ctrl + c`退出,再來設定reverse proxy。
|
||||
|
||||
5. 修正`config/dns-conf/cloudflare.ini`的安全性問題
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; chmod 600 config/dns-conf/cloudflare.ini
|
||||
```
|
||||
|
||||
#### Setup reverse proxy
|
||||
1. 建立folder:
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; mkdir -p config/nginx/sites-available config/nginx/sites-enabled
|
||||
```
|
||||
|
||||
2. 建立以下檔案:
|
||||
- `config/nginx/sites-available/common.conf`,內容:
|
||||
```
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
```
|
||||
- `config/nginx/sites-available/common_location.conf`,內容:
|
||||
```
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
```
|
||||
- `config/nginx/sites-available/storj.conf`,內容:
|
||||
```
|
||||
upstream storj {
|
||||
server 192.168.1.11:14002;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name storj.awin.one;
|
||||
|
||||
include /config/nginx/sites-available/common.conf;
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.11:14002/;
|
||||
include /config/nginx/sites-available/common_location.conf;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
- `config/nginx/sites-available/blog.conf`,內容:
|
||||
```
|
||||
upstream blog {
|
||||
server 192.168.1.30:80;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name blog.awin.one;
|
||||
|
||||
include /config/nginx/sites-available/common.conf;
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.30:80/;
|
||||
include /config/nginx/sites-available/common_location.conf;
|
||||
}
|
||||
}
|
||||
```
|
||||
- `config/nginx/sites-available/gitea.conf`,內容:
|
||||
```
|
||||
upstream gitea {
|
||||
server 192.168.1.32:3000; ## 網址
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name gitea.awin.one; ## 網域
|
||||
|
||||
include /config/nginx/sites-available/common.conf;
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.32:3000/; ## 網址
|
||||
include /config/nginx/sites-available/common_location.conf;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 在`config/nginx/sites-enabled`裡面建立要enable的config:
|
||||
```
|
||||
cd config/nginx/sites-enabled ; ln -s ../sites-available/storj.conf . ; cd -
|
||||
```
|
||||
|
||||
4. 修改`config/nginx/nginx.conf`:
|
||||
```
|
||||
vim config/nginx/nginx.conf
|
||||
```
|
||||
找到`include /config/nginx/site-confs/*;`這一行,把它comment掉,在下面新增一行:
|
||||
```
|
||||
include /config/nginx/sites-enabled/*.conf;
|
||||
```
|
||||
|
||||
5. 啟動swag:
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; sudo docker-compose up -d
|
||||
```
|
||||
|
||||
#### Restart
|
||||
```bash
|
||||
cd ~/dockers/linuxserverswag ; sudo docker-compose restart
|
||||
```
|
||||
|
||||
#### Update certification
|
||||
1. 進到dockr的swag bash裡面:`sudo docker exec -it swag /bin/bash`
|
||||
2. 輸入 `certbot renew`
|
||||
3. ![[Pasted image 20210422205534.png]]
|
||||
|
||||
#### Trouble shooting
|
||||
- 如果遇到類似下面的錯誤:
|
||||
```
|
||||
ERROR: for swag Cannot start service swag: driver failed programming external connectivity on endpoint swag (7c527d046631e0957de0b831ca25bed296de76e2eb96378964cb0110d7fb017d): Bind for 0.0.0.0:443 failed: port is already allocated.
|
||||
```
|
||||
表示有其他程式佔住了80 port,可能是其他docker container或是service,必須先關閉它們。[^1]
|
||||
|
||||
#### 參考來源
|
||||
1. [linuxserver/docker-swag: Nginx webserver and reverse proxy with php support and a built-in Certbot (Let's Encrypt) client. It also contains fail2ban for intrusion prevention.](https://github.com/linuxserver/docker-swag)
|
||||
2. [How to set up an easy and secure reverse proxy with Docker, Nginx & Letsencrypt](https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/)
|
||||
3. [SWAG setup - LinuxServer.io](https://docs.linuxserver.io/general/swag#understanding-the-proxy-conf-structure)
|
||||
4. [NGINX Docs | NGINX Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
|
||||
5. [Nginx 設定反向代理 Reverse Proxy](https://www.opencli.com/linux/nginx-config-reverse-proxy)
|
||||
6. [用 Nginx 伺服器建立反向代理](https://noob.tw/nginx-reverse-proxy/)
|
||||
7. [俄羅斯不愧是戰鬥民族:nginx - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10188498)
|
||||
|
||||
[^1]: [啟動docker時出現「Cannot start service :driver failed programming external connectivity on endpoint xxx, Bind for 0.0.0.0:80 failed: port is already allocated 」](https://mitsuiwei.com/docker-cannot-start-service/)
|
||||
14
02. PARA/03. Resources(資源)/01. 架站/03. Trojan.md
Normal file
14
02. PARA/03. Resources(資源)/01. 架站/03. Trojan.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## 準備
|
||||
### 安裝Maria DB
|
||||
|
||||
|
||||
## 1. 下載trojan安裝檔
|
||||
```
|
||||
wget https://raw.githubusercontent.com/Jrohy/trojan/master/install.sh
|
||||
```
|
||||
|
||||
## 參考
|
||||
- [Trojan史上最全面的脚本!Trojan所有脚本集合! - 简书](https://www.jianshu.com/p/bb14fc75292d)
|
||||
- [VPS 初体验(三)在 VPS 上快速搭建 trojan 服务 - Kiku 的个人博客](https://kiku.vip/2021/10/16/%E5%9C%A8%20VPS%20%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%20trojan%20%E6%9C%8D%E5%8A%A1/)
|
||||
- [mku228/trojan: 科学上网/翻墙梯子/自由上网/trojan 搭建教程 免费机场、VPN工具 小白科学上网一键搭建VPN梯子最新2022教程](https://github.com/mku228/trojan)
|
||||
- [Jrohy/trojan: trojan多用户管理部署程序, 支持web页面管理](https://github.com/Jrohy/trojan)
|
||||
48
02. PARA/03. Resources(資源)/01. 架站/04. Gitea.md
Normal file
48
02. PARA/03. Resources(資源)/01. 架站/04. Gitea.md
Normal file
@@ -0,0 +1,48 @@
|
||||
## docker-compose.yml
|
||||
```yaml
|
||||
version: "3"
|
||||
|
||||
networks:
|
||||
gitea:
|
||||
external: false
|
||||
|
||||
services:
|
||||
server:
|
||||
image: gitea/gitea:latest
|
||||
container_name: gitea
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- DB_TYPE=mysql
|
||||
- DB_HOST=db:3306
|
||||
- DB_NAME=gitea
|
||||
- DB_USER=gitea
|
||||
- DB_PASSWD=gitea
|
||||
- TZ=Asia/Taipei
|
||||
restart: always
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- ./datas/gitea:/data
|
||||
ports:
|
||||
- "3000:80"
|
||||
- "322:22"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: mysql:8
|
||||
restart: always
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=gitea
|
||||
- MYSQL_USER=gitea
|
||||
- MYSQL_PASSWORD=gitea
|
||||
- MYSQL_DATABASE=gitea
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- ./datas/db:/var/lib/mysql
|
||||
```
|
||||
|
||||
## 文件
|
||||
- [Gitea Docs: Config Cheat Sheet](https://docs.gitea.io/zh-tw/config-cheat-sheet/)
|
||||
2
02. PARA/03. Resources(資源)/50. 軍武/虎式.md
Normal file
2
02. PARA/03. Resources(資源)/50. 軍武/虎式.md
Normal file
@@ -0,0 +1,2 @@
|
||||
[二次大戰德軍虎式戰車考證 - Mobile01](https://www.mobile01.com/topicdetail.php?f=637&t=5384277)
|
||||
[戰車之王:米歇爾.魏特曼 @ "Loyalty Is My Honor忠誠即是我的榮耀"WAFFEN-SS 武裝親衛隊簡介 :: 痞客邦 ::](https://mdoohan74.pixnet.net/blog/post/8820801-%E6%88%B0%E8%BB%8A%E4%B9%8B%E7%8E%8B%3A%E7%B1%B3%E6%AD%87%E7%88%BE.%E9%AD%8F%E7%89%B9%E6%9B%BC)
|
||||
6
02. PARA/03. Resources(資源)/51. 模型/Traxxas Sledge.md
Normal file
6
02. PARA/03. Resources(資源)/51. 模型/Traxxas Sledge.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 升級零件
|
||||
1. [差速齒(9579R)](https://youtu.be/t2HpcLTBsTc)
|
||||
2. [前保桿(6835)](https://www.youtube.com/watch?v=aWxCktCLTI8)
|
||||
1. 需要螺絲
|
||||
1. m3x30
|
||||
2. 平頭m4x20
|
||||
51
02. PARA/03. Resources(資源)/51. 模型/舊化作例.md
Normal file
51
02. PARA/03. Resources(資源)/51. 模型/舊化作例.md
Normal file
@@ -0,0 +1,51 @@
|
||||
### 德國四號
|
||||
![[德國四號_舊化做例_001.jpg|160]]
|
||||
|
||||
### 德國Tiger I
|
||||
![[Tiger I 做例_001.jpg]]
|
||||
![[Tiger I 做例_002.jpg]]
|
||||
![[Tiger I 做例_003.jpg]]
|
||||
![[Tiger I 做例_004.jpg]]
|
||||
![[Tiger I 做例_005.jpg]]
|
||||
![[Tiger I 做例_006.jpg]]
|
||||
|
||||
參考:
|
||||
- [Panzerkampfwagen VI Tiger Sd.Kfz.181, ‘Tiger I’ - Tanks Encyclopedia](https://tanks-encyclopedia.com/ww2/germany/panzer-vi_tiger.php)
|
||||
- [Tiger I Information Center - Paint Schemes](http://www.alanhamby.com/paints.shtml)
|
||||
|
||||
### 德國虎王
|
||||
![[德國虎王_作例_001.jpg]]
|
||||
![[德國虎王_作例_002.jpg]]
|
||||
![[德國虎王_作例_003.jpg]]
|
||||
![[德國虎王_作例_004.jpg]]
|
||||
|
||||
參考:
|
||||
- [Panzerkampfwagen Tiger Ausf.B (Sd.Kfz.182) Tiger II](https://tanks-encyclopedia.com/ww2/germany/panzer-vi_konigstiger.php)
|
||||
|
||||
### Selbstfahrlafette IV
|
||||
- [大麥克斯](https://zh.wikipedia.org/wiki/%E5%A4%A7%E9%BA%A5%E5%85%8B%E6%96%AF)
|
||||
![[Dicker Max_001.jpg]]
|
||||
![[Dicker Max_002.jpg]]
|
||||
![[Dicker Max_003.jpg]]
|
||||
![[Dicker Max_004.jpg]]
|
||||
![[Dicker Max_005.jpg]]
|
||||
![[Dicker Max_006.jpg]]
|
||||
![[Dicker Max_007.jpg]]
|
||||
![[Dicker Max_008.jpg]]
|
||||
![[Dicker Max_009.jpg]]
|
||||
![[Dicker Max_010.jpg]]
|
||||
![[Dicker Max_011.jpg]]
|
||||
![[Dicker Max_012.jpg]]
|
||||
![[Dicker Max_013.jpg]]
|
||||
![[Dicker Max_014.jpg]]
|
||||
![[Dicker Max_015.jpg]]
|
||||
![[Dicker Max_016.jpg]]
|
||||
![[Dicker Max_017.jpg]]
|
||||
![[Dicker Max_018.jpg]]
|
||||
![[Dicker Max_019.jpg]]
|
||||
![[Dicker Max_020.jpg]]
|
||||
![[Dicker Max_021.jpg]]
|
||||
![[Dicker Max_022.jpg]]
|
||||
![[Dicker Max_023.jpg]]
|
||||
![[Dicker Max_024.jpg]]
|
||||
![[Dicker Max_025.jpg]]
|
||||
10
02. PARA/03. Resources(資源)/99. templates/blogHeader.md
Normal file
10
02. PARA/03. Resources(資源)/99. templates/blogHeader.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Title:
|
||||
Date: {{date:YYYY-MM-DD}} {{time:HH:mm}}
|
||||
Modified: {{date:YYYY-MM-DD}} {{time:HH:mm}}
|
||||
Category:
|
||||
Tags:
|
||||
Slug:
|
||||
Authors: Awin Huang
|
||||
Summary:
|
||||
|
||||
This is the content of post.
|
||||
1
02. PARA/03. Resources(資源)/99. templates/date.md
Normal file
1
02. PARA/03. Resources(資源)/99. templates/date.md
Normal file
@@ -0,0 +1 @@
|
||||
{{date:YYYY-MM-DD}}
|
||||
7
02. PARA/03. Resources(資源)/99. templates/front matter.md
Normal file
7
02. PARA/03. Resources(資源)/99. templates/front matter.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
date: {{date}}
|
||||
time: {{time:HH:mm:ss}}
|
||||
description:
|
||||
---
|
||||
6
02. PARA/03. Resources(資源)/99. templates/note.md
Normal file
6
02. PARA/03. Resources(資源)/99. templates/note.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
date: {{date}}
|
||||
tags:
|
||||
aliases:
|
||||
-
|
||||
---
|
||||
5
02. PARA/03. Resources(資源)/99. templates/table.md
Normal file
5
02. PARA/03. Resources(資源)/99. templates/table.md
Normal file
@@ -0,0 +1,5 @@
|
||||
| header | header | header |
|
||||
|:------:|:------ | ------:|
|
||||
| 1-2 | 1-1 | 1-3 |
|
||||
| 2-2 | 2-1 | 2-3 |
|
||||
| 3-2 | 3-1 | 3-3 |
|
||||
8
02. PARA/03. Resources(資源)/99. templates/thisWeek.md
Normal file
8
02. PARA/03. Resources(資源)/99. templates/thisWeek.md
Normal file
@@ -0,0 +1,8 @@
|
||||
- Week{{date:ww}}: {{date:YYYY-MM-DD(ddd)}}
|
||||
- 星期一:
|
||||
- 星期二:
|
||||
- 星期三:
|
||||
- 星期四:
|
||||
- 星期五:
|
||||
- 星期六:
|
||||
- 星期日:
|
||||
47
02. PARA/03. Resources(資源)/99. templates/日記.md
Normal file
47
02. PARA/03. Resources(資源)/99. templates/日記.md
Normal file
@@ -0,0 +1,47 @@
|
||||
時間:{{time:HH:mm:ss}}
|
||||
|
||||
### TAG
|
||||
|
||||
|
||||
### All TODOs
|
||||
```tasks
|
||||
not done
|
||||
path does not include 2021
|
||||
path does not include 2022/01
|
||||
path does not include 2022/02
|
||||
path does not include 2022/03
|
||||
path does not include 2022/04
|
||||
path does not include 004 - Archives(歸檔)
|
||||
path does not include 001. Kong
|
||||
group by folder
|
||||
group by filename
|
||||
group by heading
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Doing TODOs
|
||||
```tasks
|
||||
not done
|
||||
has start date
|
||||
happens before tomorrow
|
||||
path does not include 2021
|
||||
path does not include 2022/01
|
||||
path does not include 2022/02
|
||||
path does not include 2022/03
|
||||
path does not include 2022/04
|
||||
path does not include 004 - Archives(歸檔)
|
||||
path does not include 001. Kong
|
||||
group by folder
|
||||
group by filename
|
||||
group by heading
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 新增TODO
|
||||
#### 私事
|
||||
|
||||
#### 公事
|
||||
|
||||
### 今日回顧
|
||||
11
02. PARA/03. Resources(資源)/99. templates/讀書筆記.md
Normal file
11
02. PARA/03. Resources(資源)/99. templates/讀書筆記.md
Normal file
@@ -0,0 +1,11 @@
|
||||
書名:
|
||||
日期:{{date:YYYY-MM-DD(ddd)}}
|
||||
Link:
|
||||
|
||||
# 筆記
|
||||
|
||||
|
||||
# 心得
|
||||
|
||||
|
||||
# TODO
|
||||
1
02. PARA/03. Resources(資源)/Android operating.md
Normal file
1
02. PARA/03. Resources(資源)/Android operating.md
Normal file
@@ -0,0 +1 @@
|
||||
- [如何透過 adb command line 指令啟動 Android App](https://kkboxsqa.wordpress.com/2014/08/20/%E5%A6%82%E4%BD%95%E9%80%8F%E9%81%8E-adb-command-line-%E6%8C%87%E4%BB%A4%E5%95%9F%E5%8B%95-android-app/)
|
||||
164
02. PARA/03. Resources(資源)/Android programming.md
Normal file
164
02. PARA/03. Resources(資源)/Android programming.md
Normal file
@@ -0,0 +1,164 @@
|
||||
## Build AOSP
|
||||
### Build compile environment
|
||||
1. Install Ubuntu 18.04
|
||||
2. Install packages: `sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig`
|
||||
- https://source.android.com/setup/build/initializing
|
||||
3. Install Repo
|
||||
```
|
||||
mkdir ~/bin
|
||||
PATH=~/bin:$PATH
|
||||
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo ;\
|
||||
chmod a+x ~/bin/repo ;\
|
||||
gpg --recv-key 8BB9AD793E8E6153AF0F9A4416530D5E920F5C65 ;\
|
||||
curl https://storage.googleapis.com/git-repo-downloads/repo.asc | gpg --verify - ~/bin/repo
|
||||
```
|
||||
- https://source.android.com/setup/develop#installing-repo
|
||||
4. Download AOSP source
|
||||
1. Create folder for AOSP
|
||||
```
|
||||
mkdir -p ~/codes/aosp ; cd ~/codes/aosp
|
||||
```
|
||||
2. Setup git
|
||||
```
|
||||
git config --global user.name AwinHuang ;\
|
||||
git config --global user.email awinhuang@gmail.com
|
||||
```
|
||||
3. Download source code
|
||||
```
|
||||
repo init -u https://android.googlesource.com/platform/manifest ;\
|
||||
repo sync -j8
|
||||
```
|
||||
- 如果要切換某一個特定版本可以使用`-b`,例如:`repo init -u https://android.googlesource.com/platform/manifest -b android-10.0.0_r47`。
|
||||
- 要知道版本tag可以查看:https://source.android.com/setup/start/build-numbers#source-code-tags-and-builds
|
||||
5. Build code
|
||||
```
|
||||
source build/envsetup.sh ;\
|
||||
lunch aosp_arm-eng ;\
|
||||
make clobber ;\
|
||||
make -j16
|
||||
```
|
||||
- `make clobber`用來刪除build資料夾
|
||||
|
||||
### Reference
|
||||
- [GitHub - henrymorgen/android-knowledge-system: Android应用开发最强原创知识体系](https://github.com/henrymorgen/android-knowledge-system)
|
||||
- [Android AOSP基础(二)AOSP源码和内核源码下载 | BATcoder - 刘望舒](http://liuwangshu.cn/framework/aosp/2-download-aosp.html)
|
||||
- [Android AOSP基础(三)Android系统源码的整编和单编 | BATcoder - 刘望舒](http://liuwangshu.cn/framework/aosp/3-compiling-aosp.html)
|
||||
|
||||
## Build kernel
|
||||
1. Download the code
|
||||
```
|
||||
mkdir -p ~/codes/kernel ;\
|
||||
cd ~/codes/kernel ;\
|
||||
repo init -u https://android.googlesource.com/kernel/manifest ;\
|
||||
repo sync -j16
|
||||
```
|
||||
|
||||
2. Compile
|
||||
```
|
||||
build/build.sh
|
||||
```
|
||||
- 如果遇到`Command 'java' not found, but can be installed with:`
|
||||
- 依序安裝
|
||||
- `sudo apt install default-jre`
|
||||
- `sudo apt install openjdk-11-jre-headless`
|
||||
- `sudo apt install openjdk-8-jre-headless`
|
||||
- 執行 `sudo update-alternatives --config java`
|
||||
- 選擇 `/usr/lib/jvm/java-11-openjdk-amd64/bin/java`
|
||||
- 再次compile
|
||||
- `source build/envsetup.sh`
|
||||
- `mm idegen`
|
||||
|
||||
3. 產生android.iml和android.ipr
|
||||
在source code跟目錄下執行:`development/tools/idegen/idegen.sh`
|
||||
|
||||
### Reference
|
||||
- [Android kernel源码下载与编译](https://blog.csdn.net/u010164190/article/details/106561022)
|
||||
|
||||
|
||||
## Android App programming
|
||||
- R的全名:`<PACKAGE_NAME> + .R`,例如package name是`com.awin.testapp`,那全名是`com.awin.testapp.R`。
|
||||
- AndroidX = Android eXtension
|
||||
- Layout
|
||||
- layout_margin: 物件與其他物件的距離
|
||||
- layout_gravity: 物件在容器內的位置(靠左、靠右、置中...)
|
||||
- textApperance: 字型大小
|
||||
- Extensions
|
||||
- Android 4.1 沒有自動加入的extension
|
||||
- 打開build.gradle,在`plugins`區塊中加入:
|
||||
```
|
||||
id 'kotlin-kapt'
|
||||
id 'kotlin-android-extensions'
|
||||
```
|
||||
|
||||
- 使用ViewModel & LiveData
|
||||
- 確認有 `kotlin-kapt` 這個plugin。
|
||||
![[Pasted image 20210330102148.png]]
|
||||
- [Android jetpack所有library](https://developer.android.com/jetpack/androidx/explorer)
|
||||
- [Android jetpack - Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle)
|
||||
- 從`Declaring dependencies`這區塊複製必要的module
|
||||
![[Pasted image 20210330110411.png]]
|
||||
```
|
||||
def lifecycle_version = "2.3.1"
|
||||
|
||||
// ViewModel
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
// LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
// Annotation processor
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||
```
|
||||
|
||||
- Create a ViewModel
|
||||
```kotlin
|
||||
viewModel = ViewModelProvider(this).get(GuessViewModel::class.java)
|
||||
```
|
||||
|
||||
- Observe a live data in ViewModel
|
||||
```kotlin
|
||||
viewModel.counter.observe(this, Observer {
|
||||
counter.setText(it.toString())
|
||||
})
|
||||
```
|
||||
counter這個變數是包含在ViewModel裡面的live data,我們的資料則是放在counter裡面的「value」。
|
||||
所以如果要取用我們的data,則是:`viewModel.counter.value`。
|
||||
|
||||
- 使用LiveData
|
||||
- `val counter = MutableLiveData<Int>()`
|
||||
|
||||
### Use ViewBinding
|
||||
ViewBinding is used to replace Kotlin Synthetics.
|
||||
1. 在`app/build.gradle`中加入:
|
||||
```
|
||||
plugins {
|
||||
...
|
||||
id 'kotlin-parcelize' <-- Add this
|
||||
}
|
||||
|
||||
android {
|
||||
...
|
||||
buildFeatures { <-- Add this
|
||||
viewBinding true
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
2. 在你的activity裡面
|
||||
1. `import <APP_DOMAIN_NAME>.databinding.<ACTIVITY_NAME>Binding`
|
||||
假如:APP_DOMAIN_NAME是`com.example.testmultisectioncyclerview`,ACTIVITY_NAME是`ActivityMain`,那就是:
|
||||
`import com.example.testmultisectioncyclerview.databinding.ActivityMainBinding`
|
||||
2. 用`lateinit`宣告一個變數,變數名稱是activity的名字加上binding,例如`ActivityMain`就是:
|
||||
`private lateinit var activityBinding: ActivityMainBinding`
|
||||
3. 在`onCreate()`中,就可以用`activityBinding`來取得view與其他元件了:
|
||||
```
|
||||
activityBinding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(activityBinding.root) <-- root就是view
|
||||
```
|
||||
|
||||
## ADB usage
|
||||
- [如何透過 adb command line 指令啟動 Android App](https://kkboxsqa.wordpress.com/2014/08/20/%E5%A6%82%E4%BD%95%E9%80%8F%E9%81%8E-adb-command-line-%E6%8C%87%E4%BB%A4%E5%95%9F%E5%8B%95-android-app/)
|
||||
|
||||
# MISC
|
||||
## 教學文
|
||||
- [Android Template 小技巧 及 寫程式常見的問題](https://www.eeaseries.com/2021/01/android-template.html?m=1)
|
||||
- [Jetpack Compose 基础知识](https://developers.google.com/codelabs/jetpack-compose-basics?hl=zh-cn#0)
|
||||
- [一文带你了解适配Android 11分区存储](https://zhuanlan.zhihu.com/p/354632087)
|
||||
39
02. PARA/03. Resources(資源)/C++17.md
Normal file
39
02. PARA/03. Resources(資源)/C++17.md
Normal file
@@ -0,0 +1,39 @@
|
||||
- 變數宣告的方式變了
|
||||
- Old: `int a = 3;`
|
||||
- New: `int a {3};`
|
||||
|
||||
- `if`裡面可以宣告變數
|
||||
```cpp
|
||||
if (auto a {3}; a > b) {
|
||||
// Do something
|
||||
}
|
||||
```
|
||||
|
||||
- `unique_ptr`: 無法複製的指標
|
||||
- 傳統方法:
|
||||
```cpp
|
||||
unique_ptr<uint8_t[]> buffer = new uint8_t[256];
|
||||
```
|
||||
- 新方法:
|
||||
```cpp
|
||||
auto buffer = std::make_unique<uint8_t[]>(256);
|
||||
```
|
||||
- `share_ptr`: 可以複製,但要避免循環參考問題
|
||||
|
||||
- 透過refernce傳遞array參數
|
||||
- 考慮一個帶有長度的陣列要傳到function裡面,但是又希望在function面可以指定陣列長度
|
||||
```cpp
|
||||
double value[] { 1.0, 2.0, 3.0 }; // Error!
|
||||
double value[] { 1.0, 2.0, 3.0, 4.0, 5.0 }; // Pass!
|
||||
|
||||
double average(const double (&array)[5]) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- 用 `std::string_view` 代替 `const std::string&`。
|
||||
|
||||
## Multi-Thread
|
||||
### 使用`std::async`
|
||||
- [C++ 使用 Async 非同步函數開發平行化計算程式教學](https://blog.gtwang.org/programming/cpp-11-async-function-parallel-computing-tutorial/)
|
||||
- [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic)
|
||||
39
02. PARA/03. Resources(資源)/C++17/C++17.md
Normal file
39
02. PARA/03. Resources(資源)/C++17/C++17.md
Normal file
@@ -0,0 +1,39 @@
|
||||
- 變數宣告的方式變了
|
||||
- Old: `int a = 3;`
|
||||
- New: `int a {3};`
|
||||
|
||||
- `if`裡面可以宣告變數
|
||||
```cpp
|
||||
if (auto a {3}; a > b) {
|
||||
// Do something
|
||||
}
|
||||
```
|
||||
|
||||
- `unique_ptr`: 無法複製的指標
|
||||
- 傳統方法:
|
||||
```cpp
|
||||
unique_ptr<uint8_t[]> buffer = new uint8_t[256];
|
||||
```
|
||||
- 新方法:
|
||||
```cpp
|
||||
auto buffer = std::make_unique<uint8_t[]>(256);
|
||||
```
|
||||
- `share_ptr`: 可以複製,但要避免循環參考問題
|
||||
|
||||
- 透過refernce傳遞array參數
|
||||
- 考慮一個帶有長度的陣列要傳到function裡面,但是又希望在function面可以指定陣列長度
|
||||
```cpp
|
||||
double value[] { 1.0, 2.0, 3.0 }; // Error!
|
||||
double value[] { 1.0, 2.0, 3.0, 4.0, 5.0 }; // Pass!
|
||||
|
||||
double average(const double (&array)[5]) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- 用 `std::string_view` 代替 `const std::string&`。
|
||||
|
||||
## Multi-Thread
|
||||
### 使用`std::async`
|
||||
- [C++ 使用 Async 非同步函數開發平行化計算程式教學](https://blog.gtwang.org/programming/cpp-11-async-function-parallel-computing-tutorial/)
|
||||
- [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic)
|
||||
23
02. PARA/03. Resources(資源)/C++17/Class template.md
Normal file
23
02. PARA/03. Resources(資源)/C++17/Class template.md
Normal file
@@ -0,0 +1,23 @@
|
||||
> Class template(類別樣板)不是類別,而是建立類別的方法。
|
||||
|
||||
定義類別樣板
|
||||
```cpp
|
||||
template <template parameter list>
|
||||
class ClassName
|
||||
{
|
||||
// Template class definition
|
||||
};
|
||||
```
|
||||
|
||||
用`typename`來指定會變動的變數型態,例:
|
||||
```cpp
|
||||
template <typename T1, typename T2>
|
||||
class MyTemplateClass
|
||||
{
|
||||
public:
|
||||
T1 length;
|
||||
T2 weight;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
2
02. PARA/03. Resources(資源)/C++17/lvalue.md
Normal file
2
02. PARA/03. Resources(資源)/C++17/lvalue.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- 等號左邊的值
|
||||
- 「有固定address」的變數
|
||||
59
02. PARA/03. Resources(資源)/C++17/move operator.md
Normal file
59
02. PARA/03. Resources(資源)/C++17/move operator.md
Normal file
@@ -0,0 +1,59 @@
|
||||
move operator可以讓[[rvalue]]被參考,從而進一部的消除複製的成本。例如,以下的function會回傳一個很大的陣列:
|
||||
```cpp
|
||||
vector<int> generateBigArray() {
|
||||
const int size = 1000000;
|
||||
vector<int> array;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
array[i] = i;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
```
|
||||
|
||||
當我們呼叫這個function並把結果回傳到一個變數的時候,每一次這個大陣列都會被複製一次,例:
|
||||
```cpp
|
||||
vector<int> atemp = generateBigArray(); // 複製1000000個int
|
||||
```
|
||||
|
||||
如果使用[[rvalue]] reference就可以避開這些複製,例:
|
||||
```cpp
|
||||
vector<int>&& atemp = generateBigArray(); // 已經建立好的array會直接「移動」到atemp,省下了複製的步驟
|
||||
```
|
||||
|
||||
## move contructor
|
||||
move contructor跟copy constructor很類似,只是參數由`&`改成了`&&`。
|
||||
例:
|
||||
```cpp
|
||||
template <typename T>
|
||||
inline Array<T>::Array(const Array&& moved) :
|
||||
size{moved.size},
|
||||
elements{moved.elements}
|
||||
{
|
||||
moved.elements = nullptr;
|
||||
}
|
||||
```
|
||||
|
||||
## move assignment operator
|
||||
```cpp
|
||||
template <typename T>
|
||||
Array<T>& Array<T>::operator=(const Array&& rhs)
|
||||
{
|
||||
if (this != &rhs) {
|
||||
delete [] elements;
|
||||
elements = rhs.elements;
|
||||
size = rhs.size;
|
||||
rhs.elements = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
```
|
||||
|
||||
## 明確的「移動」
|
||||
如果有一個現存的「大東西」,可以使用`std::move`來把它「移」到別的地方,進而避開了複製的行為。例:
|
||||
```cpp
|
||||
std::vector<std::string> my_dictionary(10000000);
|
||||
std::vector<std::string> dictionary2 = std::move(my_dictionary);
|
||||
```
|
||||
在`std::move`之後,my_dictionary的size會變成0。
|
||||
9
02. PARA/03. Resources(資源)/C++17/rvalue.md
Normal file
9
02. PARA/03. Resources(資源)/C++17/rvalue.md
Normal file
@@ -0,0 +1,9 @@
|
||||
- 等號右邊的值
|
||||
- 臨時的值,例如運算的結果
|
||||
|
||||
# rvalue參考
|
||||
可以用兩個`&`來參考rvalue。例如:
|
||||
```
|
||||
int c{5};
|
||||
int&& rtepm {c + 3};
|
||||
```
|
||||
133
02. PARA/03. Resources(資源)/Design Pattern.md
Normal file
133
02. PARA/03. Resources(資源)/Design Pattern.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
date: 2022-05-26
|
||||
time: 15:12:04
|
||||
description:
|
||||
---
|
||||
|
||||
## 策略模式(Strategy)
|
||||
策略模式可以提供不一樣的演算法,但是又不用更改程式。
|
||||
以常見的鴨子為例,有一個基礎類別Duck,如何衍生出會飛得鴨子跟不會飛的鴨子?抑或是會叫的跟不會叫的?
|
||||
第一部是將會變動的部份分離出來,讓鴨子類別不需要去在乎飛跟叫的問題。
|
||||
再來是把飛跟叫的部份包裝成另一個class,並以之為基礎類別來實做出「實際的類別」。
|
||||
|
||||
以一般C++的override方法,會用的方式大致是這樣:
|
||||
|
||||
- !!!col
|
||||
- 1
|
||||
### 基礎類別
|
||||
```cpp
|
||||
class duck {
|
||||
duck() {}
|
||||
void fly() {}
|
||||
void makeSound() {}
|
||||
void run() {}
|
||||
}
|
||||
```
|
||||
- 1
|
||||
### 衍生類別,遙控鴨子,會飛不會叫
|
||||
```cpp
|
||||
class duckRC : public duck {
|
||||
duckRC() {}
|
||||
void fly() { printf("I can fly"); }
|
||||
void makeSound() { printf("I cannot make sound!"); }
|
||||
}
|
||||
```
|
||||
- 1
|
||||
### 衍生類別,木頭鴨子,不會飛不會叫
|
||||
```cpp
|
||||
class duckWood : public duck {
|
||||
duckWood() {}
|
||||
void fly() { printf("I cannot fly"); }
|
||||
void makeSound() { printf("I cannot make sound!"); }
|
||||
}
|
||||
```
|
||||
|
||||
但是這樣的話如果基礎類別會更改的話,那麼子類別也全部都會受影響,例如,現在希望所有的鴨子都要有一個「跑」的功能,所以我們就在基礎類別裡加一個`run()`:
|
||||
```cpp
|
||||
class duck {
|
||||
void fly() {}
|
||||
void makeSound() {}
|
||||
void run() { printf("I can run"); }
|
||||
}
|
||||
```
|
||||
|
||||
結果現在木頭鴨子也能跑了,這不符合我們的設計,所以我們必須回頭更改木頭鴨子的程式,改成
|
||||
```cpp
|
||||
class duckWood : public duck {
|
||||
void fly() { printf("I cannot fly"); }
|
||||
void makeSound() { printf("I cannot make sound!"); }
|
||||
void run() { printf("I cannot run!"); } // <- 要改!
|
||||
}
|
||||
```
|
||||
|
||||
如果我們類別很多,那麼就很可能有沒改到的漏網之魚,bug就發生了。
|
||||
|
||||
### 封裝變動的部份
|
||||
比較好的方法一旦類別寫完之後,我們就不要動它了,日後新增的功能也不可以影響到他。我們把「會變動」的部份分離出來,變成各自的類別。
|
||||
為了簡化,我們討論「飛」這個功能就好,「飛」只分成「會飛」跟「不會飛」2種類別:
|
||||
|
||||
- !!!col
|
||||
- 1
|
||||
### 基礎類別,IFly
|
||||
```cpp
|
||||
class IFly {
|
||||
void doFly() = 0;
|
||||
}
|
||||
```
|
||||
- 1
|
||||
### 衍生類別,CanFly
|
||||
```cpp
|
||||
class CanFly {
|
||||
void doFly() { printf("I can fly"); }
|
||||
}
|
||||
```
|
||||
- 1
|
||||
### 衍生類別,CannotFly
|
||||
```cpp
|
||||
class CannotFly {
|
||||
void doFly() { printf("I cannot fly"); }
|
||||
}
|
||||
```
|
||||
|
||||
回到鴨子的基礎類別這邊,基礎類別`duck`本來是直接擁有`fly()`這個member function,現在我們把他改成他擁有的是`IFly`:
|
||||
```cpp
|
||||
class duck {
|
||||
duck() {}
|
||||
fly() { fly->doFly() };
|
||||
IFly* fly = nullptr;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
重寫遙控鴨子這個衍生類別:
|
||||
```cpp
|
||||
class duckRC : public duck {
|
||||
duckRC() {
|
||||
this->fly = new CanFly();
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
重寫木頭鴨子,不會飛,所以:
|
||||
```cpp
|
||||
class duckWood : public duck {
|
||||
duckWood() {
|
||||
this->fly = new CannotFly();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
現在,不管是遙控鴨子或是木頭鴨子,在被呼叫`fly()`的時候,都是根據它fly的「實際類別」來動作,遙控鴨子的`fly()`呼叫的是`CanFly`的`doFly()`,木頭鴨子的`fly()`呼叫的是`CannotFly`的`doFly()`,它們的動作完全取決於他們初始化的方式,而他們初始化的方式則取決於你的產品定義,要是今天你需要不同的飛行方式,增加新的fly類別即可,不會影響到舊有的。要是鴨子基礎類別增加了新的行為,如run,那麼就幫鴨子基礎類別的`run()`加個什麼都不做的預設動作就好,反正舊有的鴨子衍生類別本來就對這個新行為沒反應。要是`CanFly`的定義改變了,那麼你就是改變了使用`CanFly`的所有類別,這是你的定義明確的改變了,不是有程式被「額外」的改變了。
|
||||
|
||||
- !!!col
|
||||
- 1
|
||||
### 原本直接繼承的方式
|
||||
![[Pasted image 20220526182952.png]]
|
||||
- 2
|
||||
### 封裝變動的部份
|
||||
![[Pasted image 20220526183019.png]]
|
||||
|
||||
這樣做的另一個好處是fly的初始化是動態的,只要再多一個`set()` function就可以動態的切換實作,也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。
|
||||
301
02. PARA/03. Resources(資源)/FFMpeg.md
Normal file
301
02. PARA/03. Resources(資源)/FFMpeg.md
Normal file
@@ -0,0 +1,301 @@
|
||||
## Initialization
|
||||
```cpp
|
||||
av_register_all();
|
||||
avformat_network_init();
|
||||
```
|
||||
|
||||
## Command line
|
||||
利用FFMpeg來轉檔
|
||||
```
|
||||
ffmpeg -i <SOURCE.mp4> -t 10 -s <W>x<H> -pix_fmt <OUTPUT_FORMAT> <OUTPUT_FILENAME>
|
||||
```
|
||||
- `-i`:來源檔名
|
||||
- `-t`:影片長度(秒)
|
||||
- `-s`:影片大小,長x寬
|
||||
- `-pix_fmt`:輸出的影像格式
|
||||
|
||||
example:
|
||||
```
|
||||
ffmpeg -i test.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv
|
||||
```
|
||||
|
||||
## Open file
|
||||
使用`avformat_open_input()`來打開檔案。
|
||||
```cpp
|
||||
AVFormatContext* pFormatContext = NULL;
|
||||
char* path = "E:\\download\\Incredible Patagonia_1920x1080.mp4";
|
||||
AVDictionary* pDict = NULL;
|
||||
|
||||
int result = avformat_open_input(&pFormatContext, path, NULL, &pDict);
|
||||
```
|
||||
若是開啟失敗`result`會是一個非0的值。可以用`av_strerror()`來印出詳細的錯誤訊息。例:
|
||||
```cpp
|
||||
char errorMsg[1024] = { 0 };
|
||||
av_strerror(result, errorMsg, sizeof(errorMsg) - 1);
|
||||
printf("Open file %s error, code = %d, message = %s\n", path, result, errorMsg);
|
||||
```
|
||||
|
||||
### Get file information
|
||||
用`avformat_find_stream_info()`來把檔案的video/audio相關訊息放到`AVFormatContext`裡面。例:
|
||||
```cpp
|
||||
result = avformat_find_stream_info(pFormatContext, NULL);
|
||||
```
|
||||
也可以用
|
||||
```cpp
|
||||
av_dump_format(pFormatContext, 0, path, 0);
|
||||
```
|
||||
來直接印出相關訊息,不過這方法是Ffmpeg內定的,如果要印出自己想要的內容,就必須將`AVFormatContext`裡面的`AVStream`一一撈出來:
|
||||
```cpp
|
||||
for (int i = 0; i < pFormatContext->nb_streams; i++) {
|
||||
AVStream* avStream = pFormatContext->streams[i];
|
||||
|
||||
if (avStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
printf("Stream[%d] is a audio stream.\n", i);
|
||||
printf(" Sample rate: %d\n", avStream->codecpar->sample_rate);
|
||||
printf(" Format : %d\n", avStream->codecpar->format);
|
||||
printf(" Channels : %d\n", avStream->codecpar->channels);
|
||||
printf(" FPS : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
|
||||
printf(" Frame Size : %d\n", avStream->codecpar->frame_size);
|
||||
printf(" Codec ID : %d\n", avStream->codecpar->codec_id);
|
||||
} else if (avStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
printf("Stream[%d] is a video stream.\n", i);
|
||||
printf(" Width : %d\n", avStream->codecpar->width);
|
||||
printf(" Height: %d\n", avStream->codecpar->height);
|
||||
printf(" FPS : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
|
||||
printf(" Codec ID: %d\n", avStream->codecpar->codec_id);
|
||||
}
|
||||
}
|
||||
```
|
||||
這個for-loop可以找出檔案裡面的video stream index與audio stream index。
|
||||
|
||||
另一個找出video stream index與audio stream index的方法:
|
||||
```cpp
|
||||
int videoStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
||||
int audioStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
|
||||
printf("Found best video stream index: %d\n", videoStream);
|
||||
printf("Found best audio stream index: %d\n", audioStream);
|
||||
```
|
||||
|
||||
## Demux
|
||||
### 重要結構
|
||||
#### AVPackt
|
||||
##### 重要的member:
|
||||
- `AVBufferRef *buf`:buffer的reference count,基本上不會用到。
|
||||
- `int64_t pts`:顯示順序
|
||||
- `int64_t dts`:decode順序
|
||||
- `uint8_t *data`:實際存data的buffer
|
||||
- `int size`:上面那個buffer的大小
|
||||
|
||||
##### 操作function
|
||||
- `AVPacket* av_packet_alloc()`:產生一個兂的AVPacket
|
||||
- `AVPacket* av_packet_clone(const AVPacket*)`:複製一個AVPacket
|
||||
- `int av_packet_ref(AVPacket*, const AVPacket*)`:增加reference count
|
||||
- `av_packet_unref(AVPacket*)`:減少reference count
|
||||
- `void av_packet_free(AVPacket**)`:釋放AVPacket
|
||||
- `void av_init_packet(AVPacket*)`:初始化AVPacket
|
||||
- `int av_packet_from_data(AVPacket*, uint8_t*, int size)`:從自己的data
|
||||
- `av_seek_frame`:
|
||||
- `AVFormatContext*`
|
||||
- `int stream_index`:要seek的stream
|
||||
- `int64_t timestamp`:timestamp
|
||||
- `int flag`:seek的方法
|
||||
- `#define AVSEEK_FLAG_BACKWARD 1`:往後找
|
||||
- `#define AVSEEK_FLAG_BYTE 2`:以byte為單位,跳到timestamp相對應的byte
|
||||
- `#define AVSEEK_FLAG_ANY 4`:跳到任意frame,不一定要key frame
|
||||
- `#define AVSEEK_FLAG_FRAME 8`:依照frame number來找
|
||||
|
||||
## Decode
|
||||
### 重要結構
|
||||
#### AVCodecContext
|
||||
##### 重要的member
|
||||
- `int thread_count`:執行續的數量
|
||||
- `AVRational time_base`
|
||||
|
||||
##### 操作function
|
||||
- `avcodec_register_all()`:註冊所有的decoder。
|
||||
- `AVCodec* avcodec_find_decoder(enum AVCodecID)`:用codec ID來找到相對應的codec instance
|
||||
- `AVCodec* avcodec_find_decoder_by_name(const char* name)`:用codec的名子來找到相對應的codec instance
|
||||
- `AVCodecContext* avcodec_alloc_context3(const AVCodec*)`:用codec來建立`AVCodecContext`,codec只是用來解碼,解碼之後的相關資料要放在`AVCodecContext`裡面。
|
||||
- `void avcodec_free_context(AVCodecContext**)`:釋放`AVCodecContext`。
|
||||
- `int avcodec_open2(AVCodecContext*, const AVCodec*, AVDictionary*)`
|
||||
- 開始解碼
|
||||
- `AVDictionary`的選項請看`libavcodec/options_table.h`
|
||||
|
||||
#### AVFrame
|
||||
##### 重要的member
|
||||
- `uint8_t* data[AV_NUM_DATA_POINTERS]`:存放這個frame的資料
|
||||
- `int linesize[AV_NUM_DATA_POINTERS]`:存放指向各個plane的pointer
|
||||
- Plane mode
|
||||
- For video
|
||||
- 0:Y
|
||||
- 1:U
|
||||
- 2:V
|
||||
- 3:NULL
|
||||
- For audio
|
||||
- 0:Left channel
|
||||
- 1:Right channel
|
||||
- 2:NULL
|
||||
- Non plane mode
|
||||
- For video
|
||||
- 0:RGB RGB RGB
|
||||
- 1:NULL
|
||||
- `int width`:For video, frame width
|
||||
- `int height`:For video, frame height
|
||||
- `int nb_samples`:For audio
|
||||
- `int64_t pts`:這個frame的pts
|
||||
- `int64_t pkt_dts`:Packet的dts
|
||||
- `int sample_rate`:For audio,sampling rate
|
||||
- `uint64_t channel_layout`:For audio
|
||||
- `int channels`:For audio
|
||||
- `int format`:For video it's AVPixelFormat, for audio it's AVSampleFormat
|
||||
|
||||
##### 操作function
|
||||
- `AVFrame* av_frame_alloc()`:
|
||||
- `void av_frame_free(AVFrame*)`:
|
||||
- `AVFrame* av_frame_clone(const AVFrame*)`:
|
||||
- `int av_frame_ref(AVFrame* dst, const AVFrame* src)`:
|
||||
- `void av_frame_unref(AVFrame*)`:
|
||||
- `int avcodec_send_packet(AVCodecContext*, const AVPacket*)`
|
||||
- `int avcodec_receive_frame(AVCodecContext*, AVFrame*)`
|
||||
|
||||
## Frame scaling
|
||||
##### 操作function
|
||||
- `sws_getContext`
|
||||
- `sws_getCachedContext`
|
||||
```
|
||||
SwsContext* sws_getCachedContext(
|
||||
SwsContext*,
|
||||
int srcW, int srcH, enum AVPixelFormat srcFormat,
|
||||
int dstW, int dstH, enum AVPixelFormat dstFormat,
|
||||
int flags,
|
||||
SwsFilter srcFilter,
|
||||
SwsFilter dstFilter,
|
||||
const double* param)
|
||||
```
|
||||
- `sws_scale`
|
||||
```
|
||||
int sws_scale(
|
||||
SwsContext*,
|
||||
const uint8_t* const srcSlice[], // 來源buffer的pointer, 如果是plane mode,要丟srcSlice[3]
|
||||
const int srcStride[], // linesize
|
||||
int srcSliceY,
|
||||
int srcSliceH, // image height
|
||||
uint8_t* const dst[], // 目標buffer的pointer, 如果是plane mode,要丟dst[3]
|
||||
const int dstStride[]) // linesize
|
||||
```
|
||||
- `void sws_freeContext(SwsContext*)`
|
||||
|
||||
## Audio resampling
|
||||
- `SwrContext* swr_alloc()`
|
||||
- `swr_alloc_set_opts`
|
||||
```cpp
|
||||
SwrContext* swr_alloc_set_opts(
|
||||
SwrContext*,
|
||||
int64_t out_ch_layout, // Output
|
||||
AVSampleFormat outSampleFormat, // Output
|
||||
int outSampleRate, // Output
|
||||
int64_t in_ch_layout, // Input
|
||||
AVSampleFormat inSampleFormat, // Input
|
||||
int inSampleRate, // Input
|
||||
int log_offset, // 一般丟0
|
||||
void* log_ctx) // 一般丟0
|
||||
```
|
||||
- `int swr_init(SwrContext*)`
|
||||
- `void swr_free(SwrContext**)`
|
||||
- `swr_convert`
|
||||
```cpp
|
||||
int swt_convert(
|
||||
SwrContext*,
|
||||
uint8_t** outData,
|
||||
int outCount, // Sample count for one channel
|
||||
uint8_t** inData,
|
||||
int inCount) // Sample count for one channel
|
||||
```
|
||||
|
||||
## Audio 播放
|
||||
### 會用到的class
|
||||
#### QAudioFormat
|
||||
- `setSampleRate`
|
||||
- `setSampleSize`
|
||||
- `setChannelCount`
|
||||
- `setCodec("audio/pcm")`
|
||||
- `setByteOrder(QAudioFormat::LittleEndian)`
|
||||
- `setSampleType(QAudioFormat::UnSignedInt)`
|
||||
|
||||
#### QAudioOutput
|
||||
- Constuctor參數就是一個`QAudioFormat`
|
||||
- `QIODevice* start()`
|
||||
- `suspend()`
|
||||
- `resume()`
|
||||
- `bufferSize()`
|
||||
- `byteFree()`
|
||||
- `periodSize()`
|
||||
|
||||
#### QIODevice
|
||||
- `qint64 write(const char* data, qint64 len)`
|
||||
|
||||
#### QBuffer
|
||||
```cpp
|
||||
QBuffer buffer;
|
||||
|
||||
if (buffer.open(QIODevice::ReadWrite)) {
|
||||
qInfo() << "Buffer Opened!";
|
||||
QByteArray data("Hello World");
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
buffer.write(data);
|
||||
buffer.write("\r\n");
|
||||
}
|
||||
//File and device access you may need to flush the data to the device
|
||||
//buffer.flush()
|
||||
|
||||
//Move to the first position
|
||||
buffer.seek(0);
|
||||
|
||||
qInfo() << buffer.readAll(); // retuen a QVariant
|
||||
qInfo() << "closing the buffer";
|
||||
|
||||
//ALWAYS close your device!
|
||||
buffer.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
```cpp
|
||||
QAudioFormat audioFormat;
|
||||
audioFormat.setSampleRate(44100);
|
||||
audioFormat.setSampleSize(16);
|
||||
audioFormat.setChannelCount(2);
|
||||
audioFormat.setCodec("audio/pcm");
|
||||
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
audioFormat.setSampleType(QAudioFormat::UnSignedInt);
|
||||
|
||||
QAudioOutput* audioOutput = new QAudioOutput(audioFormat);
|
||||
QIODevice* ioDevice = audioOutput->start();
|
||||
|
||||
int size = audioOutput->periodSize();
|
||||
char* buffer = new char[size];
|
||||
FILE *pFile = fopen("E:\\download\\out.pcm", "rb");
|
||||
while (!feof(pFile)) {
|
||||
if (audioOutput->bytesFree() < size) {
|
||||
QThread::msleep(1);
|
||||
}
|
||||
|
||||
qint64 len = fread(buffer, 1, size, pFile);
|
||||
if (len <= 0) {
|
||||
break;
|
||||
}
|
||||
ioDevice->write(buffer, len);
|
||||
}
|
||||
fclose(pFile);
|
||||
delete buffer;
|
||||
buffer = NULL;
|
||||
```
|
||||
|
||||
### 使用FFmpeg decode audio
|
||||
- [音视频开发之旅(60) -调试分析FFmpeg (解封装部分的)常用结构体](https://zhuanlan.zhihu.com/p/441055685)
|
||||
|
||||
## 參考資料
|
||||
- [mpv.io](https://mpv.io/)
|
||||
- [《FFmpeg基础知识》](https://opensourcelibs.com/lib/zhangfangtaozft-ffmpeg)
|
||||
- [ffmpeg - 专题 - 简书](https://www.jianshu.com/c/21a6fafc8ee3)
|
||||
206
02. PARA/03. Resources(資源)/Flask.md
Normal file
206
02. PARA/03. Resources(資源)/Flask.md
Normal file
@@ -0,0 +1,206 @@
|
||||
## 執行
|
||||
### Linux
|
||||
```bash
|
||||
export FLASK_APP=RobotRunAutoServer.py
|
||||
export FLASK_DEBUG=1
|
||||
flask run --reload
|
||||
```
|
||||
|
||||
### Windows
|
||||
```
|
||||
set FLASK_APP=RobotRunAutoServer.py
|
||||
set FLASK_DEBUG=1
|
||||
flask run --reload
|
||||
```
|
||||
|
||||
## 路由
|
||||
```python
|
||||
@app.route('/')
|
||||
def index():
|
||||
return 'Index Page'
|
||||
|
||||
@app.route('/query/<SOMETHING>', methods=['GET', 'POST'])
|
||||
def query(SOMETHING):
|
||||
return 'Hello, {}!'.format(SOMETHING)
|
||||
```
|
||||
|
||||
## 靜態檔案
|
||||
```
|
||||
url_for('static', filename='style.css')
|
||||
```
|
||||
|
||||
## 模板
|
||||
```python
|
||||
from flask import render_template
|
||||
|
||||
@app.route('/hello/<name>')
|
||||
def hello(name=None):
|
||||
return render_template('hello.html', name=name)
|
||||
```
|
||||
依照 Flask 的慣例,它會在 templates 資料夾內去找模板檔,而 templates 資料夾的位置會根據這支 web app 是模組或是套件而有所不同。
|
||||
|
||||
如果是模組:
|
||||
```
|
||||
/application.py
|
||||
/templates
|
||||
/hello.html
|
||||
```
|
||||
|
||||
如果是套件:
|
||||
```
|
||||
/application
|
||||
/__init__.py
|
||||
/templates
|
||||
/hello.html
|
||||
```
|
||||
|
||||
## 存取 request 資料
|
||||
客戶端傳來的資料都放在 `request` 這個全域變數內,web app 利用 `request` 提供的資訊與客戶端互動。
|
||||
客戶端的 HTTP 方法存放在 `request` 的 `method` 屬性內,而 form 就存放在 `request` 的 `form` 屬性內。示例:
|
||||
```Python
|
||||
from flask import request
|
||||
from flask import render_template
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
if valid_login(request.form['username'], request.form['password']):
|
||||
return log_the_user_in(request.form['username'])
|
||||
else:
|
||||
error = 'Invalid username / password'
|
||||
# the code below is executed if the request method was GET or the credentials were invalid
|
||||
return render_template('login.html', error=error)
|
||||
```
|
||||
如果 `form` 屬性不存在,伺服器端會引發 `KeyError` 錯誤,客戶端則會收到 HTTP 400 錯誤。
|
||||
|
||||
如果是要取用 `URL` 參數,則使用 `args` 屬性內的 `get()` 方法:
|
||||
```Python
|
||||
searchword = request.args.get('key', '')
|
||||
```
|
||||
|
||||
如果是要取用客戶端上傳的檔案,先確定在前端 HTML 表單的設定正確的屬性 `enctype="multipart/form-data"`,瀏覽器才會正確的把檔案上傳。調用 `request` 的 `files` 屬性就可以調用到檔案:
|
||||
```Python
|
||||
from flask import request
|
||||
|
||||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
f = request.files['the_file']
|
||||
f.save('/var/www/uploads/uploaded_file.txt')
|
||||
```
|
||||
|
||||
`files` 本身是個 dictionary,所以必須呼叫它裡面的 key 才會接到真正的檔案,這個 `file` 物件的行為就像標準的 Python `file` 物件,但它有個 `save()` 方法讓我們把檔案存到自己想要的路徑。
|
||||
|
||||
如果想要沿用客戶端上傳的檔名,則調用 `filename` 屬性,但由於檔名的不可預知,可能會有安全風險,最好用 Werkzeug 的 `secure_filename()` 方法過濾掉:
|
||||
```Python
|
||||
from flask import request
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
f = request.files['the_file']
|
||||
f.save('/var/www/uploads/' + secure_filename(f.filename))
|
||||
```
|
||||
|
||||
如果是要讀取 cookie 就調用 `request` 物件的 `cookies` 屬性,如果是要設置 cookie 就用 `response` 物件的 `set_cookie()` 方法。
|
||||
|
||||
`request` 的 `cookies` 也是一個 dictionary,裡面放了所有可以調用的 cookie。
|
||||
|
||||
但是如果想使用 session 的話,Flask 有提供更完善的 session 機制可以利用,不要手工用 cookie 來管理 session。
|
||||
|
||||
讀取 cookie 的範例:
|
||||
```Python
|
||||
from flask import request
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
username = request.cookies.get('username')
|
||||
# use cookies.get(key) instead of cookies[key] to not get a KeyError if the cookie is missing.
|
||||
```
|
||||
|
||||
存入 cookie 的範例:
|
||||
```Python
|
||||
@app.route('/')
|
||||
def index():
|
||||
resp = make_response(render_template(...))
|
||||
resp.set_cookie('username', 'the username')
|
||||
return resp
|
||||
```
|
||||
|
||||
## 重導頁面與錯誤頁面
|
||||
要重導使用 `redirect()` 方法,要中斷並報錯用 `abort()` 方法:
|
||||
```Python
|
||||
from flask import abort, redirect, url_for
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
abort(401)
|
||||
this_is_never_executed()
|
||||
```
|
||||
|
||||
如果不想使用 Flask 預設的陽春錯誤頁,則利用 `errorhandler()` 修飾子來做客製:
|
||||
```Python
|
||||
from flask import render_template
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
return render_template('page_not_found.html'), 404
|
||||
```
|
||||
|
||||
注意到 `return` 那行最後面的 `404`,雖然上面的修飾子已經是 404,但 `return` 後面還是要加 `404` Flask 才認得這是 404 錯誤頁。
|
||||
|
||||
## Session
|
||||
Session 用來紀錄、辨識用戶的活動,實現方式是加密過的 cookie。
|
||||
|
||||
得先設置密鑰才能使用 session:
|
||||
```Python
|
||||
from flask import Flask, session, redirect, url_for, escape, request
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Set the secret key to some random bytes. Keep this really secret!
|
||||
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if 'username' in session:
|
||||
return f"Logged in as {escape(session['username'])}"
|
||||
return 'You are not logged in'
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
session['username'] = request.form['username']
|
||||
return redirect(url_for('index'))
|
||||
return '''
|
||||
<form method="post">
|
||||
<p><input type=text name=username>
|
||||
<p><input type=submit value=Login>
|
||||
</form>
|
||||
'''
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
# remove the username from the session if it's there
|
||||
session.pop('username', None)
|
||||
return redirect(url_for('index'))
|
||||
```
|
||||
|
||||
前面模板的章節有說過,模板引擎會幫我們把表單的 HTML 過濾掉,而在這裡沒有使用模板引擎,所以手動調用了 `escape()` 方法來濾掉 HTML 碼。
|
||||
|
||||
最後附註一點,瀏覽器可能會限制單一 cookie 容量,如果發現某個值應該要有卻調用不出來的話,想想看是不是超過 cookie 的容量上限了。
|
||||
|
||||
## Log 紀錄
|
||||
Flask app 物件有使用 Python 內建的 `logger` 模組,可以簡單調用:
|
||||
```Python
|
||||
app.logger.debug('A value for debugging')
|
||||
app.logger.warning('A warning occurred (%d apples)', 42)
|
||||
app.logger.error('An error occurred')
|
||||
```
|
||||
108
02. PARA/03. Resources(資源)/HTTP Server/Apache.md
Normal file
108
02. PARA/03. Resources(資源)/HTTP Server/Apache.md
Normal file
@@ -0,0 +1,108 @@
|
||||
## Install
|
||||
```
|
||||
sudo apt update && sudo apt install apache2
|
||||
```
|
||||
|
||||
## 測試Apache
|
||||
```
|
||||
sudo service apache2 status
|
||||
```
|
||||
|
||||
## 設置虛擬主機(Virtual Hosts)
|
||||
假設要建立2個網站*test1.ui-code.com*與*test2.ui-code.com*
|
||||
|
||||
### 建立目錄並設置權限(Permissions)
|
||||
```
|
||||
sudo mkdir -p /var/www/test1.ui-code.com/public_html
|
||||
sudo mkdir -p /var/www/test2.ui-code.com/public_html
|
||||
sudo chmod -R 755 /var/www
|
||||
```
|
||||
|
||||
### 建立測試頁面
|
||||
#### 建立test1.ui-code.com的測試頁面
|
||||
```
|
||||
sudo nano /var/www/test1.ui-code.com/public_html/index.html
|
||||
```
|
||||
填入以下內容:
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to test1.ui-code.com</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to test1.ui-code.com</h2>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 建立test2.ui-code.com的測試頁面
|
||||
```
|
||||
sudo nano /var/www/test2.ui-code.com/public_html/index.html
|
||||
```
|
||||
填入以下內容:
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to test2.ui-code.com</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to test2.ui-code.com</h2>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 建立虛擬主機文件(Virtual Host Files)
|
||||
虛擬主機文件位於 /etc/apache2/sites-available/ 中,其用於告訴 Apache 網頁伺服器如何響應(Respond )各種網域請求(Request)。
|
||||
讓我們為test1.ui-code.com 網域創建一個新的虛擬主機文件。
|
||||
```
|
||||
sudo nano /etc/apache2/sites-available/test1.ui-code.com.conf
|
||||
```
|
||||
|
||||
將以下內容貼上:
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin webmaster@test1.ui-code.com
|
||||
ServerName test1.ui-code.com
|
||||
ServerAlias www.test1.ui-code.com
|
||||
DocumentRoot /var/www/test1.ui-code.com/public_html
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
再來為test2.ui-code.com 網域創建一個新的虛擬主機文件。
|
||||
```
|
||||
sudo nano /etc/apache2/sites-available/test2.ui-code.com.conf
|
||||
```
|
||||
|
||||
將以下內容貼上:
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin webmaster@test2.ui-code.com
|
||||
ServerName test2.ui-code.com
|
||||
ServerAlias www.test2.ui-code.com
|
||||
DocumentRoot /var/www/test2.ui-code.com/public_html
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
### 啟用新的虛擬主機文件(Virtual Host Files)
|
||||
現在我們有兩個虛擬主機文件,我們需要使用 a2ensite 工具來啟用它們。
|
||||
```
|
||||
sudo a2ensite test1.ui-code.com
|
||||
sudo a2ensite test2.ui-code.com
|
||||
```
|
||||
|
||||
測試配置語法是否有錯誤。
|
||||
```
|
||||
apachectl configtest
|
||||
```
|
||||
|
||||
如果「Syntax OK」,重啟 Apache。
|
||||
```
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
## 參考
|
||||
- [[教學][Ubuntu 架站] 在 Ubuntu 20.04 安裝 Apache 網頁伺服器,並架設多個網站(多網域) | 優程式](https://ui-code.com/archives/271)
|
||||
411
02. PARA/03. Resources(資源)/HTTP Server/Nginx.md
Normal file
411
02. PARA/03. Resources(資源)/HTTP Server/Nginx.md
Normal file
@@ -0,0 +1,411 @@
|
||||
## Reverse Proxy(Layer4)
|
||||
[[02. PARA/03. Resources(資源)/HTTP Server/Nginx#Reverse Proxy]]所用的方法雖然可以反向代理多個網站,但是對於像是Trojan這種TLS不行被中斷的服務來說,會導致handshake失敗,所以需要用Nginx的stream來做Layer 4的轉發。
|
||||
|
||||
### docker-compose.yaml
|
||||
需要先把`nginx.conf`與`mime.types`給copy到data目錄下。
|
||||
依序執行下面2個命令:
|
||||
```shell
|
||||
sudo docker run --rm -it nginx cat /etc/nginx/nginx.conf > nginx.conf
|
||||
sudo docker run --rm -it nginx cat /etc/nginx/mime.types > mime.types
|
||||
```
|
||||
然後:
|
||||
```shell
|
||||
mkdir data ;\
|
||||
mv nginx.conf mime.types data
|
||||
```
|
||||
|
||||
建立`docker-compose.yaml`
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
nginx_reverseproxy_l4:
|
||||
container_name: nginx
|
||||
restart: always
|
||||
image: nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./data:/etc/nginx
|
||||
```
|
||||
|
||||
修改`data/nginx.conf`:
|
||||
```nginx
|
||||
events {
|
||||
worker_connections 1024; <-- event 不用動
|
||||
}
|
||||
|
||||
stream {
|
||||
map $ssl_preread_server_name $backend_name {
|
||||
tjn.awin.one trojan;
|
||||
storj.awin.one swag;
|
||||
}
|
||||
|
||||
# trojan
|
||||
upstream trojan {
|
||||
server 192.168.1.31:443;
|
||||
}
|
||||
|
||||
# swag
|
||||
upstream swag {
|
||||
server 192.168.1.20:44320;
|
||||
}
|
||||
|
||||
# 监听 443 并开启 ssl_preread
|
||||
server {
|
||||
listen 80 reuseport;
|
||||
listen 443 reuseport;
|
||||
listen [::]:443 reuseport;
|
||||
proxy_pass $backend_name;
|
||||
ssl_preread on;
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
... <-- http 不用動
|
||||
}
|
||||
```
|
||||
|
||||
2022/05/17更新:
|
||||
```nginx
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
stream {
|
||||
map $ssl_preread_server_name $backend_name {
|
||||
tjn.awin.one trojan;
|
||||
storj.awin.one swag;
|
||||
blog.awin.one swag;
|
||||
gitea.awin.one swag;
|
||||
}
|
||||
|
||||
# trojan
|
||||
upstream trojan {
|
||||
server 192.168.1.31:443;
|
||||
}
|
||||
|
||||
# swag
|
||||
upstream swag {
|
||||
server 192.168.1.20:44320;
|
||||
}
|
||||
|
||||
# 监听 443 并开启 ssl_preread
|
||||
server {
|
||||
listen 443 reuseport;
|
||||
listen [::]:443 reuseport;
|
||||
proxy_pass $backend_name;
|
||||
ssl_preread on;
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
|
||||
## 新增這一段,沒有這一段會造成http無法連線,所以拿憑證會失敗
|
||||
server {
|
||||
listen 80;
|
||||
server_name tjn.awin.one;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.31:80;
|
||||
|
||||
# 把 IP、Protocol 等 header 都一起送給反向代理的 server
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
重點來源:
|
||||
1. [Trojan 共用 443 端口方案 - 程小白](https://www.chengxiaobai.cn/record/trojan-shared-443-port-scheme.html)
|
||||
2. [NaiveProxy + Trojan + 多HTTPS站点共存,复用443端口 | 心底的河流](https://lhy.life/20200815-naiveproxy+trojan/)
|
||||
3. `$ssl_preread_server_name`的官方說明:[Module ngx_stream_ssl_preread_module](http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html)
|
||||
|
||||
## Reverse Proxy
|
||||
|
||||
> **重要**:確定docker與docker-compose已經安裝好。
|
||||
> 參考[[RaspberryPi#Docker]]與[[RaspberryPi#docker-compose]]
|
||||
|
||||
### Use SWAG docker
|
||||
[swag](https://github.com/linuxserver/docker-swag)(之前叫做letsencrypt)是linuxserver.io包裝的Nginx webserver和reverse proxy的container。
|
||||
|
||||
#### Setup HTTPS
|
||||
1. 建立folder
|
||||
```bash
|
||||
mkdir -p ~/dockers/linuxserverswag ; cd ~/dockers/linuxserverswag
|
||||
```
|
||||
|
||||
2. 建立`docker-compose.yaml`:
|
||||
```
|
||||
vim docker-compose.yaml
|
||||
```
|
||||
|
||||
填入內容如下:
|
||||
```yaml
|
||||
version: "2.1"
|
||||
services:
|
||||
swag:
|
||||
image: ghcr.io/linuxserver/swag
|
||||
container_name: swag
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Asia/Taipei
|
||||
- URL=awin.one
|
||||
- SUBDOMAINS=wildcard
|
||||
- VALIDATION=dns
|
||||
- CERTPROVIDER= #optional
|
||||
- DNSPLUGIN=cloudflare #optional
|
||||
- PROPAGATION= #optional
|
||||
- DUCKDNSTOKEN= #optional
|
||||
- EMAIL=awinhuang@gmail.com
|
||||
- ONLY_SUBDOMAINS=false #optional
|
||||
- EXTRA_DOMAINS= #optional
|
||||
- STAGING=false #optional
|
||||
- MAXMINDDB_LICENSE_KEY= #optional
|
||||
volumes:
|
||||
- ./config:/config
|
||||
ports:
|
||||
- 44320:443
|
||||
- 8020:80 #optional
|
||||
restart: unless-stopped
|
||||
|
||||
```
|
||||
|
||||
3. 先跑一次:
|
||||
```bash
|
||||
sudo docker-compose up
|
||||
```
|
||||
會發現有錯誤,這是正常的,錯誤訊息像這樣:
|
||||
```
|
||||
swag | Unsafe permissions on credentials configuration file: /config/dns-conf/cloudflare.ini
|
||||
swag | Cleaning up challenges
|
||||
swag | Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address and Global key?)
|
||||
swag | ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.
|
||||
```
|
||||
按`ctrl + c`退出。這時候`config`目錄也會有swag所mapping出來的相關檔案。修改`config/dns-conf/cloudflare.ini`:
|
||||
```bash
|
||||
vim config/dns-conf/cloudflare.ini
|
||||
```
|
||||
把`config/dns-conf/cloudflare.ini`改為:
|
||||
```
|
||||
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
|
||||
# Replace with your values
|
||||
|
||||
With global api key:
|
||||
dns_cloudflare_email = awinhuang@gmail.com
|
||||
dns_cloudflare_api_key = <YOUR_API_KEY_FROM_CLOUDFLARE>
|
||||
|
||||
# With token (comment out both lines above and uncomment below):
|
||||
#dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
|
||||
```
|
||||
|
||||
4. 再跑一次:
|
||||
```bash
|
||||
sudo docker-compose up
|
||||
```
|
||||
這一次就可以順利取得認證了,訊息像這樣:
|
||||
```
|
||||
swag | IMPORTANT NOTES:
|
||||
swag | - Congratulations! Your certificate and chain have been saved at:
|
||||
swag | /etc/letsencrypt/live/awin.one/fullchain.pem
|
||||
swag | Your key file has been saved at:
|
||||
swag | /etc/letsencrypt/live/awin.one/privkey.pem
|
||||
swag | Your certificate will expire on 2021-04-26. To obtain a new or
|
||||
swag | tweaked version of this certificate in the future, simply run
|
||||
swag | certbot again. To non-interactively renew *all* of your
|
||||
swag | certificates, run "certbot renew"
|
||||
swag | - If you like Certbot, please consider supporting our work by:
|
||||
swag |
|
||||
swag | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
|
||||
swag | Donating to EFF: https://eff.org/donate-le
|
||||
swag |
|
||||
swag | New certificate generated; starting nginx
|
||||
swag | Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind,
|
||||
swag | and add a new env variable "MAXMINDDB_LICENSE_KEY", set to your license key.
|
||||
swag | [cont-init.d] 50-config: exited 0.
|
||||
swag | [cont-init.d] 60-renew: executing...
|
||||
swag | The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am).
|
||||
swag | [cont-init.d] 60-renew: exited 0.
|
||||
swag | [cont-init.d] 70-templates: executing...
|
||||
swag | [cont-init.d] 70-templates: exited 0.
|
||||
swag | [cont-init.d] 99-custom-files: executing...
|
||||
swag | [custom-init] no custom files found exiting...
|
||||
swag | [cont-init.d] 99-custom-files: exited 0.
|
||||
swag | [cont-init.d] done.
|
||||
swag | [services.d] starting services
|
||||
swag | [services.d] done.
|
||||
swag | nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
|
||||
swag | Server ready
|
||||
```
|
||||
最後一行的`swag | Server ready`表示server已經跑起來了。先按下`ctrl + c`退出,再來設定reverse proxy。
|
||||
|
||||
5. 修正`config/dns-conf/cloudflare.ini`的安全性問題
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; chmod 600 config/dns-conf/cloudflare.ini
|
||||
```
|
||||
|
||||
#### Setup reverse proxy
|
||||
1. 建立folder:
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; mkdir -p config/nginx/sites-available config/nginx/sites-enabled
|
||||
```
|
||||
|
||||
2. 建立以下檔案:
|
||||
- `config/nginx/sites-available/common.conf`,內容:
|
||||
```
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
```
|
||||
- `config/nginx/sites-available/common_location.conf`,內容:
|
||||
```
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
```
|
||||
- `config/nginx/sites-available/storj.conf`,內容:
|
||||
```
|
||||
upstream storj {
|
||||
server 192.168.1.11:14002;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name storj.awin.one;
|
||||
|
||||
include /config/nginx/sites-available/common.conf;
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.11:14002/;
|
||||
include /config/nginx/sites-available/common_location.conf;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
3. 在`config/nginx/sites-enabled`裡面建立要enable的config:
|
||||
```
|
||||
cd config/nginx/sites-enabled ; ln -s ../sites-available/storj.conf . ; cd -
|
||||
```
|
||||
|
||||
4. 修改`config/nginx/nginx.conf`:
|
||||
```
|
||||
vim config/nginx/nginx.conf
|
||||
```
|
||||
找到`include /config/nginx/site-confs/*;`這一行,把它comment掉,在下面新增一行:
|
||||
```
|
||||
include /config/nginx/sites-enabled/*.conf;
|
||||
```
|
||||
|
||||
5. 啟動swag:
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; sudo docker-compose up -d
|
||||
```
|
||||
|
||||
#### Restart
|
||||
```bash
|
||||
cd ~/dockers/linuxserverswag ; sudo docker-compose restart
|
||||
```
|
||||
|
||||
#### Update certification
|
||||
1. 進到dockr的swag bash裡面:`sudo docker exec -it swag /bin/bash`
|
||||
2. 輸入 `certbot renew`
|
||||
3. ![[Pasted image 20210422205534.png]]
|
||||
|
||||
#### Trouble shooting
|
||||
- 如果遇到類似下面的錯誤:
|
||||
```
|
||||
ERROR: for swag Cannot start service swag: driver failed programming external connectivity on endpoint swag (7c527d046631e0957de0b831ca25bed296de76e2eb96378964cb0110d7fb017d): Bind for 0.0.0.0:443 failed: port is already allocated.
|
||||
```
|
||||
表示有其他程式佔住了80 port,可能是其他docker container或是service,必須先關閉它們。[^1]
|
||||
|
||||
#### 參考來源
|
||||
1. [linuxserver/docker-swag: Nginx webserver and reverse proxy with php support and a built-in Certbot (Let's Encrypt) client. It also contains fail2ban for intrusion prevention.](https://github.com/linuxserver/docker-swag)
|
||||
2. [How to set up an easy and secure reverse proxy with Docker, Nginx & Letsencrypt](https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/)
|
||||
3. [SWAG setup - LinuxServer.io](https://docs.linuxserver.io/general/swag#understanding-the-proxy-conf-structure)
|
||||
|
||||
-----
|
||||
- 參考
|
||||
- [NGINX Docs | NGINX Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
|
||||
- [Nginx 設定反向代理 Reverse Proxy](https://www.opencli.com/linux/nginx-config-reverse-proxy)
|
||||
- [用 Nginx 伺服器建立反向代理](https://noob.tw/nginx-reverse-proxy/)
|
||||
- [俄羅斯不愧是戰鬥民族:nginx - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10188498)
|
||||
|
||||
[^1]: [啟動docker時出現「Cannot start service :driver failed programming external connectivity on endpoint xxx, Bind for 0.0.0.0:80 failed: port is already allocated 」](https://mitsuiwei.com/docker-cannot-start-service/)
|
||||
|
||||
# 新增網站
|
||||
## 新網站自帶SSL
|
||||
進到Rasperberry Pi(192.168.1.20)之後,切換到`~/dockers/nginx_reverseproxy_l4`,並編輯`data/nginx.conf`,在裡面加入新網站的網域與要轉址的ip。
|
||||
例如,要加入一個trojan VPN,trojan VPN本身就有SSL加密,所以不用再經過swag,`data/nginx.conf`的改變如下:
|
||||
![[Pasted image 20220506145247.png]]
|
||||
|
||||
## 新http網站
|
||||
如果新的網站只是一般的http網站,那便把它掛到swag後面,這樣就可以經由https來訪問,假如要加入一個blog網站,但因為我們有2層的reverse proxy,第一層是layer 4,第2層是swag,所以如果是自帶https的要掛到layer 4之後,沒有https要先由layer 4轉到swag再轉到實際的server上。
|
||||
步驟如下:
|
||||
1. 進到Rasperberry Pi(192.168.1.20)
|
||||
2. 設定nginx_reverseproxy_l4,這邊我們需要把「沒有https」的網站由nginx_reverseproxy_l4導引到linuxserverswag
|
||||
```
|
||||
cd ~/dockers/nginx_reverseproxy_l4
|
||||
vim data/nginx.conf
|
||||
```
|
||||
加入下圖紅框的設定:
|
||||
![[Pasted image 20220516152230.png]]
|
||||
3. 切換到`~/dockers/linuxserverswag/config/nginx/sites-available`
|
||||
4. 新增一個confing檔,例如叫做`blog.conf`,內容如下:
|
||||
```
|
||||
upstream blog {
|
||||
server 192.168.1.30:80; ## 網址
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name blog.awin.one; ## 網域
|
||||
|
||||
include /config/nginx/sites-available/common.conf;
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.30:80/; ## 網址
|
||||
include /config/nginx/sites-available/common_location.conf;
|
||||
}
|
||||
}
|
||||
```
|
||||
改變3個有註解的地方就可以了。
|
||||
5. 剛剛的config檔,新增到`sites-enabled`裡面
|
||||
1. `cd ~/dockers/linuxserverswag/config/nginx/sites-enabled`
|
||||
2. `ln -s ../sites-available/blog.conf .`
|
||||
6. 重新啟動swag:`cd ~/dockers/linuxserverswag ; sudo docker-compose restart`
|
||||
2
02. PARA/03. Resources(資源)/IPFS.md
Normal file
2
02. PARA/03. Resources(資源)/IPFS.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Source code: https://github.com/ipfs/ipfs
|
||||
- Document: https://docs.ipfs.io/
|
||||
1
02. PARA/03. Resources(資源)/Linux/Programming.md
Normal file
1
02. PARA/03. Resources(資源)/Linux/Programming.md
Normal file
@@ -0,0 +1 @@
|
||||
- [The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/?fbclid=IwAR0iZMkTCkhHzGbL41qqAmdZADiGtZe7Cw556NMuXpwv4cfD0yEer0lcCiU)
|
||||
14
02. PARA/03. Resources(資源)/Linux/Timezone.md
Normal file
14
02. PARA/03. Resources(資源)/Linux/Timezone.md
Normal file
@@ -0,0 +1,14 @@
|
||||
用`timedatectl`可以看現在的時間、時區等等。也可以改變時區。
|
||||
![[Pasted image 20220516125616.png]]
|
||||
|
||||
用`timedatectl list-timezones`列出所有時區,我們已經知道我們的時區是Taipei,所以可以用grep直接把它抓出來:
|
||||
```bash
|
||||
timedatectl list-timezones | grep -i taipei
|
||||
```
|
||||
結果如下:
|
||||
![[Pasted image 20220516125742.png]]
|
||||
|
||||
設定時區:
|
||||
```
|
||||
timedatectl set-timezone Asia/Taipei
|
||||
```
|
||||
13
02. PARA/03. Resources(資源)/Linux/Ubuntu.md
Normal file
13
02. PARA/03. Resources(資源)/Linux/Ubuntu.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Hyper-V
|
||||
### Set screen resolution
|
||||
1. `sudo vim /etc/default/grub`
|
||||
2. Find `GRUB_CMDLINE_LINUX_DEFAULT`
|
||||
3. Add `video=hyperv_fb:1600x9000`, e.g. `GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=hyperv_fb:1600x900"`
|
||||
4. `sudo update-grub `
|
||||
5. `sudo reboot`
|
||||
|
||||
## VirtualBox
|
||||
### Settting
|
||||
#### 操作分享資料夾
|
||||
需要把目前的使用者加到`vboxsf` group才能夠操作分享資料夾。
|
||||
`sudo adduser $USER vboxsf`
|
||||
57
02. PARA/03. Resources(資源)/Linux/scp.md
Normal file
57
02. PARA/03. Resources(資源)/Linux/scp.md
Normal file
@@ -0,0 +1,57 @@
|
||||
`scp` 指令的語法跟一般的 `cp` 類似,只不過 `scp` 可以在不同的 Linux 主機之間複製檔案,其語法為:
|
||||
```
|
||||
scp [帳號@來源主機]:來源檔案 [帳號@目的主機]:目的檔案
|
||||
```
|
||||
|
||||
# 保留檔案時間與權限
|
||||
若要讓檔案在複製之後,還可保留原本的修改時間、存取時間與權限,可以加上 `-p` 參數:
|
||||
```
|
||||
scp -p /path/file1 myuser@192.168.0.1:/path/file2
|
||||
```
|
||||
|
||||
# 資料壓縮
|
||||
若要將資料壓縮之後再傳送,減少網路頻寬的使用量,可以加上 `-C` 參數:
|
||||
```
|
||||
scp -C /path/file1 myuser@192.168.0.1:/path/file2
|
||||
```
|
||||
|
||||
# 限制傳輸速度
|
||||
若要限制網路的使用頻寬,可以用 `-l` 指定可用的網路頻寬上限值(單位為 Kbit/s):
|
||||
```
|
||||
# 限制傳輸速度為 400 Kbit/s
|
||||
scp -l 400 /path/file1 myuser@192.168.0.1:/path/file2
|
||||
```
|
||||
|
||||
這樣就會限制 `scp` 只能使用 `400` Kbit/s,也就是 `400 / 8 = 50` KB/s。
|
||||
|
||||
# 自訂連接埠
|
||||
一般 SSH 伺服器的連接埠號為 22,如果遇到使用非標準埠號的伺服器,可以用 `-P` 來指定埠號。若遠端的 SSH 伺服器使用 `2222` 這個連接埠,我們就可以這樣複製檔案:
|
||||
```
|
||||
# 使用 2222 連接埠
|
||||
scp -P 2222 /path/file1 myuser@192.168.0.1:/path/file2
|
||||
```
|
||||
|
||||
# IPv4 與 IPv6
|
||||
`-4` 與 `-6` 兩個參數分別可以讓 `scp` 使用 IPv4 與 IPv6 來傳輸資料:
|
||||
```
|
||||
# 使用 IPv4
|
||||
scp -4 /path/file1 myuser@192.168.0.1:/path/file2
|
||||
|
||||
# 使用 IPv6
|
||||
scp -6 /path/file1 myuser@192.168.0.1:/path/file2
|
||||
```
|
||||
|
||||
# 更快的方法:使用SSH+TAR
|
||||
```
|
||||
ssh 使用者@主機 "cd 目標目錄 ;tar -zcvf - 目標" | cat > 目標.tar.gz
|
||||
```
|
||||
|
||||
例:
|
||||
```
|
||||
ssh 192.168.0.22 "cd /var ;tar -zcvf - log" | cat > 22_log.tar.gz
|
||||
```
|
||||
|
||||
# 參考
|
||||
- [SSH + TAR 取代 SCP @ Vexed's Blog :: 隨意窩 Xuite日誌](https://blog.xuite.net/vexed/tech/586811949)
|
||||
- [Linux中互传文件:ssh+tar 与Scp 比较 - 简书](https://www.jianshu.com/p/856a2dc883e0)
|
||||
- [轉貼--ssh tar 命令把遠端檔拉回來或推過去 --- 山城風雲的點滴](http://jimsung168.blogspot.com/2014/01/ssh-tar.html)
|
||||
29
02. PARA/03. Resources(資源)/MkDocs.md
Normal file
29
02. PARA/03. Resources(資源)/MkDocs.md
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
https://www.mkdocs.org/
|
||||
|
||||
## Commands
|
||||
`mkdocs new [dir-name]` - Create a new project.
|
||||
`mkdocs serve` - Start the live-reloading docs server. Goto `http://127.0.0.1:8000` to view WEB pages.
|
||||
`mkdocs build` - Build the documentation site.
|
||||
`mkdocs build --clean` - Clean the output folder.
|
||||
`mkdocs -h` - Print help message and exit.
|
||||
|
||||
## Theme
|
||||
#### Change theme to [mkdocs-material](https://squidfunk.github.io/mkdocs-material/getting-started/)
|
||||
1. Install by pip
|
||||
```
|
||||
pip install mkdocs-material
|
||||
```
|
||||
2. Modify `mkdocs.yml`
|
||||
```
|
||||
site_name: RobotRunDoc
|
||||
nav:
|
||||
- Home: index.md
|
||||
- 'User Guide':
|
||||
- 'Installation': 'Installation/installation.md'
|
||||
- 'APIs': 'APIs/Kong.md'
|
||||
- About: about.md
|
||||
|
||||
theme:
|
||||
name: material <-- HERE
|
||||
```
|
||||
134
02. PARA/03. Resources(資源)/MySQL.md
Normal file
134
02. PARA/03. Resources(資源)/MySQL.md
Normal file
@@ -0,0 +1,134 @@
|
||||
## Install MySQL
|
||||
- 下載MySQL: https://downloads.mysql.com/archives/installer/
|
||||
- 安裝
|
||||
- 安裝之後,執行MSQL Installer - Community
|
||||
- ![[Pasted image 20210423152623.png]]
|
||||
- 選擇MySQL Server的Reconfigure
|
||||
- ![[Pasted image 20210423152747.png]]
|
||||
- 依序設定port、帳號密碼。
|
||||
|
||||
## Install MariaDB
|
||||
1. `sudo apt install mariadb-server`
|
||||
2. Configuring MariaDB: `sudo mysql_secure_installation`
|
||||
這步驟會問幾個問題,大概是:
|
||||
1. 輸入目前root的密碼
|
||||
2. 是否要切換為unix_socket? -> No
|
||||
3. 要變更root的密碼嗎?-> No
|
||||
3. Testing MariaDB: `sudo systemctl status mariadb`
|
||||
4. Show version: `sudo mysqladmin version`
|
||||
|
||||
## Syntax
|
||||
### Basic
|
||||
login as root: `mysql -u root -p`
|
||||
|
||||
### User management
|
||||
#### Create user
|
||||
```sql
|
||||
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'test123test!';
|
||||
```
|
||||
|
||||
#### Remove user
|
||||
```sql
|
||||
DROP USER 'testuser'@'localhost';
|
||||
```
|
||||
|
||||
#### Grant user's permission
|
||||
Below commands grant user's permission to *CREATE* and *DROP*.
|
||||
```sql
|
||||
GRANT CREATE ON *.* TO 'testuser'@'localhost';
|
||||
GRANT DROP ON tutorial_database.* TO 'testuser'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
#### Remove user's permission
|
||||
```sql
|
||||
REVOKE <permission> ON database.table FROM 'user'@'localhost';
|
||||
```
|
||||
上面命令中的`<permission>`必須替換成下面的其中一個:
|
||||
- ALL - Allow complete access to a specific database. If a database is not specified, then allow complete access to the entirety of MySQL.
|
||||
- CREATE - Allow a user to create databases and tables.
|
||||
- DELETE - Allow a user to delete rows from a table.
|
||||
- DROP - Allow a user to drop databases and tables.
|
||||
- EXECUTE - Allow a user to execute stored routines.
|
||||
- GRANT OPTION - Allow a user to grant or remove another user's privileges.
|
||||
- INSERT - Allow a user to insert rows from a table.
|
||||
- SELECT - Allow a user to select data from a database.
|
||||
- SHOW DATABASES- Allow a user to view a list of all databases.
|
||||
- UPDATE - Allow a user to update rows in a table.
|
||||
|
||||
例如:`REVOKE CREATE ON *.* FROM 'testuser'@'localhost';`
|
||||
最後,不要忘記`FLUSH PRIVILEGES;`
|
||||
|
||||
#### Show user's permission
|
||||
```sql
|
||||
SHOW GRANTS FOR 'testuser'@'localhost';
|
||||
```
|
||||
|
||||
#### Change user's password
|
||||
```sql
|
||||
ALTER USER 'user-name'@'localhost' IDENTIFIED BY 'NEW_USER_PASSWORD';
|
||||
```
|
||||
|
||||
#### View all users
|
||||
```sql
|
||||
SELECT User,Host FROM mysql.user;
|
||||
```
|
||||
|
||||
### Database
|
||||
#### Show all databases
|
||||
```sql
|
||||
SHOW DATABASES;
|
||||
```
|
||||
|
||||
### Create table
|
||||
The rule:
|
||||
```sql
|
||||
CREATE TABLE table_name (column_name column_type);
|
||||
```
|
||||
|
||||
Example:
|
||||
```sql
|
||||
CREATE TABLE tutorials_tbl(
|
||||
tutorial_id INT NOT NULL AUTO_INCREMENT,
|
||||
tutorial_title VARCHAR(100) NOT NULL,
|
||||
tutorial_author VARCHAR(40) NOT NULL,
|
||||
submission_date DATE,
|
||||
PRIMARY KEY ( tutorial_id )
|
||||
);
|
||||
```
|
||||
|
||||
```sql
|
||||
use autostation;
|
||||
CREATE TABLE station_state(
|
||||
id CHAR(255) NOT NULL,
|
||||
ip CHAR(255) NOT NULL,
|
||||
dhcp_ip CHAR(255) NOT NULL,
|
||||
name CHAR(255) NOT NULL,
|
||||
setupfilemd5 CHAR(255) NOT NULL,
|
||||
setupfileversion CHAR(255) NOT NULL,
|
||||
status CHAR(255) NOT NULL,
|
||||
update_time DATETIME NOT NULL,
|
||||
PRIMARY KEY ( id )
|
||||
);
|
||||
```
|
||||
|
||||
### Insert
|
||||
The rule:
|
||||
```
|
||||
INSERT INTO <table_name> ( field1, field2,...fieldN )
|
||||
VALUES
|
||||
( value1, value2,...valueN );
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
INSERT INTO client_state ( field1, field2,...fieldN )
|
||||
VALUES
|
||||
( value1, value2,...valueN );
|
||||
```
|
||||
|
||||
## 參考
|
||||
- [Create a MySQL User on Linux via Command Line | Liquid Web](https://www.liquidweb.com/kb/create-a-mysql-user-on-linux-via-command-line/)
|
||||
- [Grant Permissions to a MySQL User on Linux via Command Line | Liquid Web](https://www.liquidweb.com/kb/grant-permissions-to-a-mysql-user-on-linux-via-command-line/)
|
||||
- [Remove Permissions for a MySQL User on Linux via Command Line - Liquid Web](https://www.liquidweb.com/kb/remove-permissions-for-a-mysql-user-on-linux-via-command-line/)
|
||||
- [Remove a MySQL User on Linux via Command Line - Liquid Web](https://www.liquidweb.com/kb/remove-a-mysql-user-on-linux-via-command-line/)
|
||||
2
02. PARA/03. Resources(資源)/NextDNS.md
Normal file
2
02. PARA/03. Resources(資源)/NextDNS.md
Normal file
@@ -0,0 +1,2 @@
|
||||
DNS服務公司,用途如下:
|
||||
- [量身打造、100% 客製化抗廣告的 NextDNS - jkgtw's blog](https://www.jkg.tw/p3382/)
|
||||
292
02. PARA/03. Resources(資源)/Nginx.md
Normal file
292
02. PARA/03. Resources(資源)/Nginx.md
Normal file
@@ -0,0 +1,292 @@
|
||||
## Reverse Proxy(Layer4)
|
||||
[[02. PARA/03. Resources(資源)/Nginx#Reverse Proxy]]所用的方法雖然可以反向代理多個網站,但是對於像是Trojan這種TLS不行被中斷的服務來說,會導致handshake失敗,所以需要用Nginx的stream來做Layer 4的轉發。
|
||||
|
||||
### docker-compose.yaml
|
||||
需要先把`nginx.conf`與`mime.types`給copy到data目錄下。
|
||||
依序執行下面2個命令:
|
||||
```shell
|
||||
sudo docker run --rm -it nginx cat /etc/nginx/nginx.conf > nginx.conf
|
||||
sudo docker run --rm -it nginx cat /etc/nginx/mime.types > mime.types
|
||||
```
|
||||
然後:
|
||||
```shell
|
||||
mkdir data ;\
|
||||
mv nginx.conf mime.types data
|
||||
```
|
||||
|
||||
建立`docker-compose.yaml`
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
nginx_reverseproxy_l4:
|
||||
container_name: nginx
|
||||
restart: always
|
||||
image: nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./data:/etc/nginx
|
||||
```
|
||||
|
||||
修改`data/nginx.conf`:
|
||||
```nginx
|
||||
events {
|
||||
worker_connections 1024; <-- event 不用動
|
||||
}
|
||||
|
||||
stream {
|
||||
map $ssl_preread_server_name $backend_name {
|
||||
tjn.awin.one trojan;
|
||||
storj.awin.one swag;
|
||||
}
|
||||
|
||||
# trojan
|
||||
upstream trojan {
|
||||
server 192.168.1.31:443;
|
||||
}
|
||||
|
||||
# swag
|
||||
upstream swag {
|
||||
server 192.168.1.20:44320;
|
||||
}
|
||||
|
||||
# 监听 443 并开启 ssl_preread
|
||||
server {
|
||||
listen 80 reuseport;
|
||||
listen 443 reuseport;
|
||||
listen [::]:443 reuseport;
|
||||
proxy_pass $backend_name;
|
||||
ssl_preread on;
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
... <-- http 不用動
|
||||
}
|
||||
```
|
||||
|
||||
重點來源:
|
||||
1. [Trojan 共用 443 端口方案 - 程小白](https://www.chengxiaobai.cn/record/trojan-shared-443-port-scheme.html)
|
||||
2. [NaiveProxy + Trojan + 多HTTPS站点共存,复用443端口 | 心底的河流](https://lhy.life/20200815-naiveproxy+trojan/)
|
||||
3. `$ssl_preread_server_name`的官方說明:[Module ngx_stream_ssl_preread_module](http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html)
|
||||
|
||||
## Reverse Proxy
|
||||
|
||||
> **重要**:確定docker與docker-compose已經安裝好。
|
||||
> 參考[[RaspberryPi#Docker]]與[[RaspberryPi#docker-compose]]
|
||||
|
||||
### Use SWAG docker
|
||||
[swag](https://github.com/linuxserver/docker-swag)(之前叫做letsencrypt)是linuxserver.io包裝的Nginx webserver和reverse proxy的container。
|
||||
|
||||
#### Setup HTTPS
|
||||
1. 建立folder
|
||||
```bash
|
||||
mkdir -p ~/dockers/linuxserverswag ; cd ~/dockers/linuxserverswag
|
||||
```
|
||||
|
||||
2. 建立`docker-compose.yaml`:
|
||||
```
|
||||
vim docker-compose.yaml
|
||||
```
|
||||
|
||||
填入內容如下:
|
||||
```yaml
|
||||
version: "2.1"
|
||||
services:
|
||||
swag:
|
||||
image: ghcr.io/linuxserver/swag
|
||||
container_name: swag
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Asia/Taipei
|
||||
- URL=awin.one
|
||||
- SUBDOMAINS=wildcard
|
||||
- VALIDATION=dns
|
||||
- CERTPROVIDER= #optional
|
||||
- DNSPLUGIN=cloudflare #optional
|
||||
- PROPAGATION= #optional
|
||||
- DUCKDNSTOKEN= #optional
|
||||
- EMAIL=awinhuang@gmail.com
|
||||
- ONLY_SUBDOMAINS=false #optional
|
||||
- EXTRA_DOMAINS= #optional
|
||||
- STAGING=false #optional
|
||||
- MAXMINDDB_LICENSE_KEY= #optional
|
||||
volumes:
|
||||
- ./config:/config
|
||||
ports:
|
||||
- 44320:443
|
||||
- 8020:80 #optional
|
||||
restart: unless-stopped
|
||||
|
||||
```
|
||||
|
||||
3. 先跑一次:
|
||||
```bash
|
||||
sudo docker-compose up
|
||||
```
|
||||
會發現有錯誤,這是正常的,錯誤訊息像這樣:
|
||||
```
|
||||
swag | Unsafe permissions on credentials configuration file: /config/dns-conf/cloudflare.ini
|
||||
swag | Cleaning up challenges
|
||||
swag | Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address and Global key?)
|
||||
swag | ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.
|
||||
```
|
||||
按`ctrl + c`退出。這時候`config`目錄也會有swag所mapping出來的相關檔案。修改`config/dns-conf/cloudflare.ini`:
|
||||
```bash
|
||||
vim config/dns-conf/cloudflare.ini
|
||||
```
|
||||
把`config/dns-conf/cloudflare.ini`改為:
|
||||
```
|
||||
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
|
||||
# Replace with your values
|
||||
|
||||
With global api key:
|
||||
dns_cloudflare_email = awinhuang@gmail.com
|
||||
dns_cloudflare_api_key = <YOUR_API_KEY_FROM_CLOUDFLARE>
|
||||
|
||||
# With token (comment out both lines above and uncomment below):
|
||||
#dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
|
||||
```
|
||||
|
||||
4. 再跑一次:
|
||||
```bash
|
||||
sudo docker-compose up
|
||||
```
|
||||
這一次就可以順利取得認證了,訊息像這樣:
|
||||
```
|
||||
swag | IMPORTANT NOTES:
|
||||
swag | - Congratulations! Your certificate and chain have been saved at:
|
||||
swag | /etc/letsencrypt/live/awin.one/fullchain.pem
|
||||
swag | Your key file has been saved at:
|
||||
swag | /etc/letsencrypt/live/awin.one/privkey.pem
|
||||
swag | Your certificate will expire on 2021-04-26. To obtain a new or
|
||||
swag | tweaked version of this certificate in the future, simply run
|
||||
swag | certbot again. To non-interactively renew *all* of your
|
||||
swag | certificates, run "certbot renew"
|
||||
swag | - If you like Certbot, please consider supporting our work by:
|
||||
swag |
|
||||
swag | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
|
||||
swag | Donating to EFF: https://eff.org/donate-le
|
||||
swag |
|
||||
swag | New certificate generated; starting nginx
|
||||
swag | Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind,
|
||||
swag | and add a new env variable "MAXMINDDB_LICENSE_KEY", set to your license key.
|
||||
swag | [cont-init.d] 50-config: exited 0.
|
||||
swag | [cont-init.d] 60-renew: executing...
|
||||
swag | The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am).
|
||||
swag | [cont-init.d] 60-renew: exited 0.
|
||||
swag | [cont-init.d] 70-templates: executing...
|
||||
swag | [cont-init.d] 70-templates: exited 0.
|
||||
swag | [cont-init.d] 99-custom-files: executing...
|
||||
swag | [custom-init] no custom files found exiting...
|
||||
swag | [cont-init.d] 99-custom-files: exited 0.
|
||||
swag | [cont-init.d] done.
|
||||
swag | [services.d] starting services
|
||||
swag | [services.d] done.
|
||||
swag | nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
|
||||
swag | Server ready
|
||||
```
|
||||
最後一行的`swag | Server ready`表示server已經跑起來了。先按下`ctrl + c`退出,再來設定reverse proxy。
|
||||
|
||||
5. 修正`config/dns-conf/cloudflare.ini`的安全性問題
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; chmod 600 config/dns-conf/cloudflare.ini
|
||||
```
|
||||
|
||||
#### Setup reverse proxy
|
||||
1. 建立folder:
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; mkdir -p config/nginx/sites-available config/nginx/sites-enabled
|
||||
```
|
||||
|
||||
2. 建立以下檔案:
|
||||
- `config/nginx/sites-available/common.conf`,內容:
|
||||
```
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
```
|
||||
- `config/nginx/sites-available/common_location.conf`,內容:
|
||||
```
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
```
|
||||
- `config/nginx/sites-available/storj.conf`,內容:
|
||||
```
|
||||
upstream storj {
|
||||
server 192.168.1.11:14002;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name storj.awin.one;
|
||||
|
||||
include /config/nginx/sites-available/common.conf;
|
||||
include /config/nginx/ssl.conf;
|
||||
|
||||
location / {
|
||||
proxy_pass http://192.168.1.11:14002/;
|
||||
include /config/nginx/sites-available/common_location.conf;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
3. 在`config/nginx/sites-enabled`裡面建立要enable的config:
|
||||
```
|
||||
cd config/nginx/sites-enabled ; ln -s ../sites-available/storj.conf . ; cd -
|
||||
```
|
||||
|
||||
4. 修改`config/nginx/nginx.conf`:
|
||||
```
|
||||
vim config/nginx/nginx.conf
|
||||
```
|
||||
找到`include /config/nginx/site-confs/*;`這一行,把它comment掉,在下面新增一行:
|
||||
```
|
||||
include /config/nginx/sites-enabled/*.conf;
|
||||
```
|
||||
|
||||
5. 啟動swag:
|
||||
```
|
||||
cd ~/dockers/linuxserverswag ; sudo docker-compose up -d
|
||||
```
|
||||
|
||||
#### Restart
|
||||
```bash
|
||||
cd ~/dockers/linuxserverswag ; sudo docker-compose restart
|
||||
```
|
||||
|
||||
#### Update certification
|
||||
1. 進到dockr的swag bash裡面:`sudo docker exec -it swag /bin/bash`
|
||||
2. 輸入 `certbot renew`
|
||||
3. ![[Pasted image 20210422205534.png]]
|
||||
|
||||
#### Trouble shooting
|
||||
- 如果遇到類似下面的錯誤:
|
||||
```
|
||||
ERROR: for swag Cannot start service swag: driver failed programming external connectivity on endpoint swag (7c527d046631e0957de0b831ca25bed296de76e2eb96378964cb0110d7fb017d): Bind for 0.0.0.0:443 failed: port is already allocated.
|
||||
```
|
||||
表示有其他程式佔住了80 port,可能是其他docker container或是service,必須先關閉它們。[^1]
|
||||
|
||||
#### 參考來源
|
||||
1. [linuxserver/docker-swag: Nginx webserver and reverse proxy with php support and a built-in Certbot (Let's Encrypt) client. It also contains fail2ban for intrusion prevention.](https://github.com/linuxserver/docker-swag)
|
||||
2. [How to set up an easy and secure reverse proxy with Docker, Nginx & Letsencrypt](https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/)
|
||||
3. [SWAG setup - LinuxServer.io](https://docs.linuxserver.io/general/swag#understanding-the-proxy-conf-structure)
|
||||
|
||||
-----
|
||||
- 參考
|
||||
- [NGINX Docs | NGINX Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
|
||||
- [Nginx 設定反向代理 Reverse Proxy](https://www.opencli.com/linux/nginx-config-reverse-proxy)
|
||||
- [用 Nginx 伺服器建立反向代理](https://noob.tw/nginx-reverse-proxy/)
|
||||
- [俄羅斯不愧是戰鬥民族:nginx - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10188498)
|
||||
|
||||
[^1]: [啟動docker時出現「Cannot start service :driver failed programming external connectivity on endpoint xxx, Bind for 0.0.0.0:80 failed: port is already allocated 」](https://mitsuiwei.com/docker-cannot-start-service/)
|
||||
63
02. PARA/03. Resources(資源)/Obsidian.md
Normal file
63
02. PARA/03. Resources(資源)/Obsidian.md
Normal file
@@ -0,0 +1,63 @@
|
||||
## 使用
|
||||
### 操作
|
||||
- `Ctrl-Enter`: 可以增加TODO的框框,或是用來更改框框的狀態
|
||||
|
||||
### 自訂快速鍵
|
||||
- `Alt+Insert`: 插入樣板
|
||||
- `Ctrl + Shift + <-`: 摺疊所有標題 ^e897a3
|
||||
- `Ctrl + Shift + ->`: 展開所有標題 ^5587cd
|
||||
|
||||
### 搜尋
|
||||
用 `file:` 開頭來指定指**只搜尋檔名**
|
||||
用 `content:` 開頭來指定**只指搜尋內容**
|
||||
用 `tag:` 開頭來搜尋標籤,例: `tag:#mydata`
|
||||
用 `path:` 來搜尋特定路徑下的檔案,路徑必須要用`""`包圍,例:`path:"path/to/foder" search_name`
|
||||
|
||||
## 設定
|
||||
目前所使用的theme是`Obsdn-dark-rmx`
|
||||
CSS路徑:`<vault>/obsidian.css`
|
||||
|
||||
### CSS
|
||||
#### 讓中文字型使用**微軟正黑體**
|
||||
```
|
||||
.CodeMirror pre.CodeMirror-line {
|
||||
font-size: 14px;
|
||||
font-family: Fira Code, Microsoft JhengHei, Source Code Pro, monospace;
|
||||
}
|
||||
```
|
||||
不用照抄,重點是在`font-family`裡面,加入`Microsoft JhengHei`,愈前面優先順序愈大。
|
||||
|
||||
#### 顯示**Bullet Point Relationship Lines**
|
||||
|
||||
```
|
||||
/*=== Bullet Point Relationship Lines ===*/
|
||||
.cm-hmd-list-indent, ul ul {
|
||||
position: relative;
|
||||
}
|
||||
.cm-hmd-list-indent::before, ul ul::before {
|
||||
content:'';
|
||||
border-left: 1px solid rgba(0, 122, 255, 0.55);
|
||||
position: absolute;
|
||||
}
|
||||
.cm-hmd-list-indent::before {
|
||||
left: 0;
|
||||
top: -5px;
|
||||
bottom: -4px;
|
||||
}
|
||||
ul ul::before {
|
||||
left: -11px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
```
|
||||
在`obsidian.css`最後端加入上面的CSS就可以顯示**Bullet Point Relationship Lines**,如:
|
||||
![[Pasted image 20201206094618.png]]
|
||||
|
||||
來源:
|
||||
- https://www.youtube.com/watch?v=ea9hkXem1f4
|
||||
- https://gist.github.com/emisjerry/36d1d115ae22cd0902fc528875f86414
|
||||
|
||||
|
||||
## Plugin
|
||||
### Dataview
|
||||
- [Source code](https://github.com/blacksmithgu/obsidian-dataview)
|
||||
- [介紹:[Obs#26] Dataview外掛: 製作索引筆記利器;動態查詢筆記資料 - YouTube](https://www.youtube.com/watch?v=dkWA7Qd0CLA&list=PLWg9zacwOnwfcpVm5pAKgOHms7PntsgJS&index=18)
|
||||
389
02. PARA/03. Resources(資源)/OpenCV.md
Normal file
389
02. PARA/03. Resources(資源)/OpenCV.md
Normal file
@@ -0,0 +1,389 @@
|
||||
## Build code
|
||||
### 前置作業
|
||||
1. 要有NVIDIA GPU
|
||||
2. 安裝NVIDIA GPU driver
|
||||
3. 安裝[CUDA](https://developer.nvidia.com/cuda-downloads)
|
||||
4. 安裝[cuDNN](https://developer.nvidia.com/rdp/cudnn-download)
|
||||
5. 安裝[CMake](https://cmake.org/download/)
|
||||
|
||||
### CMake && Visual Studio
|
||||
1. 下載OpenCV:[opencv](https://github.com/opencv/opencv)
|
||||
2. 解壓縮後放到`D:\temp\build_opencv\opencv-4.5.3\source`
|
||||
3. 建立`D:\temp\build_opencv\opencv-4.5.3\build`
|
||||
4. 下載OpenCV contrib:[opencv_contrib](https://github.com/opencv/opencv_contrib)
|
||||
5. 解壓縮後放到`D:\temp\build_opencv\opencv_contrib`
|
||||
6. 打開cmake-gui
|
||||
7. 如圖設定
|
||||
![[Pasted image 20210818115317.png]]
|
||||
8. 用`Add Entry`先加入以下define
|
||||
![[Pasted image 20210818115507.png]]
|
||||
如果是檔名,type就選FILEPATH,如果是目錄,type就選PATH
|
||||
- PYTHON3_EXECUTABLE=C:/python39/python.exe
|
||||
- PYTHON3_INCLUDE_DIR=C:/python39/include
|
||||
- PYTHON3_LIBRARY=C:/python39/libs/python39.lib
|
||||
- PYTHON3_NUMPY_INCLUDE_DIRS=C:/python39/Lib/site-packages/numpy/core/include
|
||||
- PYTHON3_PACKAGES_PATH=C:/python39/Lib/site-packages
|
||||
|
||||
如果要build win32 + Python 3.6.3 x86的話,改為以下設定:
|
||||
- PYTHON3_EXECUTABLE=C:/Python363/python.exe
|
||||
- PYTHON3_INCLUDE_DIR=C:/Python363/include
|
||||
- PYTHON3_LIBRARY=C:/Python363/libs/python36.lib
|
||||
- PYTHON3_NUMPY_INCLUDE_DIRS=C:/Python363/Lib/site-packages/numpy/core/include
|
||||
- PYTHON3_PACKAGES_PATH=C:/Python363/Lib/site-packages
|
||||
9. 按Configure,會跳出一個視窗,platform選x64
|
||||
![[Pasted image 20210818115809.png]]
|
||||
10. 這些要打勾
|
||||
- BUILD_opencv_world
|
||||
- BUILD_opencv_python3
|
||||
- OPENCV_DNN_CUDA
|
||||
- OPENCV_PYTHON3_VERSION
|
||||
- OPENCV_FORCE_PYTHON_LIBS
|
||||
- OPENCV_ENABLE_NONFREE
|
||||
- ENABLE_FAST_MATH
|
||||
- WITH_CUDA
|
||||
- WITH_OPENMP
|
||||
- OPENCV_EXTRA_MODULES_PATH=D:/temp/build_opencv/opencv_contrib/modules
|
||||
11. 再按一次Configure,必須沒有錯誤的跑完,像是
|
||||
```
|
||||
General configuration for OpenCV 4.5.3 =====================================
|
||||
Version control: unknown
|
||||
|
||||
Extra modules:
|
||||
Location (extra): D:/temp/build_opencv/opencv_contrib/modules
|
||||
Version control (extra): 4.5.3-6-g907efb96
|
||||
|
||||
Platform:
|
||||
Timestamp: 2021-08-18T03:40:06Z
|
||||
Host: Windows 10.0.19043 AMD64
|
||||
CMake: 3.21.1
|
||||
CMake generator: Visual Studio 16 2019
|
||||
CMake build tool: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin/MSBuild.exe
|
||||
MSVC: 1929
|
||||
Configuration: Debug Release
|
||||
|
||||
CPU/HW features:
|
||||
Baseline: SSE SSE2 SSE3
|
||||
requested: SSE3
|
||||
Dispatched code generation: SSE4_1 SSE4_2 FP16 AVX AVX2 AVX512_SKX
|
||||
requested: SSE4_1 SSE4_2 AVX FP16 AVX2 AVX512_SKX
|
||||
SSE4_1 (17 files): + SSSE3 SSE4_1
|
||||
SSE4_2 (2 files): + SSSE3 SSE4_1 POPCNT SSE4_2
|
||||
FP16 (1 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX
|
||||
AVX (5 files): + SSSE3 SSE4_1 POPCNT SSE4_2 AVX
|
||||
AVX2 (31 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2
|
||||
AVX512_SKX (7 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2 AVX_512F AVX512_COMMON AVX512_SKX
|
||||
|
||||
C/C++:
|
||||
Built as dynamic libs?: YES
|
||||
C++ standard: 11
|
||||
C++ Compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Tools/MSVC/14.29.30037/bin/Hostx64/x64/cl.exe (ver 19.29.30040.0)
|
||||
C++ flags (Release): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /MP /MD /O2 /Ob2 /DNDEBUG
|
||||
C++ flags (Debug): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /MP /MDd /Zi /Ob0 /Od /RTC1
|
||||
C Compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Tools/MSVC/14.29.30037/bin/Hostx64/x64/cl.exe
|
||||
C flags (Release): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /MP /MD /O2 /Ob2 /DNDEBUG
|
||||
C flags (Debug): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /MP /MDd /Zi /Ob0 /Od /RTC1
|
||||
Linker flags (Release): /machine:x64 /INCREMENTAL:NO
|
||||
Linker flags (Debug): /machine:x64 /debug /INCREMENTAL
|
||||
ccache: NO
|
||||
Precompiled headers: NO
|
||||
Extra dependencies: cudart_static.lib nppc.lib nppial.lib nppicc.lib nppidei.lib nppif.lib nppig.lib nppim.lib nppist.lib nppisu.lib nppitc.lib npps.lib cublas.lib cudnn.lib cufft.lib -LIBPATH:C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.4/lib/x64
|
||||
3rdparty dependencies:
|
||||
|
||||
OpenCV modules:
|
||||
To be built: aruco barcode bgsegm bioinspired calib3d ccalib core cudaarithm cudabgsegm cudacodec cudafeatures2d cudafilters cudaimgproc cudalegacy cudaobjdetect cudaoptflow cudastereo cudawarping cudev datasets dnn dnn_objdetect dnn_superres dpm face features2d flann fuzzy gapi hfs highgui img_hash imgcodecs imgproc intensity_transform line_descriptor mcc ml objdetect optflow phase_unwrapping photo plot python3 quality rapid reg rgbd saliency shape stereo stitching structured_light superres surface_matching text tracking ts video videoio videostab wechat_qrcode world xfeatures2d ximgproc xobjdetect xphoto
|
||||
Disabled: -
|
||||
Disabled by dependency: -
|
||||
Unavailable: alphamat cvv freetype hdf java julia matlab ovis python2 python2 sfm viz
|
||||
Applications: tests perf_tests apps
|
||||
Documentation: NO
|
||||
Non-free algorithms: NO
|
||||
|
||||
Windows RT support: NO
|
||||
|
||||
GUI:
|
||||
Win32 UI: YES
|
||||
VTK support: NO
|
||||
|
||||
Media I/O:
|
||||
ZLib: build (ver 1.2.11)
|
||||
JPEG: build-libjpeg-turbo (ver 2.1.0-62)
|
||||
WEBP: build (ver encoder: 0x020f)
|
||||
PNG: build (ver 1.6.37)
|
||||
TIFF: build (ver 42 - 4.2.0)
|
||||
JPEG 2000: build (ver 2.4.0)
|
||||
OpenEXR: build (ver 2.3.0)
|
||||
HDR: YES
|
||||
SUNRASTER: YES
|
||||
PXM: YES
|
||||
PFM: YES
|
||||
|
||||
Video I/O:
|
||||
DC1394: NO
|
||||
FFMPEG: YES (prebuilt binaries)
|
||||
avcodec: YES (58.134.100)
|
||||
avformat: YES (58.76.100)
|
||||
avutil: YES (56.70.100)
|
||||
swscale: YES (5.9.100)
|
||||
avresample: YES (4.0.0)
|
||||
GStreamer: NO
|
||||
DirectShow: YES
|
||||
Media Foundation: YES
|
||||
DXVA: YES
|
||||
|
||||
Parallel framework: Concurrency
|
||||
|
||||
Trace: YES (with Intel ITT)
|
||||
|
||||
Other third-party libraries:
|
||||
Intel IPP: 2020.0.0 Gold [2020.0.0]
|
||||
at: D:/temp/build_opencv/opencv-4.5.3/build/3rdparty/ippicv/ippicv_win/icv
|
||||
Intel IPP IW: sources (2020.0.0)
|
||||
at: D:/temp/build_opencv/opencv-4.5.3/build/3rdparty/ippicv/ippicv_win/iw
|
||||
Lapack: NO
|
||||
Eigen: NO
|
||||
Custom HAL: NO
|
||||
Protobuf: build (3.5.1)
|
||||
|
||||
NVIDIA CUDA: YES (ver 11.4, CUFFT CUBLAS)
|
||||
NVIDIA GPU arch: 35 37 50 52 60 61 70 75 80 86
|
||||
NVIDIA PTX archs:
|
||||
|
||||
cuDNN: YES (ver 8.2.2)
|
||||
|
||||
OpenCL: YES (NVD3D11)
|
||||
Include path: D:/temp/build_opencv/opencv-4.5.3/source/3rdparty/include/opencl/1.2
|
||||
Link libraries: Dynamic load
|
||||
|
||||
Python 3:
|
||||
Interpreter: C:/python39/python.exe (ver 3.9.6)
|
||||
Libraries: C:/python39/libs/python39.lib (ver 3.9.6)
|
||||
numpy: C:/python39/Lib/site-packages/numpy/core/include (ver 1.19.5)
|
||||
install path: C:/python39/Lib/site-packages/cv2/python-3.9
|
||||
|
||||
Python (for build): C:/python39/python.exe
|
||||
|
||||
Java:
|
||||
ant: NO
|
||||
JNI: NO
|
||||
Java wrappers: NO
|
||||
Java tests: NO
|
||||
|
||||
Install to: D:/temp/build_opencv/opencv-4.5.3/build/install
|
||||
-----------------------------------------------------------------
|
||||
|
||||
Configuring done
|
||||
```
|
||||
12. 按下Generate按鈕就會生sln檔案。
|
||||
|
||||
|
||||
### CMake by command
|
||||
用command line的話就可以將多個平台一次編起來,不用一直改GUI。
|
||||
寫了一個`build.bat`可以編譯不同版本與平台:
|
||||
```bat
|
||||
rem build.bat
|
||||
echo on
|
||||
rem build.bat
|
||||
rem win32 ARCH=%1, win32/x64
|
||||
rem 15 VS_CODE=%2, 15/16
|
||||
rem 2017 VS_VERSION=%3, 2017/2019
|
||||
rem DEBUG BUILD_TYPE=%4, DEBUG/RELEASE
|
||||
rem "C:/Python363/python.exe" PYTHON3_EXE=%5
|
||||
rem "C:/Python363/include" PYTHON3_INCLUDE=%6
|
||||
rem "C:/Python363/libs/python36.lib" PYTHON3_LIB=%7
|
||||
rem "C:/Python363/Lib/site-packages/numpy/core/include" PYTHON3_NP_INCLUDE=%8
|
||||
rem "C:/Python363/Lib/site-packages" PYTHON3_PACKAGES=%9
|
||||
rem 3.6.3 PY_VERSION=%10
|
||||
rem 1 CLEAN_BUILD=%11, 1: Delete build folder, 0: Do nothing
|
||||
|
||||
set DO_CONFIG=1
|
||||
set DO_BUILD=1
|
||||
set ARCH=%1
|
||||
set VS_CODE=%2
|
||||
set VS_VERSION=%3
|
||||
set BUILD_TYPE=%4
|
||||
set PYTHON3_EXE=%5
|
||||
set PYTHON3_INCLUDE=%6
|
||||
set PYTHON3_LIB=%7
|
||||
set PYTHON3_NP_INCLUDE=%8
|
||||
set PYTHON3_PACKAGES=%9
|
||||
set GENERATOR="Visual Studio %VS_CODE% %VS_VERSION%"
|
||||
shift
|
||||
set PY_VERSION=%9
|
||||
set CV_VERSION=4.5.3
|
||||
set CV_SOURCE="opencv-%CV_VERSION%\source"
|
||||
set CV_BUILD=build\vs%VS_VERSION%.%ARCH%.%BUILD_TYPE%
|
||||
set CV_EXTRA_MODULES="opencv_contrib\modules"
|
||||
shift
|
||||
set CLEAN_BUILD=%9
|
||||
|
||||
if %CLEAN_BUILD% == 1 (
|
||||
rmdir /s /q %CV_BUILD%
|
||||
)
|
||||
|
||||
if %DO_CONFIG% == 1 (
|
||||
mkdir %CV_BUILD%
|
||||
|
||||
"C:\Program Files\CMake\bin\cmake.exe" ^
|
||||
-B%CV_BUILD% ^
|
||||
-H%CV_SOURCE% ^
|
||||
-G%GENERATOR% ^
|
||||
-A%ARCH% ^
|
||||
-DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^
|
||||
-DOPENCV_EXTRA_MODULES_PATH=%CV_EXTRA_MODULES% ^
|
||||
-DCMAKE_INSTALL_PREFIX=%CV_BUILD% ^
|
||||
-DINSTALL_PYTHON_EXAMPLES=OFF ^
|
||||
-DINSTALL_C_EXAMPLES=OFF ^
|
||||
-DBUILD_opencv_python3=ON ^
|
||||
-DPYTHON3_EXECUTABLE=%PYTHON3_EXE% ^
|
||||
-DPYTHON3_INCLUDE_DIR=%PYTHON3_INCLUDE% ^
|
||||
-DPYTHON3_LIBRARY=%PYTHON3_LIB% ^
|
||||
-DPYTHON3_NUMPY_INCLUDE_DIRS=%PYTHON3_NP_INCLUDE% ^
|
||||
-DPYTHON3_PACKAGES_PATH=%PYTHON3_PACKAGES% ^
|
||||
-DOPENCV_PYTHON3_VERSION=%PY_VERSION% ^
|
||||
-DOPENCV_FORCE_PYTHON_LIBS=ON ^
|
||||
-DBUILD_opencv_world=ON ^
|
||||
-DOPENCV_ENABLE_NONFREE=ON ^
|
||||
-DENABLE_FAST_MATH=ON ^
|
||||
-DWITH_OPENMP=ON ^
|
||||
-DWITH_OPENGL=ON
|
||||
)
|
||||
|
||||
if %DO_BUILD% == 1 (
|
||||
"C:\Program Files\CMake\bin\cmake.exe" --build %CV_BUILD% --target INSTALL --config %BUILD_TYPE%
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
這樣以後就可以用參數的方法來設定,譬如要使用vs2019來編譯x64 release的話,就可以這樣下:
|
||||
```bat
|
||||
rem build_vs2019.x64.RELEASE.bat
|
||||
echo off
|
||||
rem ----------------------------------------------------------------------------------------------------
|
||||
rem
|
||||
rem x64, RELEASE, 2019, CLEAN
|
||||
rem
|
||||
set ARCH=x64
|
||||
set VS_CODE=16
|
||||
set VS_VERSION=2019
|
||||
set BUILD_TYPE=RELEASE
|
||||
set PYTHON3_EXE="C:/Python39/python.exe"
|
||||
set PYTHON3_INCLUDE="C:/Python39/include"
|
||||
set PYTHON3_LIB="C:/Python39/libs/python39.lib"
|
||||
set PYTHON3_NP_INCLUDE="C:/Python39/Lib/site-packages/numpy/core/include"
|
||||
set PYTHON3_PACKAGES="C:/Python39/Lib/site-packages"
|
||||
set PY_VERSION=3.9
|
||||
set CLEAN_BUILD=1
|
||||
echo "============================================================"
|
||||
echo "Build %ARCH%.%VS_VERSION%(%VS_CODE%).%BUILD_TYPE%, Python=%PY_VERSION%, CLEAN_BUILD=%CLEAN_BUILD%"
|
||||
echo ""
|
||||
call build.bat ^
|
||||
%ARCH% %VS_CODE% %VS_VERSION% %BUILD_TYPE% %PYTHON3_EXE% ^
|
||||
%PYTHON3_INCLUDE% %PYTHON3_LIB% %PYTHON3_NP_INCLUDE% %PYTHON3_PACKAGES% ^
|
||||
%PY_VERSION% %CLEAN_BUILD%
|
||||
rem ----------------------------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
### 參考
|
||||
- [Build OpenCV GPU Version On Windows 10](https://medium.com/chung-yi/build-opencv-gpu-version-on-windows-10-c37a33437525)
|
||||
- [Cannot install openCV 3.1.0 with python3. CMAKE not including or linking python correctly](https://stackoverflow.com/questions/42638342/cannot-install-opencv-3-1-0-with-python3-cmake-not-including-or-linking-python)
|
||||
- [在Windows上编译带CUDA(GPU)的OpenCV](https://shaogui.life/2021/03/08/%E5%9C%A8Windows%E4%B8%8A%E7%BC%96%E8%AF%91%E5%B8%A6CUDA(GPU)%E7%9A%84opencv/)
|
||||
- [How to use OpenCV DNN Module with Nvidia GPU on Windows](https://learnopencv.com/how-to-use-opencv-dnn-module-with-nvidia-gpu-on-windows/)
|
||||
- [Accelerate OpenCV 4.5.0 on Windows – build with CUDA and python bindings - James Bowley](https://jamesbowley.co.uk/accelerate-opencv-4-5-0-on-windows-build-with-cuda-and-python-bindings/#python_bindings)
|
||||
|
||||
## 影像處理
|
||||
- sift
|
||||
```python
|
||||
def adjust_gamma(image, gamma=1.0):
|
||||
invGamma = 1.0 / gamma
|
||||
table = np.array([((i / 255.0) ** invGamma) * 255
|
||||
for i in np.arange(0, 256)]).astype("uint8")
|
||||
|
||||
return cv2.LUT(image, table)
|
||||
|
||||
|
||||
def sift_compare(path_a, path_b):
|
||||
'''
|
||||
Use SIFT features to measure image similarity
|
||||
@args:
|
||||
{str} path_a: the path to an image file
|
||||
{str} path_b: the path to an image file
|
||||
@returns:
|
||||
TODO
|
||||
'''
|
||||
# initialize the sift feature detector
|
||||
orb = cv2.ORB_create()
|
||||
|
||||
# get the images
|
||||
img_a = cv2.imread(path_a)
|
||||
img_b = cv2.imread(path_b)
|
||||
img_a = adjust_gamma(img_a, 0.1)
|
||||
img_b = adjust_gamma(img_b, 0.1)
|
||||
|
||||
# find the keypoints and descriptors with SIFT
|
||||
kp_a, desc_a = orb.detectAndCompute(img_a, None)
|
||||
kp_b, desc_b = orb.detectAndCompute(img_b, None)
|
||||
# print(f'len kp_a = {len(kp_a)}, len desc_a = {len(desc_a)}')
|
||||
# print(f'type kp_b = {type(kp_b)}, type desc_b = {type(desc_b)}')
|
||||
# print(f'len kp_b = {len(kp_b)}, len desc_b = {len(desc_b) if desc_b else 0}')
|
||||
|
||||
if desc_a is None or desc_b is None:
|
||||
# rr.LOG('Error: desc_a = {}, desc_b = {}'.format(type(desc_a), type(desc_b)))
|
||||
# rr.LOG('Error: Return score 0.')
|
||||
return 0
|
||||
|
||||
# Initialize the bruteforce matcher
|
||||
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
|
||||
|
||||
# match.distance is a float between {0:100} - lower means more similar
|
||||
matches = bf.match(desc_a, desc_b)
|
||||
similar_regions = [i for i in matches if i.distance < 70]
|
||||
|
||||
# print('Length of similar_regions = {}'.format(len(similar_regions)))
|
||||
# print('Length of matches = {}'.format(len(matches)))
|
||||
|
||||
sorted_matches = sorted(matches, key = lambda x:x.distance)
|
||||
good_matches = sorted_matches[:int(len(sorted_matches) * 0.5)]
|
||||
similar_regions2 = [i for i in good_matches if i.distance < 70]
|
||||
# print('Length of good_matches = {}'.format(len(good_matches)))
|
||||
|
||||
if len(matches) == 0:
|
||||
return 0
|
||||
return len(similar_regions) / len(matches)
|
||||
```
|
||||
|
||||
### Color tempertature
|
||||
code
|
||||
```
|
||||
img1 = cv2.imread(file)
|
||||
B, G, R = cv2.split(img1)
|
||||
avgB = cv2.mean(B)[0]
|
||||
avgG = cv2.mean(G)[0]
|
||||
avgR = cv2.mean(R)[0]
|
||||
|
||||
# X = avgR * -0.14282 + avgG * 1.54924 + avgB * -0.95641
|
||||
# Y = avgR * -0.32466 + avgG * 1.57837 + avgB * -0.73191
|
||||
# Z = avgR * -0.68202 + avgG * 0.77073 + avgB * 0.56332
|
||||
X = 2.789 * avgR + 1.7517 * avgG + 1.1302 * avgB
|
||||
Y = 1 * avgR + 4.5907 * avgG + 0.0601 * avgB
|
||||
Z = 0 * avgR + 0.0565 * avgG + 5.5943 * avgB
|
||||
x = X / (X + Y + Z)
|
||||
y = Y / (X + Y + Z)
|
||||
n = (x - 0.3320) / (0.1858 - y)
|
||||
# n = (0.23881 * avgR + 0.25499 * avgG + -0.58291 * avgB) / (0.11109 * avgR + -0.85406 * avgG + 0.52289 * avgB)
|
||||
CCT = 449 * pow(n, 3) + 3525 * pow(n, 2) + 6823.3 * n + 5520.33
|
||||
# CCT = 437 * pow(n, 3) + 3601 * pow(n, 2) + 6831 * n + 5517
|
||||
|
||||
# print(f'x = {x}')
|
||||
# print(f'y = {y}')
|
||||
# print(f'n = {n}')
|
||||
print(f'{file}: CCT = {CCT}')
|
||||
```
|
||||
|
||||
- [How to Calculate the Color Temperature / Tint of the Colors in an Image?](https://dsp.stackexchange.com/questions/8949/how-to-calculate-the-color-temperature-tint-of-the-colors-in-an-image)
|
||||
- [Calculating Color Temperature and Illuminance using the TAOS TCS3414CS Digital Color Sensor_](https://ams.com/documents/20143/80162/TCS34xx_AN000517_1-00.pdf)
|
||||
- [Java RGB转色温(CCT)](https://www.codeleading.com/article/16404945886/)
|
||||
- [Calculate color temperature (CCT) from CIE 1931 xy coordinates](https://www.waveformlighting.com/tech/calculate-color-temperature-cct-from-cie-1931-xy-coordinates)
|
||||
- [OpenCV: Color conversions](https://docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html)
|
||||
- [sRGB色彩空間 - 維基百科,自由的百科全書](https://zh.wikipedia.org/wiki/SRGB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4)
|
||||
- [Color temperature - Wikipedia](https://en.wikipedia.org/wiki/Color_temperature#Approximation)
|
||||
- [What color is a blackbody? - some pixel rgb values](http://www.vendian.org/mncharity/dir3/blackbody/)
|
||||
2
02. PARA/03. Resources(資源)/OpenGL.md
Normal file
2
02. PARA/03. Resources(資源)/OpenGL.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- [opengl-tutorial](http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-1-opening-a-window/)
|
||||
- [Learn OpenGL, extensive tutorial resource for learning Modern OpenGL](https://learnopengl.com/)
|
||||
26
02. PARA/03. Resources(資源)/Pelican blog.md
Normal file
26
02. PARA/03. Resources(資源)/Pelican blog.md
Normal file
@@ -0,0 +1,26 @@
|
||||
## Create a site
|
||||
Use `pelican-quickstart` to create a new site.
|
||||
|
||||
## Plugin
|
||||
```bash
|
||||
git clone --recursive https://github.com/getpelican/pelican-plugins.git
|
||||
```
|
||||
|
||||
## Theme
|
||||
先把所有佈景主題都clone下來:
|
||||
```bash
|
||||
git clone --recursive https://github.com/getpelican/pelican-themes.git
|
||||
```
|
||||
|
||||
把`pelicanconf.py`裡面的`THEME`指向theme的目錄就可以換佈景主題了。例如要用[[blue-penguin](https://github.com/jody-frankowski/blue-penguin)]這一個主題。把`pelicanconf.py`裡面加入`THEME = 'pelican-themes/blue-penguin'`就可以了。
|
||||
|
||||
## 預覽
|
||||
```
|
||||
make html
|
||||
make serve
|
||||
```
|
||||
|
||||
參考:
|
||||
- [koko's Note – Python - 安裝 Pelican Theme 來改變你的靜態網站主題](https://note.koko.guru/install-pelican-theme.html)
|
||||
- [nest theme](https://github.com/molivier/nest)
|
||||
- [Flex theme](https://github.com/alexandrevicenzi/Flex/wiki/Custom-Settings)
|
||||
244
02. PARA/03. Resources(資源)/Proxmox VE.md
Normal file
244
02. PARA/03. Resources(資源)/Proxmox VE.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# 安裝
|
||||
## 下載ISO
|
||||
- [Get the free Proxmox VE ISO installer](https://www.proxmox.com/en/downloads/category/iso-images-pve)
|
||||
|
||||
## 準備USB disk
|
||||
- 用[Rufus](https://rufus.ie/)的話
|
||||
1. 在遇到詢問是否要下載 Grub 時,請選擇「否」
|
||||
2. 必須使用DD mode來建立開機碟。(參考:[Prepare Installation Media - Proxmox VE](https://pve.proxmox.com/wiki/Prepare_Installation_Media#_instructions_for_windows))
|
||||
![[Pasted image 20210128212917.png]]
|
||||
|
||||
# 設定
|
||||
## 關閉「闔上螢幕後休眠」
|
||||
打開`/etc/systemd/logind.conf`:
|
||||
```
|
||||
nano /etc/systemd/logind.conf
|
||||
```
|
||||
找到下面兩行,把值改成ignore:
|
||||
```
|
||||
HandleLidSwitch=ignore
|
||||
HandleLidSwitchDocked=ignore
|
||||
```
|
||||
然後重開機:
|
||||
```
|
||||
systemctl restart systemd-logind.service
|
||||
```
|
||||
圖示:
|
||||
![[Pasted image 20210129194144.png]]
|
||||
|
||||
## 增加硬碟
|
||||
先用`lsblk`列出所有硬碟,這裡假設`sda`是我們的開機磁碟,我要要新增`sdb`:
|
||||
```
|
||||
root@pve:~# lsblk
|
||||
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
|
||||
sda 8:0 0 931.5G 0 disk <-- 目前在用的
|
||||
├─sda1 8:1 0 1007K 0 part
|
||||
├─sda2 8:2 0 512M 0 part
|
||||
└─sda3 8:3 0 931G 0 part
|
||||
sdb 8:16 0 111.8G 0 disk <-- 要新增的
|
||||
├─sdb1 8:17 0 100M 0 part
|
||||
├─sdb2 8:18 0 16M 0 part
|
||||
├─sdb3 8:19 0 111.1G 0 part
|
||||
└─sdb4 8:20 0 563M 0 part
|
||||
```
|
||||
|
||||
然後安裝`parted`,我們要用它來分割硬碟:
|
||||
```
|
||||
apt install parted
|
||||
```
|
||||
|
||||
開始分割:
|
||||
```
|
||||
parted /dev/sdb mklabel gpt
|
||||
```
|
||||
|
||||
建立primary partition,格式為`ext4`:
|
||||
```
|
||||
parted -a opt /dev/sdb mkpart primary ext4 0% 100%
|
||||
```
|
||||
|
||||
再來將分割好的硬碟格式化為`ext4`,label命名為`data2`:
|
||||
```
|
||||
mkfs.ext4 -L data2 /dev/sdb1
|
||||
```
|
||||
|
||||
再用`lsblk`看一次,會發現sdb已經重新分割成1個partition了:
|
||||
```
|
||||
root@pve:~# lsblk
|
||||
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
|
||||
sda 8:0 0 931.5G 0 disk
|
||||
├─sda1 8:1 0 1007K 0 part
|
||||
├─sda2 8:2 0 512M 0 part
|
||||
└─sda3 8:3 0 931G 0 part
|
||||
sdb 8:16 0 111.8G 0 disk
|
||||
└─sdb1 8:17 0 111.8G 0 part
|
||||
```
|
||||
|
||||
用`lsblk -fs`可以看到每一個硬碟的檔案系統格式:
|
||||
```
|
||||
root@pve:~# lsblk -fs
|
||||
NAME FSTYPE LABEL UUID FSAVAIL FSUSE% MOUNTPOINT
|
||||
sda1 zfs_member rpool 11775123664036754029
|
||||
└─sda zfs_member rpool 11775123664036754029
|
||||
sda2 vfat rpool 32D0-3449
|
||||
└─sda zfs_member rpool 11775123664036754029
|
||||
sda3 zfs_member rpool 11775123664036754029
|
||||
└─sda zfs_member rpool 11775123664036754029
|
||||
sdb1 ext4 data2 bc6d2c41-a3ca-4b0f-a5de-51ee28ae9cec <-- 剛剛分割的
|
||||
└─sdb
|
||||
```
|
||||
|
||||
接下來,將新硬碟掛載到檔案目錄上,先建立一個新目錄來掛載新硬碟:
|
||||
```shell
|
||||
mkdir -p /mnt/data
|
||||
```
|
||||
|
||||
接下來編輯`/etc/fstab`,將新硬碟寫進來,這樣開機之後才會自動把它掛載起來:
|
||||
```
|
||||
nano /etc/fstab
|
||||
```
|
||||
加入這一行(注意:**data2**要改成你自己的label):
|
||||
```
|
||||
LABEL=data2 /mnt/data ext4 defaults 0 2
|
||||
```
|
||||
|
||||
剛新硬碟掛起來:
|
||||
```
|
||||
mount -a
|
||||
```
|
||||
|
||||
用`df`就可以看到新硬碟了:
|
||||
```
|
||||
root@pve:~# df
|
||||
Filesystem 1K-blocks Used Available Use% Mounted on
|
||||
udev 16288892 0 16288892 0% /dev
|
||||
tmpfs 3262688 9324 3253364 1% /run
|
||||
rpool/ROOT/pve-1 942667136 1267584 941399552 1% /
|
||||
tmpfs 16313440 43680 16269760 1% /dev/shm
|
||||
tmpfs 5120 0 5120 0% /run/lock
|
||||
tmpfs 16313440 0 16313440 0% /sys/fs/cgroup
|
||||
rpool 941399680 128 941399552 1% /rpool
|
||||
rpool/data 941399680 128 941399552 1% /rpool/data
|
||||
rpool/ROOT 941399680 128 941399552 1% /rpool/ROOT
|
||||
/dev/fuse 30720 16 30704 1% /etc/pve
|
||||
tmpfs 3262688 0 3262688 0% /run/user/0
|
||||
/dev/sdb1 114854020 61464 108915208 1% /mnt/data <-- 新硬碟在這裡
|
||||
```
|
||||
|
||||
參考:
|
||||
- [How to add storage to Proxmox](https://nubcakes.net/index.php/2019/03/05/how-to-add-storage-to-proxmox/)
|
||||
|
||||
## 增加iSCSI磁碟
|
||||
### 增加需要CHAP認證的iSCSI磁碟
|
||||
1. 先確認找的到iSCSI磁碟
|
||||
```
|
||||
iscsiadm -m discovery -t st -p 192.168.1.11:3260
|
||||
```
|
||||
如果有找到的話會回傳一串IQN值,像是:
|
||||
```
|
||||
root@pve:~# iscsiadm -m discovery -t st -p 192.168.1.11:3260
|
||||
192.168.1.11:3260,1 iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3
|
||||
[fe80::211:32ff:fe20:eadd]:3260,1 iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3
|
||||
```
|
||||
2. 有IQN就可以用下列的命令連線與掛載:
|
||||
```
|
||||
iscsiadm -m node -T iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3 --op update --name node.session.auth.username --value=名字
|
||||
iscsiadm -m node -T iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3 --op update --name node.session.auth.password --value=密碼
|
||||
iscsiadm -m node -T iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3 -l #連線
|
||||
iscsiadm -m node -o update -n node.startup -v automatic #設定開機自動掛載
|
||||
```
|
||||
|
||||
## 增加NFS磁碟
|
||||
1. 先在Synology上開一個NFS disk,設定如下:
|
||||
![[Pasted image 20220506091522.png]]
|
||||
2. 再到Proxmox的 Datacenter->Storage->Add 來增加一個 *NFS*,設定如下
|
||||
![[Pasted image 20220506091624.png]]
|
||||
|
||||
### 更改NFS mount為soft
|
||||
1. 編輯`/etc/pve/storage.cfg`
|
||||
2. 做如下修改
|
||||
![[Pasted image 20220506095531.png]]
|
||||
|
||||
### 參考
|
||||
- [[經驗分享]Proxmox VE 採用 NFS 連接儲存的重點事項](http://blog.jason.tools/2019/02/pve-nfs-mount.html)
|
||||
|
||||
## 設定VM備份目錄
|
||||
如果將VM或LXC備份到某個目錄,先建立要備份的目錄:
|
||||
```shell
|
||||
mkdir -p /mnt/data/backup/
|
||||
```
|
||||
|
||||
再來用WEB UI,操作如下:
|
||||
![[Pasted image 20210129202041.png]]
|
||||
![[Pasted image 20210129202047.png]]
|
||||
|
||||
最後再到 Datacenter->Backups,建立一個scheule來備份就可以了:
|
||||
![[Pasted image 20210129202231.png]]
|
||||
|
||||
## 將資料備份到NAS
|
||||
1. 先在NAS開一個share folder跟一個帳號。
|
||||
![[Pasted image 20210202190402.png]]
|
||||
![[Pasted image 20210202190537.png]]
|
||||
2. Proxmox:到裡將剛剛新開的folder給掛載起來。
|
||||
![[Pasted image 20210202190640.png]]
|
||||
會跳出一個視窗,如下圖來填,記得**content**那一欄有4個要選。
|
||||
![[Pasted image 20210202190709.png]]
|
||||
3. Proxmox:到 Datacenter->Backup 新增一個排程。
|
||||
![[Pasted image 20210202190903.png]]
|
||||
一樣會跳出一個視窗,依需求來填,要注意的是**Storage**必須是前一步驟的**ID**,**Selection Mode**可以選擇**All**。
|
||||
![[Pasted image 20210202191150.png]]
|
||||
|
||||
參考:
|
||||
- [HASS + Proxmox: Automatic Backups to Synology NAS](https://kleypot.com/automatic-offline-backups/)
|
||||
|
||||
## 更新
|
||||
### 加入更新來源
|
||||
編輯`/etc/apt/sources.list`,加入:
|
||||
```
|
||||
deb http://ftp.debian.org/debian bullseye main contrib
|
||||
deb http://ftp.debian.org/debian bullseye-updates main contrib
|
||||
|
||||
# PVE pve-no-subscription repository provided by proxmox.com,
|
||||
# NOT recommended for production use
|
||||
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription
|
||||
|
||||
# security updates
|
||||
deb http://security.debian.org/debian-security bullseye-security main contrib
|
||||
```
|
||||
|
||||
### 取消訂閱服務
|
||||
編輯`/etc/apt/sources.list.d/pve-enterprise.list`,把下面這行注釋掉:
|
||||
```
|
||||
deb https://enterprise.proxmox.com/debian/pve buster pve-enterprise
|
||||
```
|
||||
也就是變成:
|
||||
```
|
||||
#deb https://enterprise.proxmox.com/debian/pve buster pve-enterprise
|
||||
```
|
||||
|
||||
使用`apt update`來更新套件。
|
||||
使用`apt dist-upgrade`來升級系統版本。
|
||||
|
||||
## 重灌後要做的事情
|
||||
1. 建立ZFS pool。
|
||||
2. 確認S.M.A.R.T. 是否啟用,預設是啟用的。
|
||||
`smartctl -a /dev/<SDA_N>`
|
||||
1. 打開IOMMU
|
||||
2. 打開vm aware
|
||||
3. 增加NFS共享磁碟
|
||||
4. 排程備份
|
||||
5. 上傳安裝Windows需要的驅動ISO
|
||||
1. [Windows VirtIO Drivers](https://pve.proxmox.com/wiki/Windows_VirtIO_Drivers)
|
||||
6. 把常用的VM轉為template
|
||||
7. 安裝[Cockpit-Linux Server](https://pvecli.xuan2host.com/cockpit/), 讓您的PVE有更棒的圖形管理介面。
|
||||
|
||||
## 參考
|
||||
- [套件功能的更新(Proxmox update)](https://wiki.freedomstu.com/books/proxmox-ve-%E8%99%9B%E6%93%AC%E7%B3%BB%E7%B5%B1%E8%A8%98%E9%8C%84/page/%E5%A5%97%E4%BB%B6%E5%8A%9F%E8%83%BD%E7%9A%84%E6%9B%B4%E6%96%B0%EF%BC%88proxmox-update%EF%BC%89)
|
||||
- [裝完PVE後的11件必作清單 (中文翻譯)](https://www.youtube.com/watch?v=pY4Lm2Hoqik)
|
||||
- [Before I do anything on Proxmox, I do this first...](https://www.youtube.com/watch?v=GoZaMgEgrHw&t=0s)
|
||||
|
||||
# Trouble shooting
|
||||
- *Emergency mode*,表示開機失敗,請檢查`/etc/fstab`是不是有無法掛載的disk。
|
||||
|
||||
## 參考
|
||||
- [[Fix] Getting out of emergency mode : Proxmox](https://www.reddit.com/r/Proxmox/comments/hai75k/fix_getting_out_of_emergency_mode/)
|
||||
56
02. PARA/03. Resources(資源)/Python/argparse.ArgumentParser.md
Normal file
56
02. PARA/03. Resources(資源)/Python/argparse.ArgumentParser.md
Normal file
@@ -0,0 +1,56 @@
|
||||
一個範例:
|
||||
```python
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-u", "--update_tool", default=WIN_FW_UPDATER_PATH, help="The path of win_fw_updater.exe.")
|
||||
parser.add_argument("-f", "--firmware", required=True, help="The path of ITB file.")
|
||||
parser.add_argument("-w", "--waittime_update_firmware", default=600, type=int, help="Wait time for update the firmware.")
|
||||
parser.add_argument("-g", "--ignore_check_file_path", action='store_true', help="Skip check the existence of file.")
|
||||
args = parser.parse_args()
|
||||
```
|
||||
|
||||
#### 要求user一定要設定的參數
|
||||
使用`required=True`
|
||||
例如:
|
||||
```python
|
||||
parser.add_argument("-f", "--firmware", required=True, help="The path of ITB file.")
|
||||
```
|
||||
如果使用者沒有下`-f`(或者`--firmware=XXX`)就會報錯,如下:
|
||||
```bash
|
||||
FwUpdateCheck.py: error: the following arguments are required: -f/--firmware
|
||||
```
|
||||
|
||||
|
||||
#### 有設定才會產生的參數
|
||||
使用`action='store_true'`或`action='store_false'`
|
||||
例如:
|
||||
```python
|
||||
parser.add_argument("-g", "--ignore_check_file_path", action='store_true', help="Skip check the existence of file.")
|
||||
```
|
||||
當使用者沒有設置`-g`時,`args.ignore_check_file_path`為`False`,當設置時,`args.ignore_check_file_path`為`True`。
|
||||
|
||||
#### 使用預設值
|
||||
例如:
|
||||
```python
|
||||
parser.add_argument("-u", "--update_tool", default="C:\\tool.exe", help="The path of win_fw_updater.exe.")
|
||||
```
|
||||
用`default=<Something>`來設定參數的預設值,上面的例子中,`args.update_tool`的預設值為`C:\tool.exe`。
|
||||
另外可以用`type=<Object type>`來指定預設值的型別。例如:
|
||||
```python
|
||||
parser.add_argument("-n", "--number", default=50, type=int, help="Assign a number")
|
||||
```
|
||||
上例中,`args.number`的預設值是50,型別是`int`,所以可以直接運算,不需要再經過`int(args.number)`這樣的轉換。
|
||||
|
||||
#### 限制使用者的選擇
|
||||
Example:
|
||||
```python
|
||||
parser.add_argument('move', choices=['rock', 'paper', 'scissors'])
|
||||
```
|
||||
使用`choices=<list>`來限定輸入的選項,上例中,使用者只能輸入'rock'、'paper'、'scissors'這三個字串中的其中一個,否則會報錯:
|
||||
```bash
|
||||
error: argument move: invalid choice: 'fire' (choose from 'rock', 'paper', 'scissors')
|
||||
```
|
||||
|
||||
-----
|
||||
- https://docs.python.org/zh-tw/3/library/argparse.html
|
||||
48
02. PARA/03. Resources(資源)/Python/decorator.md
Normal file
48
02. PARA/03. Resources(資源)/Python/decorator.md
Normal file
@@ -0,0 +1,48 @@
|
||||
## 在decorator內取得function的default argument與class member
|
||||
```python
|
||||
import sys
|
||||
import inspect
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def exampleDecorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
print(f"Decorator: call by {func.__name__}")
|
||||
def get_default_args(func):
|
||||
signature = inspect.signature(func)
|
||||
return {
|
||||
k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty
|
||||
}
|
||||
|
||||
## Get default
|
||||
defaultKwargs = get_default_args(func)
|
||||
defaultKwargs.update(kwargs)
|
||||
print(f"Decorator: args = {args}, kwargs = {kwargs}, defaultKwargs = {defaultKwargs}")
|
||||
|
||||
objectInstance = args[0]
|
||||
if hasattr(objectInstance, 'defaultArg1'):
|
||||
print(f'objectInstance has defaultArg1, a.defaultArg1({type(objectInstance.defaultArg1)}) = {objectInstance.defaultArg1}')
|
||||
if objectInstance.defaultArg1:
|
||||
## Do something here
|
||||
print("Decorator: some message...")
|
||||
else:
|
||||
print('objectInstance does not have defaultArg1')
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class ExampleClass():
|
||||
def __init__(self, defaultArg1=True, defaultArg2="SomeString"):
|
||||
self.defaultArg1 = defaultArg1
|
||||
self.defaultArg2 = defaultArg2
|
||||
print(f'self.defaultArg1 = {self.defaultArg1}, self.defaultArg2 = {self.defaultArg2}')
|
||||
|
||||
@exampleDecorator
|
||||
def run(self, arg1=1, arg2=2):
|
||||
print(f"ExampleClass.run(), arg1 = {arg1}, arg2 = {arg2}")
|
||||
|
||||
example = ExampleClass()
|
||||
example.run()
|
||||
```
|
||||
126
02. PARA/03. Resources(資源)/Python/logging.md
Normal file
126
02. PARA/03. Resources(資源)/Python/logging.md
Normal file
@@ -0,0 +1,126 @@
|
||||
### 準備
|
||||
```
|
||||
import logging
|
||||
```
|
||||
|
||||
### logging level
|
||||
| level | level number | funtion |
|
||||
|:---------|:-------------|:---------------------|
|
||||
| NOTSET | 0 | |
|
||||
| DEBUG | 10 | `logging.debug()` |
|
||||
| INFO | 20 | `logging.info()` |
|
||||
| WARNING | 30 | `logging.warning()` |
|
||||
| ERROR | 40 | `logging.error()` |
|
||||
| CRITICAL | 50 | `logging.critical()` |
|
||||
|
||||
```
|
||||
import logging
|
||||
|
||||
LOG_FORMAT = '%(asctime)s %(levelname)s: %(message)s'
|
||||
LOG_FILENAME = 'C:\\RobotRun\\Output\\RobotRunDocUpdater.log'
|
||||
|
||||
logging.basicConfig(level=logging.INFO, filename=LOG_FILENAME, filemode='a', format=LOG_FORMAT)
|
||||
|
||||
logging.info('logging start')
|
||||
```
|
||||
|
||||
### Print Exception
|
||||
`logging` 模組也提供可以紀錄完整的堆疊追蹤 (stack traces),若在 `logging.error()` 加上 `exc_info` 參數,並將該參數設為 `True`,就可以紀錄 Exception,如下:
|
||||
```python
|
||||
import logging
|
||||
|
||||
try:
|
||||
x = 5 / 0
|
||||
except:
|
||||
logging.error("Catch an exception.", exc_info=True)
|
||||
```
|
||||
也可以使用`logging.exception("Catch an exception.")`,效果跟`logging.error("Catch an exception.", exc_info=True)`一樣。
|
||||
|
||||
### 自訂 logging 輸出格式
|
||||
預設的訊息輸出格式只有 `levelname`、`name`、`message`,下面是其他相關的資訊:
|
||||
|
||||
| 格式化字串 | 說明 |
|
||||
|:------------------|:---------------------------------------------------------------------|
|
||||
| `%(asctime)s` | 日期時間, 格式為 `YYYY-MM-DD HH:mm:SS,ms`,例如:2018-12-13 17:20:30,567 |
|
||||
| `%(filename)s` | 模組檔名 |
|
||||
| `%(funcName)s` | 函數名稱 |
|
||||
| `%(levelname)s` | 日誌的等級名稱 |
|
||||
| `%(levelno)s` | 日誌的等級數值 |
|
||||
| `%(lineno)d` | 呼叫日誌函數所在的行數 |
|
||||
| `%(message)s` | 訊息 |
|
||||
| `%(module)s` | 模組名稱 |
|
||||
| `%(name)s` | logger 的名稱 |
|
||||
| `%(pathname)s` | 檔案的完整路徑 (如果可用) |
|
||||
| `%(process)d` | process ID (如果可用) |
|
||||
| `%(thread)d` | 執行緒 ID (如果可用) |
|
||||
| `%(threradName)s` | 執行緒名稱 |
|
||||
|
||||
例:
|
||||
```python
|
||||
FORMAT = '%(asctime)s %(levelname)s: %(message)s'
|
||||
logging.basicConfig(level=logging.DEBUG, format=FORMAT)
|
||||
logging.debug('debug message') --> 2018-12-13 17:40:34,604 DEBUG: debug message
|
||||
```
|
||||
|
||||
### 儲存log
|
||||
只要在 `logging.basicConfig()` 內的 `filename` 參數設定要儲存的日誌檔名,就可以將 logging 儲存:
|
||||
```python
|
||||
import logging
|
||||
|
||||
FORMAT = '%(asctime)s %(levelname)s: %(message)s'
|
||||
logging.basicConfig(level=logging.DEBUG, filename='myLog.log', filemode='w', format=FORMAT)
|
||||
logging.debug('debug message')
|
||||
```
|
||||
預設 `filemode` 參數是設為 `a`,代表 append (附加) 的意思,每次執行程式時,Logging 會將新的訊息加在舊的訊息後面,不會覆蓋舊的訊息。若要改成新訊息覆蓋就訊息,那可以將 `filemode` 參數設為 `w`,代表 write 的意思。
|
||||
|
||||
### 儲存log也輸出到console
|
||||
`logging`有4個主要module:
|
||||
- Logger:暴露了應用程式程式碼能直接使用的介面。
|
||||
- Handler:將(記錄器產生的)日誌記錄傳送至合適的目的地。
|
||||
- Filter:提供了更好的粒度控制,它可以決定輸出哪些日誌記錄。
|
||||
- Formatter:指明瞭最終輸出中日誌記錄的佈局。
|
||||
|
||||
#### Handler
|
||||
其中`Handlers`有以下幾類:
|
||||
1. `logging.StreamHandler` -> 控制檯輸出
|
||||
使用這個Handler可以向類似與`sys.stdout`或者`sys.stderr`的任何檔案物件(file object)輸出資訊。
|
||||
它的建構函式是: `StreamHandler([strm])` 其中`strm`引數是一個檔案物件。預設是`sys.stderr`。
|
||||
2. `logging.FileHandler` -> 檔案輸出
|
||||
和StreamHandler類似,用於向一個檔案輸出日誌資訊。不過`FileHandler`會幫你開啟這個檔案。
|
||||
它的建構函式是:`FileHandler(filename[,mode])` filename是檔名,必須指定一個檔名。 `mode`是檔案的開啟方式。預設是`'a'`,即新增到檔案末端。
|
||||
3. `logging.handlers.RotatingFileHandler` -> 按照大小自動分割日誌檔案,一旦達到指定的大小重新生成檔案
|
||||
這個Handler類似於上面的`FileHandler`,但是它可以管理檔案大小。當檔案達到一定大小之後,它會自動將當前日誌檔案改名,然後建立一個新的同名日誌檔案繼續輸出。比如日誌檔案是chat.log。當chat.log達到指定的大小之後,`RotatingFileHandler`自動把 檔案改名為chat.log.1。不過,如果chat.log.1已經存在,會先把chat.log.1重新命名為chat.log.2。
|
||||
最後重新建立 chat.log,繼續輸出日誌資訊。它的建構函式是:`RotatingFileHandler(filename[, mode[, maxBytes[, backupCount]]])`,其中`filename`和`mode`兩個引數和FileHandler一樣。`maxBytes`用於指定日誌檔案的最大檔案大小。如果maxBytes為0,意味著日誌檔案可以無限大,這時上面描述的重新命名過程就不會發生。 `backupCount`用於指定保留的備份檔案的個數。比如,如果指定為2,當上面描述的重新命名過程發生時,原有的chat.log.2並不會被更名,而是被刪除。
|
||||
4. `logging.handlers.TimedRotatingFileHandler` -> 按照時間自動分割日誌檔案
|
||||
這個Handler和`RotatingFileHandler`類似,不過,它沒有通過判斷檔案大小來決定何時重新建立日誌檔案,而是間隔一定時間就自動建立新的日誌檔案。重新命名的過程與`RotatingFileHandler`類似,不過新的檔案不是附加數字,而是當前時間。它的建構函式是:`TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])`,其中`filename`引數和`backupCount`引數和`RotatingFileHandler`具有相同的意義。`interval`是時間間隔。 `when`引數是一個字串。表示時間間隔的單位,不區分大小寫。它有以下取值: S 秒 M 分 H 小時 D 天 W 每星期(`interval==0`時代表星期一) midnight 每天凌晨。
|
||||
|
||||
#### Formatters
|
||||
Formatters預設的時間格式為`%Y-%m-%d %H:%M:%S`
|
||||
|
||||
#### Example
|
||||
新增2個handler,一個輸出到螢幕上,一個寫到檔案裡。寫到檔案裡的那個handler必須是`logging.handlers.RotatingFileHandler`,超過1MB時會自動分割。
|
||||
```python
|
||||
import logging
|
||||
import logging.handlers
|
||||
|
||||
logger = logging.getLogger(filename) # filename就是你要存log的檔名
|
||||
|
||||
shell_print = logging.StreamHandler() # 往螢幕上輸出
|
||||
shell_print.setFormatter(format_str) # 設定螢幕上顯示的格式
|
||||
|
||||
file_print = logging.handlers.RotatingFileHandler(
|
||||
filename=filename,
|
||||
mode='a',
|
||||
maxBytes=1024*1024,
|
||||
backupCount=backCount,
|
||||
encoding='utf-8')
|
||||
file_print.setFormatter(format_str) # 設定檔案裡寫入的格式
|
||||
|
||||
logger.addHandler(sh) # 把物件加到logger裡
|
||||
logger.addHandler(th)
|
||||
```
|
||||
|
||||
-----
|
||||
參考:
|
||||
- [Python - 日誌 (logging) 模組](https://titangene.github.io/article/python-logging.html)
|
||||
- [`logging` — Logging facility for Python](https://docs.python.org/3/library/logging.html#module-logging "logging: Flexible event logging system for applications.")
|
||||
61
02. PARA/03. Resources(資源)/Python/opencv.md
Normal file
61
02. PARA/03. Resources(資源)/Python/opencv.md
Normal file
@@ -0,0 +1,61 @@
|
||||
### 將camera包裝成class
|
||||
```python
|
||||
class CameraCv(object):
|
||||
def __init__(self, videoSource=0):
|
||||
self.videoSource = videoSource
|
||||
self.camera = None
|
||||
self.cameraWidth = 0
|
||||
self.cameraHeight = 0
|
||||
self.cameraPreviewThreadHandle = None
|
||||
self.cameraPreviewThreadStopEvent = threading.Event()
|
||||
self.lastframeRGB = None
|
||||
self.latestFrame = None
|
||||
|
||||
def start(self):
|
||||
print("Open Camera")
|
||||
self.camera = cv2.VideoCapture(self.videoSource, cv2.CAP_DSHOW)
|
||||
if not self.camera.isOpened():
|
||||
raise ValueError("Unable to open video source {}".format(self.videoSource))
|
||||
|
||||
# Get video source width and height
|
||||
self.cameraWidth = self.camera.get(cv2.CAP_PROP_FRAME_WIDTH)
|
||||
self.cameraHeight = self.camera.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
||||
|
||||
self.cameraPreviewThreadStopEvent.clear()
|
||||
self.cameraPreviewThreadHandle = threading.Thread(target=self.collectFrame, daemon=True, args=())
|
||||
self.cameraPreviewThreadHandle.start()
|
||||
|
||||
def stop(self):
|
||||
print("Close Camera")
|
||||
self.cameraPreviewThreadStopEvent.set()
|
||||
if self.camera.isOpened():
|
||||
self.camera.release()
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
def collectFrame(self):
|
||||
while True:
|
||||
ret, frame = self.camera.read()
|
||||
if ret:
|
||||
# Return a boolean success flag and the current frame converted to BGR
|
||||
self.lastframeRGB = frame
|
||||
self.latestFrame = ImageTk.PhotoImage(image=Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
|
||||
|
||||
if self.cameraPreviewThreadStopEvent.is_set():
|
||||
break
|
||||
|
||||
time.sleep(0.016)
|
||||
|
||||
def draw(self, container):
|
||||
if self.latestFrame is not None:
|
||||
container.imgtk = self.latestFrame
|
||||
container.configure(image=self.latestFrame)
|
||||
|
||||
def read(self):
|
||||
return self.camera.read()
|
||||
|
||||
def getLastFrameRgb(self):
|
||||
return self.lastframeRGB
|
||||
|
||||
def saveFrame(self, filepath):
|
||||
cv2.imwrite(filepath, self.getLastFrameRgb())
|
||||
```
|
||||
49
02. PARA/03. Resources(資源)/Python/subprocess.md
Normal file
49
02. PARA/03. Resources(資源)/Python/subprocess.md
Normal file
@@ -0,0 +1,49 @@
|
||||
### subprocess.Popen
|
||||
```python
|
||||
import subprocess
|
||||
|
||||
process = subprocess.Popen(['echo', 'More output'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = process.communicate()
|
||||
stdout, stderr
|
||||
```
|
||||
|
||||
Input arguments is a list.
|
||||
Notice `communicate()` will **block** until process was finished.
|
||||
And the output string `stdout` and `stderr` is of type `byte`. You can convert the output to `string` by:
|
||||
```python
|
||||
new_string = stdout.decode('utf-8')
|
||||
```
|
||||
or use `universal_newlines=True` in `subprocess.Popen()`. Example:
|
||||
```python
|
||||
process = subprocess.Popen(['ping', '-c 4', 'python.org'],
|
||||
stdout=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
```
|
||||
The `.poll()` will return the exit code of process. If process is still running. `.poll()` will return `None`. Example:
|
||||
```python
|
||||
process = subprocess.Popen(['ping', '-c 4', 'python.org'], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
|
||||
while True:
|
||||
output = process.stdout.readline()
|
||||
print(output.strip())
|
||||
# Do something else
|
||||
return_code = process.poll()
|
||||
if return_code is not None:
|
||||
print('RETURN CODE', return_code)
|
||||
# Process has finished, read rest of the output
|
||||
for output in process.stdout.readlines():
|
||||
print(output.strip())
|
||||
break
|
||||
```
|
||||
|
||||
-----
|
||||
參考:
|
||||
- [docs.python.org: `subprocess.Popen`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
|
||||
|
||||
### subprocess.run
|
||||
`subprocess.run()`跟`subprocess.Popen()`是一樣的行為,差別是`subprocess.run()`會在process執行完畢之後才return,也就是說流程會被block住。
|
||||
`subprocess.run()`會回傳一個型別是`subprocess.CompletedProcess`的object.
|
||||
|
||||
-----
|
||||
參考:
|
||||
- [docs.python.org: _class_ `subprocess.CompletedProcess`](https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess)
|
||||
2
02. PARA/03. Resources(資源)/Python/threading.md
Normal file
2
02. PARA/03. Resources(資源)/Python/threading.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- [Python 多執行緒 threading 模組平行化程式設計教學 - G. T. Wang](https://blog.gtwang.org/programming/python-threading-multithreaded-programming-tutorial/)
|
||||
- [Python — 多線程. 介紹 | by Jease | Jease隨筆 | Medium](https://medium.com/jeasee%E9%9A%A8%E7%AD%86/python-%E5%A4%9A%E7%B7%9A%E7%A8%8B-eb36272e604b)
|
||||
96
02. PARA/03. Resources(資源)/Python/tkinter.md
Normal file
96
02. PARA/03. Resources(資源)/Python/tkinter.md
Normal file
@@ -0,0 +1,96 @@
|
||||
### 把matplotlib包裝成獨立視窗
|
||||
```python
|
||||
class Plot2D(Frame):
|
||||
def __init__(self, parent, dataCollector, **kwargs):
|
||||
Frame.__init__(self, parent.mainWindow, **kwargs)
|
||||
|
||||
self.parent = parent
|
||||
self.mainWindows = Toplevel(parent.mainWindow)
|
||||
self.mainWindows.title("AF State")
|
||||
self.figure = plt.Figure(figsize=(9,5), dpi=100)
|
||||
self.figure.suptitle('AF value plot', fontsize=16)
|
||||
self.ax = self.figure.add_subplot(111)
|
||||
self.canvas = FigureCanvasTkAgg(self.figure, master=self.mainWindows)
|
||||
self.canvas.get_tk_widget().pack(fill='both')
|
||||
self.axline = None
|
||||
|
||||
self.dataCollector = dataCollector
|
||||
self.dataCollector.start()
|
||||
|
||||
def close(self):
|
||||
print("Plot2D close")
|
||||
self.mainWindows.destroy()
|
||||
self.dataCollector.stop()
|
||||
self.dataCollector = None
|
||||
|
||||
def draw(self):
|
||||
if self.dataCollector:
|
||||
datax, datay = self.dataCollector.getPlotData()
|
||||
self.ax.clear()
|
||||
self.ax.set_xlabel('Last {} datas'.format(self.dataCollector.getDataLength()))
|
||||
self.axline, = self.ax.plot(datax, datay)
|
||||
self.canvas.draw()
|
||||
|
||||
def getWindow(self):
|
||||
return self.mainWindows
|
||||
|
||||
def getLastData(self):
|
||||
return self.dataCollector.getLastData()
|
||||
```
|
||||
|
||||
其中這一行:
|
||||
```python
|
||||
self.mainWindows = Toplevel(parent.mainWindow)
|
||||
```
|
||||
是用來開一個新的視窗,其中的`parent.mainWindow`就是用`tk.TK()`所產生出來的root。
|
||||
|
||||
因為需要一直更新資料,所以需要的一個`DataCollector`來提供資料,`DataCollector`會提供畫圖需要的list,如:
|
||||
```python
|
||||
datax, datay = self.dataCollector.getPlotData()
|
||||
```
|
||||
|
||||
`DataCollector`的定義如下:
|
||||
```python
|
||||
class AfStateCollector(threading.Thread):
|
||||
def __init__(self, dataLength=100, pollingInterval=0.033):
|
||||
threading.Thread.__init__(self)
|
||||
self.dataLength = dataLength
|
||||
self.pollingInterval = pollingInterval
|
||||
self.stopEvent = threading.Event()
|
||||
self.data = []
|
||||
self.xdata = []
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.stopEvent.is_set():
|
||||
break
|
||||
|
||||
afValue = self.readAf()
|
||||
self.data.append(afValue)
|
||||
self.xdata.append(len(self.xdata))
|
||||
if len(self.data) > self.dataLength:
|
||||
self.data = self.data[-self.dataLength:]
|
||||
self.xdata = list(range(self.dataLength))
|
||||
|
||||
# print(f'afValue = {afValue}')
|
||||
time.sleep(self.pollingInterval)
|
||||
|
||||
print("AfStateCollector stopped.")
|
||||
|
||||
def readAf(self):
|
||||
ReadTestXUreg_cmd = "lvreg testxu read 10"
|
||||
ReadTestXUreg_cmd_process = subprocess.Popen(ReadTestXUreg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
outstring, err = ReadTestXUreg_cmd_process.communicate()
|
||||
outstring = outstring.strip().decode('utf-8')
|
||||
outstring = int(outstring, 16)
|
||||
outstring_H = (outstring & 0xFF00) / 256
|
||||
outstring_L = outstring & 0xFF
|
||||
outAFStat = int(outstring_L * 256 + outstring_H)
|
||||
|
||||
return outAFStat
|
||||
```
|
||||
|
||||
- [Python GUI之tkinter視窗視窗教程大集合(看這篇就夠了) - IT閱讀](https://www.itread01.com/content/1547705544.html)
|
||||
- [【Python】改善 VideoCapture 的影像延遲 | 夏恩的程式筆記 - 點部落](https://dotblogs.com.tw/shaynling/2017/12/28/091936)
|
||||
- [Displaying a video feed with OpenCV and Tkinter - PyImageSearch](https://www.pyimagesearch.com/2016/05/30/displaying-a-video-feed-with-opencv-and-tkinter/)
|
||||
96
02. PARA/03. Resources(資源)/Python/檢測工具.md
Normal file
96
02. PARA/03. Resources(資源)/Python/檢測工具.md
Normal file
@@ -0,0 +1,96 @@
|
||||
## 單元測試
|
||||
### [pytest](https://docs.pytest.org/en/7.1.x/)
|
||||
Pytest 不僅可以幫助我們運行測試,還可以幫助我們配置如何運行它們、運行哪些文件等等……Pytest 有一個配置文件 `pytest.ini`,您可以在其中描述它的配置,例如哪個版本應該是 Pytest 或者哪些是測試文件,例如下列。
|
||||
```ini
|
||||
# pytet.ini
|
||||
[pytest]
|
||||
minversion = 6.0
|
||||
addopts = -ra -q — cov=src — cov-report=html
|
||||
python_files = test_*.py
|
||||
```
|
||||
|
||||
### [tox](https://tox.wiki/en/latest/)
|
||||
Tox 是一個通用的virtualenv管理和測試命令行工具。
|
||||
使用不同的 Python 版本和解釋器檢查您的包是否正確安裝
|
||||
在每個環境中運行您的測試,配置您選擇的測試工具
|
||||
作為持續集成服務器的前端,大大減少樣板文件並合併 CI 和基於 shell 的測試。
|
||||
Tox 也有它的配置文件。
|
||||
```ini
|
||||
[tox]
|
||||
isolated_build = True
|
||||
envlist = py{38}
|
||||
|
||||
[testenv]
|
||||
usedevelop = true
|
||||
deps = -r src/requirements_dev.txt
|
||||
```
|
||||
|
||||
|
||||
## 程式檢查工具
|
||||
用來檢查程式是否符合coding style、PEP8之類的規範
|
||||
### [pylint](https://github.com/PyCQA/pylint)
|
||||
Pylint config: create `.pylintrc` file
|
||||
```
|
||||
[MESSAGES CONTROL]
|
||||
disable=
|
||||
missing-docstring,
|
||||
too-few-public-methods[REPORTS]
|
||||
output-format=colorized
|
||||
files-output=no
|
||||
reports=no
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
```
|
||||
|
||||
### [flake8](https://github.com/pycqa/flake8)
|
||||
Flake8 config: create `.flake8` file
|
||||
```
|
||||
[flake8]
|
||||
ignore = E203, E266, E501, W503, F403, F401, E402
|
||||
max-line-length = 120
|
||||
max-complexity = 18
|
||||
select = B,C,E,F,W,T4,B9
|
||||
exclude =
|
||||
.git,
|
||||
tests
|
||||
```
|
||||
|
||||
### [mypy](http://www.mypy-lang.org/)
|
||||
|
||||
## Git hook
|
||||
### pre-commit
|
||||
Pre-commit 是一個創建 git hook的framework,以確保您的代碼編寫與您定義的代碼樣式相對應。
|
||||
它會掃描您的原始碼並運行您將在預提交配置文件中定義的所有檢查器:`.pre-commit-config.yaml`
|
||||
```
|
||||
repos:
|
||||
- repo: 'https://gitlab.com/pycqa/flake8'
|
||||
rev: 3.8.2
|
||||
hooks:
|
||||
- id: flake8
|
||||
name: Style Guide Enforcement (flake8)
|
||||
args:
|
||||
- '--max-line-length=120'
|
||||
- repo: 'https://github.com/pre-commit/mirrors-mypy'
|
||||
rev: v0.720
|
||||
hooks:
|
||||
- id: mypy
|
||||
name: Optional Static Typing for Python (mypy)
|
||||
```
|
||||
|
||||
## 漏洞檢查
|
||||
### [SonarQube](https://www.sonarqube.org/)
|
||||
有很多用於漏洞掃描的工具,但我們將看看[Sonarqube](https://www.sonarqube.org/)。Sonarqube 是用於代碼質量和安全掃描的開源強大工具,是該行業的領先工具之一。
|
||||
更多在[官方文檔](https://docs.sonarqube.org/latest/)中。
|
||||
您可以使用 Docker 映像設置本地 Sonarqube 服務器並定義`sonar-project.properties`
|
||||
```
|
||||
# must be unique in a given SonarQube instance
|
||||
sonar.projectKey=python_app_blueprint
|
||||
# --- optional properties ---
|
||||
# defaults to project key
|
||||
#sonar.projectName=My project
|
||||
# defaults to 'not provided'
|
||||
#sonar.projectVersion=1.0
|
||||
# Path is relative to the sonar-project.properties file. Defaults to .
|
||||
#sonar.sources=.
|
||||
# Encoding of the source code. Default is default system encoding
|
||||
#sonar.sourceEncoding=UTF-8
|
||||
```
|
||||
21
02. PARA/03. Resources(資源)/QT/Dropdown button.md
Normal file
21
02. PARA/03. Resources(資源)/QT/Dropdown button.md
Normal file
@@ -0,0 +1,21 @@
|
||||
![[Pasted image 20220519094358.png]]
|
||||
|
||||
1. Button必須是QToolButton.
|
||||
2. 建立menu.
|
||||
3. 建立action.
|
||||
4. 把action加到menu裡面
|
||||
5. 把menu設定給button
|
||||
6. code example:
|
||||
```cpp
|
||||
QMenu* saveFrameMenu = new QMenu;
|
||||
saveRawAction = new QAction(QIcon(QPixmap(":/image/resources/button-raw.png")), "SaveRaw", this);
|
||||
saveJpgAction = new QAction(QIcon(QPixmap(":/image/resources/button-jpg.png")), "SaveJpg", this);
|
||||
saveBmpAction = new QAction(QIcon(QPixmap(":/image/resources/button-bmp.png")), "SaveBmp", this);
|
||||
saveFrameMenu->addAction(saveRawAction);
|
||||
saveFrameMenu->addAction(saveJpgAction);
|
||||
saveFrameMenu->addAction(saveBmpAction);
|
||||
ui.toolButtonSaveFrame->setMenu(saveFrameMenu);
|
||||
ui.toolButtonSaveFrame->setDefaultAction(saveJpgAction);
|
||||
ui.toolButtonSaveFrame->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
ui.toolButtonSaveFrame->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
```
|
||||
9
02. PARA/03. Resources(資源)/QT/QVariant.md
Normal file
9
02. PARA/03. Resources(資源)/QT/QVariant.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Qt有許多UI元件都可以另外設定相依的「資料」,例如`QCombobox`,這樣我們就不必老是依賴選項的文字來做許多麻煩的處理。
|
||||
例如,`QCombobox.addItem()`可以夾帶一個`QVariant`,這個`QVariant`就可以夾帶一個pointer。
|
||||
要讓`QVariant`支援`std::shared_ptr`的方式很簡單,只要在你程式的最上方加入你要使用的type就可以,例如,我要支援我自己的class AwSentinelDevice,就這樣寫:
|
||||
```cpp
|
||||
#include <memory>
|
||||
|
||||
Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<AwSentinelDevice>);
|
||||
```
|
||||
734
02. PARA/03. Resources(資源)/QT/Qt.md
Normal file
734
02. PARA/03. Resources(資源)/QT/Qt.md
Normal file
@@ -0,0 +1,734 @@
|
||||
## Prepare
|
||||
### Windows 10 SDK
|
||||
下載並安裝[Windows 10 SDK](https://developer.microsoft.com/zh-tw/windows/downloads/windows-10-sdk/),安裝
|
||||
**Debugging Tools for Windows**就可以了。
|
||||
![[chrome_20210309_110223_934x685.png]]
|
||||
|
||||
然後在QtCreator裡面設置Debugger。
|
||||
![[NVIDIA_Share_20210309_110738_1920x1080.png]]
|
||||
|
||||
### Visual Studio 2017
|
||||
- ![[Pasted image 20210312144218.png]]
|
||||
- ![[Pasted image 20210312144242.png]]
|
||||
- ![[Pasted image 20210312144405.png]]
|
||||
- ![[Pasted image 20210312144523.png]]
|
||||
|
||||
## PRO file 語法
|
||||
### 導入lib
|
||||
```
|
||||
QT += <lib> <lib2>
|
||||
```
|
||||
|
||||
### 導入include path
|
||||
```
|
||||
INCLUDEPATH += <INCLUDE_PATH>
|
||||
```
|
||||
若使用相對路徑,必須相對於makefile生成的路徑。
|
||||
也可以使用`$$PWD`來生成絕對路徑,例:
|
||||
```
|
||||
INCLUDEPATH += $$PWD/../../include
|
||||
```
|
||||
|
||||
> 可以用`message()`來印出變數:`message($$PWD)`
|
||||
|
||||
### 導入lib
|
||||
用`-L`指定library path,用`-l`指定libraray name。例:
|
||||
```
|
||||
LIBS += -L"../../lib" -lopencv_world320
|
||||
```
|
||||
|
||||
### 指定執行目錄
|
||||
```
|
||||
DESTDIR += ../../bin
|
||||
```
|
||||
|
||||
### 指定執行檔檔名
|
||||
```
|
||||
TARGET = <NEW_FILENAME>
|
||||
```
|
||||
|
||||
- Import QT pro file into Visual Studio.
|
||||
- `qmake -tp vc <QT_PROJECT_FILE>`
|
||||
- 使用VS工具import。
|
||||
|
||||
## 基本類別(class)
|
||||
### QIODevice
|
||||
An abstract class that wraps input and output for most devices
|
||||
|
||||
### QDataStream
|
||||
依序寫入的資料,也可以依序被讀出來。資料會被編碼過,所以用Text editor會看到亂碼。
|
||||
|
||||
### QTextStream
|
||||
可以依序寫入字串,也可以依序被讀出來,因為寫入的是字串,所以用Text editor就可以看到資料。
|
||||
|
||||
### QLockFile
|
||||
創造一個在操作時可以獨佔的檔案。避免race condition的情形。
|
||||
```cpp
|
||||
QLockFile lock(fileName() +".lock");
|
||||
lock.setStaleLockTime(30000);
|
||||
|
||||
if(lock.tryLock()) {
|
||||
...
|
||||
lock.unlock();
|
||||
} else {
|
||||
qInfo() << "Could not lock the file!";
|
||||
qint64 pid;
|
||||
QString host;
|
||||
QString application;
|
||||
|
||||
if(lock.getLockInfo(&pid,&host,&application)) {
|
||||
qInfo() << "The file is locked by:";
|
||||
qInfo() << "Pid: " << pid;
|
||||
qInfo() << "Host: " << host;
|
||||
qInfo() << "Application: " << application;
|
||||
|
||||
} else {
|
||||
qInfo() << "File is locked, but we can't get the info!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### QtMessageHandler
|
||||
可以用來重新導向或攔截`qInfo()`、`qDebug()`、`qWarning()`、`qCritical()`、`qFatal()`這些function
|
||||
```cpp
|
||||
const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
|
||||
|
||||
void lhandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
|
||||
QString txt;
|
||||
switch (type) {
|
||||
case QtInfoMsg:
|
||||
txt = QString("Info: %1 in %2").arg(msg);
|
||||
break;
|
||||
case QtDebugMsg:
|
||||
txt = QString("Debug: %1").arg(msg);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
txt = QString("Warning: %1").arg(msg);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
txt = QString("Critical: %1").arg(msg);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
txt = QString("Fatal: %1").arg(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
qInstallMessageHandler(lhandler);
|
||||
|
||||
qInfo() << "This is a info message";
|
||||
qDebug() << "This is a debug message";
|
||||
qWarning() << "This is a warning message";
|
||||
qCritical() << "This is a critical message";
|
||||
qFatal("THIS IS SPARTA!!!");
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
```
|
||||
|
||||
也可以把這機制封裝在class裡面
|
||||
```cpp
|
||||
// logger.h
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <iostream>
|
||||
#include <QTextStream>
|
||||
|
||||
class logger : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit logger(QObject *parent = nullptr);
|
||||
|
||||
static bool logging;
|
||||
static QString filename;
|
||||
static void attach();
|
||||
static void handler(QtMsgType type, const QMessageLogContext &context, const QString & msg);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
||||
```
|
||||
|
||||
```cpp
|
||||
// logger.cpp
|
||||
#include "logger.h"
|
||||
|
||||
QString logger::filename = QDir::currentPath() + QDir::separator() + "log.txt";
|
||||
bool logger::logging = false;
|
||||
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
|
||||
|
||||
logger::logger(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void logger::attach()
|
||||
{
|
||||
logger::logging = true;
|
||||
qInstallMessageHandler(logger::handler);
|
||||
|
||||
}
|
||||
|
||||
void logger::handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
// Here, write log message to file
|
||||
|
||||
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
|
||||
}
|
||||
```
|
||||
|
||||
### Logging category
|
||||
Logging category可以用來把輸出的log前面加上一個字串以便分類。
|
||||
```cpp
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
//Declare a logging category
|
||||
Q_DECLARE_LOGGING_CATEGORY(network);
|
||||
Q_LOGGING_CATEGORY(network, "network");
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
qInfo() << "test"; // stdout: "test"
|
||||
qInfo(network) << "test"; // stdout: "network: test"
|
||||
|
||||
//turn it off
|
||||
QLoggingCategory::setFilterRules("network.debug=false");
|
||||
|
||||
//Will not log
|
||||
qDebug(network) << This message will not shown;
|
||||
|
||||
if(!network().isDebugEnabled()) {
|
||||
QLoggingCategory::setFilterRules("network.debug=true");
|
||||
qDebug(network) << "We turned it back on";
|
||||
}
|
||||
|
||||
qDebug(network) << "You see this because you turn the message on";
|
||||
return a.exec();
|
||||
}
|
||||
```
|
||||
|
||||
## QThread
|
||||
1. 繼承自QThread。
|
||||
2. 重載`run()`。
|
||||
3. 呼叫`start()`來開始thread。
|
||||
|
||||
## QWidget
|
||||
### 設定signal與對應的slot
|
||||
1. 在class header裡面加入`Q_OBJECT`。
|
||||
2. 在constructor裡,呼叫`connect()`來建立對應關係:`connect(SOURCE_OBJECT, SIGNAL(clicked()), DEST_OBJECT, SLOT(close()))`。
|
||||
|
||||
### Postion
|
||||
1. `QRect geometry();` 取得x, y, width, height
|
||||
2. `setGeometry();` 設定x, y, width, height
|
||||
3. `x()`, `y()`, `width()`, `height()` 單獨取得x, y, width, height
|
||||
4. `move(x, y);` 只設定x, y
|
||||
5. `resize(width, height);` 只設定width, height
|
||||
|
||||
### Window state
|
||||
1. `setWindowState(Qt::WindowMaximized);`
|
||||
1. `Qt::WindowMaximized` -> `showMaximized()`
|
||||
2. `Qt::WindowMinimized` -> `showMinimized()`
|
||||
3. `Qt::WindowNoState` -> `showNormal()`
|
||||
4. `Qt::WindowFullScreen` -> `showFullScreen()`
|
||||
|
||||
### Window Style
|
||||
1. `CustomizeWindowHint()`
|
||||
2. `setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint)`
|
||||
1. Qt::WindowCloseButtonHint
|
||||
2. Qt::WindowMinimizeButtonHint
|
||||
3. `setWindowFlag(Qt::WindowCloseButtonHint, false)` // 取消close按鈕
|
||||
1. Qt::WindowCloseButtonHint
|
||||
2. Qt::WindowMinimizeButtonHint
|
||||
4. 沒有邊框的視窗:`setWindowFlags(Qt::FramelessWindowHint)`
|
||||
5. 透明背景: `setAttribute(Qt::WA_TranslucentBackground, true)`
|
||||
6. Style sheet: https://doc.qt.io/qt-5/stylesheet-syntax.html#style-rules
|
||||
|
||||
### Layout
|
||||
- QVBoxLayout
|
||||
```cpp
|
||||
QVBoxLayout* pVlayout = new QVBoxLayout();
|
||||
widget->setLayout(pVlayout);
|
||||
|
||||
// Add button to layout
|
||||
QPushButton* button = new QPushButton("My Button");
|
||||
pVlayout->addWidget(button);
|
||||
```
|
||||
|
||||
- `setContentsMargin()`:設定元與layout的距離
|
||||
- `setSpacing()`:設定元件與元件之間的距離
|
||||
|
||||
- QFormLayout
|
||||
- Iterate all items
|
||||
```cpp
|
||||
QFormLayout* layout = (QFormLayout*)this->layout();
|
||||
|
||||
for (int i = 0; i < layout->rowCount(); i++) {
|
||||
QLayoutItem* item = layout->itemAt(i, QFormLayout::FieldRole);
|
||||
QWidget* widget = (QWidget*)item->widget();
|
||||
|
||||
if (widget) {
|
||||
QString className = widget->metaObject()->className();
|
||||
|
||||
if (className == "QLineEdit") {
|
||||
QLineEdit* edit = (QLineEdit*)widget;
|
||||
edit->setText("Text changed");
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### QSizePolicly
|
||||
- QSizePolicly::GrowFlag:必要時可以超過
|
||||
- QSizePolicly::ExpandFlag:放到最大
|
||||
- QSizePolicly::ShrinkFlag:必要時可以小於建議尺寸
|
||||
- QSizePolicly::IgnoreFlag:忽略
|
||||
|
||||
## QLabel
|
||||
- `QLabel.setTextIneractionFlags()`:
|
||||
|
||||
## QSlider
|
||||
### Style
|
||||
#### groove
|
||||
```
|
||||
QSlider::groove {
|
||||
border: 1px solid #999999;
|
||||
height: 28px;
|
||||
background: rgba(155, 155, 155, 200);
|
||||
border-radius: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
#### handle
|
||||
```
|
||||
QSlider::handle {
|
||||
border: 1px solid #FF0000;
|
||||
width: 20px;
|
||||
margin: -10px 0;
|
||||
background: rgba(255, 0, 0, 200);
|
||||
border-radius: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
#### add-page
|
||||
```
|
||||
QSlider::add-page {
|
||||
background: rgba(255, 0, 0, 200);
|
||||
}
|
||||
```
|
||||
|
||||
#### sub-page
|
||||
```
|
||||
QSlider::add-page {
|
||||
background: rgba(0, 255, 0, 200);
|
||||
}
|
||||
```
|
||||
![[Pasted image 20210318115725.png]]
|
||||
|
||||
## QListWidget
|
||||
- Insert a QLineEdit
|
||||
```cpp
|
||||
QLineEdit* lineEdit = new QLineEdit("A line edit here");
|
||||
ui->listWidget->setItemWidget(new QListWidgetItem("", ui->listWidget), lineEdit);
|
||||
```
|
||||
|
||||
- Insert a QSpinBox
|
||||
```
|
||||
QSpinBox* spinBox = new QSpinBox();
|
||||
ui->listWidget->setItemWidget(new QListWidgetItem("Test item 5", ui->listWidget), spinBox);
|
||||
```
|
||||
|
||||
### Stylesheet example
|
||||
```
|
||||
QTableView {
|
||||
selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FF92BB, stop: 1 white);
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
QTableView QTableCornerButton::section {
|
||||
background: red;
|
||||
border: 2px outset red;
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
QTableView::indicator:unchecked {
|
||||
background-color: #d7d6d5
|
||||
}
|
||||
```
|
||||
|
||||
## QTableWidget
|
||||
- setColumnCount()
|
||||
- https://doc.qt.io/qt-5/qtablewidget.html#setColumnCount
|
||||
- setHorizontalHeaderItem(column_index, new QTableWidgetItem("Header1"))
|
||||
- https://doc.qt.io/qt-5/qtablewidget.html#setHorizontalHeaderItem
|
||||
- setColumnWidth(column_index, column_width)
|
||||
- https://doc.qt.io/qt-5/qtableview.html#setColumnWidth
|
||||
- Bind Header's event
|
||||
- `connect(ui.tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(SectionClicked(int)));`
|
||||
|
||||
- Style
|
||||
```
|
||||
QHeaderView::section:checked{ /* Focus */
|
||||
background-color: rgba(55, 55, 55,255 );
|
||||
}
|
||||
QHeaderView::section:hover{ /* 滑鼠懸停 */
|
||||
background-color: rgba(85, 10, 10,255 );
|
||||
}
|
||||
QTableWidget{
|
||||
background-color: rgb(27, 122, 239);
|
||||
alternate-background-color: rgb(26, 81, 232);
|
||||
color:white;
|
||||
}
|
||||
```
|
||||
|
||||
## QTreeWidget
|
||||
- Data create & insert
|
||||
- `insertTopLevelItem(int index, QTreeWidgetItem*)`
|
||||
- `addTopLevelItem(QTreeWidgetItem*)`
|
||||
- `new QTreeWidgetItem(QTreeWidget*)`
|
||||
|
||||
- Insert widget
|
||||
- `void setItemWidget(QTreeWidgetItem* item, int column, QWidget* widget)`
|
||||
|
||||
- Iterate items
|
||||
- `QTreeWidgetItem* topLevelItem(int index) const`
|
||||
- `int topLevelItemCount() const`
|
||||
|
||||
- Get item
|
||||
- `QList<QTreeItemWidget*> selectedItems() const`
|
||||
|
||||
- Style
|
||||
```
|
||||
/* 有旁節點,沒有與其他元素相鄰 */
|
||||
QTreeView::branch:has-siblings:!adjoins-item {
|
||||
border-image: url(vline.png) 0;
|
||||
}
|
||||
|
||||
/* 有旁節點,與其他元素相鄰 */
|
||||
QTreeView::branch:has-siblings:adjoins-item {
|
||||
border-image: url(branch-more.png) 0;
|
||||
}
|
||||
|
||||
/* 沒有子節點,沒有旁節點,與其他元素相鄰 */
|
||||
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
|
||||
border-image: url(branch-end.png) 0;
|
||||
}
|
||||
|
||||
/* 收合狀態,有子節點,沒有旁節點 */
|
||||
QTreeView::branch:closed:has-children:!has-siblings,
|
||||
/* 收合狀態,有子節點,有旁節點 */
|
||||
QTreeView::branch:closed:has-children:has-siblings {
|
||||
border-image: none; image: url(branch-closed.png);
|
||||
}
|
||||
|
||||
/* 打開狀態,有子節點,沒有旁節點 */
|
||||
QTreeView::branch:open:has-children:!has-siblings,
|
||||
/* 打開狀態,有子節點,有旁節點 */
|
||||
QTreeView::branch:open:has-children:has-siblings {
|
||||
border-image: none;
|
||||
image: url(branch-open.png);
|
||||
}
|
||||
```
|
||||
|
||||
## QStatusBar
|
||||
- `showMessage(QString, int miniSeconds)`: 顯示文字,一段時間後消失
|
||||
|
||||
## QToolBar
|
||||
- `addAction(QIcon, QString)`
|
||||
- `addAction(QString)`
|
||||
- `setToolButtonStyle(int)`
|
||||
|
||||
## QDockWidget
|
||||
- https://blog.csdn.net/czyt1988/article/details/51209619
|
||||
|
||||
## QJson
|
||||
- [C++(Qt5)入門範例:各種Json範例](https://lolikitty.pixnet.net/blog/post/206254516)
|
||||
|
||||
## QFile
|
||||
Read UTF-8 content from file.
|
||||
```cpp
|
||||
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QTextStream fileRead(&file);
|
||||
fileRead.setCodec("UTF-8");
|
||||
|
||||
QString fileContent = fileRead.readAll();
|
||||
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
|
||||
|
||||
file.close();
|
||||
|
||||
parseJson(fileContent);
|
||||
}
|
||||
else {
|
||||
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
|
||||
}
|
||||
```
|
||||
|
||||
## String
|
||||
### UTF-8
|
||||
1. 對於n bytes的字元,第一個byte的前n個bits是1,第n+1個bit是0,第2個byte以後,都以`10`開頭。
|
||||
1. 例:
|
||||
2. 1 byte character: 0x0XXXXXXX。
|
||||
3. 2 bytes character: 0x110XXXXXX 0x10XXXXXX。
|
||||
4. 3 bytes character: 0x1110XXXXX 0x10XXXXXX 0x10XXXXXX。
|
||||
5. 4 bytes character: 0x11110XXXX 0x10XXXXXX 0x10XXXXXX 0x10XXXXXX。
|
||||
2. 例:
|
||||
1. 「A」是一個1 byte字元,編碼為`0x01000001`。
|
||||
2. 「Á」是一個2 bytes字元,編碼為`0x11000011 0x10000001`。
|
||||
3. 「好」是一個3 bytes字元,編碼為`0x11100101 0x10100101 0x10111101`。
|
||||
3. [UTF-8 to binary轉換網站](https://onlineutf8tools.com/convert-utf8-to-binary)
|
||||
2. BOM
|
||||
1. Big endian: `FE FF`
|
||||
2. Little endian: `FF FE`
|
||||
|
||||
### QString
|
||||
- 16 bits QChar(unsigned short), UNIDODE 4.0
|
||||
- `==`: compare string
|
||||
- `isNull()`: If has a value
|
||||
- `isEmpty()`: If contained value == ""
|
||||
- `QString::number()`: 接收一個數字,把數字轉為字串。
|
||||
- `QString::toInt()`、`QString::toDouble()`:把字串轉為Int或Double。
|
||||
- 讀取UTF-8的字串:
|
||||
```
|
||||
codec = QTextCodec::codecFromName("UTF-8");
|
||||
QTextCodec::setCodecForLocale(codec);
|
||||
```
|
||||
然後用`QString::fromLocal8Bit()`來讀取字串,該字串就會被當作UTF-8來處理。
|
||||
- `QTextCodec::availableCodecs()`可以列出QT所支援的格式。
|
||||
- 在Visual Studio中,強制文件用UTF-8來存檔:`#pragma execution_character_set("UTF-8")`
|
||||
- 在Visual Studio中,預設是以CP950存檔,所以字串**都不是**unicode,而是**multi-character**,所以要從Windows讀進來的字串,一律需要使用`QString::fromLocal8Bit("")`,而一旦轉為QString之後,就是UTF-8了,若需要再輸出回Windows,則需要`QString.toLocal8Bit().toStdString()`。
|
||||
- 例:
|
||||
```cpp
|
||||
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QTextStream fileRead(&file);
|
||||
fileRead.setCodec("UTF-8");
|
||||
|
||||
QString fileContent = fileRead.readAll();
|
||||
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
|
||||
|
||||
file.close();
|
||||
|
||||
parseJson(fileContent);
|
||||
}
|
||||
else {
|
||||
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
|
||||
}
|
||||
```
|
||||
- Visual Studio 2017 debug QString: [调试时直接显示QString的字符值(包含windows和linux)](https://blog.csdn.net/iamdbl/article/details/78343642)
|
||||
|
||||
#### Search
|
||||
- `QString.indexOf()`
|
||||
- `QString.lastIndexOf()`
|
||||
|
||||
#### Trim
|
||||
- `QString.chop(5)`:去掉結尾的5個字元,inplace轉換,沒有回傳新字串。
|
||||
- `QString.left(5)`:取最左邊5個字元
|
||||
- `QString.right(5)`:取最右邊5個字元
|
||||
|
||||
#### Replace
|
||||
- `QString.replace("original", "new")`:將"original"替換為"new"。
|
||||
- 也支援regular expression
|
||||
|
||||
#### Split
|
||||
- `QString.split(",")`:用","把字串分割,並回傳一個`QStringList`。
|
||||
|
||||
#### Regular Expression
|
||||
- `.`:匹配任意1個字元
|
||||
- `+`:此類型的匹配字元可以1個以上
|
||||
- `*`:匹配任意多個字元
|
||||
- `^`:表示開始位置
|
||||
- `$`:表示結束位置
|
||||
- `?`:表示前一個匹配條件不一定要成立
|
||||
- `[]`:用括號來限制要匹配的字元
|
||||
|
||||
## Event
|
||||
- 如果有特殊需求,可以重載`QWidget::event(QEvent*)`來達成。
|
||||
|
||||
### MouseEvent
|
||||
- Get coordinate of component: `QMouseEvent::x()` & `QMouseEvent::y()`
|
||||
- Get coordinate of window: `QMouseEvent::windowPos().x()` & `QMouseEvent::windowPos().y()`
|
||||
- Get coordinate of screen: `QMouseEvent::screenPos().x()` & `QMouseEvent::screenPos().y()`
|
||||
- `QPoint mapToGlobal(QPoint)`: Translates the widget coordinate pos to global screen coordinates.
|
||||
- `QCursor::pos().x()` & `QCursor::pos().x()`: 也可以獲得screen coordinate。
|
||||
- Get click
|
||||
```
|
||||
if (mouseEvent->buttons() & Qt::LeftButton) // or Qt::RightButton, Qt::MiddleButton
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
### MouseCursor
|
||||
- 設定cursor click的位置:`QCursor cursor = QCursor(pixmap, -1, -1)`
|
||||
- (-1, -1) 表示用cursor中心點為click點
|
||||
- ( 0, 0) 表示用cursor左上角為click點
|
||||
- 更換cursor:`setCursor(Qt::ArrowCursor)`
|
||||
|
||||
### ResizeEvent
|
||||
- `void resizeEvent(QResizeEvent*)`
|
||||
|
||||
|
||||
## QOpenGLWidget
|
||||
- `void paintGL()`
|
||||
- `void initialGL()`
|
||||
- `void resizeGL(int w, int h)`
|
||||
|
||||
## QGLShaderProgram
|
||||
- `addShaderFromSourceCode`
|
||||
- `bindAttributeLocation`:設定傳入的變數
|
||||
- `uniformLocation`:取得變數
|
||||
|
||||
## GL概念
|
||||
### Vertex
|
||||
#### Coordinate
|
||||
```
|
||||
float *vertexData = new float[12] {
|
||||
1.0f, -1.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f,
|
||||
};
|
||||
```
|
||||
|
||||
### Texture
|
||||
#### Coordinate
|
||||
材質座標永遠都在第一象限
|
||||
```
|
||||
float* textureVertexData = new float[8] {
|
||||
1.0f, 0.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
};
|
||||
```
|
||||
|
||||
#### 建立材質
|
||||
- `glGenTextures(1, t);`
|
||||
- `glBindTexture(GL_TEXTURE_2D, *t);`
|
||||
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);`
|
||||
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);`
|
||||
|
||||
#### 寫入材質
|
||||
- `glActiveTexture(GL_TEXTURE0);`
|
||||
- `glBindTexture(GL_TEXTURE_2D, idY);`
|
||||
- `glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, pixelW, pixelH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, plane[0]);`
|
||||
```
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0, // 細節,預設0
|
||||
GL_LUMINANCE, // GPU內部格式
|
||||
pixelW,
|
||||
pixelH,
|
||||
0,
|
||||
GL_LUMINANCE, // Pixel format
|
||||
GL_UNSIGNED_BYTE, // Data format
|
||||
plane[0]);
|
||||
```
|
||||
- `glTexSubImage2D()`:修改texture
|
||||
- `glUniform1i(textureUniformY, 0);`
|
||||
- `glDrawArray(GL_TRIANGLE_STRIP, 0, 4);`
|
||||
|
||||
## GLSL
|
||||
|
||||
### 內建變數
|
||||
- `gl_FragColor`
|
||||
|
||||
### 3種變數類型
|
||||
- `varying`:vertex與shader共用,vertex運算完之後傳給shader使用。
|
||||
- `attribute`:vertex使用,由`bindAttributeLocation`傳入
|
||||
- `uniform`:由user傳入的資料,由`uniformLocation`得到pointer address,再由`glUniform1i(textureUniformY, 0)`來設定。
|
||||
|
||||
### Vertex Shader
|
||||
```
|
||||
attribute vec4 vertexIn;
|
||||
attribute vec2 textureIn;
|
||||
varying vec2 textureOut;
|
||||
|
||||
void main() {
|
||||
gl_position = vertexIn;
|
||||
textureOut = textureIn;
|
||||
}
|
||||
```
|
||||
|
||||
### Fragment Shader
|
||||
```
|
||||
varying vec2 textureOut;
|
||||
uniform sampler2D texY;
|
||||
uniform sampler2D texU;
|
||||
uniform sampler2D texV;
|
||||
|
||||
void main() {
|
||||
vec3 yuv;
|
||||
vec3 rgb;
|
||||
|
||||
yuv.x = textue2D(texY, textureOut).r;
|
||||
yuv.y = textue2D(texU, textureOut).r - 0.5;
|
||||
yuv.z = textue2D(texV, textureOut).r - 0.5;
|
||||
rgb = mat3(
|
||||
1, 1, 1,
|
||||
0, -0.39465, 2.03211,
|
||||
1.13983, -0.5806, 0) * yuv;
|
||||
gl_FragColor = vec4(rgb, 1);
|
||||
}
|
||||
```
|
||||
|
||||
### APIs
|
||||
畫出矩形與材質
|
||||
- `glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);`
|
||||
- `glEnableVertexAttribArray(ATTRIB_VERTEX);`
|
||||
- `glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);`
|
||||
- `glEnableVertexAttribArray(ATTRIB_TEXTURE);`
|
||||
|
||||
|
||||
|
||||
# 檔案操作
|
||||
## 開啟檔案對話框
|
||||
```c++
|
||||
QString selectedFilepath = QFileDialog::getOpenFileName(
|
||||
this,
|
||||
tr("Select test plan file"), // 檔案選擇對話框左上角的提示
|
||||
tr("."), // 檔案選擇對話框一開始的路徑
|
||||
"(*.json);;All files(*.*)"); // 可選擇的副檔名
|
||||
```
|
||||
使用者所選擇的檔名會放在`selectedFilepath`,可以用`selectedFilepath.isEmpty()`來判斷使用者是不是真的有選擇檔案或是按了取消鍵。
|
||||
|
||||
## 存檔對話框
|
||||
```c++
|
||||
QString fileName = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
tr("Save Address Book"), "", // 檔案選擇對話框左上角的提示
|
||||
tr("Address Book (*.abk);;All Files (*)")); // 存檔的副檔名
|
||||
```
|
||||
|
||||
# MISC
|
||||
## 教學文
|
||||
- [Qt教程,Qt5编程入门教程(非常详细)](http://c.biancheng.net/qt/)
|
||||
734
02. PARA/03. Resources(資源)/Qt.md
Normal file
734
02. PARA/03. Resources(資源)/Qt.md
Normal file
@@ -0,0 +1,734 @@
|
||||
## Prepare
|
||||
### Windows 10 SDK
|
||||
下載並安裝[Windows 10 SDK](https://developer.microsoft.com/zh-tw/windows/downloads/windows-10-sdk/),安裝
|
||||
**Debugging Tools for Windows**就可以了。
|
||||
![[chrome_20210309_110223_934x685.png]]
|
||||
|
||||
然後在QtCreator裡面設置Debugger。
|
||||
![[NVIDIA_Share_20210309_110738_1920x1080.png]]
|
||||
|
||||
### Visual Studio 2017
|
||||
- ![[Pasted image 20210312144218.png]]
|
||||
- ![[Pasted image 20210312144242.png]]
|
||||
- ![[Pasted image 20210312144405.png]]
|
||||
- ![[Pasted image 20210312144523.png]]
|
||||
|
||||
## PRO file 語法
|
||||
### 導入lib
|
||||
```
|
||||
QT += <lib> <lib2>
|
||||
```
|
||||
|
||||
### 導入include path
|
||||
```
|
||||
INCLUDEPATH += <INCLUDE_PATH>
|
||||
```
|
||||
若使用相對路徑,必須相對於makefile生成的路徑。
|
||||
也可以使用`$$PWD`來生成絕對路徑,例:
|
||||
```
|
||||
INCLUDEPATH += $$PWD/../../include
|
||||
```
|
||||
|
||||
> 可以用`message()`來印出變數:`message($$PWD)`
|
||||
|
||||
### 導入lib
|
||||
用`-L`指定library path,用`-l`指定libraray name。例:
|
||||
```
|
||||
LIBS += -L"../../lib" -lopencv_world320
|
||||
```
|
||||
|
||||
### 指定執行目錄
|
||||
```
|
||||
DESTDIR += ../../bin
|
||||
```
|
||||
|
||||
### 指定執行檔檔名
|
||||
```
|
||||
TARGET = <NEW_FILENAME>
|
||||
```
|
||||
|
||||
- Import QT pro file into Visual Studio.
|
||||
- `qmake -tp vc <QT_PROJECT_FILE>`
|
||||
- 使用VS工具import。
|
||||
|
||||
## 基本類別(class)
|
||||
### QIODevice
|
||||
An abstract class that wraps input and output for most devices
|
||||
|
||||
### QDataStream
|
||||
依序寫入的資料,也可以依序被讀出來。資料會被編碼過,所以用Text editor會看到亂碼。
|
||||
|
||||
### QTextStream
|
||||
可以依序寫入字串,也可以依序被讀出來,因為寫入的是字串,所以用Text editor就可以看到資料。
|
||||
|
||||
### QLockFile
|
||||
創造一個在操作時可以獨佔的檔案。避免race condition的情形。
|
||||
```cpp
|
||||
QLockFile lock(fileName() +".lock");
|
||||
lock.setStaleLockTime(30000);
|
||||
|
||||
if(lock.tryLock()) {
|
||||
...
|
||||
lock.unlock();
|
||||
} else {
|
||||
qInfo() << "Could not lock the file!";
|
||||
qint64 pid;
|
||||
QString host;
|
||||
QString application;
|
||||
|
||||
if(lock.getLockInfo(&pid,&host,&application)) {
|
||||
qInfo() << "The file is locked by:";
|
||||
qInfo() << "Pid: " << pid;
|
||||
qInfo() << "Host: " << host;
|
||||
qInfo() << "Application: " << application;
|
||||
|
||||
} else {
|
||||
qInfo() << "File is locked, but we can't get the info!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### QtMessageHandler
|
||||
可以用來重新導向或攔截`qInfo()`、`qDebug()`、`qWarning()`、`qCritical()`、`qFatal()`這些function
|
||||
```cpp
|
||||
const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
|
||||
|
||||
void lhandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
|
||||
QString txt;
|
||||
switch (type) {
|
||||
case QtInfoMsg:
|
||||
txt = QString("Info: %1 in %2").arg(msg);
|
||||
break;
|
||||
case QtDebugMsg:
|
||||
txt = QString("Debug: %1").arg(msg);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
txt = QString("Warning: %1").arg(msg);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
txt = QString("Critical: %1").arg(msg);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
txt = QString("Fatal: %1").arg(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
qInstallMessageHandler(lhandler);
|
||||
|
||||
qInfo() << "This is a info message";
|
||||
qDebug() << "This is a debug message";
|
||||
qWarning() << "This is a warning message";
|
||||
qCritical() << "This is a critical message";
|
||||
qFatal("THIS IS SPARTA!!!");
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
```
|
||||
|
||||
也可以把這機制封裝在class裡面
|
||||
```cpp
|
||||
// logger.h
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <iostream>
|
||||
#include <QTextStream>
|
||||
|
||||
class logger : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit logger(QObject *parent = nullptr);
|
||||
|
||||
static bool logging;
|
||||
static QString filename;
|
||||
static void attach();
|
||||
static void handler(QtMsgType type, const QMessageLogContext &context, const QString & msg);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
||||
```
|
||||
|
||||
```cpp
|
||||
// logger.cpp
|
||||
#include "logger.h"
|
||||
|
||||
QString logger::filename = QDir::currentPath() + QDir::separator() + "log.txt";
|
||||
bool logger::logging = false;
|
||||
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
|
||||
|
||||
logger::logger(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void logger::attach()
|
||||
{
|
||||
logger::logging = true;
|
||||
qInstallMessageHandler(logger::handler);
|
||||
|
||||
}
|
||||
|
||||
void logger::handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
// Here, write log message to file
|
||||
|
||||
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
|
||||
}
|
||||
```
|
||||
|
||||
### Logging category
|
||||
Logging category可以用來把輸出的log前面加上一個字串以便分類。
|
||||
```cpp
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
//Declare a logging category
|
||||
Q_DECLARE_LOGGING_CATEGORY(network);
|
||||
Q_LOGGING_CATEGORY(network, "network");
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
qInfo() << "test"; // stdout: "test"
|
||||
qInfo(network) << "test"; // stdout: "network: test"
|
||||
|
||||
//turn it off
|
||||
QLoggingCategory::setFilterRules("network.debug=false");
|
||||
|
||||
//Will not log
|
||||
qDebug(network) << This message will not shown;
|
||||
|
||||
if(!network().isDebugEnabled()) {
|
||||
QLoggingCategory::setFilterRules("network.debug=true");
|
||||
qDebug(network) << "We turned it back on";
|
||||
}
|
||||
|
||||
qDebug(network) << "You see this because you turn the message on";
|
||||
return a.exec();
|
||||
}
|
||||
```
|
||||
|
||||
## QThread
|
||||
1. 繼承自QThread。
|
||||
2. 重載`run()`。
|
||||
3. 呼叫`start()`來開始thread。
|
||||
|
||||
## QWidget
|
||||
### 設定signal與對應的slot
|
||||
1. 在class header裡面加入`Q_OBJECT`。
|
||||
2. 在constructor裡,呼叫`connect()`來建立對應關係:`connect(SOURCE_OBJECT, SIGNAL(clicked()), DEST_OBJECT, SLOT(close()))`。
|
||||
|
||||
### Postion
|
||||
1. `QRect geometry();` 取得x, y, width, height
|
||||
2. `setGeometry();` 設定x, y, width, height
|
||||
3. `x()`, `y()`, `width()`, `height()` 單獨取得x, y, width, height
|
||||
4. `move(x, y);` 只設定x, y
|
||||
5. `resize(width, height);` 只設定width, height
|
||||
|
||||
### Window state
|
||||
1. `setWindowState(Qt::WindowMaximized);`
|
||||
1. `Qt::WindowMaximized` -> `showMaximized()`
|
||||
2. `Qt::WindowMinimized` -> `showMinimized()`
|
||||
3. `Qt::WindowNoState` -> `showNormal()`
|
||||
4. `Qt::WindowFullScreen` -> `showFullScreen()`
|
||||
|
||||
### Window Style
|
||||
1. `CustomizeWindowHint()`
|
||||
2. `setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint)`
|
||||
1. Qt::WindowCloseButtonHint
|
||||
2. Qt::WindowMinimizeButtonHint
|
||||
3. `setWindowFlag(Qt::WindowCloseButtonHint, false)` // 取消close按鈕
|
||||
1. Qt::WindowCloseButtonHint
|
||||
2. Qt::WindowMinimizeButtonHint
|
||||
4. 沒有邊框的視窗:`setWindowFlags(Qt::FramelessWindowHint)`
|
||||
5. 透明背景: `setAttribute(Qt::WA_TranslucentBackground, true)`
|
||||
6. Style sheet: https://doc.qt.io/qt-5/stylesheet-syntax.html#style-rules
|
||||
|
||||
### Layout
|
||||
- QVBoxLayout
|
||||
```cpp
|
||||
QVBoxLayout* pVlayout = new QVBoxLayout();
|
||||
widget->setLayout(pVlayout);
|
||||
|
||||
// Add button to layout
|
||||
QPushButton* button = new QPushButton("My Button");
|
||||
pVlayout->addWidget(button);
|
||||
```
|
||||
|
||||
- `setContentsMargin()`:設定元與layout的距離
|
||||
- `setSpacing()`:設定元件與元件之間的距離
|
||||
|
||||
- QFormLayout
|
||||
- Iterate all items
|
||||
```cpp
|
||||
QFormLayout* layout = (QFormLayout*)this->layout();
|
||||
|
||||
for (int i = 0; i < layout->rowCount(); i++) {
|
||||
QLayoutItem* item = layout->itemAt(i, QFormLayout::FieldRole);
|
||||
QWidget* widget = (QWidget*)item->widget();
|
||||
|
||||
if (widget) {
|
||||
QString className = widget->metaObject()->className();
|
||||
|
||||
if (className == "QLineEdit") {
|
||||
QLineEdit* edit = (QLineEdit*)widget;
|
||||
edit->setText("Text changed");
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### QSizePolicly
|
||||
- QSizePolicly::GrowFlag:必要時可以超過
|
||||
- QSizePolicly::ExpandFlag:放到最大
|
||||
- QSizePolicly::ShrinkFlag:必要時可以小於建議尺寸
|
||||
- QSizePolicly::IgnoreFlag:忽略
|
||||
|
||||
## QLabel
|
||||
- `QLabel.setTextIneractionFlags()`:
|
||||
|
||||
## QSlider
|
||||
### Style
|
||||
#### groove
|
||||
```
|
||||
QSlider::groove {
|
||||
border: 1px solid #999999;
|
||||
height: 28px;
|
||||
background: rgba(155, 155, 155, 200);
|
||||
border-radius: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
#### handle
|
||||
```
|
||||
QSlider::handle {
|
||||
border: 1px solid #FF0000;
|
||||
width: 20px;
|
||||
margin: -10px 0;
|
||||
background: rgba(255, 0, 0, 200);
|
||||
border-radius: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
#### add-page
|
||||
```
|
||||
QSlider::add-page {
|
||||
background: rgba(255, 0, 0, 200);
|
||||
}
|
||||
```
|
||||
|
||||
#### sub-page
|
||||
```
|
||||
QSlider::add-page {
|
||||
background: rgba(0, 255, 0, 200);
|
||||
}
|
||||
```
|
||||
![[Pasted image 20210318115725.png]]
|
||||
|
||||
## QListWidget
|
||||
- Insert a QLineEdit
|
||||
```cpp
|
||||
QLineEdit* lineEdit = new QLineEdit("A line edit here");
|
||||
ui->listWidget->setItemWidget(new QListWidgetItem("", ui->listWidget), lineEdit);
|
||||
```
|
||||
|
||||
- Insert a QSpinBox
|
||||
```
|
||||
QSpinBox* spinBox = new QSpinBox();
|
||||
ui->listWidget->setItemWidget(new QListWidgetItem("Test item 5", ui->listWidget), spinBox);
|
||||
```
|
||||
|
||||
### Stylesheet example
|
||||
```
|
||||
QTableView {
|
||||
selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FF92BB, stop: 1 white);
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
QTableView QTableCornerButton::section {
|
||||
background: red;
|
||||
border: 2px outset red;
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
QTableView::indicator:unchecked {
|
||||
background-color: #d7d6d5
|
||||
}
|
||||
```
|
||||
|
||||
## QTableWidget
|
||||
- setColumnCount()
|
||||
- https://doc.qt.io/qt-5/qtablewidget.html#setColumnCount
|
||||
- setHorizontalHeaderItem(column_index, new QTableWidgetItem("Header1"))
|
||||
- https://doc.qt.io/qt-5/qtablewidget.html#setHorizontalHeaderItem
|
||||
- setColumnWidth(column_index, column_width)
|
||||
- https://doc.qt.io/qt-5/qtableview.html#setColumnWidth
|
||||
- Bind Header's event
|
||||
- `connect(ui.tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(SectionClicked(int)));`
|
||||
|
||||
- Style
|
||||
```
|
||||
QHeaderView::section:checked{ /* Focus */
|
||||
background-color: rgba(55, 55, 55,255 );
|
||||
}
|
||||
QHeaderView::section:hover{ /* 滑鼠懸停 */
|
||||
background-color: rgba(85, 10, 10,255 );
|
||||
}
|
||||
QTableWidget{
|
||||
background-color: rgb(27, 122, 239);
|
||||
alternate-background-color: rgb(26, 81, 232);
|
||||
color:white;
|
||||
}
|
||||
```
|
||||
|
||||
## QTreeWidget
|
||||
- Data create & insert
|
||||
- `insertTopLevelItem(int index, QTreeWidgetItem*)`
|
||||
- `addTopLevelItem(QTreeWidgetItem*)`
|
||||
- `new QTreeWidgetItem(QTreeWidget*)`
|
||||
|
||||
- Insert widget
|
||||
- `void setItemWidget(QTreeWidgetItem* item, int column, QWidget* widget)`
|
||||
|
||||
- Iterate items
|
||||
- `QTreeWidgetItem* topLevelItem(int index) const`
|
||||
- `int topLevelItemCount() const`
|
||||
|
||||
- Get item
|
||||
- `QList<QTreeItemWidget*> selectedItems() const`
|
||||
|
||||
- Style
|
||||
```
|
||||
/* 有旁節點,沒有與其他元素相鄰 */
|
||||
QTreeView::branch:has-siblings:!adjoins-item {
|
||||
border-image: url(vline.png) 0;
|
||||
}
|
||||
|
||||
/* 有旁節點,與其他元素相鄰 */
|
||||
QTreeView::branch:has-siblings:adjoins-item {
|
||||
border-image: url(branch-more.png) 0;
|
||||
}
|
||||
|
||||
/* 沒有子節點,沒有旁節點,與其他元素相鄰 */
|
||||
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
|
||||
border-image: url(branch-end.png) 0;
|
||||
}
|
||||
|
||||
/* 收合狀態,有子節點,沒有旁節點 */
|
||||
QTreeView::branch:closed:has-children:!has-siblings,
|
||||
/* 收合狀態,有子節點,有旁節點 */
|
||||
QTreeView::branch:closed:has-children:has-siblings {
|
||||
border-image: none; image: url(branch-closed.png);
|
||||
}
|
||||
|
||||
/* 打開狀態,有子節點,沒有旁節點 */
|
||||
QTreeView::branch:open:has-children:!has-siblings,
|
||||
/* 打開狀態,有子節點,有旁節點 */
|
||||
QTreeView::branch:open:has-children:has-siblings {
|
||||
border-image: none;
|
||||
image: url(branch-open.png);
|
||||
}
|
||||
```
|
||||
|
||||
## QStatusBar
|
||||
- `showMessage(QString, int miniSeconds)`: 顯示文字,一段時間後消失
|
||||
|
||||
## QToolBar
|
||||
- `addAction(QIcon, QString)`
|
||||
- `addAction(QString)`
|
||||
- `setToolButtonStyle(int)`
|
||||
|
||||
## QDockWidget
|
||||
- https://blog.csdn.net/czyt1988/article/details/51209619
|
||||
|
||||
## QJson
|
||||
- [C++(Qt5)入門範例:各種Json範例](https://lolikitty.pixnet.net/blog/post/206254516)
|
||||
|
||||
## QFile
|
||||
Read UTF-8 content from file.
|
||||
```cpp
|
||||
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QTextStream fileRead(&file);
|
||||
fileRead.setCodec("UTF-8");
|
||||
|
||||
QString fileContent = fileRead.readAll();
|
||||
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
|
||||
|
||||
file.close();
|
||||
|
||||
parseJson(fileContent);
|
||||
}
|
||||
else {
|
||||
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
|
||||
}
|
||||
```
|
||||
|
||||
## String
|
||||
### UTF-8
|
||||
1. 對於n bytes的字元,第一個byte的前n個bits是1,第n+1個bit是0,第2個byte以後,都以`10`開頭。
|
||||
1. 例:
|
||||
2. 1 byte character: 0x0XXXXXXX。
|
||||
3. 2 bytes character: 0x110XXXXXX 0x10XXXXXX。
|
||||
4. 3 bytes character: 0x1110XXXXX 0x10XXXXXX 0x10XXXXXX。
|
||||
5. 4 bytes character: 0x11110XXXX 0x10XXXXXX 0x10XXXXXX 0x10XXXXXX。
|
||||
2. 例:
|
||||
1. 「A」是一個1 byte字元,編碼為`0x01000001`。
|
||||
2. 「Á」是一個2 bytes字元,編碼為`0x11000011 0x10000001`。
|
||||
3. 「好」是一個3 bytes字元,編碼為`0x11100101 0x10100101 0x10111101`。
|
||||
3. [UTF-8 to binary轉換網站](https://onlineutf8tools.com/convert-utf8-to-binary)
|
||||
2. BOM
|
||||
1. Big endian: `FE FF`
|
||||
2. Little endian: `FF FE`
|
||||
|
||||
### QString
|
||||
- 16 bits QChar(unsigned short), UNIDODE 4.0
|
||||
- `==`: compare string
|
||||
- `isNull()`: If has a value
|
||||
- `isEmpty()`: If contained value == ""
|
||||
- `QString::number()`: 接收一個數字,把數字轉為字串。
|
||||
- `QString::toInt()`、`QString::toDouble()`:把字串轉為Int或Double。
|
||||
- 讀取UTF-8的字串:
|
||||
```
|
||||
codec = QTextCodec::codecFromName("UTF-8");
|
||||
QTextCodec::setCodecForLocale(codec);
|
||||
```
|
||||
然後用`QString::fromLocal8Bit()`來讀取字串,該字串就會被當作UTF-8來處理。
|
||||
- `QTextCodec::availableCodecs()`可以列出QT所支援的格式。
|
||||
- 在Visual Studio中,強制文件用UTF-8來存檔:`#pragma execution_character_set("UTF-8")`
|
||||
- 在Visual Studio中,預設是以CP950存檔,所以字串**都不是**unicode,而是**multi-character**,所以要從Windows讀進來的字串,一律需要使用`QString::fromLocal8Bit("")`,而一旦轉為QString之後,就是UTF-8了,若需要再輸出回Windows,則需要`QString.toLocal8Bit().toStdString()`。
|
||||
- 例:
|
||||
```cpp
|
||||
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QTextStream fileRead(&file);
|
||||
fileRead.setCodec("UTF-8");
|
||||
|
||||
QString fileContent = fileRead.readAll();
|
||||
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
|
||||
|
||||
file.close();
|
||||
|
||||
parseJson(fileContent);
|
||||
}
|
||||
else {
|
||||
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
|
||||
}
|
||||
```
|
||||
- Visual Studio 2017 debug QString: [调试时直接显示QString的字符值(包含windows和linux)](https://blog.csdn.net/iamdbl/article/details/78343642)
|
||||
|
||||
#### Search
|
||||
- `QString.indexOf()`
|
||||
- `QString.lastIndexOf()`
|
||||
|
||||
#### Trim
|
||||
- `QString.chop(5)`:去掉結尾的5個字元,inplace轉換,沒有回傳新字串。
|
||||
- `QString.left(5)`:取最左邊5個字元
|
||||
- `QString.right(5)`:取最右邊5個字元
|
||||
|
||||
#### Replace
|
||||
- `QString.replace("original", "new")`:將"original"替換為"new"。
|
||||
- 也支援regular expression
|
||||
|
||||
#### Split
|
||||
- `QString.split(",")`:用","把字串分割,並回傳一個`QStringList`。
|
||||
|
||||
#### Regular Expression
|
||||
- `.`:匹配任意1個字元
|
||||
- `+`:此類型的匹配字元可以1個以上
|
||||
- `*`:匹配任意多個字元
|
||||
- `^`:表示開始位置
|
||||
- `$`:表示結束位置
|
||||
- `?`:表示前一個匹配條件不一定要成立
|
||||
- `[]`:用括號來限制要匹配的字元
|
||||
|
||||
## Event
|
||||
- 如果有特殊需求,可以重載`QWidget::event(QEvent*)`來達成。
|
||||
|
||||
### MouseEvent
|
||||
- Get coordinate of component: `QMouseEvent::x()` & `QMouseEvent::y()`
|
||||
- Get coordinate of window: `QMouseEvent::windowPos().x()` & `QMouseEvent::windowPos().y()`
|
||||
- Get coordinate of screen: `QMouseEvent::screenPos().x()` & `QMouseEvent::screenPos().y()`
|
||||
- `QPoint mapToGlobal(QPoint)`: Translates the widget coordinate pos to global screen coordinates.
|
||||
- `QCursor::pos().x()` & `QCursor::pos().x()`: 也可以獲得screen coordinate。
|
||||
- Get click
|
||||
```
|
||||
if (mouseEvent->buttons() & Qt::LeftButton) // or Qt::RightButton, Qt::MiddleButton
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
### MouseCursor
|
||||
- 設定cursor click的位置:`QCursor cursor = QCursor(pixmap, -1, -1)`
|
||||
- (-1, -1) 表示用cursor中心點為click點
|
||||
- ( 0, 0) 表示用cursor左上角為click點
|
||||
- 更換cursor:`setCursor(Qt::ArrowCursor)`
|
||||
|
||||
### ResizeEvent
|
||||
- `void resizeEvent(QResizeEvent*)`
|
||||
|
||||
|
||||
## QOpenGLWidget
|
||||
- `void paintGL()`
|
||||
- `void initialGL()`
|
||||
- `void resizeGL(int w, int h)`
|
||||
|
||||
## QGLShaderProgram
|
||||
- `addShaderFromSourceCode`
|
||||
- `bindAttributeLocation`:設定傳入的變數
|
||||
- `uniformLocation`:取得變數
|
||||
|
||||
## GL概念
|
||||
### Vertex
|
||||
#### Coordinate
|
||||
```
|
||||
float *vertexData = new float[12] {
|
||||
1.0f, -1.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f,
|
||||
};
|
||||
```
|
||||
|
||||
### Texture
|
||||
#### Coordinate
|
||||
材質座標永遠都在第一象限
|
||||
```
|
||||
float* textureVertexData = new float[8] {
|
||||
1.0f, 0.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
};
|
||||
```
|
||||
|
||||
#### 建立材質
|
||||
- `glGenTextures(1, t);`
|
||||
- `glBindTexture(GL_TEXTURE_2D, *t);`
|
||||
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);`
|
||||
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);`
|
||||
|
||||
#### 寫入材質
|
||||
- `glActiveTexture(GL_TEXTURE0);`
|
||||
- `glBindTexture(GL_TEXTURE_2D, idY);`
|
||||
- `glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, pixelW, pixelH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, plane[0]);`
|
||||
```
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0, // 細節,預設0
|
||||
GL_LUMINANCE, // GPU內部格式
|
||||
pixelW,
|
||||
pixelH,
|
||||
0,
|
||||
GL_LUMINANCE, // Pixel format
|
||||
GL_UNSIGNED_BYTE, // Data format
|
||||
plane[0]);
|
||||
```
|
||||
- `glTexSubImage2D()`:修改texture
|
||||
- `glUniform1i(textureUniformY, 0);`
|
||||
- `glDrawArray(GL_TRIANGLE_STRIP, 0, 4);`
|
||||
|
||||
## GLSL
|
||||
|
||||
### 內建變數
|
||||
- `gl_FragColor`
|
||||
|
||||
### 3種變數類型
|
||||
- `varying`:vertex與shader共用,vertex運算完之後傳給shader使用。
|
||||
- `attribute`:vertex使用,由`bindAttributeLocation`傳入
|
||||
- `uniform`:由user傳入的資料,由`uniformLocation`得到pointer address,再由`glUniform1i(textureUniformY, 0)`來設定。
|
||||
|
||||
### Vertex Shader
|
||||
```
|
||||
attribute vec4 vertexIn;
|
||||
attribute vec2 textureIn;
|
||||
varying vec2 textureOut;
|
||||
|
||||
void main() {
|
||||
gl_position = vertexIn;
|
||||
textureOut = textureIn;
|
||||
}
|
||||
```
|
||||
|
||||
### Fragment Shader
|
||||
```
|
||||
varying vec2 textureOut;
|
||||
uniform sampler2D texY;
|
||||
uniform sampler2D texU;
|
||||
uniform sampler2D texV;
|
||||
|
||||
void main() {
|
||||
vec3 yuv;
|
||||
vec3 rgb;
|
||||
|
||||
yuv.x = textue2D(texY, textureOut).r;
|
||||
yuv.y = textue2D(texU, textureOut).r - 0.5;
|
||||
yuv.z = textue2D(texV, textureOut).r - 0.5;
|
||||
rgb = mat3(
|
||||
1, 1, 1,
|
||||
0, -0.39465, 2.03211,
|
||||
1.13983, -0.5806, 0) * yuv;
|
||||
gl_FragColor = vec4(rgb, 1);
|
||||
}
|
||||
```
|
||||
|
||||
### APIs
|
||||
畫出矩形與材質
|
||||
- `glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);`
|
||||
- `glEnableVertexAttribArray(ATTRIB_VERTEX);`
|
||||
- `glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);`
|
||||
- `glEnableVertexAttribArray(ATTRIB_TEXTURE);`
|
||||
|
||||
|
||||
|
||||
# 檔案操作
|
||||
## 開啟檔案對話框
|
||||
```c++
|
||||
QString selectedFilepath = QFileDialog::getOpenFileName(
|
||||
this,
|
||||
tr("Select test plan file"), // 檔案選擇對話框左上角的提示
|
||||
tr("."), // 檔案選擇對話框一開始的路徑
|
||||
"(*.json);;All files(*.*)"); // 可選擇的副檔名
|
||||
```
|
||||
使用者所選擇的檔名會放在`selectedFilepath`,可以用`selectedFilepath.isEmpty()`來判斷使用者是不是真的有選擇檔案或是按了取消鍵。
|
||||
|
||||
## 存檔對話框
|
||||
```c++
|
||||
QString fileName = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
tr("Save Address Book"), "", // 檔案選擇對話框左上角的提示
|
||||
tr("Address Book (*.abk);;All Files (*)")); // 存檔的副檔名
|
||||
```
|
||||
|
||||
# MISC
|
||||
## 教學文
|
||||
- [Qt教程,Qt5编程入门教程(非常详细)](http://c.biancheng.net/qt/)
|
||||
2
02. PARA/03. Resources(資源)/RC.md
Normal file
2
02. PARA/03. Resources(資源)/RC.md
Normal file
@@ -0,0 +1,2 @@
|
||||
* 好盈電變調整中立點:[【RC】FLYSKY 富斯NB4 遥控器基本功能使用教程_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1pJ411y7yB/)
|
||||
* [(28) 陳春期--HB D812 差速器調教教學 - YouTube](https://www.youtube.com/watch?v=dSYQBz3T6gU)
|
||||
134
02. PARA/03. Resources(資源)/RaspberryPi.md
Normal file
134
02. PARA/03. Resources(資源)/RaspberryPi.md
Normal file
@@ -0,0 +1,134 @@
|
||||
### 登入
|
||||
- 帳號:`pi`
|
||||
- 密碼:`<你自己設的密碼>`
|
||||
|
||||
### 設定固定IP
|
||||
#### Ethernet
|
||||
打開`/etc/dhcpcd.conf`,設定如下:
|
||||
```
|
||||
interface eth0
|
||||
static ip_address=192.168.1.20/24
|
||||
static routers=192.168.1.1
|
||||
static domain_name_servers=168.95.1.1 1.1.1.1
|
||||
```
|
||||
|
||||
#### WIFI
|
||||
打開`/etc/dhcpcd.conf`,設定如下:
|
||||
```
|
||||
interface wlan0
|
||||
static ip_address=192.168.1.21/24
|
||||
static routers=192.168.1.1
|
||||
static domain_name_servers=168.95.1.1 1.1.1.1
|
||||
```
|
||||
|
||||
確定`dhcpcd`與`networking`是enabled,不確定可以直接用下面的命令來啟動。
|
||||
```shell
|
||||
systemctl enable dhcpcd ;\
|
||||
systemctl enable networking
|
||||
```
|
||||
|
||||
### 更新記憶卡容量
|
||||
- [Taiwan-RaspberryPi | 台灣樹莓派 - (1)更新記憶卡容量](https://www.taiwan-raspberrypi.com/start/setting/1%E6%9B%B4%E6%96%B0%E8%A8%98%E6%86%B6%E5%8D%A1%E5%AE%B9%E9%87%8F/)
|
||||
|
||||
### 設定免密碼登入
|
||||
1. 將自己的public key copy到RaspberryPi上:
|
||||
```
|
||||
scp ~/.ssh/rpi.pub pi@<IP_ADDR>:~/.ssh/
|
||||
```
|
||||
2. 登入到RaspberryPi,這一次要輸入密碼:
|
||||
```shell
|
||||
ssh pi@<IP_ADDR>
|
||||
```
|
||||
3. (現在是在RaspberryPi上操作)把剛剛的public key加到`authorized_keys`裡面:
|
||||
```bash
|
||||
cd ~/.ssh ;\
|
||||
cat id_rsa.pub >> authorized_keys
|
||||
```
|
||||
|
||||
## 安裝
|
||||
### Python3
|
||||
```shell
|
||||
sudo apt install python3
|
||||
```
|
||||
|
||||
### Docker
|
||||
#### Install
|
||||
```shell
|
||||
# Install some required packages first
|
||||
sudo apt update ; sudo apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
|
||||
|
||||
# Get the Docker signing key for packages
|
||||
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -
|
||||
|
||||
# Add the Docker official repos
|
||||
echo "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
|
||||
$(lsb_release -cs) stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list
|
||||
|
||||
# Install Docker
|
||||
sudo apt update ; sudo apt install -y --no-install-recommends docker-ce cgroupfs-mount
|
||||
```
|
||||
|
||||
#### Confirm installation
|
||||
```shell
|
||||
sudo docker run --rm hello-world
|
||||
```
|
||||
如果成功,會有如下的訊息:
|
||||
![[Pasted image 20210125183754.png]]
|
||||
|
||||
### docker-compose
|
||||
```shell
|
||||
# Install required packages
|
||||
sudo apt update ; sudo apt install -y python3-pip libffi-dev
|
||||
|
||||
# Install Docker Compose from pip (using Python3)
|
||||
# This might take a while
|
||||
sudo pip3 install docker-compose
|
||||
```
|
||||
|
||||
#### Confirm installation
|
||||
```shell
|
||||
docker-compose -version
|
||||
```
|
||||
會輸出docker-compose的版本,類似下面:
|
||||
```
|
||||
docker-compose version 1.28.0, build unknown
|
||||
```
|
||||
|
||||
## 更新
|
||||
```shell
|
||||
sudo apt-get update `# 1. 更新 /etc/apt/sources.list 底下的套件清單。` ;\
|
||||
sudo apt-get upgrade `# 2. 比對套件清單決定是否需要更新,但如果要更新的套件有相依性問題,則放棄更新。` ;\
|
||||
sudo apt-get dist-upgrade `# 3. 會處理新版本套件與相依性套件的衝突,並試著安裝/移除有問題的套件來完成更新。` ;\
|
||||
sudo apt-get autoremove `# 4. 刪除之前因為有相依性而安裝,但現在已經不再使用的套件(非必要)。` ;\
|
||||
sudo apt-get autoclean `# 5. 清除下載到 /var/cache/apt/archives 的 .deb 套件檔(非必要)` ;\
|
||||
sudo rpi-update `# 6. 更新核心和韌體到最新版本(可能不穩定),因此更新前一定要先備份重要資料`
|
||||
```
|
||||
|
||||
## 備份
|
||||
1. 下載必須的工具
|
||||
```
|
||||
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
|
||||
sudo chmod +x pishrink.sh
|
||||
sudo mv pishrink.sh /usr/local/bin
|
||||
```
|
||||
1. 插入隨身碟,並將隨身碟mount起來。記住mount的路徑,這邊假設是`/mnt/usb0`。
|
||||
2. 用`lsblk`確認SD卡的device node,這邊假設SD卡是`/dev/mmcblk0`。
|
||||
3. 輸入以下指令,將整片microSD卡備份到USB隨身碟
|
||||
```
|
||||
sudo dd if=/dev/mmcblk0 of=/mnt/usb0/PiBackup_20220513.img bs=1M
|
||||
```
|
||||
這個命令會將整張SD卡dummp到隨身碟,SD卡有多大,產生的img檔就會有多大。
|
||||
這個命令會花上好一點時間。
|
||||
> `dd`命令並不會顯示任何進度或是訊息,若是希望在執行過程中看到一些訊息,可以加入`status=progress`,將可以看到速度的訊息。
|
||||
> 但是`progress`這個小工具是需要另外安裝的,可以用`sudo apt install progress`來安裝。
|
||||
> 例:`sudo dd if=/dev/mmcblk0 of=/mnt/usb0/PiBackup_20220513.img bs=1M status=progress`
|
||||
5. 切換到隨身碟的目錄:`cd /mnt/usb0`。
|
||||
6. 用以下命令來把img變小:
|
||||
```
|
||||
sudo pishrink.sh -z PiBackup_20220513.img
|
||||
```
|
||||
這個命令也會花上好一點時間。
|
||||
|
||||
### 參考
|
||||
- [[Raspberry Pi] 備份樹莓派系統Micro SD卡的最佳做法_PiShrink @ skybow](https://skybow.pixnet.net/blog/post/121176894-%5Braspberry-pi%5D-%E5%82%99%E4%BB%BD%E6%A8%B9%E8%8E%93%E6%B4%BEsd%E5%8D%A1%E7%9A%84%E6%9C%80%E4%BD%B3%E5%81%9A%E6%B3%95)
|
||||
1
02. PARA/03. Resources(資源)/SLAM.md
Normal file
1
02. PARA/03. Resources(資源)/SLAM.md
Normal file
@@ -0,0 +1 @@
|
||||
* [gaoxiang12/slambook](https://github.com/gaoxiang12/slambook)
|
||||
30
02. PARA/03. Resources(資源)/Scripts.md
Normal file
30
02. PARA/03. Resources(資源)/Scripts.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## youtube-dl
|
||||
### List format
|
||||
```shell
|
||||
youtube-dl -F <YOUTUBE_URL>
|
||||
```
|
||||
|
||||
e.g.
|
||||
```shell
|
||||
youtube-dl -F https://www.youtube.com/watch?v=Pbzn79TSRO0
|
||||
```
|
||||
|
||||
### Download
|
||||
```shell
|
||||
youtube-dl -f <FORMAT_ID> <YOUTUBE_URL>
|
||||
```
|
||||
|
||||
### Download video-only & audio-only & merge to MKV
|
||||
```shell
|
||||
youtube-dl -f <VIDEO_ONLY_ID>+<AUDIO_ONLY_ID> --merge-output-format mkv <YOUTUBE_URL>
|
||||
```
|
||||
|
||||
e.g.
|
||||
```shell
|
||||
youtube-dl -f 313+251 --merge-output-format mkv https://www.youtube.com/watch?v=Pbzn79TSRO0
|
||||
```
|
||||
|
||||
### Download video to MP3
|
||||
```shell
|
||||
youtube-dl -i --extract-audio --audio-format mp3 --audio-quality 0 https://www.youtube.com/watch?v=IruVsWrfPqs
|
||||
```
|
||||
80
02. PARA/03. Resources(資源)/Storj.md
Normal file
80
02. PARA/03. Resources(資源)/Storj.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Documentation
|
||||
- [Introduction - Storj](https://documentation.storj.io/)
|
||||
|
||||
## Setup
|
||||
Pull image
|
||||
```shell
|
||||
sudo docker pull storjlabs/storagenode:latest
|
||||
```
|
||||
|
||||
Do this **once**.
|
||||
```shell
|
||||
sudo docker run --rm -e SETUP="true" \
|
||||
--mount type=bind,source="/volume1/homes/awin/Storj/key",destination=/app/identity \
|
||||
--mount type=bind,source="/volume1/docker/Storj/data",destination=/app/config \
|
||||
--name storagenode storjlabs/storagenode:latest
|
||||
```
|
||||
|
||||
## Run
|
||||
```shell
|
||||
sudo docker run -d --restart unless-stopped --stop-timeout 300 \
|
||||
-p 28967:28967 \
|
||||
-p 28967:28967/udp \
|
||||
-p 14002:14002 \
|
||||
-e WALLET="0x9Ce80345355Ad8C17991620E13d8423900CEDcd0" \
|
||||
-e EMAIL="awinhuang@gmail.com" \
|
||||
-e ADDRESS="awin.myds.me:28967" \
|
||||
-e STORAGE="2TB" \
|
||||
--mount type=bind,source="/volume1/homes/awin/Storj/key",destination=/app/identity \
|
||||
--mount type=bind,source="/volume1/docker/Storj/data",destination=/app/config \
|
||||
--name storagenode storjlabs/storagenode:latest
|
||||
```
|
||||
|
||||
## Update configuration
|
||||
### Linux
|
||||
1. Stop docker container
|
||||
```shell
|
||||
sudo docker stop -t 300 storagenode ;\
|
||||
sudo docker rm storagenode ;\
|
||||
sudo docker pull storjlabs/storagenode
|
||||
```
|
||||
2. Run docker again. Check [[Storj#Run]]. [^1]
|
||||
3. Or, create a task, and run below command:
|
||||
```shell
|
||||
echo "Stop storagenode" ;\
|
||||
sudo docker stop -t 300 storagenode ;\
|
||||
sudo docker rm storagenode ;\
|
||||
echo "Pull storagenode" ;\
|
||||
sudo docker pull storjlabs/storagenode ;\
|
||||
echo "Start storagenode" ;\
|
||||
sudo docker run -d --restart unless-stopped --stop-timeout 300 \
|
||||
-p 28967:28967 \
|
||||
-p 14002:14002 \
|
||||
-e WALLET="0x9Ce80345355Ad8C17991620E13d8423900CEDcd0" \
|
||||
-e EMAIL="awinhuang@gmail.com" \
|
||||
-e ADDRESS="awin.myds.me:28967" \
|
||||
-e STORAGE="2TB" \
|
||||
--mount type=bind,source="/volume1/homes/awin/Storj/key",destination=/app/identity \
|
||||
--mount type=bind,source="/volume1/docker/Storj/data",destination=/app/config \
|
||||
--name storagenode storjlabs/storagenode:latest ;\
|
||||
sudo docker ps -a
|
||||
```
|
||||
|
||||
## Start/Stop service
|
||||
### Windows
|
||||
#### Start service
|
||||
In PowerShell:
|
||||
```powershell
|
||||
Start-Service storagenode
|
||||
```
|
||||
|
||||
#### Stop service
|
||||
In PowerShell:
|
||||
```powershell
|
||||
Stop-Service storagenode
|
||||
```
|
||||
|
||||
[^2]
|
||||
|
||||
[^1]: [How do I change values like wallet address or storage capacity? - Storj](https://documentation.storj.io/resources/faq/how-do-i-change-my-parameters-such-as-payout-address-allotted-storage-space-and-bandwidth)
|
||||
[^2]: [How do I shutdown my node for system maintenance? - Storj](https://documentation.storj.io/resources/faq/system-maintenance)
|
||||
7
02. PARA/03. Resources(資源)/Tool Setup/Chrome.md
Normal file
7
02. PARA/03. Resources(資源)/Tool Setup/Chrome.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## Cache to Ramdisk
|
||||
1. 關閉 Chrome
|
||||
2. 開啟檔案總管至以下路徑:`%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default`
|
||||
3. 刪除 Cache 資料夾
|
||||
4. 用管理員權限開啟 cmd
|
||||
5. 輸入以下指令以建立 Symbolic Link: `mklink /d "%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default\Cache\" "R:\TEMP\Chrome\"`
|
||||
6. (Optional) 如果有多個使用者的話: `mklink /d "%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Profile 1\Cache\" "R:\TEMP\Chrome\"`
|
||||
274
02. PARA/03. Resources(資源)/Tool Setup/Obisidian.md
Normal file
274
02. PARA/03. Resources(資源)/Tool Setup/Obisidian.md
Normal file
@@ -0,0 +1,274 @@
|
||||
---
|
||||
tags:
|
||||
aliases:
|
||||
date: 2022-05-26
|
||||
time: 14:51:12
|
||||
description:
|
||||
---
|
||||
|
||||
## YAML front matter
|
||||
官方說明:[YAML front matter - Obsidian Help](https://help.obsidian.md/Advanced+topics/YAML+front+matter)
|
||||
目前使用的front matter是[[front matter]]。
|
||||
|
||||
## 自動產生時間日期
|
||||
Obsidian在抽入template的時候,可以使用`{{date}}`來表示日期,用`{{time}}`來表示時間。
|
||||
|
||||
### `{{date}}` & `{{time}}`
|
||||
`{{date}}`產生的時間格式為`2022-05-26`,可以使用參數來更改格式,例如不要讓月份與日期自動補0,可以用`{{date:YYYY-M-D}}`。
|
||||
時間也一樣,`{{time}}`產生的格式為`14:48:45`,也有其他參數來變更格式,其他更詳細參考[Moment.js | Docs](https://momentjs.com/docs/#/displaying/)。
|
||||
|
||||
## Customize CSS
|
||||
路徑是`<valut_name>\.obsidian\snippets\obsidian.css`
|
||||
Obsidian預設定義了很多CSS class,只要更改這些class就可以定義自己喜歡的外觀。
|
||||
- [Obsidian自定义样式修改教程 - 知乎](https://zhuanlan.zhihu.com/p/373888121)
|
||||
- [obsidian-css-snippets](https://github.com/Dmytro-Shulha/obsidian-css-snippets/tree/master/Snippets)
|
||||
- [使用 CSS 代码片段增强 Obsidian 视觉效果(一) | ReadingHere](https://www.readinghere.com/blog/using-css-snippets-to-enhance-obsidian-visuals-cn/)
|
||||
|
||||
## My CSS
|
||||
The CSS content:
|
||||
```css
|
||||
/*************************************************************
|
||||
* Headers
|
||||
*/
|
||||
.cm-header-1, .markdown-preview-section h1 {
|
||||
color: #081f37;
|
||||
}
|
||||
|
||||
.cm-header-2, .markdown-preview-section h2 {
|
||||
color: #1e549f;
|
||||
}
|
||||
|
||||
.cm-header-3, .markdown-preview-section h3 {
|
||||
color: #2e79ba;
|
||||
}
|
||||
|
||||
.cm-header-4, .markdown-preview-section h4 {
|
||||
color: #5fc9f3;
|
||||
}
|
||||
|
||||
.cm-header-5, .markdown-preview-section h5 {
|
||||
color: #415865;
|
||||
}
|
||||
|
||||
.cm-header-6, .markdown-preview-section h6 {
|
||||
color: #7a9eb1;
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* List
|
||||
*/
|
||||
.cm-list-1 {
|
||||
font-family: Cascadia Code;
|
||||
}
|
||||
|
||||
.cm-list-2 {
|
||||
font-family: Cascadia Code;
|
||||
}
|
||||
|
||||
.cm-list-3 {
|
||||
font-family: Cascadia Code;
|
||||
}
|
||||
|
||||
.cm-list-4 {
|
||||
font-family: Cascadia Code;
|
||||
}
|
||||
|
||||
.cm-list-5 {
|
||||
font-family: Cascadia Code;
|
||||
}
|
||||
|
||||
span.cm-formatting-list-ul {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
span.cm-formatting-list-ul:after {
|
||||
content: '• '; /* ITS theme; for Blue Topaz */
|
||||
margin-left: -12px;
|
||||
color: var(--accent); /* ITS theme; for Blue Topaz --text-normal */
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
ol {
|
||||
font-family: Cascadia Code;
|
||||
padding-inline-start: 2.5rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
font-family: Cascadia Code;
|
||||
padding-inline-start: 2.4rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
/*Make list marker to be a circle*/
|
||||
input[type="checkbox"],
|
||||
.cm-formatting-task {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--text-faint);
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cm-s-obsidian span.cm-formatting-task {
|
||||
color: transparent;
|
||||
width: 1.1em !important;
|
||||
height: 1.1em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked,
|
||||
.cm-formatting-task.cm-property {
|
||||
background-color: var(--text-accent-hover);
|
||||
border: 1px solid var(--text-accent-hover);
|
||||
background-position: center;
|
||||
background-size: 60%;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('data:image/svg+xml; utf8, <svg width="12px" height="10px" viewBox="0 0 12 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(-4.000000, -6.000000)" fill="%23ffffff"><path d="M8.1043257,14.0367999 L4.52468714,10.5420499 C4.32525014,10.3497722 4.32525014,10.0368095 4.52468714,9.8424863 L5.24777413,9.1439454 C5.44721114,8.95166768 5.77142411,8.95166768 5.97086112,9.1439454 L8.46638057,11.5903727 L14.0291389,6.1442083 C14.2285759,5.95193057 14.5527889,5.95193057 14.7522259,6.1442083 L15.4753129,6.84377194 C15.6747499,7.03604967 15.6747499,7.35003511 15.4753129,7.54129009 L8.82741268,14.0367999 C8.62797568,14.2290777 8.3037627,14.2290777 8.1043257,14.0367999"></path></g></g></svg>');
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* External link & Internal link
|
||||
*/
|
||||
.cm-s-obsidian span.cm-link,
|
||||
.cm-s-obsidian span.cm-url,
|
||||
.cm-s-obsidian span.cm-hmd-internal-link {
|
||||
color: #0000EE;
|
||||
font-family: Cascadia Code;
|
||||
}
|
||||
|
||||
a.external-link {
|
||||
color: #0000EE;
|
||||
font-family: Cascadia Code;
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Outline
|
||||
*/
|
||||
.outline {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.outline .tree-item {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.outline .tree-item-self {
|
||||
padding-top: 0.2rem;
|
||||
padding-bottom: 0.1rem;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.3rem;
|
||||
}
|
||||
|
||||
.outline .tree-item-collapse {
|
||||
left: 0.1rem;
|
||||
}
|
||||
|
||||
.outline .tree-item-inner{
|
||||
position:relative;
|
||||
padding-top: 0.2rem;
|
||||
/* padding-left: 1rem; */
|
||||
padding-left: 1.7em;
|
||||
text-indent: -0.8em;
|
||||
margin-left: 0.2rem;
|
||||
/* font-size: 0.9em; */
|
||||
}
|
||||
|
||||
.outline .tree-item-children {
|
||||
margin-left: 0.7rem;
|
||||
padding-left: 0.5rem;
|
||||
margin-top: -0.3rem;
|
||||
padding-top: 0.3rem;
|
||||
border-left: 1px solid var(--sidebar-marks, var(--background-modifier-border));
|
||||
border-radius: 4px;
|
||||
transition:all 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.outline .tree-item-children:hover {
|
||||
border-left-color: var(--sidebar-marks-hover, var(--background-secondary));
|
||||
}
|
||||
|
||||
.outline .collapse-icon + .tree-item-inner {
|
||||
font-weight: 400;
|
||||
padding-left: 0.2rem;
|
||||
/* margin-left: 0rem; */
|
||||
/* font-size: 1em; */
|
||||
}
|
||||
|
||||
.outline .collapse-icon {
|
||||
margin-top: 0.2rem;
|
||||
margin-left: -0.4rem;
|
||||
margin-right: -0.4rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************
|
||||
* Sidebar
|
||||
*/
|
||||
/*Vault name*/
|
||||
.nav-folder.mod-root > .nav-folder-title {
|
||||
padding-left: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: bolder;
|
||||
top: -6px; /* higher */
|
||||
cursor: default;
|
||||
color: var(--base2);
|
||||
}
|
||||
|
||||
.nav-folder-title,
|
||||
.nav-file-title {
|
||||
font-size: 0.8em;
|
||||
font-family: consolas;
|
||||
line-height: 0.8em;
|
||||
}
|
||||
|
||||
.nav-folder-title {
|
||||
font-weight: bold;
|
||||
color: #132743;
|
||||
}
|
||||
|
||||
.nav-file-title {
|
||||
color: #407088;
|
||||
}
|
||||
|
||||
.nav-folder,
|
||||
.nav-file {
|
||||
margin: 0 !important;
|
||||
border-left: 1px solid rgba(118, 158, 165, 0.2);
|
||||
}
|
||||
|
||||
.cm-quote {
|
||||
line-height: 1.2em;
|
||||
/*font-style: italic;*/
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************
|
||||
* Quote
|
||||
*/
|
||||
.cm-formatting-quote:before {
|
||||
margin-right: -0.1em;
|
||||
color: var(--color-10) !important;
|
||||
font-family: "ico";
|
||||
content: "\edd5";
|
||||
}
|
||||
|
||||
|
||||
/*************************************************************
|
||||
* Code block(inline)
|
||||
*/
|
||||
.cm-s-obsidian span.cm-inline-code {
|
||||
/*font-size: 1em;*/
|
||||
}
|
||||
```
|
||||
|
||||
## 參考
|
||||
- [使用 CSS 代码片段增强 Obsidian 视觉效果(一) | ReadingHere](https://www.readinghere.com/blog/using-css-snippets-to-enhance-obsidian-visuals-cn/)
|
||||
- [obsidian-css-snippets](https://github.com/Dmytro-Shulha/obsidian-css-snippets/tree/master/Snippets)
|
||||
- [Obsidian自定义样式修改教程 - 知乎](https://zhuanlan.zhihu.com/p/373888121)
|
||||
24
02. PARA/03. Resources(資源)/Tool Setup/SublimeText.md
Normal file
24
02. PARA/03. Resources(資源)/Tool Setup/SublimeText.md
Normal file
@@ -0,0 +1,24 @@
|
||||
```json
|
||||
// Settings in here override those in "Default/Preferences.sublime-settings",
|
||||
// and are overridden in turn by syntax-specific settings.
|
||||
{
|
||||
"close_windows_when_empty": true,
|
||||
"draw_indent_guides": false,
|
||||
"font_face": "Fira Code",
|
||||
"font_size": 11,
|
||||
"highlight_modified_tabs": true,
|
||||
"ignored_packages":
|
||||
[
|
||||
"Vintage"
|
||||
],
|
||||
"show_full_path": true,
|
||||
"show_tab_close_buttons": false,
|
||||
"spell_check": false,
|
||||
"tab_size": 4,
|
||||
"translate_tabs_to_spaces": true,
|
||||
"use_tab_stops": true,
|
||||
"draw_white_space": "all",
|
||||
"trim_trailing_white_space_on_save": true,
|
||||
"word_separators": "./\\()\"'-:,.;<>~!@#%^&*|+=[]{}`~?"
|
||||
}
|
||||
```
|
||||
6
02. PARA/03. Resources(資源)/Tool Setup/VirtualBox.md
Normal file
6
02. PARA/03. Resources(資源)/Tool Setup/VirtualBox.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 讓Virtualbox與Hyper-V並存
|
||||
用Administrator打開Powershell,輸入以下指令:
|
||||
```
|
||||
cd "C:\Program Files\Oracle\VirtualBox"
|
||||
./VBoxManage.exe setextradata global "VBoxInternal/NEM/UseRing0Runloop" 0
|
||||
```
|
||||
328
02. PARA/03. Resources(資源)/Tool Setup/Visual Studio Code.md
Normal file
328
02. PARA/03. Resources(資源)/Tool Setup/Visual Studio Code.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# 快速鍵
|
||||
## 顯示快速鍵列表
|
||||
- Windows: `Ctrl + k` + `Ctrl + s`
|
||||
- Mac: `⌘ + k` + `⌘ + s`
|
||||
|
||||
## 分割目前視窗
|
||||
- Windows: `Ctrl + \`
|
||||
- Mac: `⌘ + \`
|
||||
|
||||
## 程式格式化
|
||||
### 格式化整個文件
|
||||
`Shift + Alf + f`
|
||||
|
||||
### 格式化選取的範圍
|
||||
`Ctrl + k` + `Ctrl + f`
|
||||
|
||||
### setting.json
|
||||
- `"editor.formatOnType": true`:輸入一行後,自動格式化目前這一行。
|
||||
- `"editor.formatOnSave": true`:儲存時格式化檔案。
|
||||
- `"editor.formatOnPaste": true`:貼上程式碼時格式化貼上的內容。
|
||||
|
||||
```json
|
||||
{
|
||||
"python.terminal.activateEnvironment": true, // 自動啟動環境
|
||||
"python.linting.pylintEnabled": true, // 需要 pip install pylint
|
||||
"python.linting.enabled": true,
|
||||
"python.autoComplete.addBrackets": true, // 自動為自動完成的function加上括號
|
||||
"python.languageServer": "Pylance",
|
||||
"python.analysis.completeFunctionParens": true,
|
||||
"jupyter.sendSelectionToInteractiveWindow": true,
|
||||
"jupyter.interactiveWindowMode": "perFile",
|
||||
|
||||
"terminal.integrated.fontFamily": "Fira Code", // For Windows
|
||||
"terminal.integrated.profiles.windows": {
|
||||
"Command Prompt": {
|
||||
"path": [
|
||||
"${env:windir}\\Sysnative\\cmd.exe",
|
||||
"${env:windir}\\System32\\cmd.exe"
|
||||
],
|
||||
"args": [],
|
||||
"icon": "terminal-cmd"
|
||||
},
|
||||
"Git Bash": {
|
||||
"source": "Git Bash"
|
||||
},
|
||||
"PowerShell -NoProfile": {
|
||||
"source": "PowerShell",
|
||||
"args": ["-NoProfile"]
|
||||
}
|
||||
},
|
||||
"terminal.integrated.defaultProfile.windows": "Command Prompt",
|
||||
"terminal.integrated.cwd": "${fileDirname}",
|
||||
|
||||
// "editor.fontFamily": "Cascadia code Light",
|
||||
// "editor.fontFamily": "PragmataPro Liga",
|
||||
// "editor.fontFamily": "Fira Code Retina",
|
||||
"editor.fontFamily": "Iosevka Expanded",
|
||||
// "editor.fontFamily": "Hasklig Light",
|
||||
"editor.fontSize": 14,
|
||||
"editor.fontLigatures": true,
|
||||
"editor.fontWeight": "normal",
|
||||
"editor.minimap.renderCharacters": false,
|
||||
"editor.renderWhitespace": "boundary",
|
||||
"editor.renderIndentGuides": false,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 4,
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
|
||||
"outline.showVariables": false,
|
||||
|
||||
"sync.gist": "aaee0ee8733879ef2da2eb1b4bf8a993",
|
||||
"sync.quietSync": false,
|
||||
"sync.removeExtensions": true,
|
||||
"sync.syncExtensions": true,
|
||||
"sync.autoDownload": false,
|
||||
"sync.autoUpload": false,
|
||||
"sync.forceDownload": false,
|
||||
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
|
||||
"explorer.confirmDelete": false,
|
||||
|
||||
"workbench.colorTheme": "One Dark Pro",
|
||||
"workbench.iconTheme": "material-icon-theme",
|
||||
"workbench.editorAssociations": {
|
||||
"*.ipynb": "jupyter.notebook.ipynb"
|
||||
},
|
||||
|
||||
"oneDarkPro.vivid": true,
|
||||
"gitlens.advanced.messages": {
|
||||
"suppressImproperWorkspaceCasingWarning": true
|
||||
},
|
||||
"workbench.colorCustomizations": {
|
||||
"editor.lineHighlightBackground": "#1073cf2d",
|
||||
"editor.lineHighlightBorder": "#9fced11f"
|
||||
},
|
||||
"editor.wordWrap": "off",
|
||||
"diffEditor.wordWrap": "off",
|
||||
"editor.guides.indentation": false,
|
||||
"editor.guides.bracketPairs": false,
|
||||
}
|
||||
```
|
||||
|
||||
## 折疊程式碼
|
||||
### 收起目前區塊
|
||||
- Windows: `Ctrl + Shift + [`
|
||||
- Mac: `⌥ + ⌘ + [`
|
||||
|
||||
### 打開目前區塊
|
||||
- Windows: `Ctrl + Shift + ]`
|
||||
- Mac: `⌥ + ⌘ + ]`
|
||||
|
||||
### 收起目前區塊(整個檔案)
|
||||
- Windows: `Ctrl + (K => 0) (zero)`
|
||||
- Mac: `⌘ + (K => 0) (zero)`
|
||||
|
||||
### 打開目前區塊(整個檔案)
|
||||
- Windows: `Ctrl + (K => J) `
|
||||
- Mac: `⌘ + (K => J)`
|
||||
|
||||
## 在「已開啟的檔案」間跳轉
|
||||
`Ctrl + tab`
|
||||
|
||||
# Plugin
|
||||
## Setting Sync
|
||||
- 參考:[https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync)
|
||||
~~- GIST Token: `e0f7c5233e3c6dafee77047f61ea74f0d01d24e1`~~
|
||||
- GIST Token: `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`
|
||||
- GIST ID: [`aaee0ee8733879ef2da2eb1b4bf8a993`](https://gist.github.com/AwinHuang/aaee0ee8733879ef2da2eb1b4bf8a993)
|
||||
- GIST address: [https://gist.github.com/AwinHuang/aaee0ee8733879ef2da2eb1b4bf8a993](https://gist.github.com/AwinHuang/aaee0ee8733879ef2da2eb1b4bf8a993)
|
||||
|
||||
# Code snippets
|
||||
## html.json
|
||||
```json
|
||||
{
|
||||
"HTML template": {
|
||||
"prefix": "HTML_template",
|
||||
"body": [
|
||||
"<!doctype html>",
|
||||
"",
|
||||
"<html lang=\"en\">",
|
||||
"<head>",
|
||||
" <meta charset=\"utf-8\">",
|
||||
" <meta name=\"description\" content=\"Awin's HTML template\">",
|
||||
" <meta name=\"author\" content=\"Awin Huang\">",
|
||||
" <script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js\"></script>",
|
||||
" <title>A HTML template</title>",
|
||||
"</head>",
|
||||
"",
|
||||
"<body>",
|
||||
" <H1>Awin's HTML template</H1>",
|
||||
" <p>Click to hide</p>",
|
||||
"</body>",
|
||||
"</html>",
|
||||
"",
|
||||
"<script>",
|
||||
" $(document).ready(function(){",
|
||||
" $(\"p\").click(function(){",
|
||||
" $(this).hide();",
|
||||
" });",
|
||||
" });",
|
||||
"</script>",
|
||||
],
|
||||
"description": "HTML template"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## python.json
|
||||
```json
|
||||
{
|
||||
"Python template": {
|
||||
"prefix": "Python_Template",
|
||||
"body": [
|
||||
"import sys",
|
||||
"import argparse",
|
||||
"",
|
||||
"",
|
||||
"def main(args=None):",
|
||||
" ${1:pass}",
|
||||
"",
|
||||
"",
|
||||
"if __name__ == '__main__':",
|
||||
" parser = argparse.ArgumentParser()",
|
||||
" parser.add_argument(\"first_file\", help=\"The first file\")",
|
||||
" parser.add_argument(\"-s\", \"--sample_args\", default=\"sample_args\", help=\"Modify this arguments for you\")",
|
||||
" args = parser.parse_args()",
|
||||
" main(args)",
|
||||
" sys.exit(0)"
|
||||
],
|
||||
"description": "Python script template"
|
||||
},
|
||||
|
||||
"F Print": {
|
||||
"prefix": "f-print",
|
||||
"body": [
|
||||
"print(f'$1 = {$1}')"
|
||||
],
|
||||
"description": "print() with f-string and default {}"
|
||||
},
|
||||
"Q Print": {
|
||||
"prefix": "q-print",
|
||||
"body": [
|
||||
"print('$1 = {}, '.format($1))"
|
||||
],
|
||||
"description": "print() with f-string and default {}"
|
||||
},
|
||||
"Debug RobotRun": {
|
||||
"prefix": "debug_robotrun",
|
||||
"body": [
|
||||
"import os",
|
||||
"import sys",
|
||||
"sys.path.insert(0, 'D:/codes/logitech/')",
|
||||
"import RobotRun",
|
||||
"print('+------------------------------------------------------------------------------+')",
|
||||
"print('| |')",
|
||||
"print('| RobotRun: {}'.format(RobotRun.__file__))",
|
||||
"print('| |')",
|
||||
"print('+------------------------------------------------------------------------------+')",
|
||||
],
|
||||
"description": "Change RobotRun to local version"
|
||||
},
|
||||
"Flask template": {
|
||||
"prefix": "Flask_Template",
|
||||
"body": [
|
||||
"## Flask template",
|
||||
"## Author: Awin Huang",
|
||||
"## Date: 2020/04/09",
|
||||
"",
|
||||
"import os, sys",
|
||||
"import datetime",
|
||||
"import json",
|
||||
"from flask import Flask, render_template, request",
|
||||
"",
|
||||
"",
|
||||
"app = Flask(__name__)",
|
||||
"",
|
||||
"## Setup log",
|
||||
"handler = logging.FileHandler('flask.log', delay=False)",
|
||||
"handler.setLevel(logging.INFO)",
|
||||
"app.logger.addHandler(handler)",
|
||||
"app.logger.setLevel(logging.INFO)",
|
||||
"",
|
||||
"",
|
||||
"def info_log(msg):",
|
||||
" app.logger.info(msg)",
|
||||
" # print(msg)",
|
||||
"",
|
||||
"",
|
||||
"def error_log(msg):",
|
||||
" app.logger.error(msg)",
|
||||
"",
|
||||
"",
|
||||
"@app.route('/')",
|
||||
"def index():",
|
||||
" info_log('Return main page to user.')",
|
||||
" return 'Hello, this is main page'",
|
||||
"",
|
||||
"",
|
||||
"## Receive a GET request",
|
||||
"@app.route('/get_get', methods=['GET'])",
|
||||
"def run_testcase():",
|
||||
" command = request.args.get('command')",
|
||||
" value = 'This is value for GET'",
|
||||
" return {",
|
||||
" 'command': command,",
|
||||
" 'value': value",
|
||||
" }",
|
||||
"",
|
||||
"",
|
||||
"## Receive a POST request",
|
||||
"@app.route('/get_post', methods=['POST'])",
|
||||
"def get_post():",
|
||||
" command = request.form['command']",
|
||||
" value = 'This is value for POST'",
|
||||
" return {",
|
||||
" 'command': command:",
|
||||
" 'value': value",
|
||||
" }",
|
||||
"",
|
||||
"",
|
||||
"if __name__ == '__main__':",
|
||||
" app.debug = True",
|
||||
" app.run(host='0.0.0.0')",
|
||||
],
|
||||
"description": "Flask template"
|
||||
},
|
||||
"Datetime now": {
|
||||
"prefix": "now_dt",
|
||||
"body": [
|
||||
"datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S:%f')"
|
||||
],
|
||||
"description": "Get datetime.now() with format '%Y-%m-%d %H:%M:%S:%f'"
|
||||
},
|
||||
"Run process": {
|
||||
"prefix": "runprocess",
|
||||
"body": [
|
||||
"import multiprocessing as mp",
|
||||
"",
|
||||
"process = mp.Process(target=task_set.run, args=(self.task_sync, args))",
|
||||
"process.start()",
|
||||
"process.join()"
|
||||
],
|
||||
"description": "Run function in a process"
|
||||
},
|
||||
"Sleep with dots": {
|
||||
"prefix": "sleepdots",
|
||||
"body": [
|
||||
"for i in range($1):",
|
||||
" import time",
|
||||
" print(\".\", end=\"\", flush=True)",
|
||||
" time.sleep(1)",
|
||||
],
|
||||
"description": "Sleep and print \".\" every second"
|
||||
},
|
||||
"Sleep with numbers": {
|
||||
"prefix": "sleepnum",
|
||||
"body": [
|
||||
"for i in range($1):",
|
||||
" print(f\"{i+1} \", end=\"\", flush=True)",
|
||||
" time.sleep(1)",
|
||||
],
|
||||
"description": "Sleep and print number every second"
|
||||
},
|
||||
}
|
||||
|
||||
```
|
||||
165
02. PARA/03. Resources(資源)/Tool Setup/Windows Setup.md
Normal file
165
02. PARA/03. Resources(資源)/Tool Setup/Windows Setup.md
Normal file
@@ -0,0 +1,165 @@
|
||||
### Install tools
|
||||
#### 先安裝
|
||||
- 手動安裝[Google Drive](https://www.google.com/drive/download/),以取得本檔案。
|
||||
|
||||
#### 自動安裝
|
||||
1. 安裝[Chocolatey](https://chocolatey.org/),用Administrator身份打開powershell,輸入下列指令:
|
||||
```
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
|
||||
```
|
||||
2. 用安裝常用的工具
|
||||
```
|
||||
choco install 7zip adobereader vscode hxd sublimetext4 microsoft-windows-terminal androidstudio intellijidea-community git winmerge freefilesync freedownloadmanager gsudo firacode cascadiacode sourcecodepro --yes
|
||||
```
|
||||
|
||||
列表:
|
||||
- 7zip
|
||||
- adobereader
|
||||
- vscode
|
||||
- hxd
|
||||
- [sublimetext4](https://community.chocolatey.org/packages/sublimetext4/4.0.0.412100)
|
||||
- microsoft-windows-terminal
|
||||
- androidstudio
|
||||
- [intellijidea-community](https://chocolatey.org/packages/intellijidea-community)
|
||||
- git
|
||||
- winmerge
|
||||
- [freefilesync](https://chocolatey.org/packages/freefilesync)
|
||||
- [freedownloadmanager](https://chocolatey.org/packages/FreeDownloadManager)
|
||||
- [gsudo](https://community.chocolatey.org/packages/gsudo)
|
||||
- [firacode](https://community.chocolatey.org/packages/FiraCode)
|
||||
- [cascadiacode](https://community.chocolatey.org/packages/cascadiacode)
|
||||
- [sourcecodepro](https://community.chocolatey.org/packages/SourceCodePro)
|
||||
|
||||
移除:
|
||||
- [TeraCopy](https://chocolatey.org/packages/TeraCopy)
|
||||
- googlechrome
|
||||
- make
|
||||
|
||||
#### 手動安裝
|
||||
1. Google drive
|
||||
2. Google drive(Logitech)
|
||||
3. Python 3.6.3
|
||||
4. Python 3.9
|
||||
6. Visual Studio 2017
|
||||
7. Visual Studio 2019
|
||||
8. Office 365
|
||||
9. Lightroom
|
||||
10. [Enpass](https://www.enpass.io/)
|
||||
11. [ShareX](https://getsharex.com/)
|
||||
12. [win32diskimager](https://sourceforge.net/projects/win32diskimager/)
|
||||
13. [卡巴斯基](https://www.kaspersky.com.tw/)
|
||||
|
||||
#### Portable App
|
||||
1. Aegisub portable
|
||||
2. Audacity 2.3.3
|
||||
3. Cheat Engine 7.0
|
||||
4. cmder v1.3.12
|
||||
5. ConEmu
|
||||
6. ConvertZZ.1.0.0.3
|
||||
7. CrystalDiskMark 6.0.1 x64
|
||||
8. EzMeta
|
||||
9. ffmpeg-2020-09-20-full_build
|
||||
10. FileZillaPortable
|
||||
11. Geek Uninstaller 1.4.7
|
||||
12. HxDPortable
|
||||
13. ImgBurnPortable
|
||||
14. IntelliJ IDEA
|
||||
15. JDownloader 2.0
|
||||
16. PhraseExpress
|
||||
17. Process Explorer 16.21
|
||||
18. Q-Dir 9.01
|
||||
19. Rufus
|
||||
20. Sandboxie
|
||||
21. [Speccy](https://www.ccleaner.com/speccy)
|
||||
22. [ThunderbirdPortable](https://portableapps.com/apps/internet/thunderbird_portable)
|
||||
23. [WindowGrid 1.3.11](http://windowgrid.net/)
|
||||
24. [wiztree_3_35_portable](https://wiztreefree.com/download)
|
||||
|
||||
### Upgrade
|
||||
#### Upgrade by Chocolately
|
||||
```
|
||||
choco upgrade all -y
|
||||
```
|
||||
|
||||
### Setup
|
||||
#### Setup doskey in **Command Prompt**
|
||||
1. 切換到`Document`資料夾。
|
||||
2. 建立`cmdinit.cmd`,內容如下:
|
||||
```
|
||||
@echo off
|
||||
doskey sl="C:\Program Files\Sublime Text 3\sublime_text.exe"
|
||||
doskey ll=dir
|
||||
doskey rrp="cd C:\Python363\lib\site-packages\RobotRun" $T C:
|
||||
doskey rra=cd "G:\My Drive\codes\Projects\RobotRunApplications" $T G:
|
||||
doskey gpull=git pull origin master
|
||||
doskey gpush=git push origin master
|
||||
doskey gs=git status
|
||||
doskey gd=git diff
|
||||
doskey e.=explorer.exe .
|
||||
```
|
||||
|
||||
#### Setup bashrc in **Git bash**
|
||||
1. 打開`~/.bashrc`。
|
||||
2. 內容如下:
|
||||
```bash
|
||||
export PATH="/c/Users/ahuang11/AppData/Local/Android/Sdk/platform-tools:$PATH"
|
||||
|
||||
alias adb="/c/EasyAVEngine/Tools/Android/ADB/adb.exe"
|
||||
alias sl="/c/Program\ Files/Sublime\ Text/subl.exe"
|
||||
alias fastboot='/c/Users/ahuang11/AppData/Local/Android/Sdk/platform-tools/fastboot.exe'
|
||||
alias rrp='cd /c/Python363/lib/site-packages/RobotRun'
|
||||
alias rra='cd /c/RobotRun'
|
||||
alias rrd='cd /g/My\ Drive/codes/Projects/RobotRunDocs'
|
||||
alias rro='cd /c/RobotRun/Output'
|
||||
alias coderrp='cd "/c/Python363/lib/site-packages/RobotRun" ; code "/c/Python363/lib/site-packages/RobotRun"'
|
||||
alias coderra='cd "/c/RobotRun"; code "/c/RobotRun"'
|
||||
alias coderras='code "/d/GoogleDriveLogi/codes/Projects/RobotRunAutoServer"'
|
||||
alias ctest='code "/g/My Drive/codes/test"'
|
||||
alias ad='adb devices'
|
||||
alias fd='fastboot devices'
|
||||
alias e.='explorer.exe .'
|
||||
|
||||
##----- Connection -----
|
||||
alias gods='ssh awin@192.168.1.11'
|
||||
alias gorp320='ssh pi@192.168.1.20'
|
||||
|
||||
##----- Git -----
|
||||
alias gs="git status"
|
||||
alias gd="git diff"
|
||||
alias gpull='git pull origin master'
|
||||
alias gpush='git push origin master'
|
||||
alias gpushmain='git push origin main'
|
||||
alias gc='git clone'
|
||||
alias gclogi='git clone --config user.name="Awin Huang" --config user.email=ahuang11@logitech.com $@'
|
||||
|
||||
##----- Python enviroment swich -----
|
||||
alias pyv='echo PY_PYTHON=$PY_PYTHON'
|
||||
|
||||
function set_py() {
|
||||
echo "Original Python verison is \"$PY_PYTHON\""
|
||||
export PY_PYTHON=$1
|
||||
echo " New Python verison is \"$PY_PYTHON\""
|
||||
|
||||
if [ ! -z "$2" ]
|
||||
then
|
||||
py "${@:2}"
|
||||
fi
|
||||
}
|
||||
|
||||
function py36() {
|
||||
set_py "3.6.3" "$@"
|
||||
}
|
||||
|
||||
function py39() {
|
||||
set_py "3.9" "$@"
|
||||
}
|
||||
```
|
||||
|
||||
#### Setup Windows Terminal
|
||||
|
||||
1. 開啟Windows Terminal。
|
||||
2. 按`ctrl + ,`打開設定,之後參考[[Windows Terminal]]。
|
||||
|
||||
#### Setup WSL2
|
||||
- [[安裝筆記] Windows 10 安裝 Linux 子系統 (WSL2) | Kenmingの鮮思維](http://www.kenming.idv.tw/note_window10_install_wsl2/)
|
||||
- [[安裝筆記] Windows 10 WSL 2 安裝 Docker Desktop (含更改 Docker Image 路徑) | Kenmingの鮮思維](http://www.kenming.idv.tw/win10_wsl2_install_docker-desktop/)
|
||||
159
02. PARA/03. Resources(資源)/Tool Setup/Windows Terminal.md
Normal file
159
02. PARA/03. Resources(資源)/Tool Setup/Windows Terminal.md
Normal file
@@ -0,0 +1,159 @@
|
||||
## Setup
|
||||
### New tab as Administrator
|
||||
- [使用系統管理員身分開啟 Windows Terminal 分頁](https://blog.poychang.net/run-windows-terminal-as-administrator-with-elevated-admin-permissions/)
|
||||
|
||||
### Use powerline in Git-Bash
|
||||
- [Light & simple powerline theme for Git bash for windows](https://github.com/diesire/git_bash_windows_powerline)
|
||||
|
||||
#### Install
|
||||
```shell
|
||||
cd $HOME
|
||||
mkdir -p .bash/themes/git_bash_windows_powerline
|
||||
git clone https://github.com/diesire/git_bash_windows_powerline.git .bash/themes/git_bash_windows_powerline
|
||||
```
|
||||
|
||||
And add following lines to `~/.bashrc`.
|
||||
```
|
||||
# Theme
|
||||
THEME=$HOME/.bash/themes/git_bash_windows_powerline/theme.bash
|
||||
if [ -f $THEME ]; then
|
||||
. $THEME
|
||||
fi
|
||||
unset THEME
|
||||
```
|
||||
|
||||
參考:
|
||||
- [powerline/fonts: Patched fonts for Powerline users.](https://github.com/powerline/fonts)
|
||||
|
||||
## Settings.json
|
||||
```json
|
||||
// This file was initially generated by Windows Terminal 1.6.10571.0
|
||||
// It should still be usable in newer versions, but newer versions might have additional
|
||||
// settings, help text, or changes that you will not see unless you clear this file
|
||||
// and let us generate a new one for you.
|
||||
|
||||
// To view the default settings, hold "alt" while clicking on the "Settings" button.
|
||||
// For documentation on these settings, see: https://aka.ms/terminal-documentation
|
||||
// This file was initially generated by Windows Terminal 1.2.2381.0
|
||||
// It should still be usable in newer versions, but newer versions might have additional
|
||||
// settings, help text, or changes that you will not see unless you clear this file
|
||||
// and let us generate a new one for you.
|
||||
|
||||
// To view the default settings, hold "alt" while clicking on the "Settings" button.
|
||||
// For documentation on these settings, see: https://aka.ms/terminal-documentation
|
||||
{
|
||||
"$schema": "https://aka.ms/terminal-profiles-schema",
|
||||
|
||||
"defaultProfile": "{00000000-0000-0000-ba54-000000000002}",
|
||||
|
||||
// You can add more global application settings here.
|
||||
// To learn more about global settings, visit https://aka.ms/terminal-global-settings
|
||||
|
||||
// If enabled, selections are automatically copied to your clipboard.
|
||||
"copyOnSelect": false,
|
||||
|
||||
// If enabled, formatted data is also copied to your clipboard
|
||||
"copyFormatting": false,
|
||||
|
||||
// Start position
|
||||
"initialCols": 205,
|
||||
"initialRows": 30,
|
||||
"initialPosition": "15,400", // x,y
|
||||
|
||||
// A profile specifies a command to execute paired with information about how it should look and feel.
|
||||
// Each one of them will appear in the 'New Tab' dropdown,
|
||||
// and can be invoked from the commandline with `wt.exe -p xxx`
|
||||
// To learn more about profiles, visit https://aka.ms/terminal-profile-settings
|
||||
"profiles":
|
||||
{
|
||||
"defaults":
|
||||
{
|
||||
// Put settings here that you want to apply to all profiles.
|
||||
},
|
||||
"list":
|
||||
[
|
||||
{
|
||||
// Make changes here to the powershell.exe profile.
|
||||
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
|
||||
"name": "Windows PowerShell",
|
||||
"commandline": "powershell.exe",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"guid": "{41dd7a51-f0e1-4420-a2ec-1a7130b7e950}",
|
||||
"name": "Windows PowerShell(Administrator)",
|
||||
"commandline": "gsudo.exe powershell.exe",
|
||||
"hidden": false,
|
||||
"colorScheme": "Solarized Dark",
|
||||
"fontFace": "Fira Code",
|
||||
"icon" : "C:\\Users\\awinh\\OneDrive\\圖片\\icon\\console_red.png"
|
||||
},
|
||||
{
|
||||
// Make changes here to the cmd.exe profile.
|
||||
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
|
||||
"name": "Command Prompt",
|
||||
"commandline": "cmd.exe",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}",
|
||||
"name": "Azure Cloud Shell",
|
||||
"source": "Windows.Terminal.Azure",
|
||||
"hidden": false,
|
||||
},
|
||||
{
|
||||
"guid": "{00000000-0000-0000-ba54-000000000002}",
|
||||
"name" : "Bash",
|
||||
"commandline": "%PROGRAMFILES%/git/usr/bin/bash.exe -i -l",
|
||||
"icon": "%PROGRAMFILES%/Git/mingw64/share/git/git-for-windows.ico",
|
||||
"startingDirectory" : "D:\\GoogleDrive\\codes",
|
||||
"fontFace" : "Fira Code",
|
||||
"fontSize" : 11,
|
||||
"historySize" : 9000,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
// Add custom color schemes to this array.
|
||||
// To learn more about color schemes, visit https://aka.ms/terminal-color-schemes
|
||||
"schemes": [],
|
||||
|
||||
// Add custom keybindings to this array.
|
||||
// To unbind a key combination from your defaults.json, set the command to "unbound".
|
||||
// To learn more about keybindings, visit https://aka.ms/terminal-keybindings
|
||||
"keybindings":
|
||||
[
|
||||
// Copy and paste are bound to Ctrl+Shift+C and Ctrl+Shift+V in your defaults.json.
|
||||
// These two lines additionally bind them to Ctrl+C and Ctrl+V.
|
||||
// To learn more about selection, visit https://aka.ms/terminal-selection
|
||||
{ "command": {"action": "copy", "singleLine": false }, "keys": "ctrl+c" },
|
||||
{ "command": "paste", "keys": "ctrl+v" },
|
||||
|
||||
// Press Ctrl+Shift+F to open the search box
|
||||
{ "command": "find", "keys": "ctrl+shift+f" },
|
||||
|
||||
// Press Alt+Shift+D to open a new pane.
|
||||
// - "split": "auto" makes this pane open in the direction that provides the most surface area.
|
||||
// - "splitMode": "duplicate" makes the new pane use the focused pane's profile.
|
||||
// To learn more about panes, visit https://aka.ms/terminal-panes
|
||||
{ "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" },
|
||||
// Full screen
|
||||
{ "command": "toggleFullscreen", "keys": "alt+x" },
|
||||
// Open new default tab
|
||||
{ "command": "newTab", "keys": "ctrl+t" },
|
||||
// Close current pane
|
||||
{ "command": "closePane", "keys": "ctrl+w" },
|
||||
// Split pane in vertical
|
||||
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": "ctrl+shift+E" },
|
||||
// Split pane in horizontal
|
||||
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": "ctrl+shift+O" },
|
||||
// Goto next tab
|
||||
{ "command": "nextTab", "keys": "ctrl+pagedown" },
|
||||
// Goto previous tab
|
||||
{ "command": "prevTab", "keys": "ctrl+pageup" },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
- [Windows Terminal 美化 for WSL 2 Ubuntu (zsh + zim + powerlevel10k)](http://www.kenming.idv.tw/windows-terminal-%e7%be%8e%e5%8c%96-for-wsl-2-ubuntu-zsh-zim-powerlevel10k/)
|
||||
5
02. PARA/03. Resources(資源)/Tool Setup/freefilesync.md
Normal file
5
02. PARA/03. Resources(資源)/Tool Setup/freefilesync.md
Normal file
@@ -0,0 +1,5 @@
|
||||
1. ![[Pasted image 20210321201359.png]]
|
||||
2. ![[Pasted image 20210321201503.png]]
|
||||
|
||||
參考:
|
||||
- [FreeFileSync: RealTimeSync - YouTube](https://www.youtube.com/watch?v=9KXo6yOhTWo)
|
||||
27
02. PARA/03. Resources(資源)/Tool Setup/vim.md
Normal file
27
02. PARA/03. Resources(資源)/Tool Setup/vim.md
Normal file
@@ -0,0 +1,27 @@
|
||||
### `~/.vimrc`
|
||||
```vim
|
||||
set t_Co=256
|
||||
colorscheme koehler
|
||||
|
||||
set nocompatible
|
||||
syntax on
|
||||
set showmode
|
||||
set showcmd
|
||||
set encoding=utf-8
|
||||
|
||||
set cindent
|
||||
set expandtab
|
||||
set tabstop=4
|
||||
set softtabstop=4
|
||||
set shiftwidth=4
|
||||
|
||||
set number
|
||||
set cursorline
|
||||
"set textwidth=80
|
||||
set ruler
|
||||
|
||||
set showmatch
|
||||
set hlsearch
|
||||
set incsearch
|
||||
set ignorecase
|
||||
```
|
||||
24
02. PARA/03. Resources(資源)/Trojan.md
Normal file
24
02. PARA/03. Resources(資源)/Trojan.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## 準備
|
||||
1. 新增一台container,先更新container:
|
||||
```
|
||||
apt update ;\
|
||||
apu upgrade -y ;\
|
||||
apt install curl vim -y
|
||||
```
|
||||
2. 安裝trojan:
|
||||
```
|
||||
mkdir -p ~/trojan ;\
|
||||
cd ~/trojan
|
||||
curl -O https://raw.githubusercontent.com/atrandys/trojan/master/trojan_mult.sh ; chmod +x trojan_mult.sh ; ./trojan_mult.sh
|
||||
```
|
||||
3. 
|
||||
4. 
|
||||
5. 
|
||||
6. 
|
||||
|
||||
-----
|
||||
參考:
|
||||
1. [Trojan 上網架設(替代VPN)爬梯子 科學上網 利器 - TWIDC](https://lab.twidc.net/trojan-%E4%B8%8A%E7%B6%B2%E6%9E%B6%E7%BD%AE%E6%9B%BF%E4%BB%A3vpn/)
|
||||
2. [Trojan搭建私人VPN - Lingme](https://lingmin.me/2020/03/03/TrojanVPS/)
|
||||
3. [[转载+修改]使用Trojan-Go科学上网 - 996 RIP](https://typecho.996.rip/index.php/archives/20/)
|
||||
4. Source code: [Releases · trojan-gfw/trojan](https://github.com/trojan-gfw/trojan/releases)
|
||||
130
02. PARA/03. Resources(資源)/UML.md
Normal file
130
02. PARA/03. Resources(資源)/UML.md
Normal file
@@ -0,0 +1,130 @@
|
||||

|
||||
|
||||
玩轉設計模式 - 看懂UML類圖
|
||||
================
|
||||
|
||||
前言
|
||||
--
|
||||
|
||||
作為設計模式系列的第一篇,我準備先分享下如何看懂UML類圖,看完這篇文章,你就能看懂類圖中各個類之間的關係以及線條、箭頭都代表什麼意思。 同時,我們會將類圖所表達的含義和最終的代碼實現對應起來; 有了這些知識,看後面章節的設計模式結構圖就沒有什麼問題了。
|
||||
|
||||
先看一個例子
|
||||
------
|
||||
|
||||
下面是我畫的一張類圖,使用的工具是StarUML,Mac版本[下載地址](https://macwk.com/soft/staruml)
|
||||
|
||||

|
||||
|
||||
- 數碼是斜體的,表示數碼是一個抽象類
|
||||
- 數碼有兩個繼承類,分別是手機和電腦,他們之間的關係是**實現**關係,用帶空心箭頭的虛線表示
|
||||
- iPhone與手機之間也是繼承關係,他們之間的關係是**泛化**關係,用帶空心箭頭的實線表示
|
||||
- 手機由屏幕和主板等組合而成,他們之間的關係是**組合**關係,用帶實心菱形的實線表示
|
||||
- 程序員上班必須要用電腦,他們之間的關係是**依賴**關係,用帶箭頭的虛線表示
|
||||
- 程序員與公司之間是**聚合**關係,用帶空心菱形的實線表示
|
||||
- 程序員一般都會有自己的工牌,他們之間的關係是**關聯**關係,用實線表示
|
||||
|
||||
以上例子和圖中我都把類的六種關係特別標註出來了,下面我們具體講講這六種關係
|
||||
|
||||
類之間的關係
|
||||
------
|
||||
|
||||
類的繼承結構表現在UML中為:**實現**和**泛化**
|
||||
|
||||
繼承關係為 is-a的關係;兩個對象之間如果可以用 is-a 來表示,就是繼承關係
|
||||
|
||||
### 實現(Realization)
|
||||
|
||||
“數碼”是一個抽象概念,在現實中並不能直接用來定義對象,必須指明它的子類,比如手機和電腦。“數碼”這個類在java中可以用接口或者抽象類表示,在C++中用抽象類表示。
|
||||
|
||||
代碼實現:實現關係表現為繼承抽象類。
|
||||
|
||||
### 泛化(Generalization)
|
||||
|
||||
手機在現實中有實現,可以用來定義具體的對象,iphone是手機的子類,手機和iphone的關係為泛化關係。
|
||||
|
||||
代碼實現:泛化關係表現為繼承非抽象類。
|
||||
|
||||
### 聚合(Aggregation)
|
||||
|
||||
聚合表示兩個對象之間是整體和部分的**弱關係,生命週期不同步**,部分的生命週期可以超越整體,比如程序員和公司,公司倒閉了,但程序員還在,還可以去其他公司。
|
||||
|
||||
代碼實現:聚合關係以**成員變量**的形式實現,只是成員變量的賦值時機是在**類方法**裡,代碼如下:
|
||||
|
||||
```java
|
||||
//第一種是在set方法裡直接設置子類的實例
|
||||
class Company {
|
||||
private Programmer programmer;
|
||||
|
||||
public void setProgrammer(JavaProgrammer javaProgrammer) {
|
||||
programmer = javaProgrammer;
|
||||
}
|
||||
}
|
||||
|
||||
//第二種方式是在set方法裡直接寫死,直接定義一個初始值
|
||||
class Company {
|
||||
private Programmer programmer;
|
||||
|
||||
public void setProgrammer() {
|
||||
programmer = new JavaProgrammer();
|
||||
}
|
||||
}
|
||||
複製代碼
|
||||
```
|
||||
|
||||
### 組合(Composition)
|
||||
|
||||
組合表示兩個對象之間是整體和部分的**強關係,生命週期一致**,部分的生命週期不能超越整體,或者說組合中的整體不能缺少部分。就像手機不能缺少屏幕和主板一樣。
|
||||
|
||||
代碼實現:組合關係以**成員變量**的形式實現,只是成員變量的賦值時機是在**類構造方法**裡,代碼如下:
|
||||
|
||||
```java
|
||||
//手機由屏幕組成
|
||||
class Phone {
|
||||
private Screen screen;
|
||||
|
||||
//創建手機的時候同時創建屏幕
|
||||
public Phone() {
|
||||
screen = new Screen();
|
||||
}
|
||||
}
|
||||
複製代碼
|
||||
```
|
||||
|
||||
### 關聯(Association)
|
||||
|
||||
關聯表示的是兩個不同對象之間存在的固定的對應關係,**它是一種靜態關係,和運行狀態無關**。
|
||||
|
||||
代碼實現:關聯關係也是以成員變量的形式實現,只是成員變量的賦值時機是在聲明這個變量的時候,代碼如下:
|
||||
|
||||
```java
|
||||
//程序員都會有工牌
|
||||
class Programmer {
|
||||
private Card card = new Card();
|
||||
}
|
||||
複製代碼
|
||||
```
|
||||
|
||||
### 依賴(Dependency)
|
||||
|
||||
依賴表示的是**一個對象在運行期間會用到另一個對象的關係**,與關聯關係不同的是,它是一種臨時性的關係,通常在**運行期間**產生,並且隨著運行時的變化,依賴關係也可能發生變化。比如程序員在工作的時候會用到電腦,程序員是依賴電腦的。
|
||||
|
||||
代碼實現:依賴關係主要可以通過**方法參數、方法局部變量、靜態方法**三種方式實現,方法參數代碼如下:
|
||||
|
||||
```java
|
||||
class Programmer {
|
||||
//程序員工作的時候需要用到電腦
|
||||
public void work(Computer computer) {
|
||||
computer.work();
|
||||
}
|
||||
}
|
||||
複製代碼
|
||||
```
|
||||
|
||||
總結
|
||||
--
|
||||
|
||||

|
||||
|
||||
|
||||
-----
|
||||
- [玩轉設計模式 - 看懂UML類圖](https://juejin.cn/post/6914946238756421639)
|
||||
1
02. PARA/03. Resources(資源)/V2Ray.md
Normal file
1
02. PARA/03. Resources(資源)/V2Ray.md
Normal file
@@ -0,0 +1 @@
|
||||
- [自建最強科學上網5+:V2Ray + Caddy + Tls + HTTP/2 - 美博園](https://allinfa.com/v2ray-caddy-tls-http2-plus.html)
|
||||
0
02. PARA/03. Resources(資源)/WEB Programming.md
Normal file
0
02. PARA/03. Resources(資源)/WEB Programming.md
Normal file
1
02. PARA/03. Resources(資源)/__其他.md
Normal file
1
02. PARA/03. Resources(資源)/__其他.md
Normal file
@@ -0,0 +1 @@
|
||||
- [技术面试最后反问面试官的话](https://github.com/yifeikong/reverse-interview-zh)
|
||||
25
02. PARA/03. Resources(資源)/docker.md
Normal file
25
02. PARA/03. Resources(資源)/docker.md
Normal file
@@ -0,0 +1,25 @@
|
||||
## Container
|
||||
### list container
|
||||
`docker ps`會列出執行中的container(但是停止的不會)
|
||||
```bash
|
||||
sudo docker ps
|
||||
```
|
||||
|
||||
如果也要列出已停止的container
|
||||
```bash
|
||||
sudo docker ps -a
|
||||
```
|
||||
|
||||
### 刪除container
|
||||
Container必須是停止狀態才可以刪除
|
||||
```bash
|
||||
sudo docker rm <CONTAINER_ID>
|
||||
```
|
||||
|
||||
## Image
|
||||
### list images
|
||||
```bash
|
||||
sudo docker image ls
|
||||
or
|
||||
sudo docker images
|
||||
```
|
||||
4
02. PARA/03. Resources(資源)/frp.md
Normal file
4
02. PARA/03. Resources(資源)/frp.md
Normal file
@@ -0,0 +1,4 @@
|
||||
frp是一個可用於內網穿透的高性能的反向代理應用,可以作為兩個內網機器通過公網IP進行橋接的橋樑。通過其支持各種服務和傳輸協議,我們就可以實現一系列遠程控制操作。(frp的 Github 主頁上也用結構圖解釋了具體的工作原理,感興趣的話可以前往了解。)
|
||||
|
||||
- [用开源免费的内网穿透工具 frp,实现远程桌面和文件传输 - 少数派](https://sspai.com/post/60852)
|
||||
|
||||
36
02. PARA/03. Resources(資源)/git.md
Normal file
36
02. PARA/03. Resources(資源)/git.md
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
#### apply
|
||||
`git diff` 生出一個 diff 檔,而 `git apply` 把這個 diff 檔 apply 到某個 branch 上。
|
||||
```
|
||||
git diff ${A_COMMIT_HASH} ${B_COMMIT_HASH} > xxx.patch
|
||||
git apply xxx.patch
|
||||
```
|
||||
- 如果在 `git apply` 的過程中遇到 trailing whitespace error 的話,可以參考這篇文章:[git - My diff contains trailing whitespace - how to get rid of it? - Stack Overflow](https://stackoverflow.com/questions/14509950/my-diff-contains-trailing-whitespace-how-to-get-rid-of-it),透過加入 `--whitespace=warn` 或 `--whitespace=nowarn` 參數來解決。
|
||||
- [^1]
|
||||
|
||||
|
||||
[^1]: [(2018 iThome 鐵人賽) Day 11: 使用 Git 時如何做出跨 repo 的 cherry-pick - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10194972)
|
||||
|
||||
|
||||
### Trouble shooting
|
||||
- 如果在Windows上git clone遇到,例如:`error: invalid path`、`fatal: unable to checkout working tree`
|
||||
```
|
||||
$ gclogi git@github.com-logi:LogiVideoFW/VC_Bolide_TableHub.git
|
||||
Cloning into 'VC_Bolide_TableHub'...
|
||||
remote: Enumerating objects: 159, done.
|
||||
remote: Counting objects: 100% (159/159), done.
|
||||
remote: Compressing objects: 100% (134/134), done.
|
||||
remote: Total 85001 (delta 71), reused 84 (delta 21), pack-reused 84842
|
||||
Receiving objects: 100% (85001/85001), 599.21 MiB | 6.18 MiB/s, done.
|
||||
Resolving deltas: 100% (17824/17824), done.
|
||||
error: invalid path 'zynqLNX/kernel-source/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/aux.c'
|
||||
fatal: unable to checkout working tree
|
||||
warning: Clone succeeded, but checkout failed.
|
||||
You can inspect what was checked out with 'git status'
|
||||
and retry with 'git restore --source=HEAD :/'
|
||||
```
|
||||
有可能是因為檔名與NTFS規則有所衝突,解法:
|
||||
```
|
||||
cd <REPO_NAME>
|
||||
git config core.protectNTFS false; git reset --hard HEAD
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user