diff --git a/404.html b/404.html index 8c22915b2..eb438009b 100644 --- a/404.html +++ b/404.html @@ -12,7 +12,7 @@ } -
AC Dustbin

404 - not found

+
AC Dustbin

404 - not found

diff --git a/assets/data/blog/cheatsheet/index.json b/assets/data/blog/cheatsheet/index.json index d534ea11a..76d56f448 100644 --- a/assets/data/blog/cheatsheet/index.json +++ b/assets/data/blog/cheatsheet/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"blog: cheatsheet","name":"cheatsheet","type":"blog","belongsTo":{"edges":[{"node":{"id":"77","title":"换源集合","path":"/post/pkg-source/","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","path":"/post/linux-cht/","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"82","title":"Bash Cheatsheet","path":"/post/bash-cht/","summary":"","createdAt":"2020-04-18T04:48:39.000Z","lastEditedAt":"2020-06-30T06:59:32.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"83","title":"FFmpeg Cheatsheet","path":"/post/ffmpeg-cht/","summary":"","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"84","title":"Markdown Cheatsheet","path":"/post/markdown-cht/","summary":"\n

Tricks which are less known to markdown starters

\n","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"85","title":"Vim Cheatsheet","path":"/post/vim-cht/","summary":"","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"86","title":"Windows CMD Cheatsheet","path":"/post/win-cmd-cht/","summary":"","createdAt":"2020-04-18T04:49:03.000Z","lastEditedAt":"2020-08-06T13:16:42.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"153","title":"Jupyter etc. Cheatsheet","path":"/post/jupyter-cht/","summary":"\n

主要是一些数据与绘图处理相关

\n","createdAt":"2020-12-05T01:25:56.000Z","lastEditedAt":"2021-09-20T13:32:35.000Z","image":"/blog/img/8c93085b.png","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$AEH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AxxAB/9k=","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"}]}},{"node":{"id":"154","title":"Git CheatSheet","path":"/post/git-cht/","summary":"","createdAt":"2020-12-09T07:32:18.000Z","lastEditedAt":"2021-03-20T05:04:22.000Z","image":"/blog/img/4f1e424e.png","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGhABAAMAAwAAAAAAAAAAAAAAAQACESEx8P/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDO2rfNEV5fdycRA//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"169","title":"高频公式表","path":"/post/freq-formula/","summary":"\n

名书镇贴

\n

东西慢慢加进来好了,顺便看看公式渲染的情况。

\n","createdAt":"2021-04-05T00:51:35.000Z","lastEditedAt":"2021-04-05T01:01:29.000Z","image":"/blog/img/9553808b.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AED/8QAFxABAQEB$EhEv/EABUBAQE$AME/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzmzU5gKBP//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"175","title":"WSL Cheatsheet","path":"/post/wsl-cht/","summary":"\n

WSL issues or tricks I had to search for again and again

\n","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"blog: cheatsheet","name":"cheatsheet","type":"blog","belongsTo":{"edges":[{"node":{"id":"77","title":"换源集合","path":"/post/pkg-source/","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","path":"/post/linux-cht/","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"82","title":"Bash Cheatsheet","path":"/post/bash-cht/","summary":"","createdAt":"2020-04-18T04:48:39.000Z","lastEditedAt":"2020-06-30T06:59:32.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"83","title":"FFmpeg Cheatsheet","path":"/post/ffmpeg-cht/","summary":"","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"84","title":"Markdown Cheatsheet","path":"/post/markdown-cht/","summary":"\n

Tricks which are less known to markdown starters

\n","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"85","title":"Vim Cheatsheet","path":"/post/vim-cht/","summary":"","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"86","title":"Windows CMD Cheatsheet","path":"/post/win-cmd-cht/","summary":"","createdAt":"2020-04-18T04:49:03.000Z","lastEditedAt":"2020-08-06T13:16:42.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"153","title":"Jupyter etc. Cheatsheet","path":"/post/jupyter-cht/","summary":"\n

主要是一些数据与绘图处理相关

\n","createdAt":"2020-12-05T01:25:56.000Z","lastEditedAt":"2021-09-20T13:32:35.000Z","image":"/blog/img/8c93085b.png","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$AEH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AxxAB/9k=","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"}]}},{"node":{"id":"154","title":"Git CheatSheet","path":"/post/git-cht/","summary":"","createdAt":"2020-12-09T07:32:18.000Z","lastEditedAt":"2021-03-20T05:04:22.000Z","image":"/blog/img/4f1e424e.png","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGhABAAMAAwAAAAAAAAAAAAAAAQACESEx8P/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDO2rfNEV5fdycRA//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"169","title":"高频公式表","path":"/post/freq-formula/","summary":"\n

名书镇贴

\n

东西慢慢加进来好了,顺便看看公式渲染的情况。

\n","createdAt":"2021-04-05T00:51:35.000Z","lastEditedAt":"2021-04-05T01:01:29.000Z","image":"/blog/img/9553808b.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AED/8QAFxABAQEB$EhEv/EABUBAQE$AME/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzmzU5gKBP//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"175","title":"WSL Cheatsheet","path":"/post/wsl-cht/","summary":"\n

WSL issues or tricks I had to search for again and again

\n","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"181","title":"Physics Knowledge Sharing","path":"/post/physics-share/","summary":"\n

Sharing some of my physics notes / slides

\n","createdAt":"2022-05-21T11:23:41.000Z","lastEditedAt":"2022-05-21T11:23:41.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/index.json b/assets/data/index.json index 469933764..90eba4dfa 100644 --- a/assets/data/index.json +++ b/assets/data/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"name":"moments","description":"听说我不发朋友圈?","logo":"/blog/img/fa50d2ff.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q==","path":"/blog/moments/"}},{"node":{"name":"cheatsheet","description":"One sheet to get them all!","logo":"/blog/img/2795820f.jpg","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFhABAQE$BEh/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ANe1QBf/2Q==","path":"/blog/cheatsheet/"}},{"node":{"name":"programming","description":"Exciting or strange things for programming which worth noting","logo":"/blog/img/3a308f44.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$QME/8QAHxAAAQMDBQAAAAAAAAAAAAAAAQACAxEhURITNEGh/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ABrI9gm9RYDo4UtMOfUs4j1nQf/Z","path":"/blog/programming/"}}]},"allPost":{"edges":[{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"179","title":"Another Move of My Blog?","path":"/post/moving-blog-to-astro/","summary":"\n

It really needs a serious discussion.

\n","createdAt":"2022-04-06T04:00:10.000Z","lastEditedAt":"2022-05-01T08:55:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"180","title":"免费下网易云会员歌曲 MP3 竟如此简单?","path":"/post/netease-music-bp/","summary":"\n

我坦白,我是标题党

\n","createdAt":"2022-05-01T08:50:29.000Z","lastEditedAt":"2022-05-01T08:50:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"name":"moments","description":"听说我不发朋友圈?","logo":"/blog/img/fa50d2ff.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q==","path":"/blog/moments/"}},{"node":{"name":"cheatsheet","description":"One sheet to get them all!","logo":"/blog/img/2795820f.jpg","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFhABAQE$BEh/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ANe1QBf/2Q==","path":"/blog/cheatsheet/"}},{"node":{"name":"programming","description":"Exciting or strange things for programming which worth noting","logo":"/blog/img/3a308f44.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$QME/8QAHxAAAQMDBQAAAAAAAAAAAAAAAQACAxEhURITNEGh/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ABrI9gm9RYDo4UtMOfUs4j1nQf/Z","path":"/blog/programming/"}}]},"allPost":{"edges":[{"node":{"id":"181","title":"Physics Knowledge Sharing","path":"/post/physics-share/","summary":"\n

Sharing some of my physics notes / slides

\n","createdAt":"2022-05-21T11:23:41.000Z","lastEditedAt":"2022-05-21T11:23:41.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"179","title":"Another Move of My Blog?","path":"/post/moving-blog-to-astro/","summary":"\n

It really needs a serious discussion.

\n","createdAt":"2022-04-06T04:00:10.000Z","lastEditedAt":"2022-05-01T08:55:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/labels/index.json b/assets/data/labels/index.json index 7b65d798a..ccc44864b 100644 --- a/assets/data/labels/index.json +++ b/assets/data/labels/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/","belongsTo":{"totalCount":5}}},{"node":{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gridsome","type":"tag","name":"gridsome","color":"00a672","path":"/tag/gridsome/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: flask","type":"tag","name":"flask","color":"000000","path":"/tag/flask/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: vscode","type":"tag","name":"vscode","color":"0083d0","path":"/tag/vscode/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: network","type":"tag","name":"network","color":"A5F306","path":"/tag/network/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: minecraft","type":"tag","name":"minecraft","color":"674a34","path":"/tag/minecraft/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/","belongsTo":{"totalCount":3}}},{"node":{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/","belongsTo":{"totalCount":3}}},{"node":{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/","belongsTo":{"totalCount":15}}},{"node":{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/","belongsTo":{"totalCount":12}}},{"node":{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/","belongsTo":{"totalCount":6}}},{"node":{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/","belongsTo":{"totalCount":2}}},{"node":{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/","belongsTo":{"totalCount":43}}}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/","belongsTo":{"totalCount":5}}},{"node":{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gridsome","type":"tag","name":"gridsome","color":"00a672","path":"/tag/gridsome/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: flask","type":"tag","name":"flask","color":"000000","path":"/tag/flask/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: vscode","type":"tag","name":"vscode","color":"0083d0","path":"/tag/vscode/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: network","type":"tag","name":"network","color":"A5F306","path":"/tag/network/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: minecraft","type":"tag","name":"minecraft","color":"674a34","path":"/tag/minecraft/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/","belongsTo":{"totalCount":3}}},{"node":{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/","belongsTo":{"totalCount":3}}},{"node":{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/","belongsTo":{"totalCount":15}}},{"node":{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/","belongsTo":{"totalCount":13}}},{"node":{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/","belongsTo":{"totalCount":6}}},{"node":{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/","belongsTo":{"totalCount":2}}},{"node":{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/","belongsTo":{"totalCount":43}}}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/damn-gpg/index.json b/assets/data/post/damn-gpg/index.json index fa78c9a8a..f29391a19 100644 --- a/assets/data/post/damn-gpg/index.json +++ b/assets/data/post/damn-gpg/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"55","title":"Damning GPG Key","summary":"\n

Why GPG key keeps annoying me! 整天 Fail 有意思吗?

\n","body":"\n

Read the doc carefully and don't forget to tell git what gpg key to use.

\n
\n

Finally signed commit with success on Windows machine, and I happily did the same on my Ubuntu virtual machine.

\n

However, GitHub said that the commits by my windows machine was unverified but the ones by Ubuntu was verified.

\n

WTH? That's IMPOSSIBLE! I even copied the private keys to windows machine and without luck.

\n

Alright. The email setting was different between two machines and GitHub requires that the email used to commit MUST equals the email (a.k.a. comment) of GPG key.

\n
\n

And today, when I have succeeded in signing many commits in different repos, I failed to sign this repo...

\n

Type:

\n
git config -l\n
\n

And I saw two user.signingkey there... Interesting ...

\n

One is global and one is local, the local one is introduced in the early age when I configure the GPG key generated by windows locally and forgot to remove it...

\n
\n

Alright, damn GPG again.

\n

When I set up gpg keys on WSL today, odd things happend again:

\n
\n
\n \n shell\n
error: gpg failed to sign the data\nfatal: failed to write commit object\n\n> echo \"test\" | gpg2 --clearsign\ngpg: signing failed: Inappropriate ioctl for device\ngpg: [stdin]: clear-sign failed: Inappropriate ioctl for device
\n

GPG NEEDS A FOLLISH TTY?!

\n
export GPG_TTY=$(tty)\n
\n

That solved the problem

\n
\n

Oh, god damn it! The first sign after start up always fail on WSL Ubuntu. Type:

\n
\n
\n \n shell\n
echo \"test\" | gpg2 --clearsign
\n

again and it shows:

\n
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'\ngpg: can't connect to the agent: IPC connect call failed\ngpg: can't connect to the agent: IPC connect call failed\ngpg: keydb_search failed: No agent running\ngpg: no default secret key: No agent running\ngpg: [stdin]: clear-sign failed: No agent running\n
\n

No agent running. Just need to enable gpg-agent on start up:

\n
\n
\n \n shell\n
echo 'eval $(gpg-agent --daemon 2>/dev/null)' >> ~/.bashrc
\n

To fix unsafe ownership, run:

\n
\n
\n \n shell\n
sudo chown -R $USER:$USER ~/.gnupg\nsudo find ~/.gnupg -type d -exec chmod 700 {} \\;\nsudo find ~/.gnupg -type f -exec chmod 600 {} \\;
\n

Reference:

\n","createdAt":"2020-01-07T12:16:11.000Z","lastEditedAt":"2020-07-17T14:07:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"55","title":"Damning GPG Key","summary":"\n

Why GPG key keeps annoying me! 整天 Fail 有意思吗?

\n","body":"\n

Read the doc carefully and don't forget to tell git what gpg key to use.

\n
\n

Finally signed commit with success on Windows machine, and I happily did the same on my Ubuntu virtual machine.

\n

However, GitHub said that the commits by my windows machine was unverified but the ones by Ubuntu was verified.

\n

WTH? That's IMPOSSIBLE! I even copied the private keys to windows machine and without luck.

\n

Alright. The email setting was different between two machines and GitHub requires that the email used to commit MUST equals the email (a.k.a. comment) of GPG key.

\n
\n

And today, when I have succeeded in signing many commits in different repos, I failed to sign this repo...

\n

Type:

\n
git config -l\n
\n

And I saw two user.signingkey there... Interesting ...

\n

One is global and one is local, the local one is introduced in the early age when I configure the GPG key generated by windows locally and forgot to remove it...

\n
\n

Alright, damn GPG again.

\n

When I set up gpg keys on WSL today, odd things happend again:

\n
\n
\n \n shell\n
error: gpg failed to sign the data\nfatal: failed to write commit object\n\n> echo \"test\" | gpg2 --clearsign\ngpg: signing failed: Inappropriate ioctl for device\ngpg: [stdin]: clear-sign failed: Inappropriate ioctl for device
\n

GPG NEEDS A FOLLISH TTY?!

\n
export GPG_TTY=$(tty)\n
\n

That solved the problem

\n
\n

Oh, god damn it! The first sign after start up always fail on WSL Ubuntu. Type:

\n
\n
\n \n shell\n
echo \"test\" | gpg2 --clearsign
\n

again and it shows:

\n
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'\ngpg: can't connect to the agent: IPC connect call failed\ngpg: can't connect to the agent: IPC connect call failed\ngpg: keydb_search failed: No agent running\ngpg: no default secret key: No agent running\ngpg: [stdin]: clear-sign failed: No agent running\n
\n

No agent running. Just need to enable gpg-agent on start up:

\n
\n
\n \n shell\n
echo 'eval $(gpg-agent --daemon 2>/dev/null)' >> ~/.bashrc
\n

To fix unsafe ownership, run:

\n
\n
\n \n shell\n
sudo chown -R $USER:$USER ~/.gnupg\nsudo find ~/.gnupg -type d -exec chmod 700 {} \\;\nsudo find ~/.gnupg -type f -exec chmod 600 {} \\;
\n

Reference:

\n","createdAt":"2020-01-07T12:16:11.000Z","lastEditedAt":"2020-07-17T14:07:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/docker-tag/index.json b/assets/data/post/docker-tag/index.json index 7ec3d73b5..823bd7c5d 100644 --- a/assets/data/post/docker-tag/index.json +++ b/assets/data/post/docker-tag/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"75","title":"列出 USTC Docker 镜像的标签","summary":"\n

不再为网络条件发愁!

\n","body":"\n

众所周知,如果网络条件好的话,可以使用官方的 API 获取镜像标签列表。

\n

如 v2 接口:

\n
\n
\n \n shell\n
#!/bin/bash\ni = 0\n\nwhile [ $? == 0 ]\ndo\n   i = $(( i + 1 ))\n   curl https://registry.hub.docker.com/v2/repositories/library/nginx/tags/?page=$i | jq '.\"results\"[][\"name\"]'\n\ndone
\n

但是天朝。。

\n

网易和阿里的官方 API 里都要求有 API Key 签名认证之类,比较繁琐

\n

故先尝试使用 USTC 的镜像,缺点是速度慢、标签不全。。根据一波猜测,得到了 USTC 标签的 API:

\n
https://docker.mirrors.ustc.edu.cn/v2/library/nginx/tags/list\n
\n

同理又意外获得了网易的,标签全!

\n
https://hub-mirror.c.163.com/v2/library/nginx/tags/list\n
\n

于是根据 https://stackoverflow.com/a/39454426/8810271 代码改编:

\n
\n
\n \n shell\n
#!/bin/bash\n\nif [ $# -lt 1 ]\nthen\ncat << HELP\n\ndockertags  --  list all tags for a Docker image on a remote registry.\n\nEXAMPLE:\n    - list all tags for ubuntu:\n       dockertags ubuntu\n\n    - list all php tags containing apache:\n       dockertags php apache\n\nHELP\nexit\nfi\n\nimage = \"$1\"\ntags = `curl https://hub-mirror.c.163.com/v2/library/${image}/tags/list | sed -e 's/.*\\[//g' -e 's/\"//g' -e 's/\\]\\}$//g' -e 's/,/\\n/g'`\n\nif [ -n \"$2\" ]\nthen\n    tags = ` echo \"${tags}\" | grep \"$2\" `\nfi\n\necho \"${tags}\"
","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"75","title":"列出 USTC Docker 镜像的标签","summary":"\n

不再为网络条件发愁!

\n","body":"\n

众所周知,如果网络条件好的话,可以使用官方的 API 获取镜像标签列表。

\n

如 v2 接口:

\n
\n
\n \n shell\n
#!/bin/bash\ni = 0\n\nwhile [ $? == 0 ]\ndo\n   i = $(( i + 1 ))\n   curl https://registry.hub.docker.com/v2/repositories/library/nginx/tags/?page=$i | jq '.\"results\"[][\"name\"]'\n\ndone
\n

但是天朝。。

\n

网易和阿里的官方 API 里都要求有 API Key 签名认证之类,比较繁琐

\n

故先尝试使用 USTC 的镜像,缺点是速度慢、标签不全。。根据一波猜测,得到了 USTC 标签的 API:

\n
https://docker.mirrors.ustc.edu.cn/v2/library/nginx/tags/list\n
\n

同理又意外获得了网易的,标签全!

\n
https://hub-mirror.c.163.com/v2/library/nginx/tags/list\n
\n

于是根据 https://stackoverflow.com/a/39454426/8810271 代码改编:

\n
\n
\n \n shell\n
#!/bin/bash\n\nif [ $# -lt 1 ]\nthen\ncat << HELP\n\ndockertags  --  list all tags for a Docker image on a remote registry.\n\nEXAMPLE:\n    - list all tags for ubuntu:\n       dockertags ubuntu\n\n    - list all php tags containing apache:\n       dockertags php apache\n\nHELP\nexit\nfi\n\nimage = \"$1\"\ntags = `curl https://hub-mirror.c.163.com/v2/library/${image}/tags/list | sed -e 's/.*\\[//g' -e 's/\"//g' -e 's/\\]\\}$//g' -e 's/,/\\n/g'`\n\nif [ -n \"$2\" ]\nthen\n    tags = ` echo \"${tags}\" | grep \"$2\" `\nfi\n\necho \"${tags}\"
","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/ffmpeg-cht/index.json b/assets/data/post/ffmpeg-cht/index.json index f2fafaf2a..7f6089348 100644 --- a/assets/data/post/ffmpeg-cht/index.json +++ b/assets/data/post/ffmpeg-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"83","title":"FFmpeg Cheatsheet","summary":"","body":"\n

Basic

\n

Get help from command line

\n
\n
\n \n shell\n
ffmpeg -h
\n
\nSome useful help here\n
Global options (affect whole program instead of just one file:\n-loglevel loglevel  set logging level\n-v loglevel         set logging level\n-y                  overwrite output files\n-n                  never overwrite output files\n\nVideo options:\n-r rate             set frame rate (Hz value, fraction or abbreviation)\n-s size             set frame size (WxH or abbreviation)\n-aspect aspect      set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)\n-vn                 disable video\n-vcodec codec       force video codec ('copy' to copy stream)\n-vf filter_graph    set video filters\n-ab bitrate         audio bitrate (please use -b:a)\n-b bitrate          video bitrate (please use -b:v)\n\nAudio options:\n-aq quality         set audio quality (codec-specific)\n-ar rate            set audio sampling rate (in Hz)\n-ac channels        set number of audio channels\n-an                 disable audio\n-acodec codec       force audio codec ('copy' to copy stream)\n-vol volume         change audio volume (256=normal)\n-af filter_graph    set audio filters\n
\n
\n

Examples

\n

去除和取出声音

\n
\n
\n \n shell\n
ffmpeg -i example.mkv -c copy -an nosound.mkv\nffmpeg -i input.avi -vn -acodec copy output.aac
\n

裁剪

\n
\n
\n \n shell\n
ffmpeg -i input.m4a -ss 0 -t 18 cut.m4a
\n\n

拼接

\n
\n
\n \n shell\n
ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4
\n

list.txt 内容为:

\n
file 1.mp4\nfile 2.mp4\n
\n

生成静音的音频

\n
\n
\n \n shell\n
ffmpeg -f lavfi -i anullsrc -t 5 -c:a libvorbis output.ogg
\n

把视频和音频合起来

\n
\n
\n \n shell\n
ffmpeg -i all.mp4 -i all.m4a -c:v copy -c:a aac -strict experimental output.mp4
\n

合理转成 GIF

\n

The standard way to use ffmpeg for GIFs is

\n

Generate a palette from the video

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -vf palettegen palette.png
\n

Then,

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -i palette.png -filter_complex paletteuse -r 10 -s 320x480 file.gif
\n

More options documented here.

\n

所以使用我的 ffmpegroup脚本,第二步就是

\n
\n
\n \n shell\n
ffmpegroup mp4/gif -z\"-i palette.png -filter_complex paletteuse -r 10 -s 200x200 -y\"
\n

直接倍速

\n
\n
\n \n shell\n
ffmpeg -i input.mkv -filter:v \"setpts=PTS/60\" output.mkv
\n

或者更快的,但是会有音频问题,而且转出来帧率吓死人,并没有多少压缩作用,请慎用:

\n
\n
\n \n shell\n
ffmpeg -itsscale 0.01666 -i input.mkv -c copy output.mkv
","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"83","title":"FFmpeg Cheatsheet","summary":"","body":"\n

Basic

\n

Get help from command line

\n
\n
\n \n shell\n
ffmpeg -h
\n
\nSome useful help here\n
Global options (affect whole program instead of just one file:\n-loglevel loglevel  set logging level\n-v loglevel         set logging level\n-y                  overwrite output files\n-n                  never overwrite output files\n\nVideo options:\n-r rate             set frame rate (Hz value, fraction or abbreviation)\n-s size             set frame size (WxH or abbreviation)\n-aspect aspect      set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)\n-vn                 disable video\n-vcodec codec       force video codec ('copy' to copy stream)\n-vf filter_graph    set video filters\n-ab bitrate         audio bitrate (please use -b:a)\n-b bitrate          video bitrate (please use -b:v)\n\nAudio options:\n-aq quality         set audio quality (codec-specific)\n-ar rate            set audio sampling rate (in Hz)\n-ac channels        set number of audio channels\n-an                 disable audio\n-acodec codec       force audio codec ('copy' to copy stream)\n-vol volume         change audio volume (256=normal)\n-af filter_graph    set audio filters\n
\n
\n

Examples

\n

去除和取出声音

\n
\n
\n \n shell\n
ffmpeg -i example.mkv -c copy -an nosound.mkv\nffmpeg -i input.avi -vn -acodec copy output.aac
\n

裁剪

\n
\n
\n \n shell\n
ffmpeg -i input.m4a -ss 0 -t 18 cut.m4a
\n\n

拼接

\n
\n
\n \n shell\n
ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4
\n

list.txt 内容为:

\n
file 1.mp4\nfile 2.mp4\n
\n

生成静音的音频

\n
\n
\n \n shell\n
ffmpeg -f lavfi -i anullsrc -t 5 -c:a libvorbis output.ogg
\n

把视频和音频合起来

\n
\n
\n \n shell\n
ffmpeg -i all.mp4 -i all.m4a -c:v copy -c:a aac -strict experimental output.mp4
\n

合理转成 GIF

\n

The standard way to use ffmpeg for GIFs is

\n

Generate a palette from the video

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -vf palettegen palette.png
\n

Then,

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -i palette.png -filter_complex paletteuse -r 10 -s 320x480 file.gif
\n

More options documented here.

\n

所以使用我的 ffmpegroup脚本,第二步就是

\n
\n
\n \n shell\n
ffmpegroup mp4/gif -z\"-i palette.png -filter_complex paletteuse -r 10 -s 200x200 -y\"
\n

直接倍速

\n
\n
\n \n shell\n
ffmpeg -i input.mkv -filter:v \"setpts=PTS/60\" output.mkv
\n

或者更快的,但是会有音频问题,而且转出来帧率吓死人,并没有多少压缩作用,请慎用:

\n
\n
\n \n shell\n
ffmpeg -itsscale 0.01666 -i input.mkv -c copy output.mkv
","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/freq-formula/index.json b/assets/data/post/freq-formula/index.json index 54f5547b8..b83b99769 100644 --- a/assets/data/post/freq-formula/index.json +++ b/assets/data/post/freq-formula/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"169","title":"高频公式表","summary":"\n

名书镇贴

\n

东西慢慢加进来好了,顺便看看公式渲染的情况。

\n","body":"\n
柱坐标系下的梯度散度旋度公式
\n

$$
\n(\\nabla u)_r = \\frac{\\partial u}{\\partial r},\\ (\\nabla u)_{\\theta} = \\frac 1 r \\frac{\\partial u}{\\partial \\theta},\\ (\\nabla u)_z = \\frac{\\partial u}{\\partial z}
\n$$

\n

$$
\n\\nabla \\cdot \\mathbf{A} = \\frac 1 r \\frac{\\partial (r A_r)}{\\partial r} + \\frac 1 r \\frac{\\partial A_{\\theta}}{\\partial \\theta} + \\frac{\\partial A_z}{\\partial z}
\n$$

\n

$$
\n\\nabla \\times \\mathbf{A} = \\frac 1 r \\left[\\frac{\\partial A_z}{\\partial \\theta} - \\frac{\\partial (rA_{\\theta})}{\\partial z}\\right]\\mathbf{e_r} + \\left[\\frac{\\partial A_r}{\\partial z} - \\frac{\\partial A_z}{\\partial r}\\right]\\mathbf{e_{\\theta}} + \\frac 1 r \\left[\\frac{\\partial (rA_{\\theta})}{\\partial r} - \\frac{\\partial A_r}{\\partial \\theta}\\right]\\mathbf{e_z}
\n$$

\n
球坐标系下的梯度散度旋度公式
\n

$$
\n(\\nabla u)_r = \\frac{\\partial u}{\\partial r},\\ (\\nabla u)_{\\theta} = \\frac 1 r \\frac{\\partial u}{\\partial \\theta},\\ (\\nabla u)_{\\phi} = \\frac{1}{r \\sin \\theta} \\frac{\\partial u}{\\partial \\phi}
\n$$

\n

$$
\n\\nabla \\cdot \\mathbf{A} = \\frac{1}{r^2} \\frac{\\partial (r^2 A_r)}{\\partial r} + \\frac{1}{r \\sin \\theta} \\frac{\\partial (\\sin \\theta A_{\\theta})}{\\partial \\theta} + \\frac{1}{r \\sin \\theta} \\frac{\\partial A_{\\phi}}{\\partial \\phi}
\n$$

\n

$$
\n\\nabla \\times \\mathbf{A} = \\frac{1}{r \\sin \\theta} \\left[\\frac{\\partial (\\sin \\theta A_{\\phi})}{\\partial \\theta} - \\frac{\\partial A_{\\theta}}{\\partial \\phi}\\right]\\mathbf{e_r} + \\frac 1 r \\left[\\frac{1}{\\sin \\theta}\\frac{\\partial A_r}{\\partial \\phi} - \\frac{\\partial (r A_{\\phi})}{\\partial r}\\right]\\mathbf{e_{\\theta}} + \\frac 1 r \\left[\\frac{\\partial (rA_{\\theta})}{\\partial r} - \\frac{\\partial A_r}{\\partial \\theta}\\right]\\mathbf{e_{\\phi}}
\n$$

","createdAt":"2021-04-05T00:51:35.000Z","lastEditedAt":"2021-04-05T01:01:29.000Z","image":"/blog/img/9553808b.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AED/8QAFxABAQEB$EhEv/EABUBAQE$AME/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzmzU5gKBP//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"169","title":"高频公式表","summary":"\n

名书镇贴

\n

东西慢慢加进来好了,顺便看看公式渲染的情况。

\n","body":"\n
柱坐标系下的梯度散度旋度公式
\n

$$\n\n(\\nabla u)_r = \\frac{\\partial u}{\\partial r},\\ (\\nabla u)_{\\theta} = \\frac 1 r \\frac{\\partial u}{\\partial \\theta},\\ (\\nabla u)_z = \\frac{\\partial u}{\\partial z}\n\n$$

\n

$$\n\n\\nabla \\cdot \\mathbf{A} = \\frac 1 r \\frac{\\partial (r A_r)}{\\partial r} + \\frac 1 r \\frac{\\partial A_{\\theta}}{\\partial \\theta} + \\frac{\\partial A_z}{\\partial z}\n\n$$

\n

$$\n\n\\nabla \\times \\mathbf{A} = \\frac 1 r \\left[\\frac{\\partial A_z}{\\partial \\theta} - \\frac{\\partial (rA_{\\theta})}{\\partial z}\\right]\\mathbf{e_r} + \\left[\\frac{\\partial A_r}{\\partial z} - \\frac{\\partial A_z}{\\partial r}\\right]\\mathbf{e_{\\theta}} + \\frac 1 r \\left[\\frac{\\partial (rA_{\\theta})}{\\partial r} - \\frac{\\partial A_r}{\\partial \\theta}\\right]\\mathbf{e_z}\n\n$$

\n
球坐标系下的梯度散度旋度公式
\n

$$\n\n(\\nabla u)_r = \\frac{\\partial u}{\\partial r},\\ (\\nabla u)_{\\theta} = \\frac 1 r \\frac{\\partial u}{\\partial \\theta},\\ (\\nabla u)_{\\phi} = \\frac{1}{r \\sin \\theta} \\frac{\\partial u}{\\partial \\phi}\n\n$$

\n

$$\n\n\\nabla \\cdot \\mathbf{A} = \\frac{1}{r^2} \\frac{\\partial (r^2 A_r)}{\\partial r} + \\frac{1}{r \\sin \\theta} \\frac{\\partial (\\sin \\theta A_{\\theta})}{\\partial \\theta} + \\frac{1}{r \\sin \\theta} \\frac{\\partial A_{\\phi}}{\\partial \\phi}\n\n$$

\n

$$\n\n\\nabla \\times \\mathbf{A} = \\frac{1}{r \\sin \\theta} \\left[\\frac{\\partial (\\sin \\theta A_{\\phi})}{\\partial \\theta} - \\frac{\\partial A_{\\theta}}{\\partial \\phi}\\right]\\mathbf{e_r} + \\frac 1 r \\left[\\frac{1}{\\sin \\theta}\\frac{\\partial A_r}{\\partial \\phi} - \\frac{\\partial (r A_{\\phi})}{\\partial r}\\right]\\mathbf{e_{\\theta}} + \\frac 1 r \\left[\\frac{\\partial (rA_{\\theta})}{\\partial r} - \\frac{\\partial A_r}{\\partial \\theta}\\right]\\mathbf{e_{\\phi}}\n\n$$

","createdAt":"2021-04-05T00:51:35.000Z","lastEditedAt":"2021-04-05T01:01:29.000Z","image":"/blog/img/9553808b.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AED/8QAFxABAQEB$EhEv/EABUBAQE$AME/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzmzU5gKBP//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/from-python-to-js/index.json b/assets/data/post/from-python-to-js/index.json index 18da1427e..190b315e8 100644 --- a/assets/data/post/from-python-to-js/index.json +++ b/assets/data/post/from-python-to-js/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"70","title":"From Python to JavaScript","summary":"\n

Although the title Top Ten Mistakes Python Programmers Make When Learning JavaScript may sound better, I have only noticed a few mistakes now. And I will update this post when I make more mistakes 😄

\n","body":"\n

The new Operator

\n

You can simply create an instance of a class by ClassName(...) in Python. But in JavaScript you need the new operator. It can be easily forgotten!

\n

See also https://stackoverflow.com/q/1646698/8810271, https://stackoverflow.com/q/383402/8810271

\n

But why sometimes it still works when I forget new? For example:

\n
\n
\n \n js\n
> b = new Date()\n2020-02-08T05:20:14.069Z\n> c = Date()\n'Sat Feb 08 2020 13:20:19 GMT+0800 (GMT+08:00)'
\n

As the second link shows, there is a trick:

\n
\n
\n \n js\n
function foo() {\n   // if user accidentally omits the new keyword, this will \n   // silently correct the problem...\n   if ( !(this instanceof foo) )\n      return new foo();\n   // constructor logic follows...\n}
\n

However, you must use new if you are using class:

\n
\n
\n \n js\n
> class Bar {\n... constructor() {\n..... if ( !(this instanceof Bar) ) return new Bar()\n..... this.foo = 1\n..... }\n... }\nundefined\n> Bar()\nThrown:\nTypeError: Class constructor Bar cannot be invoked without 'new'\n> new Bar()\nBar { foo: 1 }
\n

Element in Array or Key in Object / dict

\n

In Python, we have:

\n
\n
\n \n python\n
>>> 3 in [1, 2, 3]\nTrue\n>>> 3 in {1: 2, 3: 4}\nTrue
\n

However, in JavaScript:

\n
\n
\n \n js\n
> 3 in [1, 2, 3]\nfalse\n> 3 in {1: 2, 3: 4}\ntrue
\n

That's because in JavaScript, even Array act like a Object:

\n
\n
\n \n js\n
> let a = [1, 2, 3]\nundefined\n> a.b = 5\n5\n> a\n[ 1, 2, 3, b: 5 ]
\n

So, JS just always detects keys. To determine whether an element in Array, you should do:

\n
\n
\n \n js\n
> a.includes(3)\ntrue
\n

Weird map / Are they Different?

\n
\n
\n \n python\n
>>> list(map(int, ['1', '2', '3']))\n[1, 2, 3]\n>>> list(map(lambda s: int(s), ['1', '2', '3']))\n[1, 2, 3]
\n
\n
\n \n js\n
> ['1', '2', '3'].map(parseInt)\n[ 1, NaN, NaN ]\n> ['1', '2', '3'].map(s => parseInt(s))\n[ 1, 2, 3 ]\n> [1, 2, 3].map(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\n[ undefined, undefined, undefined ]\n> [1, 2, 3].map(s => console.log(s))\n1\n2\n3\n[ undefined, undefined, undefined ]\n> [1, 2, 3].forEach(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\nundefined
\n

mapwill pass 3 parameters: value, index, and the Array itself, as shown in [1, 2, 3].map(console.log)

\n

And, passing too many arguments will not cause error:

\n
\n
\n \n js\n
> const g = s => console.log(s)\nundefined\n> g(1, 2, 3)\n1\nundefined
\n

And as for parseInt, there is a second parameter (see below). That's why parseInt and s => parseInt(s) are different.

\n

parseInt v.s. int

\n
\n

parseInt(string [, radix])

\n

Parameters

\n

string

\n

The value to parse. If this argument is not a string, then it is converted to one using the ToString abstract operation. Leading whitespace in this argument is ignored.

\n

radix Optional

\n

An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the string. Be careful—this does not default to 10!

\n
\n
\n

If radix is undefined, 0, or unspecified, JavaScript assumes the following:

\n
    \n
  1. If the input string begins with \"0x\" or \"0X\" (a zero, followed by lowercase or uppercase X), radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number.
  2. \n
  3. If the input string begins with \"0\" (a zero), radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 clarifies that 10 (decimal) should be used, but not all browsers support this yet. For this reason, always specify a radix when using parseInt.
  4. \n
  5. If the input string begins with any other value, the radix is 10 (decimal).
  6. \n
\n

If the first character cannot be converted to a number, parseInt returns NaN unless the radix is bigger than 10.

\n
\n

Well, that makes sense. And I'm going to specify a radix when using parseInt.

\n

And note that Python is more friendly:

\n
class int(object)\n |  int([x]) -> integer\n |  int(x, base=10) -> integer\n |\n |  Convert a number or string to an integer, or return 0 if no arguments\n |  are given.  If x is a number, return x.__int__().  For floating point\n |  numbers, this truncates towards zero.\n |\n |  If x is not a number or if base is given, then x must be a string,\n |  bytes, or bytearray instance representing an integer literal in the\n |  given base.  The literal can be preceded by '+' or '-' and be surrounded\n |  by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.\n |  Base 0 means to interpret the base from the string as an integer literal.\n |  >>> int('0b100', base=0)\n |  4\n
\n

Dead Variable

\n

Based on https://stackoverflow.com/a/54980674/8810271

\n
\n
\n \n js\n
> let a = nothing\nThrown:\nReferenceError: nothing is not defined\n> let a = 1\nThrown:\nSyntaxError: Identifier 'a' has already been declared\n> a = 1\nThrown:\nReferenceError: a is not defined
\n

Once you have a typo when using let in console (such as forgetting new), the variable name will never come back. That's because variable initialization did not complete successfully, and you can't re-declare a variable that's already been declared.

\n

Worse still, you cannot delete the variable declared using let, const or var. Only things like \"global variable\" can be deleted. See also https://stackoverflow.com/q/1596782/8810271

\n

You have to reinvent a good variable name, or reopen the console.

\n

Sure Sorted?

\n
\n
\n \n js\n
> [2, 1].sort()\n[ 1, 2 ]\n> [8, 10].sort()\n[ 10, 8 ]
\n

No, JS sort is just String sorting:

\n
\n

The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

\n
\n

And if you want number sorting, write:

\n
\n
\n \n js\n
> [8, 10, 6, 19].sort((a, b) => a - b)\n[ 6, 8, 10, 19 ]
\n

How do you split?

\n

Even though expression is the same, the answer does not.

\n
\n
\n \n python\n
>>> 'asdf adsf ak g'.split(' ', 1)\n['asdf', 'adsf ak g']
\n
\n
\n \n js\n
> 'asdf adsf ak g'.split(' ', 1)\n[\"asdf\"]
\n

Hmm, I'd better split, slice and join..

\n

Mutable Default Parameter?

\n

Javascript seems not affected.

\n
\n
\n \n js\n
> const f = (a=[]) => { a.push(1); return a }\nundefined\n> f()\n[ 1 ]\n> f()\n[ 1 ]
\n
\n
\n \n python\n
>>> def f(a=[]):\n...     a.append(1)\n...     return a\n... \n>>> f()\n[1]\n>>> f()\n[1, 1]
\n

Beware of Method / Function Names

\n

Some methods / functions achieve similar, if not the same, effect, but with different names. They are so similar that I often get confused.

\n
\n
\n \n python\n
>>> 'abc'.startswith('a')\nTrue\n>>> ''.join(['a', 'b', 'c'])\n'abc'\n>>> 'abc'.index('a')\n0
\n
\n
\n \n js\n
> 'abc'.startsWith('a')\ntrue\n> ['a', 'b', 'c'].join('')\n'abc'\n> 'abc'.indexOf('a')\n0
","createdAt":"2020-02-07T04:23:51.000Z","lastEditedAt":"2021-02-22T08:52:30.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"70","title":"From Python to JavaScript","summary":"\n

Although the title Top Ten Mistakes Python Programmers Make When Learning JavaScript may sound better, I have only noticed a few mistakes now. And I will update this post when I make more mistakes 😄

\n","body":"\n

The new Operator

\n

You can simply create an instance of a class by ClassName(...) in Python. But in JavaScript you need the new operator. It can be easily forgotten!

\n

See also https://stackoverflow.com/q/1646698/8810271, https://stackoverflow.com/q/383402/8810271

\n

But why sometimes it still works when I forget new? For example:

\n
\n
\n \n js\n
> b = new Date()\n2020-02-08T05:20:14.069Z\n> c = Date()\n'Sat Feb 08 2020 13:20:19 GMT+0800 (GMT+08:00)'
\n

As the second link shows, there is a trick:

\n
\n
\n \n js\n
function foo() {\n   // if user accidentally omits the new keyword, this will \n   // silently correct the problem...\n   if ( !(this instanceof foo) )\n      return new foo();\n   // constructor logic follows...\n}
\n

However, you must use new if you are using class:

\n
\n
\n \n js\n
> class Bar {\n... constructor() {\n..... if ( !(this instanceof Bar) ) return new Bar()\n..... this.foo = 1\n..... }\n... }\nundefined\n> Bar()\nThrown:\nTypeError: Class constructor Bar cannot be invoked without 'new'\n> new Bar()\nBar { foo: 1 }
\n

Element in Array or Key in Object / dict

\n

In Python, we have:

\n
\n
\n \n python\n
>>> 3 in [1, 2, 3]\nTrue\n>>> 3 in {1: 2, 3: 4}\nTrue
\n

However, in JavaScript:

\n
\n
\n \n js\n
> 3 in [1, 2, 3]\nfalse\n> 3 in {1: 2, 3: 4}\ntrue
\n

That's because in JavaScript, even Array act like a Object:

\n
\n
\n \n js\n
> let a = [1, 2, 3]\nundefined\n> a.b = 5\n5\n> a\n[ 1, 2, 3, b: 5 ]
\n

So, JS just always detects keys. To determine whether an element in Array, you should do:

\n
\n
\n \n js\n
> a.includes(3)\ntrue
\n

Weird map / Are they Different?

\n
\n
\n \n python\n
>>> list(map(int, ['1', '2', '3']))\n[1, 2, 3]\n>>> list(map(lambda s: int(s), ['1', '2', '3']))\n[1, 2, 3]
\n
\n
\n \n js\n
> ['1', '2', '3'].map(parseInt)\n[ 1, NaN, NaN ]\n> ['1', '2', '3'].map(s => parseInt(s))\n[ 1, 2, 3 ]\n> [1, 2, 3].map(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\n[ undefined, undefined, undefined ]\n> [1, 2, 3].map(s => console.log(s))\n1\n2\n3\n[ undefined, undefined, undefined ]\n> [1, 2, 3].forEach(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\nundefined
\n

mapwill pass 3 parameters: value, index, and the Array itself, as shown in [1, 2, 3].map(console.log)

\n

And, passing too many arguments will not cause error:

\n
\n
\n \n js\n
> const g = s => console.log(s)\nundefined\n> g(1, 2, 3)\n1\nundefined
\n

And as for parseInt, there is a second parameter (see below). That's why parseInt and s => parseInt(s) are different.

\n

parseInt v.s. int

\n
\n

parseInt(string [, radix])

\n

Parameters

\n

string

\n

The value to parse. If this argument is not a string, then it is converted to one using the ToString abstract operation. Leading whitespace in this argument is ignored.

\n

radix Optional

\n

An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the string. Be careful—this does not default to 10!

\n
\n
\n

If radix is undefined, 0, or unspecified, JavaScript assumes the following:

\n
    \n
  1. If the input string begins with \"0x\" or \"0X\" (a zero, followed by lowercase or uppercase X), radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number.
  2. \n
  3. If the input string begins with \"0\" (a zero), radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 clarifies that 10 (decimal) should be used, but not all browsers support this yet. For this reason, always specify a radix when using parseInt.
  4. \n
  5. If the input string begins with any other value, the radix is 10 (decimal).
  6. \n
\n

If the first character cannot be converted to a number, parseInt returns NaN unless the radix is bigger than 10.

\n
\n

Well, that makes sense. And I'm going to specify a radix when using parseInt.

\n

And note that Python is more friendly:

\n
class int(object)\n |  int([x]) -> integer\n |  int(x, base=10) -> integer\n |\n |  Convert a number or string to an integer, or return 0 if no arguments\n |  are given.  If x is a number, return x.__int__().  For floating point\n |  numbers, this truncates towards zero.\n |\n |  If x is not a number or if base is given, then x must be a string,\n |  bytes, or bytearray instance representing an integer literal in the\n |  given base.  The literal can be preceded by '+' or '-' and be surrounded\n |  by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.\n |  Base 0 means to interpret the base from the string as an integer literal.\n |  >>> int('0b100', base=0)\n |  4\n
\n

Dead Variable

\n

Based on https://stackoverflow.com/a/54980674/8810271

\n
\n
\n \n js\n
> let a = nothing\nThrown:\nReferenceError: nothing is not defined\n> let a = 1\nThrown:\nSyntaxError: Identifier 'a' has already been declared\n> a = 1\nThrown:\nReferenceError: a is not defined
\n

Once you have a typo when using let in console (such as forgetting new), the variable name will never come back. That's because variable initialization did not complete successfully, and you can't re-declare a variable that's already been declared.

\n

Worse still, you cannot delete the variable declared using let, const or var. Only things like \"global variable\" can be deleted. See also https://stackoverflow.com/q/1596782/8810271

\n

You have to reinvent a good variable name, or reopen the console.

\n

Sure Sorted?

\n
\n
\n \n js\n
> [2, 1].sort()\n[ 1, 2 ]\n> [8, 10].sort()\n[ 10, 8 ]
\n

No, JS sort is just String sorting:

\n
\n

The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

\n
\n

And if you want number sorting, write:

\n
\n
\n \n js\n
> [8, 10, 6, 19].sort((a, b) => a - b)\n[ 6, 8, 10, 19 ]
\n

How do you split?

\n

Even though expression is the same, the answer does not.

\n
\n
\n \n python\n
>>> 'asdf adsf ak g'.split(' ', 1)\n['asdf', 'adsf ak g']
\n
\n
\n \n js\n
> 'asdf adsf ak g'.split(' ', 1)\n[\"asdf\"]
\n

Hmm, I'd better split, slice and join..

\n

Mutable Default Parameter?

\n

Javascript seems not affected.

\n
\n
\n \n js\n
> const f = (a=[]) => { a.push(1); return a }\nundefined\n> f()\n[ 1 ]\n> f()\n[ 1 ]
\n
\n
\n \n python\n
>>> def f(a=[]):\n...     a.append(1)\n...     return a\n... \n>>> f()\n[1]\n>>> f()\n[1, 1]
\n

Beware of Method / Function Names

\n

Some methods / functions achieve similar, if not the same, effect, but with different names. They are so similar that I often get confused.

\n
\n
\n \n python\n
>>> 'abc'.startswith('a')\nTrue\n>>> ''.join(['a', 'b', 'c'])\n'abc'\n>>> 'abc'.index('a')\n0
\n
\n
\n \n js\n
> 'abc'.startsWith('a')\ntrue\n> ['a', 'b', 'c'].join('')\n'abc'\n> 'abc'.indexOf('a')\n0
","createdAt":"2020-02-07T04:23:51.000Z","lastEditedAt":"2021-02-22T08:52:30.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/get-wechat-emoji/index.json b/assets/data/post/get-wechat-emoji/index.json index ef81fdb4b..1997e8491 100644 --- a/assets/data/post/get-wechat-emoji/index.json +++ b/assets/data/post/get-wechat-emoji/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"156","title":"我是如何获得微信内置表情的","summary":"\n

授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

\n","body":"\n

在 GitHub 上直接搜微信表情,搜出来的多是 https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/0.gif 系列,但这些表情不仅已经过时,而且不清晰(如 \"emoji),甚至有白底。很不方便。

\n

微信 APK

\n

很自然会想到直接从官方网站下载的 APK 提取。但是会发现 APK 里只有在 assets/newemoji 里有一些新的 emoji。分辨率是 64x64 的,很令人满意。

\n

继续搜索

\n

最后在微信开放社区里发现这个帖子,提到了 miniprogram-component-plus 项目中有微信的 emoji。在该项目中搜索 emoji 就有表情雪碧图的链接,如下图所示:

\n

\"Emoji

\n

光有雪碧图还不够,难道要手动建立对应关系?继续在该项目下搜索就发现 src/emoji/emoji_positon.less 文件里有相应样式。

\n

注意事项

\n

使用表情应当遵守微信相应许可。

","createdAt":"2020-12-28T09:46:53.000Z","lastEditedAt":"2020-12-28T09:58:39.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"156","title":"我是如何获得微信内置表情的","summary":"\n

授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

\n","body":"\n

在 GitHub 上直接搜微信表情,搜出来的多是 https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/0.gif 系列,但这些表情不仅已经过时,而且不清晰(如 \"emoji),甚至有白底。很不方便。

\n

微信 APK

\n

很自然会想到直接从官方网站下载的 APK 提取。但是会发现 APK 里只有在 assets/newemoji 里有一些新的 emoji。分辨率是 64x64 的,很令人满意。

\n

继续搜索

\n

最后在微信开放社区里发现这个帖子,提到了 miniprogram-component-plus 项目中有微信的 emoji。在该项目中搜索 emoji 就有表情雪碧图的链接,如下图所示:

\n

\"Emoji

\n

光有雪碧图还不够,难道要手动建立对应关系?继续在该项目下搜索就发现 src/emoji/emoji_positon.less 文件里有相应样式。

\n

注意事项

\n

使用表情应当遵守微信相应许可。

","createdAt":"2020-12-28T09:46:53.000Z","lastEditedAt":"2020-12-28T09:58:39.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/gh-action-cache/index.json b/assets/data/post/gh-action-cache/index.json index 22114a6ec..a257bf07c 100644 --- a/assets/data/post/gh-action-cache/index.json +++ b/assets/data/post/gh-action-cache/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"98","title":"Setup GitHub Action Cache the Right Way","summary":"\n

What on earth is happening? Why my cache never hits?

\n","body":"\n

To be clear, I am talking about https://github.com/actions/cache. Better to have a look at the doc first.

\n

What should be hashed?

\n

Make sure the file to hash does not always change on every build. After all, you want the cache hit in some cases.

\n

For example, the project is written in JavaScript and uses npm as package manager. You decided to run GitHub Actions to release the package every time you push a tag. And you choose to hash package-lock.json... Sorry, never hits. Cache is completely useless. That's because the version always changes, even thought the dependencies doesn't, which is what you mean.

\n

Instead, hash rest of the file, use tail -n +4 for example.

\n

Know what you are hashing

\n

Why? Don't I know what I am hashing?

\n

Maybe, when using glob.

\n

Cache action evaluates hash twice, which can cause problems with glob. For example, actions/cache#344

\n
\n

hashFiles('**/yarn.lock') is wrong cause it picks yarn.lock files from node_module (as part of the key) at the end of the build, but tries to restore with empty node_modules which produced different hash.

\n
\n

Be careful with cache scope

\n

GitHub doc

\n
\n

A workflow can access and restore a cache created in the current branch, the base branch (including base branches of forked repositories), or the default branch (usually master)

\n
\n

Caches between two parallel branches are not shared. And tags only have access to caches created in default branch. If you want to share cache between build on tags, you would like to keep cache in master scope. For example, use a update-cache.yml action to keep track of cache.

\n

My Solution

\n

Checkout https://github.com/AllanChain/webnav/tree/3ddd12f707fc374f907f5a2ac26aaf98a7c9a3d2/.github/workflows

","createdAt":"2020-06-21T09:16:37.000Z","lastEditedAt":"2020-07-06T10:52:21.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/"}],"reactions":[{"emoji":"👍","count":5,"users":["Yixuan-Wang","felixmosh","allenhsu","utkuozdemir","SimonSiefke"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660451382","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

Thank you for clarifying this issue.

\n

Quick question, If I need to cache a build result (Next'js cache). I'm building my app using tags pushes.

","createdAt":"2020-07-18T08:48:07.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660454644","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@felixmosh Either refer to my solution to create two workflows: update-cache runs on default branch push and rebuild on hash (cache key) change, so that latest cache is available in gh-pages on tag push.

\n

Or use on: push: branches: master, checkout with fetch-depth: 0, and use git describe --tags or git tag --points-at HEAD to tell whether this is a tag. This way, cache scope is default branch.

","createdAt":"2020-07-18T09:16:43.000Z","reactions":[{"emoji":"👍","count":1,"users":["felixmosh"]},{"emoji":"❤","count":1,"users":["pieczorx"]}]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-671185246","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

I've ended with a task triggered by push to \"production\" branch with a check for special commit message.

\n
jobs:\n  build:\n    runs-on: ubuntu-latest\n    if: \"contains(github.event.head_commit.message, 'Release v1.')\"\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          # pulls all commits (needed for lerna / semantic release to correctly version)\n          fetch-depth: \"20\"\n      - name: Fetch tags\n         run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*\n...
\n

Thank you for explaining the limits 🙏🏼

","createdAt":"2020-08-10T06:28:08.000Z","reactions":[{"emoji":"👍","count":1,"users":["utkuozdemir"]},{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"98","title":"Setup GitHub Action Cache the Right Way","summary":"\n

What on earth is happening? Why my cache never hits?

\n","body":"\n

To be clear, I am talking about https://github.com/actions/cache. Better to have a look at the doc first.

\n

What should be hashed?

\n

Make sure the file to hash does not always change on every build. After all, you want the cache hit in some cases.

\n

For example, the project is written in JavaScript and uses npm as package manager. You decided to run GitHub Actions to release the package every time you push a tag. And you choose to hash package-lock.json... Sorry, never hits. Cache is completely useless. That's because the version always changes, even thought the dependencies doesn't, which is what you mean.

\n

Instead, hash rest of the file, use tail -n +4 for example.

\n

Know what you are hashing

\n

Why? Don't I know what I am hashing?

\n

Maybe, when using glob.

\n

Cache action evaluates hash twice, which can cause problems with glob. For example, actions/cache#344

\n
\n

hashFiles('**/yarn.lock') is wrong cause it picks yarn.lock files from node_module (as part of the key) at the end of the build, but tries to restore with empty node_modules which produced different hash.

\n
\n

Be careful with cache scope

\n

GitHub doc

\n
\n

A workflow can access and restore a cache created in the current branch, the base branch (including base branches of forked repositories), or the default branch (usually master)

\n
\n

Caches between two parallel branches are not shared. And tags only have access to caches created in default branch. If you want to share cache between build on tags, you would like to keep cache in master scope. For example, use a update-cache.yml action to keep track of cache.

\n

My Solution

\n

Checkout https://github.com/AllanChain/webnav/tree/3ddd12f707fc374f907f5a2ac26aaf98a7c9a3d2/.github/workflows

","createdAt":"2020-06-21T09:16:37.000Z","lastEditedAt":"2020-07-06T10:52:21.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/"}],"reactions":[{"emoji":"👍","count":5,"users":["Yixuan-Wang","felixmosh","allenhsu","utkuozdemir","SimonSiefke"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660451382","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

Thank you for clarifying this issue.

\n

Quick question, If I need to cache a build result (Next'js cache). I'm building my app using tags pushes.

","createdAt":"2020-07-18T08:48:07.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660454644","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@felixmosh Either refer to my solution to create two workflows: update-cache runs on default branch push and rebuild on hash (cache key) change, so that latest cache is available in gh-pages on tag push.

\n

Or use on: push: branches: master, checkout with fetch-depth: 0, and use git describe --tags or git tag --points-at HEAD to tell whether this is a tag. This way, cache scope is default branch.

","createdAt":"2020-07-18T09:16:43.000Z","reactions":[{"emoji":"👍","count":1,"users":["felixmosh"]},{"emoji":"❤","count":1,"users":["pieczorx"]}]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-671185246","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

I've ended with a task triggered by push to \"production\" branch with a check for special commit message.

\n
jobs:\n  build:\n    runs-on: ubuntu-latest\n    if: \"contains(github.event.head_commit.message, 'Release v1.')\"\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          # pulls all commits (needed for lerna / semantic release to correctly version)\n          fetch-depth: \"20\"\n      - name: Fetch tags\n         run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*\n...
\n

Thank you for explaining the limits 🙏🏼

","createdAt":"2020-08-10T06:28:08.000Z","reactions":[{"emoji":"👍","count":1,"users":["utkuozdemir"]},{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/jupyter-raspi/index.json b/assets/data/post/jupyter-raspi/index.json index 93db991cc..7b4bb0a41 100644 --- a/assets/data/post/jupyter-raspi/index.json +++ b/assets/data/post/jupyter-raspi/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"64","title":"Jupyter on Raspberry Pi","summary":"","body":"\n

Start Jupyter as service

\n

From https://gist.github.com/whophil/5a2eab328d2f8c16bb31c9ceaf23164f

\n

/etc/systemd/system/jupyter.service:

\n
\n
\n \n ini\n
# After Ubuntu 16.04, Systemd becomes the default.\n# It is simpler than https://gist.github.com/Doowon/38910829898a6624ce4ed554f082c4dd\n\n[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nExecStart=jupyter-notebook --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target
\n

To use, just do it in systemctl way:

\n
\n
\n \n shell\n
sudo systemctl start jupyter\nsudo systemctl stop jupyter\n# To launch while start up\nsudo systemctl enable jupyter
\n
\n

NOTICE

\n

run systemctl daemon-reload often when changing unit file. Or it will silently use the old bad file.

\n

不要问我是怎么知道的

\n
\n

libf77blas.so.3: cannot open shared object file

\n
\n
\n \n shell\n
sudo apt install libatlas-base-dev
\n

Config Jupyter

\n

Follow https://jupyter-notebook.readthedocs.io/en/stable/public_server.html

\n

Typical Way

\n
\n
\n \n shell\n
jupyter notebook --generate-config\n# Get passwd hash\npython3 -c 'from notebook.auth import passwd;print(passwd())'\n# cd to a nice directory\n# where `nodes` means `no-des`, no passwd for private key\nopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -sha256 -nodes
\n

And here is the official example for the configuration file:

\n
\n
\n \n python\n
# Set options for certfile, ip, password, and toggle off\n# browser auto-opening\nc.NotebookApp.certfile = '/absolute/path/to/your/certificate/fullchain.pem'\nc.NotebookApp.keyfile = '/absolute/path/to/your/certificate/privkey.pem'\n# Set ip to '*' to bind on all interfaces (ips) for the public server\nc.NotebookApp.ip = '*'\nc.NotebookApp.password = 'sha1:bcd259ccf...<your hashed password here>'\nc.NotebookApp.open_browser = False\n# It is a good idea to set a known, fixed port for server access\nc.NotebookApp.port = 9999
\n
\n

⚠️ ​IMPORTANT

\n

There are so many keys in the file, be sure to modify the correct key.

\n
\n

Listen to ipv6 Only

\n
\n
\n \n python\n
c.NotebookApp.ip = '::'
\n

Jupyter Themes

\n

Installation

\n
\n
\n \n shell\n
pip3 install jupyterthemes
\n

Configuration

\n
\n
\n \n shell\n
jt -t oceans16 -T -N -fs 16 -nfs 16 -cellw 90%
\n

Explanation:

\n
-T, --toolbar         make toolbar visible\n-N, --nbname          nb name/logo visible\n-t THEME              theme name to install\n-fs MONOSIZE          code font-size\n-nfs NBFONTSIZE       notebook fontsize\n-tfs TCFONTSIZE       txtcell fontsize\n-dfs DFFONTSIZE       pandas dataframe fontsize\n-ofs OUTFONTSIZE      output area fontsize\n-mathfs MATHFONTSIZE  mathjax fontsize (in %)\n-cellw CELLWIDTH      set cell width (px or %)\n
\n
\n

Font sizes are in pt

\n
\n

Behind Nginx

\n

Reference: https://gist.github.com/christopherbaek/39e6c432e212ca7a67ffe015fe869664

\n\n
\n
\n \n python\n
c.NotebookApp.base_url = 'jupyter'\nc.NotebookApp.ip = '127.0.0.1'\nc.NotebookApp.open_browser = False\nc.NotebookApp.password = 'sha1:84cb...36f7e6'\nc.NotebookApp.port = 9999
\n\n
\n
\n \n nginx\n
location /jupyter/ {\n    proxy_pass http://127.0.0.1:9999/jupyter/;\n\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-NginX-Proxy true;\n\n    # WebSocket support\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Origin \"\";\n}
","createdAt":"2020-02-26T12:20:29.000Z","lastEditedAt":"2020-07-01T12:33:55.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"64","title":"Jupyter on Raspberry Pi","summary":"","body":"\n

Start Jupyter as service

\n

From https://gist.github.com/whophil/5a2eab328d2f8c16bb31c9ceaf23164f

\n

/etc/systemd/system/jupyter.service:

\n
\n
\n \n ini\n
# After Ubuntu 16.04, Systemd becomes the default.\n# It is simpler than https://gist.github.com/Doowon/38910829898a6624ce4ed554f082c4dd\n\n[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nExecStart=jupyter-notebook --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target
\n

To use, just do it in systemctl way:

\n
\n
\n \n shell\n
sudo systemctl start jupyter\nsudo systemctl stop jupyter\n# To launch while start up\nsudo systemctl enable jupyter
\n
\n

NOTICE

\n

run systemctl daemon-reload often when changing unit file. Or it will silently use the old bad file.

\n

不要问我是怎么知道的

\n
\n

libf77blas.so.3: cannot open shared object file

\n
\n
\n \n shell\n
sudo apt install libatlas-base-dev
\n

Config Jupyter

\n

Follow https://jupyter-notebook.readthedocs.io/en/stable/public_server.html

\n

Typical Way

\n
\n
\n \n shell\n
jupyter notebook --generate-config\n# Get passwd hash\npython3 -c 'from notebook.auth import passwd;print(passwd())'\n# cd to a nice directory\n# where `nodes` means `no-des`, no passwd for private key\nopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -sha256 -nodes
\n

And here is the official example for the configuration file:

\n
\n
\n \n python\n
# Set options for certfile, ip, password, and toggle off\n# browser auto-opening\nc.NotebookApp.certfile = '/absolute/path/to/your/certificate/fullchain.pem'\nc.NotebookApp.keyfile = '/absolute/path/to/your/certificate/privkey.pem'\n# Set ip to '*' to bind on all interfaces (ips) for the public server\nc.NotebookApp.ip = '*'\nc.NotebookApp.password = 'sha1:bcd259ccf...<your hashed password here>'\nc.NotebookApp.open_browser = False\n# It is a good idea to set a known, fixed port for server access\nc.NotebookApp.port = 9999
\n
\n

⚠️ ​IMPORTANT

\n

There are so many keys in the file, be sure to modify the correct key.

\n
\n

Listen to ipv6 Only

\n
\n
\n \n python\n
c.NotebookApp.ip = '::'
\n

Jupyter Themes

\n

Installation

\n
\n
\n \n shell\n
pip3 install jupyterthemes
\n

Configuration

\n
\n
\n \n shell\n
jt -t oceans16 -T -N -fs 16 -nfs 16 -cellw 90%
\n

Explanation:

\n
-T, --toolbar         make toolbar visible\n-N, --nbname          nb name/logo visible\n-t THEME              theme name to install\n-fs MONOSIZE          code font-size\n-nfs NBFONTSIZE       notebook fontsize\n-tfs TCFONTSIZE       txtcell fontsize\n-dfs DFFONTSIZE       pandas dataframe fontsize\n-ofs OUTFONTSIZE      output area fontsize\n-mathfs MATHFONTSIZE  mathjax fontsize (in %)\n-cellw CELLWIDTH      set cell width (px or %)\n
\n
\n

Font sizes are in pt

\n
\n

Behind Nginx

\n

Reference: https://gist.github.com/christopherbaek/39e6c432e212ca7a67ffe015fe869664

\n\n
\n
\n \n python\n
c.NotebookApp.base_url = 'jupyter'\nc.NotebookApp.ip = '127.0.0.1'\nc.NotebookApp.open_browser = False\nc.NotebookApp.password = 'sha1:84cb...36f7e6'\nc.NotebookApp.port = 9999
\n\n
\n
\n \n nginx\n
location /jupyter/ {\n    proxy_pass http://127.0.0.1:9999/jupyter/;\n\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-NginX-Proxy true;\n\n    # WebSocket support\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Origin \"\";\n}
","createdAt":"2020-02-26T12:20:29.000Z","lastEditedAt":"2020-07-01T12:33:55.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/jupyter-wolfram/index.json b/assets/data/post/jupyter-wolfram/index.json index e5cd9d21d..aac11f040 100644 --- a/assets/data/post/jupyter-wolfram/index.json +++ b/assets/data/post/jupyter-wolfram/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"76","title":"Install Wolfram With Jupyter","summary":"","body":"\n

From https://github.com/wjxway/PKU-shuakeji, I find that I can install free Wolfram Mathematica on Raspberry Pi.

\n

Go ahead and apt install wolfram-engine wolframscript

\n

Get Wolfram Language For Jupyter Guide here: https://github.com/WolframResearch/WolframLanguageForJupyter/

\n

WTF PATH

\n

If try the first approach:

\n
\n
\n \n shell\n
./configure-jupyter.wls add
\n

Error occurred:

\n
configure-jupyter.wls: Jupyter installation on Environment[\"PATH\"] not found.\n
\n

But jupyter is in PATH indeed:

\n
\n
\n \n shell\n
$ which jupyter\n/usr/local/bin/jupyter\n$ echo $PATH\n/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games
\n

However in wolframscript

\n
\n
\n \n shell\n
$ echo 'Print[Environment[\"PATH\"]]' | wolframscript\nWolfram Language 12.0.1 Engine for Linux ARM (32-bit)\nCopyright 1988-2019 Wolfram Research, Inc.\n\nIn[1]:= /opt/Wolfram/WolframEngine/12.0/Executables:/opt/Wolfram/WolframEngine/12.0/S\\\n\n>   ystemFiles/Graphics/Binaries/Linux-ARM:/home/pi/.local/bin:/usr/bin:/bin
\n

Well, PATH is different...

\n

As said in ./configure-jupyter.wls help, you can specify jupyter path by

\n
configure-jupyter.wls add \"/absolute/path/to/Wolfram-Engine-binary--not-wolframscript\" \"path/to/Jupyter-binary\"\n        adds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path\n
\n

If you try the second, you can avoid providing path to Wolfram Engine binary:

\n
\n
\n \n mathematica\n
ConfigureJupyter[\"Add\", \"JupyterInstallation\" -> \"...\"]
\n

WTF GFW

\n

However, it seems to update the paclet from server, which is very slow here...

\n

Luckily, success.

","createdAt":"2020-02-26T09:50:09.000Z","lastEditedAt":"2020-07-01T12:28:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"},{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"76","title":"Install Wolfram With Jupyter","summary":"","body":"\n

From https://github.com/wjxway/PKU-shuakeji, I find that I can install free Wolfram Mathematica on Raspberry Pi.

\n

Go ahead and apt install wolfram-engine wolframscript

\n

Get Wolfram Language For Jupyter Guide here: https://github.com/WolframResearch/WolframLanguageForJupyter/

\n

WTF PATH

\n

If try the first approach:

\n
\n
\n \n shell\n
./configure-jupyter.wls add
\n

Error occurred:

\n
configure-jupyter.wls: Jupyter installation on Environment[\"PATH\"] not found.\n
\n

But jupyter is in PATH indeed:

\n
\n
\n \n shell\n
$ which jupyter\n/usr/local/bin/jupyter\n$ echo $PATH\n/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games
\n

However in wolframscript

\n
\n
\n \n shell\n
$ echo 'Print[Environment[\"PATH\"]]' | wolframscript\nWolfram Language 12.0.1 Engine for Linux ARM (32-bit)\nCopyright 1988-2019 Wolfram Research, Inc.\n\nIn[1]:= /opt/Wolfram/WolframEngine/12.0/Executables:/opt/Wolfram/WolframEngine/12.0/S\\\n\n>   ystemFiles/Graphics/Binaries/Linux-ARM:/home/pi/.local/bin:/usr/bin:/bin
\n

Well, PATH is different...

\n

As said in ./configure-jupyter.wls help, you can specify jupyter path by

\n
configure-jupyter.wls add \"/absolute/path/to/Wolfram-Engine-binary--not-wolframscript\" \"path/to/Jupyter-binary\"\n        adds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path\n
\n

If you try the second, you can avoid providing path to Wolfram Engine binary:

\n
\n
\n \n mathematica\n
ConfigureJupyter[\"Add\", \"JupyterInstallation\" -> \"...\"]
\n

WTF GFW

\n

However, it seems to update the paclet from server, which is very slow here...

\n

Luckily, success.

","createdAt":"2020-02-26T09:50:09.000Z","lastEditedAt":"2020-07-01T12:28:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"},{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/linux-cht/index.json b/assets/data/post/linux-cht/index.json index 39faf0346..1f34ba659 100644 --- a/assets/data/post/linux-cht/index.json +++ b/assets/data/post/linux-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","body":"\n

systemctl

\n

Where are service files?

\n
/etc/systemd/system/service-name.service\n
\n

Start at Boot

\n
\n
\n \n shell\n
sudo systemctl enable sshd.service
\n

disable is the opposite.

\n

Use Environment in Service Unit File

\n
[Service]\nEnvironment=PATH=/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n
\n
\n

📔 Note

\n

$PATH do not have its special meaning here.

\n

https://askubuntu.com/q/1014480 for advanced.

\n
\n

Unit File Template

\n
\nClick to expand\n
[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nEnvironment=PATH=/home/pi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nExecStart=jupyter lab --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target\n
\n
\n

Exited Without Error Log?

\n

From https://unix.stackexchange.com/a/225407

\n

Use journalctl command. e.g.

\n
\n
\n \n shell\n
journalctl -u service-name.service
\n

-u is short for --unit. More useful options:

\n\n

apt

\n

List All Versions

\n

https://askubuntu.com/q/473886

\n
\n
\n \n shell\n
apt-cache madison chromium-browser\n# or\napt-cache showpkg lyx
\n

Install Specific Version

\n

https://askubuntu.com/q/428772

\n
\n
\n \n shell\n
# get version of installed package\napt-cache policy <package name>\n# install a specific package version\nsudo apt-get install <package name>=<version>
\n

OS Release Info

\n
\n
\n \n shell\n
cat /etc/os-release
\n

List All Users

\n
\n
\n \n shell\n
less /etc/passwd
\n

lsof

\n

LiSt Open Files. A very powerful tool.

\n

For network checking:

\n
ls -i [46][protocol][@hostname|hostaddr][:service|port]\n
\n

e.g.

\n
\n
\n \n shell\n
sudo lsof -i :80
\n

find

\n
\n
\n \n shell\n
find -iname 'qwerty'\nfind dir/ -iname 'qwerty'\nfind -name 'Qwerty'
\n

No Wireless Connection After Sleep

\n
\n
\n \n shell\n
sudo service network-manager restart
","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","body":"\n

systemctl

\n

Where are service files?

\n
/etc/systemd/system/service-name.service\n
\n

Start at Boot

\n
\n
\n \n shell\n
sudo systemctl enable sshd.service
\n

disable is the opposite.

\n

Use Environment in Service Unit File

\n
[Service]\nEnvironment=PATH=/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n
\n
\n

📔 Note

\n

$PATH do not have its special meaning here.

\n

https://askubuntu.com/q/1014480 for advanced.

\n
\n

Unit File Template

\n
\nClick to expand\n
[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nEnvironment=PATH=/home/pi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nExecStart=jupyter lab --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target\n
\n
\n

Exited Without Error Log?

\n

From https://unix.stackexchange.com/a/225407

\n

Use journalctl command. e.g.

\n
\n
\n \n shell\n
journalctl -u service-name.service
\n

-u is short for --unit. More useful options:

\n\n

apt

\n

List All Versions

\n

https://askubuntu.com/q/473886

\n
\n
\n \n shell\n
apt-cache madison chromium-browser\n# or\napt-cache showpkg lyx
\n

Install Specific Version

\n

https://askubuntu.com/q/428772

\n
\n
\n \n shell\n
# get version of installed package\napt-cache policy <package name>\n# install a specific package version\nsudo apt-get install <package name>=<version>
\n

OS Release Info

\n
\n
\n \n shell\n
cat /etc/os-release
\n

List All Users

\n
\n
\n \n shell\n
less /etc/passwd
\n

lsof

\n

LiSt Open Files. A very powerful tool.

\n

For network checking:

\n
ls -i [46][protocol][@hostname|hostaddr][:service|port]\n
\n

e.g.

\n
\n
\n \n shell\n
sudo lsof -i :80
\n

find

\n
\n
\n \n shell\n
find -iname 'qwerty'\nfind dir/ -iname 'qwerty'\nfind -name 'Qwerty'
\n

No Wireless Connection After Sleep

\n
\n
\n \n shell\n
sudo service network-manager restart
","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/markdown-cht/index.json b/assets/data/post/markdown-cht/index.json index 519256492..634c470c7 100644 --- a/assets/data/post/markdown-cht/index.json +++ b/assets/data/post/markdown-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"84","title":"Markdown Cheatsheet","summary":"\n

Tricks which are less known to markdown starters

\n","body":"\n

Escaping `

\n

The general principle is, escape ` with more `s.

\n

For example, if you want to escape a single `, just wrap it with two on each side, and add spaces, that is, `` ` ``.

\n

If you want to escape three back quotes in a code block, use 4 for the code fence:

\n
````\n```python\nprint('hello')\n```\n````\n
\n

Produces:

\n
```python\nprint('hello')\n```\n
\n

Awesome Dropdown!

\n
\nWrite Markdown!\n\n

Python Code

\n
\n
\n \n python\n
from __future__ import print_function\nprint('Hello world!')
\n
\n
\nRaw code\n
\n
\n \n html\n
<details open>\n<summary>Write Markdown!</summary>\n<!--All you need is a blank line-->\n\n**Python** *Code*\n````python\nfrom __future__ import print_function\nprint('Hello world!')\n```\n</details>
\n
\n

Reference: https://github.github.com/gfm/#html-block

\n

The trick is, the blank line terminates the html block

\n

Center a Image in GFM

\n

Use HTML tag p with align to wrap img

\n
\n
\n \n html\n
<p align=\"center\">\n  <img src=\"https://github.com/favicon.ico\">\n</p>
\n

","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"84","title":"Markdown Cheatsheet","summary":"\n

Tricks which are less known to markdown starters

\n","body":"\n

Escaping `

\n

The general principle is, escape ` with more `s.

\n

For example, if you want to escape a single `, just wrap it with two on each side, and add spaces, that is, `` ` ``.

\n

If you want to escape three back quotes in a code block, use 4 for the code fence:

\n
````\n```python\nprint('hello')\n```\n````\n
\n

Produces:

\n
```python\nprint('hello')\n```\n
\n

Awesome Dropdown!

\n
\nWrite Markdown!\n\n

Python Code

\n
\n
\n \n python\n
from __future__ import print_function\nprint('Hello world!')
\n
\n
\nRaw code\n
\n
\n \n html\n
<details open>\n<summary>Write Markdown!</summary>\n<!--All you need is a blank line-->\n\n**Python** *Code*\n````python\nfrom __future__ import print_function\nprint('Hello world!')\n```\n</details>
\n
\n

Reference: https://github.github.com/gfm/#html-block

\n

The trick is, the blank line terminates the html block

\n

Center a Image in GFM

\n

Use HTML tag p with align to wrap img

\n
\n
\n \n html\n
<p align=\"center\">\n  <img src=\"https://github.com/favicon.ico\">\n</p>
\n

","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/mysql-emoji/index.json b/assets/data/post/mysql-emoji/index.json index 7495bbe27..831a8571e 100644 --- a/assets/data/post/mysql-emoji/index.json +++ b/assets/data/post/mysql-emoji/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"22","title":"MySQL 存储 Emoji","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","body":"\n

UPDATE: See https://docs.sqlalchemy.org/en/13/dialects/mysql.html#unicode for more info

\n

utf8_bin?

\n

一开始就套用存储中文姓名的那一套,使用utf8_bin的 collation,觉得 utf8 这种万能的东西直接用就行了。可谁知给我报错:

\n
mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\\xE8\\x86\\x9C' for column 'text' at row 1\n
\n

蛤?竟有如此操作?

\n

utf8mb4_unicode_ci?

\n

一波搜索之后就看到了使用utf8mb4_unicode_ci的 collation。于是就写:

\n
\n
\n \n python\n
text = db.Column(db.Unicode(256, collation='utf8mb4_unicode_ci'))
\n

可惜并没有实质性效果

\n

暴力二进制

\n

utf8 解决不了,二进制存储总行了吧?

\n

果然,存进去并没有问题。但是当要读取出来的时候又报了奇怪的错误:

\n
\n
\n \n python\n
TypeError: string argument without an encoding
\n

明明是二进制怎么给我搞出来一个编码问题?

\n

后期查阅资料发现,SQL 似乎有一个动态类型,每列的数据类型是建议值,并不强制。其结果就是我存一个二进制编码的字符串,他就真的以为是字符类型,并且数据库表里没有存编码,导致了问题出现。

\n

疾病设计😠!!!

\n

直接escape

\n

二进制也不行,全 escape 总可以了吧!

\n
\n
\n \n python\n
>>> text.encode('unicode_escape').decode()\n>>> text.decode('unicode_escape').encode()
\n

总算成功存储和读取了!

\n

人人都说utf8mb4_unicode_ci

\n

为什么就是不行呢?

\n

哦,还要改数据库 charset, collation

\n
\n
\n \n sql\n
ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\nALTER TABLE tablename CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
\n

还要给 SQLAlchemy 加 ?charset=utf8mb4

\n
\n

Edit: 使用 SQLALCHEMY_ENGINE_OPTIONS.connect_args 不应该加 ?charset=utf8mb4,加完之后会优先认 ?charset=utf8mb4,导致 collation 默认 utf8mb4_0900_ai_ci (which is MySQL 8.0's default but not implemented in MySQL 5.7)

\n
\n

可是为什么还是不行?

\n
\n
\n \n python\n
mysql.connector.errors.DatabaseError: 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
\n

我几几年用过这编码?

\n

版本大坑

\n

我找到了mysql-connector-python的官方文档:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

\n

collation 一栏中:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Argument NameDefaultDescription
collationutf8mb4_general_ai_ci (is utf8_general_ci in 2.xWhich MySQL collation to use. The 8.x default values are generated from the latest MySQL Server 8.0 defaults.
\n
\n

什么?8.0 的默认值?我们只有 5.7 呢!

\n

虽然并不是utf8mb4_0900_ai_ci这种东西,我还是试着把链接数据库参数修改成utf8mb4_unicode_ci

\n
\n
\n \n yaml\n
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        collation: utf8mb4_unicode_ci
\n

噫!成功了!

\n

事实证明utf8mb4_0900_ai_ciutf8mb4_general_ai_ci应该是同义词一样的存在。

\n

可是:

\n
\n

MySQL Connector/Python 8.0 is highly recommended for use with MySQL Server 8.0, 5.7, 5.6, and 5.5. Please upgrade to MySQL Connector/Python 8.0.

\n
\n

好一个recommended,就给我挖这种坑?版本问题害死人!

","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/22#issuecomment-600969690","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        charset: utf8mb4\n        collation: utf8mb4_unicode_ci
\n

这里指定编码。

","createdAt":"2020-03-19T03:33:14.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"22","title":"MySQL 存储 Emoji","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","body":"\n

UPDATE: See https://docs.sqlalchemy.org/en/13/dialects/mysql.html#unicode for more info

\n

utf8_bin?

\n

一开始就套用存储中文姓名的那一套,使用utf8_bin的 collation,觉得 utf8 这种万能的东西直接用就行了。可谁知给我报错:

\n
mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\\xE8\\x86\\x9C' for column 'text' at row 1\n
\n

蛤?竟有如此操作?

\n

utf8mb4_unicode_ci?

\n

一波搜索之后就看到了使用utf8mb4_unicode_ci的 collation。于是就写:

\n
\n
\n \n python\n
text = db.Column(db.Unicode(256, collation='utf8mb4_unicode_ci'))
\n

可惜并没有实质性效果

\n

暴力二进制

\n

utf8 解决不了,二进制存储总行了吧?

\n

果然,存进去并没有问题。但是当要读取出来的时候又报了奇怪的错误:

\n
\n
\n \n python\n
TypeError: string argument without an encoding
\n

明明是二进制怎么给我搞出来一个编码问题?

\n

后期查阅资料发现,SQL 似乎有一个动态类型,每列的数据类型是建议值,并不强制。其结果就是我存一个二进制编码的字符串,他就真的以为是字符类型,并且数据库表里没有存编码,导致了问题出现。

\n

疾病设计😠!!!

\n

直接escape

\n

二进制也不行,全 escape 总可以了吧!

\n
\n
\n \n python\n
>>> text.encode('unicode_escape').decode()\n>>> text.decode('unicode_escape').encode()
\n

总算成功存储和读取了!

\n

人人都说utf8mb4_unicode_ci

\n

为什么就是不行呢?

\n

哦,还要改数据库 charset, collation

\n
\n
\n \n sql\n
ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\nALTER TABLE tablename CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
\n

还要给 SQLAlchemy 加 ?charset=utf8mb4

\n
\n

Edit: 使用 SQLALCHEMY_ENGINE_OPTIONS.connect_args 不应该加 ?charset=utf8mb4,加完之后会优先认 ?charset=utf8mb4,导致 collation 默认 utf8mb4_0900_ai_ci (which is MySQL 8.0's default but not implemented in MySQL 5.7)

\n
\n

可是为什么还是不行?

\n
\n
\n \n python\n
mysql.connector.errors.DatabaseError: 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
\n

我几几年用过这编码?

\n

版本大坑

\n

我找到了mysql-connector-python的官方文档:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

\n

collation 一栏中:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Argument NameDefaultDescription
collationutf8mb4_general_ai_ci (is utf8_general_ci in 2.xWhich MySQL collation to use. The 8.x default values are generated from the latest MySQL Server 8.0 defaults.
\n
\n

什么?8.0 的默认值?我们只有 5.7 呢!

\n

虽然并不是utf8mb4_0900_ai_ci这种东西,我还是试着把链接数据库参数修改成utf8mb4_unicode_ci

\n
\n
\n \n yaml\n
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        collation: utf8mb4_unicode_ci
\n

噫!成功了!

\n

事实证明utf8mb4_0900_ai_ciutf8mb4_general_ai_ci应该是同义词一样的存在。

\n

可是:

\n
\n

MySQL Connector/Python 8.0 is highly recommended for use with MySQL Server 8.0, 5.7, 5.6, and 5.5. Please upgrade to MySQL Connector/Python 8.0.

\n
\n

好一个recommended,就给我挖这种坑?版本问题害死人!

","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/22#issuecomment-600969690","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        charset: utf8mb4\n        collation: utf8mb4_unicode_ci
\n

这里指定编码。

","createdAt":"2020-03-19T03:33:14.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/mysql-in-wsl/index.json b/assets/data/post/mysql-in-wsl/index.json index 2c8d0ebe8..1a2c5766c 100644 --- a/assets/data/post/mysql-in-wsl/index.json +++ b/assets/data/post/mysql-in-wsl/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"71","title":"Mysql in WSL","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","body":"\n

在使用 Windows Subsystem for Linux 时,可能因为缺少对 MySQL 是预置安装,会遇到一些问题

\n

如果说

\n
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)\n
\n

可以试试建立 /var/run/mysqld 目录,并给 mysql 权限

\n

如果说

\n
No directory, logging in with HOME=/\n
\n

可以试试 sudo usermod -d /var/lib/mysql/ mysql

\n
\n

更新了一次系统后,MySQL 又无法启动了。刚开始还以为是更新的时候之前的配置被抹掉了,可并不是。。

\n

UPDATE: 不要听下面这个部分

\n

根据microsoft/WSL#3631 (comment),是 MySQL 版本太高了。。自动升到了 8.x。还要按照步骤回退。。😡

\n

但是但是,由于我作死升到了 eoan,那个仓库并不支持。所以去 https://dev.mysql.com/downloads/repo/apt/ 下载了最新的。然而,并没有 5.7 的选项😭

\n

所以只能强行安装 Ubuntu 19.04 的包了,下载对应的 deb 文件:

\n
\n
\n \n shell\n
wget https://dev.mysql.com/get/mysql-apt-config_0.8.13-1_all.deb
\n
\n

或者。。直接装 Windows 上,说不定也可。

\n
\n

UPDATE: 事实证明,是 MySQL 未正确升级所致,只需要完全删除 MySQL,再重新安装,就可以了(只要备份了重要文件)

\n
\n
\n \n shell\n
sudo apt remove --purge *mysql*\nsudo apt remove --purge *mariadb*\nsudo rm -rf /etc/mysql /var/lib/mysql\nsudo apt autoremove\nsudo apt autoclean
","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"71","title":"Mysql in WSL","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","body":"\n

在使用 Windows Subsystem for Linux 时,可能因为缺少对 MySQL 是预置安装,会遇到一些问题

\n

如果说

\n
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)\n
\n

可以试试建立 /var/run/mysqld 目录,并给 mysql 权限

\n

如果说

\n
No directory, logging in with HOME=/\n
\n

可以试试 sudo usermod -d /var/lib/mysql/ mysql

\n
\n

更新了一次系统后,MySQL 又无法启动了。刚开始还以为是更新的时候之前的配置被抹掉了,可并不是。。

\n

UPDATE: 不要听下面这个部分

\n

根据microsoft/WSL#3631 (comment),是 MySQL 版本太高了。。自动升到了 8.x。还要按照步骤回退。。😡

\n

但是但是,由于我作死升到了 eoan,那个仓库并不支持。所以去 https://dev.mysql.com/downloads/repo/apt/ 下载了最新的。然而,并没有 5.7 的选项😭

\n

所以只能强行安装 Ubuntu 19.04 的包了,下载对应的 deb 文件:

\n
\n
\n \n shell\n
wget https://dev.mysql.com/get/mysql-apt-config_0.8.13-1_all.deb
\n
\n

或者。。直接装 Windows 上,说不定也可。

\n
\n

UPDATE: 事实证明,是 MySQL 未正确升级所致,只需要完全删除 MySQL,再重新安装,就可以了(只要备份了重要文件)

\n
\n
\n \n shell\n
sudo apt remove --purge *mysql*\nsudo apt remove --purge *mariadb*\nsudo rm -rf /etc/mysql /var/lib/mysql\nsudo apt autoremove\nsudo apt autoclean
","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/physics-share/index.json b/assets/data/post/physics-share/index.json new file mode 100644 index 000000000..22c6e66f2 --- /dev/null +++ b/assets/data/post/physics-share/index.json @@ -0,0 +1 @@ +{"hash":"gridsome","data":{"post":{"id":"181","title":"Physics Knowledge Sharing","summary":"\n

Sharing some of my physics notes / slides

\n","body":"\n

All files are available here

\n

Hartree-Fock method

\n

Language: 🇨🇳

\n

My presentation at Quantum Mechanics Seminar

\n

PDF link

\n

Quantum Optics and Quantum Computation

\n

Language: 🇨🇳

\n

My presentation at Optics Seminar

\n

PDF link

","createdAt":"2022-05-21T11:23:41.000Z","lastEditedAt":"2022-05-21T11:23:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/pkg-source/index.json b/assets/data/post/pkg-source/index.json index a6bca9b59..0fc35935c 100644 --- a/assets/data/post/pkg-source/index.json +++ b/assets/data/post/pkg-source/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"77","title":"换源集合","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","body":"\n

众所周知,天朝网络环境需要换源来支撑,闲言少叙:

\n

https://mirrors.tuna.tsinghua.edu.cn/

\n

pip

\n

https://mirrors.tuna.tsinghua.edu.cn/help/pypi/

\n
\n
\n \n shell\n
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
\n

npm

\n
\n
\n \n shell\n
npm config set registry https://registry.npm.taobao.org\nnpm config set registry https://repo.huaweicloud.com/repository/npm/
\n

sharp 源

\n
\n
\n \n shell\n
yarn config set sharp_binary_host \"https://npm.taobao.org/mirrors/sharp\"
\n

electron 源

\n
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/\n
\n

docker

\n

/etc/docker/daemon.json 或者直接 Docker Desktop 的 GUI 界面里修改:

\n
\n
\n \n json\n
\"registry-mirrors\": [\n  \"https://hub-mirror.c.163.com\",\n]
\n

maven

\n

$HOME/.gradle/init.gradle

\n
\n
\n \n groovy\n
allprojects {\n  repositories {\n    maven {\n      url 'https://maven.aliyun.com/repository/public/'\n    }\n    mavenLocal()\n    mavenCentral()\n  }\n}
","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[{"emoji":"❤","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"77","title":"换源集合","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","body":"\n

众所周知,天朝网络环境需要换源来支撑,闲言少叙:

\n

https://mirrors.tuna.tsinghua.edu.cn/

\n

pip

\n

https://mirrors.tuna.tsinghua.edu.cn/help/pypi/

\n
\n
\n \n shell\n
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
\n

npm

\n
\n
\n \n shell\n
npm config set registry https://registry.npm.taobao.org\nnpm config set registry https://repo.huaweicloud.com/repository/npm/
\n

sharp 源

\n
\n
\n \n shell\n
yarn config set sharp_binary_host \"https://npm.taobao.org/mirrors/sharp\"
\n

electron 源

\n
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/\n
\n

docker

\n

/etc/docker/daemon.json 或者直接 Docker Desktop 的 GUI 界面里修改:

\n
\n
\n \n json\n
\"registry-mirrors\": [\n  \"https://hub-mirror.c.163.com\",\n]
\n

maven

\n

$HOME/.gradle/init.gradle

\n
\n
\n \n groovy\n
allprojects {\n  repositories {\n    maven {\n      url 'https://maven.aliyun.com/repository/public/'\n    }\n    mavenLocal()\n    mavenCentral()\n  }\n}
","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[{"emoji":"❤","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/pwa-skipwaiting/index.json b/assets/data/post/pwa-skipwaiting/index.json index e56bcff61..f81467e91 100644 --- a/assets/data/post/pwa-skipwaiting/index.json +++ b/assets/data/post/pwa-skipwaiting/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","body":"\n

Why skipWaiting is important?

\n

By default, a service worker takes over the page from start to end, even if new service worker is discovered and installed. This behavior ensures consistency. However if the update is important and does not have conflict with the old one, you may want the new one to activate as soon as installed.

\n

Another use case is click to refresh feature. Remember the old service worker is still in charge even though refreshing the page. To update the PWA app without leaving it, skip the waiting phase of new service worker is needed. Thus after location.reload(), new service worker with new precached assets are there. We will focus on this use case in this post.

\n

How to skipWaiting without StaleWhileRevalidate strategy

\n

As you may seen in many documentations and tutorials, it is quite straightforward:

\n
\n
\n \n js\n
// normal/service-worker.js\n\n// Change this line to make service worker different\n\nself.addEventListener('message', (event) => {\n  const message = event.data\n  if (!message) return\n  if (message.type === 'skip-waiting') {\n    console.log('trigger skipWaiting at', +new Date())\n    self.skipWaiting()\n  }\n}
\n

You may need to change the comment line to test skip waiting again.

\n

Complete code for demonstration

\n

Let's add a simple server and some HTML to complete the PWA and simulate a slow request.

\n
\n

📓 Note:

\n

You can find complete code in my GitHub repo

\n
\n
\n
\n \n html\n
<!-- body part of normal/index.html -->\n\n<p><span id=\"action\">No</span> long running request</p>\n<button id=\"upgrade\" disabled>Click to skip waiting</button>\n<script>\n  let newWorker\n  const button = document.getElementById('upgrade')\n  const listenStage = () => {\n    console.log(newWorker.state, +new Date())\n    if (newWorker.state === 'installed') {\n      button.removeAttribute('disabled')\n    } else if (newWorker.state === 'activated') {\n      location.reload()\n    }\n  }\n  navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n    if (swr.waiting) {\n      newWorker = swr.waiting\n      newWorker.onstatechange = listenStage\n      listenStage() // Trigger installed\n    }\n    swr.onupdatefound = () => {\n      newWorker = swr.installing\n      newWorker.onstatechange = listenStage\n    }\n  })\n  button.addEventListener('click', function () {\n    // If there is a slow request when upgrading\n    const actionElement = document.getElementById('action')\n    actionElement.innerText = 'Doing'\n    fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n    // Ensure slow request is alive\n    setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n    this.setAttribute('disabled', true)\n  })\n</script>
\n

The above code might be a little bit long. It does 3 things:

\n\n
\n
\n \n js\n
// index.js\n\nconst http = require('http')\nconst statik = require('node-static')\n\nconst file = new statik.Server('.')\n\nhttp.createServer(function (req, res) {\n  // Simulate slow request\n  if (req.url.endsWith('/slow.json')) {\n    setTimeout(() => {\n      res.writeHead(200)\n      res.end('{}')\n    }, 4000)\n    return\n  }\n  file.serve(req, res)\n}\n).listen(8345)\n\nconsole.log('Listening on http://localhost:8345')
\n

The server is pretty straightforward, using node-static to serve files.

\n

A favicon.ico is also needed to avoid favicon.ico not found error.

\n

Now run index.js and:

\n
    \n
  1. open http://localhost:8345/normal/
  2. \n
  3. wait service worker installed and close page
  4. \n
  5. change service-worker.js
  6. \n
  7. open the page again
  8. \n
  9. open the console, check \"Preserve log\"
  10. \n
  11. click the \"Click to skip waiting\" button
  12. \n
  13. change service-worker.js, manually reload page and test again
  14. \n
\n

You may found something like this in console:

\n
(index):16 installed 1624852106984\nservice-worker.js:7 trigger skipWaiting at 1624852110391\n(index):16 activating 1624852110391\nactivated 1624852110393\n
\n

Then page reloads, the button stay disabled because the new service worker is active and no service worker waiting.

\n

Open Network tab, you can see slow.json is canceled because of page refresh. Or in Firefox it's directly logged into console:

\n
installed 1624852139119 normal:16:17\ntrigger skipWaiting at 1624852141791 service-worker.js:7:13\nactivating 1624852141795 normal:16:17\nactivated 1624852141797 normal:16:17\nUncaught (in promise) TypeError: NetworkError when attempting to fetch resource.\n
\n

This is intended because you almost always want to cancel fetching when refreshing the page. But that's where the problem lies when using StaleWhileRevalidate strategy

\n

Still waiting after skipWaiting, when StaleWhileRevalidate

\n

Now let's add StaleWhileRevalidate strategy:

\n
\n
\n \n diff\n
diff --color -u normal/index.html stuck/index.html\n--- normal/index.html   2021-06-28 11:46:20.464842200 +0800\n+++ stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n@@ -4,7 +4,7 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Normal</title>\n+    <title>Stuck</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\ndiff --color -u normal/service-worker.js stuck/service-worker.js\n--- normal/service-worker.js    2021-06-28 11:48:22.461331300 +0800\n+++ stuck/service-worker.js     2021-06-28 11:44:30.814991700 +0800\n@@ -1,4 +1,14 @@\n-// Change this line to make service worker differenta\n+/* global workbox, importScripts */\n+\n+// Change this line to make service worker different\n+\n+importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n+\n+workbox.routing.registerRoute(\n+  /\\.json/,\n+  new workbox.strategies.StaleWhileRevalidate(),\n+  'GET'\n+)\n\n self.addEventListener('message', (event) => {\n   const message = event.data
\n

open http://localhost:8345/stuck/, repeat the above steps. Here is the example output:

\n
(index):16 installed 1624853106784\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nservice-worker.js:17 trigger skipWaiting at 1624853112301\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nworkbox-core.dev.js:45 No response found in the 'workbox-runtime-http://localhost:8345/stuck/' cache. Will wait for the network response.\nworkbox-core.dev.js:45 View the final response here.\n(index):16 activating 1624853116470\n(index):16 activated 1624853117482\nNavigated to http://localhost:8345/stuck/\n
\n

The new service worker is not activated until the request completes. Even if resource is cached:

\n
(index):16 installed 1624853346141\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nservice-worker.js:17 trigger skipWaiting at 1624853348949\n(index):16 activating 1624853352867\n(index):16 activated 1624853353871\n
\n

Though cached, StaleWhileRevalidate strategy still revalidates the resource in the background. Thus the old service worker is unable to stop until the request finishes.

\n

The solution

\n

It's natural to think of aborting the request in the old service worker. Let's add AbortController. Notice that you need to let new service worker to skip waiting, and let old service worker to abort fetches. Don't get confused.

\n
\n
\n \n diff\n
diff --color -u stuck/index.html solution/index.html\n--- stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n+++ solution/index.html 2021-06-28 12:18:54.089259800 +0800\n@@ -4,13 +4,13 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Stuck</title>\n+    <title>Solution</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\n     <button id=\"upgrade\" disabled>Click to skip waiting</button>\n     <script>\n-      let newWorker\n+      let newWorker, oldWorker\n       const button = document.getElementById('upgrade')\n       const listenStage = () => {\n         console.log(newWorker.state, +new Date())\n@@ -21,6 +21,7 @@\n         }\n       }\n       navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n+        oldWorker = swr.active\n         if (swr.waiting) {\n           console.log('Waiting...')\n           newWorker = swr.waiting\n@@ -38,7 +39,10 @@\n         fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n         // Ensure slow request is alive\n-        setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n+        setTimeout(() => {\n+          oldWorker.postMessage({ type: 'abort-connections' })\n+          newWorker.postMessage({ type: 'skip-waiting' })\n+        }, 100)\n         this.setAttribute('disabled', true)\n       })\n     </script>\ndiff --color -u stuck/service-worker.js solution/service-worker.js\n--- stuck/service-worker.js     2021-06-28 12:08:58.811435500 +0800\n+++ solution/service-worker.js  2021-06-28 12:19:48.204055800 +0800\n@@ -1,12 +1,16 @@\n /* global workbox, importScripts */\n\n-// Change this line to make service worker different\n+// Change this line to make service worker differenta\n\n importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n\n+const controller = new AbortController()\n+\n workbox.routing.registerRoute(\n-  /\\.json/,\n-  new workbox.strategies.StaleWhileRevalidate(),\n+  /.*/,\n+  new workbox.strategies.StaleWhileRevalidate({\n+    fetchOptions: { signal: controller.signal }\n+  }),\n   'GET'\n )\n\n@@ -16,5 +20,7 @@\n   if (message.type === 'skip-waiting') {\n     console.log('trigger skipWaiting at', +new Date())\n     self.skipWaiting()\n+  } else if (message.type === 'abort-connections') {\n+    controller.abort()\n   }\n })
\n

And the (truncated) output:

\n
(index):16 installed 1624853994162\nworkbox-core.dev.js:45 workbox Router is responding to: /solution/slow.json\nservice-worker.js:21 trigger skipWaiting at 1624854001606\nworkbox-strategies.dev.js:1005 Uncaught (in promise) no-response: The strategy could not generate a response for 'http://localhost:8345/solution/slow.json'. The underlying error is AbortError: The user aborted a request..\n    at StaleWhileRevalidate.makeRequest (https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-strategies.dev.js:1005:15)\n(index):16 activating 1624854001616\n(index):16 activated 1624854002627\nNavigated to http://localhost:8345/solution/\n
\n

The new service worker is immediately activated after aborting the request 🎉

\n

Bonus

\n

If you open multiple tabs at the same time, and click the button, all of them are refreshed, avoiding potential conflict between new service worker and old page. If this is not the befavior you want, you can always change the condition for page reload.

","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","body":"\n

Why skipWaiting is important?

\n

By default, a service worker takes over the page from start to end, even if new service worker is discovered and installed. This behavior ensures consistency. However if the update is important and does not have conflict with the old one, you may want the new one to activate as soon as installed.

\n

Another use case is click to refresh feature. Remember the old service worker is still in charge even though refreshing the page. To update the PWA app without leaving it, skip the waiting phase of new service worker is needed. Thus after location.reload(), new service worker with new precached assets are there. We will focus on this use case in this post.

\n

How to skipWaiting without StaleWhileRevalidate strategy

\n

As you may seen in many documentations and tutorials, it is quite straightforward:

\n
\n
\n \n js\n
// normal/service-worker.js\n\n// Change this line to make service worker different\n\nself.addEventListener('message', (event) => {\n  const message = event.data\n  if (!message) return\n  if (message.type === 'skip-waiting') {\n    console.log('trigger skipWaiting at', +new Date())\n    self.skipWaiting()\n  }\n}
\n

You may need to change the comment line to test skip waiting again.

\n

Complete code for demonstration

\n

Let's add a simple server and some HTML to complete the PWA and simulate a slow request.

\n
\n

📓 Note:

\n

You can find complete code in my GitHub repo

\n
\n
\n
\n \n html\n
<!-- body part of normal/index.html -->\n\n<p><span id=\"action\">No</span> long running request</p>\n<button id=\"upgrade\" disabled>Click to skip waiting</button>\n<script>\n  let newWorker\n  const button = document.getElementById('upgrade')\n  const listenStage = () => {\n    console.log(newWorker.state, +new Date())\n    if (newWorker.state === 'installed') {\n      button.removeAttribute('disabled')\n    } else if (newWorker.state === 'activated') {\n      location.reload()\n    }\n  }\n  navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n    if (swr.waiting) {\n      newWorker = swr.waiting\n      newWorker.onstatechange = listenStage\n      listenStage() // Trigger installed\n    }\n    swr.onupdatefound = () => {\n      newWorker = swr.installing\n      newWorker.onstatechange = listenStage\n    }\n  })\n  button.addEventListener('click', function () {\n    // If there is a slow request when upgrading\n    const actionElement = document.getElementById('action')\n    actionElement.innerText = 'Doing'\n    fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n    // Ensure slow request is alive\n    setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n    this.setAttribute('disabled', true)\n  })\n</script>
\n

The above code might be a little bit long. It does 3 things:

\n\n
\n
\n \n js\n
// index.js\n\nconst http = require('http')\nconst statik = require('node-static')\n\nconst file = new statik.Server('.')\n\nhttp.createServer(function (req, res) {\n  // Simulate slow request\n  if (req.url.endsWith('/slow.json')) {\n    setTimeout(() => {\n      res.writeHead(200)\n      res.end('{}')\n    }, 4000)\n    return\n  }\n  file.serve(req, res)\n}\n).listen(8345)\n\nconsole.log('Listening on http://localhost:8345')
\n

The server is pretty straightforward, using node-static to serve files.

\n

A favicon.ico is also needed to avoid favicon.ico not found error.

\n

Now run index.js and:

\n
    \n
  1. open http://localhost:8345/normal/
  2. \n
  3. wait service worker installed and close page
  4. \n
  5. change service-worker.js
  6. \n
  7. open the page again
  8. \n
  9. open the console, check \"Preserve log\"
  10. \n
  11. click the \"Click to skip waiting\" button
  12. \n
  13. change service-worker.js, manually reload page and test again
  14. \n
\n

You may found something like this in console:

\n
(index):16 installed 1624852106984\nservice-worker.js:7 trigger skipWaiting at 1624852110391\n(index):16 activating 1624852110391\nactivated 1624852110393\n
\n

Then page reloads, the button stay disabled because the new service worker is active and no service worker waiting.

\n

Open Network tab, you can see slow.json is canceled because of page refresh. Or in Firefox it's directly logged into console:

\n
installed 1624852139119 normal:16:17\ntrigger skipWaiting at 1624852141791 service-worker.js:7:13\nactivating 1624852141795 normal:16:17\nactivated 1624852141797 normal:16:17\nUncaught (in promise) TypeError: NetworkError when attempting to fetch resource.\n
\n

This is intended because you almost always want to cancel fetching when refreshing the page. But that's where the problem lies when using StaleWhileRevalidate strategy

\n

Still waiting after skipWaiting, when StaleWhileRevalidate

\n

Now let's add StaleWhileRevalidate strategy:

\n
\n
\n \n diff\n
diff --color -u normal/index.html stuck/index.html\n--- normal/index.html   2021-06-28 11:46:20.464842200 +0800\n+++ stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n@@ -4,7 +4,7 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Normal</title>\n+    <title>Stuck</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\ndiff --color -u normal/service-worker.js stuck/service-worker.js\n--- normal/service-worker.js    2021-06-28 11:48:22.461331300 +0800\n+++ stuck/service-worker.js     2021-06-28 11:44:30.814991700 +0800\n@@ -1,4 +1,14 @@\n-// Change this line to make service worker differenta\n+/* global workbox, importScripts */\n+\n+// Change this line to make service worker different\n+\n+importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n+\n+workbox.routing.registerRoute(\n+  /\\.json/,\n+  new workbox.strategies.StaleWhileRevalidate(),\n+  'GET'\n+)\n\n self.addEventListener('message', (event) => {\n   const message = event.data
\n

open http://localhost:8345/stuck/, repeat the above steps. Here is the example output:

\n
(index):16 installed 1624853106784\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nservice-worker.js:17 trigger skipWaiting at 1624853112301\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nworkbox-core.dev.js:45 No response found in the 'workbox-runtime-http://localhost:8345/stuck/' cache. Will wait for the network response.\nworkbox-core.dev.js:45 View the final response here.\n(index):16 activating 1624853116470\n(index):16 activated 1624853117482\nNavigated to http://localhost:8345/stuck/\n
\n

The new service worker is not activated until the request completes. Even if resource is cached:

\n
(index):16 installed 1624853346141\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nservice-worker.js:17 trigger skipWaiting at 1624853348949\n(index):16 activating 1624853352867\n(index):16 activated 1624853353871\n
\n

Though cached, StaleWhileRevalidate strategy still revalidates the resource in the background. Thus the old service worker is unable to stop until the request finishes.

\n

The solution

\n

It's natural to think of aborting the request in the old service worker. Let's add AbortController. Notice that you need to let new service worker to skip waiting, and let old service worker to abort fetches. Don't get confused.

\n
\n
\n \n diff\n
diff --color -u stuck/index.html solution/index.html\n--- stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n+++ solution/index.html 2021-06-28 12:18:54.089259800 +0800\n@@ -4,13 +4,13 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Stuck</title>\n+    <title>Solution</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\n     <button id=\"upgrade\" disabled>Click to skip waiting</button>\n     <script>\n-      let newWorker\n+      let newWorker, oldWorker\n       const button = document.getElementById('upgrade')\n       const listenStage = () => {\n         console.log(newWorker.state, +new Date())\n@@ -21,6 +21,7 @@\n         }\n       }\n       navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n+        oldWorker = swr.active\n         if (swr.waiting) {\n           console.log('Waiting...')\n           newWorker = swr.waiting\n@@ -38,7 +39,10 @@\n         fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n         // Ensure slow request is alive\n-        setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n+        setTimeout(() => {\n+          oldWorker.postMessage({ type: 'abort-connections' })\n+          newWorker.postMessage({ type: 'skip-waiting' })\n+        }, 100)\n         this.setAttribute('disabled', true)\n       })\n     </script>\ndiff --color -u stuck/service-worker.js solution/service-worker.js\n--- stuck/service-worker.js     2021-06-28 12:08:58.811435500 +0800\n+++ solution/service-worker.js  2021-06-28 12:19:48.204055800 +0800\n@@ -1,12 +1,16 @@\n /* global workbox, importScripts */\n\n-// Change this line to make service worker different\n+// Change this line to make service worker differenta\n\n importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n\n+const controller = new AbortController()\n+\n workbox.routing.registerRoute(\n-  /\\.json/,\n-  new workbox.strategies.StaleWhileRevalidate(),\n+  /.*/,\n+  new workbox.strategies.StaleWhileRevalidate({\n+    fetchOptions: { signal: controller.signal }\n+  }),\n   'GET'\n )\n\n@@ -16,5 +20,7 @@\n   if (message.type === 'skip-waiting') {\n     console.log('trigger skipWaiting at', +new Date())\n     self.skipWaiting()\n+  } else if (message.type === 'abort-connections') {\n+    controller.abort()\n   }\n })
\n

And the (truncated) output:

\n
(index):16 installed 1624853994162\nworkbox-core.dev.js:45 workbox Router is responding to: /solution/slow.json\nservice-worker.js:21 trigger skipWaiting at 1624854001606\nworkbox-strategies.dev.js:1005 Uncaught (in promise) no-response: The strategy could not generate a response for 'http://localhost:8345/solution/slow.json'. The underlying error is AbortError: The user aborted a request..\n    at StaleWhileRevalidate.makeRequest (https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-strategies.dev.js:1005:15)\n(index):16 activating 1624854001616\n(index):16 activated 1624854002627\nNavigated to http://localhost:8345/solution/\n
\n

The new service worker is immediately activated after aborting the request 🎉

\n

Bonus

\n

If you open multiple tabs at the same time, and click the button, all of them are refreshed, avoiding potential conflict between new service worker and old page. If this is not the befavior you want, you can always change the condition for page reload.

","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/python-async-subprocess/index.json b/assets/data/post/python-async-subprocess/index.json index f987b1d7e..515c82e0d 100644 --- a/assets/data/post/python-async-subprocess/index.json +++ b/assets/data/post/python-async-subprocess/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"69","title":"异步 & 异步获取命令输出","summary":"\n

与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

\n","body":"\n

Tested with Python 3.7

\n
\n

😜 TL;DR

\n

直达:异步获取命令输出

\n
\n

异步

\n

异步和多线程比较而言,就是在应该停的地方停下,让给另一个任务。这样就使任务之间的交替变得更可控,不像多线程一样会动不动出现资源竞争的操作。

\n

简单例子

\n

From https://www.tornadoweb.org/en/stable/guide/async.html

\n

synchronous:

\n
\n
\n \n python\n
from tornado.httpclient import HTTPClient\n\ndef synchronous_fetch(url):\n    http_client = HTTPClient()\n    response = http_client.fetch(url)\n    return response.body
\n

asynchronous:

\n
\n
\n \n python\n
from tornado.httpclient import AsyncHTTPClient\n\nasync def asynchronous_fetch(url):\n    http_client = AsyncHTTPClient()\n    response = await http_client.fetch(url)\n    return response.body
\n

虽然看起来除了async await之外和同步的没有什么区别,但是阻塞的函数并不能直接改成异步的函数,因为异步函数会立即返回,而且写程序的时候要明确告知什么时候停下。接下来详细讨论一下。

\n

Get Your Hands Dirty!

\n

Based on https://realpython.com/async-io-python/

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    # time.sleep(1)\n    print(\"Two\", end=\" \")\n\n\nasync def main():\n    await asyncio.gather(count(), count(), count())\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One One One Two Two Two aioplay.py executed in 1.00 seconds.\n
\n

只是直接调用count(),会返回<coroutine object count at ...>,而并不会立即执行,这和 JS 不一样。插播 JS:

\n
\n
\n \n js\n
const a = async () => console.log(1)\na()
\n
1\nPromise {\n  undefined,\n  domain:\n   ... }\n
\n

但是在一个异步函数里调用而不用await变现,会直接报错:

\n
\n
\n \n python\n
In [5]: async def count():\n   ...:     print(\"One\")\n   ...:     await asyncio.sleep(1)\n   ...:     print(\"Two\")\n   ...:\n\nIn [6]: async def run():\n   ...:     count()\n   ...:\n\nIn [7]: asyncio.run(run())\nipython:2: RuntimeWarning: coroutine 'count' was never awaited\nRuntimeWarning: Enable tracemalloc to get the object allocation traceback
\n

这又和 JS 不一样。

\n
\n

如果不使用await asyncio.sleep,而是使用time.sleep来模拟一个阻塞的操作:

\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

没有切入点,只好顺序执行

\n
\n

如果修改main,成为一个循环:

\n
\n
\n \n python\n
async def main():\n    for _ in range(3):\n        await count()
\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

因为await会等到函数结束后才进行下一步操作。asyncio.gather(count(), count(), count())正是为了让它们一起运行。

\n

获取命令输出

\n

注意 Windows 下要指定路径

\n
\n
\n \n python\n
import subprocess\n\np = subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                     shell=False, stdout=subprocess.PIPE)\nfor line in iter(p.stdout.readline, b''):\n    print(line.rstrip().decode())
\n

这里的 iter 为不常见用法,指一直调用此函数,直至返回b''

\n

或者使用with

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in iter(p.stdout.readline, b''):\n        print(line.rstrip().decode())
\n

或者

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in p.stdout:\n        print(line.rstrip().decode())
\n

异步获取命令输出

\n

可惜上述是阻塞的,不能用于异步。这就需要asyncio.subprocess出场了。

\n
\n
\n \n python\n
import asyncio\n\n\nasync def ping(prefix):\n    # Create the subprocess; redirect the standard output\n    # into a pipe.\n    proc = await asyncio.create_subprocess_exec(\n        'ping', '-c', '4', 'www.baidu.com',\n        stdout=asyncio.subprocess.PIPE)\n\n    async for data in proc.stdout:\n        line = data.decode('ascii').rstrip()\n        print(prefix + line)\n\n    # Wait for the subprocess exit.\n    await proc.wait()\n\n\nasync def main():\n    await asyncio.gather(ping('1> '), ping('2> '))\n\n# For Windows\n# https://stackoverflow.com/a/53146484/8810271\n# loop = asyncio.ProactorEventLoop()\n# asyncio.set_event_loop(loop)\n# loop.run_until_complete(get_date())\nasyncio.run(main())
\n
1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=34.2 ms\n2> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=46.6 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=38.6 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=32.5 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=34.2 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=31.7 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=35.9 ms\n1>\n1> --- www.a.shifen.com ping statistics ---\n1> 4 packets transmitted, 4 received, 0% packet loss, time 3003ms\n1> rtt min/avg/max/mdev = 34.212/35.731/38.574/1.781 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=32.7 ms\n2>\n2> --- www.a.shifen.com ping statistics ---\n2> 4 packets transmitted, 4 received, 0% packet loss, time 3004ms\n2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms\n
\n

async for?

\n
\n
\n \n python\n
async for TARGET in ITER:\n    BLOCK\nelse:\n    BLOCK2
\n

is equivalent to

\n
\n
\n \n python\n
iter = (ITER)\niter = type(iter).__aiter__(iter)\nrunning = True\nwhile running:\n    try:\n        TARGET = await type(iter).__anext__(iter)\n    except StopAsyncIteration:\n        running = False\n    else:\n        BLOCK\nelse:\n    BLOCK2
\n

如果不用类呢,就会是这个样子:

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    print(\"Two\", end=\" \")\n    return 'Yeah!'\n\n\nasync def generate_workers():\n    for _ in range(3):\n        yield await count()\n\n\nasync def main():\n    async for line in generate_workers():\n        print(line)\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One Two Yeah!\nOne Two Yeah!\nOne Two Yeah!\naioplay.py executed in 3.00 seconds.\n
","createdAt":"2020-02-05T10:53:16.000Z","lastEditedAt":"2020-06-30T08:58:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"69","title":"异步 & 异步获取命令输出","summary":"\n

与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

\n","body":"\n

Tested with Python 3.7

\n
\n

😜 TL;DR

\n

直达:异步获取命令输出

\n
\n

异步

\n

异步和多线程比较而言,就是在应该停的地方停下,让给另一个任务。这样就使任务之间的交替变得更可控,不像多线程一样会动不动出现资源竞争的操作。

\n

简单例子

\n

From https://www.tornadoweb.org/en/stable/guide/async.html

\n

synchronous:

\n
\n
\n \n python\n
from tornado.httpclient import HTTPClient\n\ndef synchronous_fetch(url):\n    http_client = HTTPClient()\n    response = http_client.fetch(url)\n    return response.body
\n

asynchronous:

\n
\n
\n \n python\n
from tornado.httpclient import AsyncHTTPClient\n\nasync def asynchronous_fetch(url):\n    http_client = AsyncHTTPClient()\n    response = await http_client.fetch(url)\n    return response.body
\n

虽然看起来除了async await之外和同步的没有什么区别,但是阻塞的函数并不能直接改成异步的函数,因为异步函数会立即返回,而且写程序的时候要明确告知什么时候停下。接下来详细讨论一下。

\n

Get Your Hands Dirty!

\n

Based on https://realpython.com/async-io-python/

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    # time.sleep(1)\n    print(\"Two\", end=\" \")\n\n\nasync def main():\n    await asyncio.gather(count(), count(), count())\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One One One Two Two Two aioplay.py executed in 1.00 seconds.\n
\n

只是直接调用count(),会返回<coroutine object count at ...>,而并不会立即执行,这和 JS 不一样。插播 JS:

\n
\n
\n \n js\n
const a = async () => console.log(1)\na()
\n
1\nPromise {\n  undefined,\n  domain:\n   ... }\n
\n

但是在一个异步函数里调用而不用await变现,会直接报错:

\n
\n
\n \n python\n
In [5]: async def count():\n   ...:     print(\"One\")\n   ...:     await asyncio.sleep(1)\n   ...:     print(\"Two\")\n   ...:\n\nIn [6]: async def run():\n   ...:     count()\n   ...:\n\nIn [7]: asyncio.run(run())\nipython:2: RuntimeWarning: coroutine 'count' was never awaited\nRuntimeWarning: Enable tracemalloc to get the object allocation traceback
\n

这又和 JS 不一样。

\n
\n

如果不使用await asyncio.sleep,而是使用time.sleep来模拟一个阻塞的操作:

\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

没有切入点,只好顺序执行

\n
\n

如果修改main,成为一个循环:

\n
\n
\n \n python\n
async def main():\n    for _ in range(3):\n        await count()
\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

因为await会等到函数结束后才进行下一步操作。asyncio.gather(count(), count(), count())正是为了让它们一起运行。

\n

获取命令输出

\n

注意 Windows 下要指定路径

\n
\n
\n \n python\n
import subprocess\n\np = subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                     shell=False, stdout=subprocess.PIPE)\nfor line in iter(p.stdout.readline, b''):\n    print(line.rstrip().decode())
\n

这里的 iter 为不常见用法,指一直调用此函数,直至返回b''

\n

或者使用with

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in iter(p.stdout.readline, b''):\n        print(line.rstrip().decode())
\n

或者

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in p.stdout:\n        print(line.rstrip().decode())
\n

异步获取命令输出

\n

可惜上述是阻塞的,不能用于异步。这就需要asyncio.subprocess出场了。

\n
\n
\n \n python\n
import asyncio\n\n\nasync def ping(prefix):\n    # Create the subprocess; redirect the standard output\n    # into a pipe.\n    proc = await asyncio.create_subprocess_exec(\n        'ping', '-c', '4', 'www.baidu.com',\n        stdout=asyncio.subprocess.PIPE)\n\n    async for data in proc.stdout:\n        line = data.decode('ascii').rstrip()\n        print(prefix + line)\n\n    # Wait for the subprocess exit.\n    await proc.wait()\n\n\nasync def main():\n    await asyncio.gather(ping('1> '), ping('2> '))\n\n# For Windows\n# https://stackoverflow.com/a/53146484/8810271\n# loop = asyncio.ProactorEventLoop()\n# asyncio.set_event_loop(loop)\n# loop.run_until_complete(get_date())\nasyncio.run(main())
\n
1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=34.2 ms\n2> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=46.6 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=38.6 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=32.5 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=34.2 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=31.7 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=35.9 ms\n1>\n1> --- www.a.shifen.com ping statistics ---\n1> 4 packets transmitted, 4 received, 0% packet loss, time 3003ms\n1> rtt min/avg/max/mdev = 34.212/35.731/38.574/1.781 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=32.7 ms\n2>\n2> --- www.a.shifen.com ping statistics ---\n2> 4 packets transmitted, 4 received, 0% packet loss, time 3004ms\n2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms\n
\n

async for?

\n
\n
\n \n python\n
async for TARGET in ITER:\n    BLOCK\nelse:\n    BLOCK2
\n

is equivalent to

\n
\n
\n \n python\n
iter = (ITER)\niter = type(iter).__aiter__(iter)\nrunning = True\nwhile running:\n    try:\n        TARGET = await type(iter).__anext__(iter)\n    except StopAsyncIteration:\n        running = False\n    else:\n        BLOCK\nelse:\n    BLOCK2
\n

如果不用类呢,就会是这个样子:

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    print(\"Two\", end=\" \")\n    return 'Yeah!'\n\n\nasync def generate_workers():\n    for _ in range(3):\n        yield await count()\n\n\nasync def main():\n    async for line in generate_workers():\n        print(line)\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One Two Yeah!\nOne Two Yeah!\nOne Two Yeah!\naioplay.py executed in 3.00 seconds.\n
","createdAt":"2020-02-05T10:53:16.000Z","lastEditedAt":"2020-06-30T08:58:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/raspi-tricks/index.json b/assets/data/post/raspi-tricks/index.json index 800e82a7c..ed6c7d7fe 100644 --- a/assets/data/post/raspi-tricks/index.json +++ b/assets/data/post/raspi-tricks/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"65","title":"Tricks about Raspberry Pi","summary":"\n

Handy tricks when playing with raspberry pi. You should know them.

\n","body":"\n

Get the Temperature

\n
\n
\n \n shell\n
cat /sys/class/thermal/thermal_zone0/temp
\n

Outputs:

\n
29482\n
\n

And here is bash script to create temperature PS1

\n
\n
\n \n shell\n
if [ -e /sys/class/thermal/thermal_zone0/temp ]; then\n    temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n    if [ $temp -lt 30000 ]; then color=2\n    elif [ $temp -lt 50000 ]; then color=3\n    else color=1\n    fi\n    prompt_section $color \"${temp::${#temp}-3}.${temp:${#temp}-3}\"\nfi
\n

Adding a dot is just mixing bash substrings (${string:offset[:length]}) with bash string lengths (${#string})

\n

vcgencmd

\n

From https://www.raspberrypi.org/documentation/raspbian/applications/vcgencmd.md

\n

To get all commands, use:

\n
\n
\n \n shell\n
vcgencmd commands
\n

Voltage

\n
\n
\n \n shell\n
vcgencmd get_throttled
\n

If voltage is okay, you will get 0x0

\n

Else, say 0x50000, convert it to binary, and see each bit:

\n
19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0\n 0  1  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
BitMeaning
0Under-voltage detected
1Arm frequency capped
2Currently throttled
3Soft temperature limit active
16Under-voltage has occurred
17Arm frequency capping has occurred
18Throttling has occurred
19Soft temperature limit has occurred
\n

Generally, the 3 0s in the middle do not have specific meaning. Just need to care about the first 5 and the last 0.

\n

Screen on / off

\n

When you start the Raspberry Pi with display / monitor / screen on, it will automatically turn on display power. If not, display power will never turn on even if you later connect it to a screen. So it is necessary to control the display power.

\n
\n
\n \n shell\n
vcgencmd display_power 1
\n

1 for on and 0 for off. bare vcgencmd display_power shows current state.

\n

It is possible to specify a certain display ID. For more info, visit the doc above.

","createdAt":"2020-01-21T08:29:47.000Z","lastEditedAt":"2020-06-24T06:04:38.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"65","title":"Tricks about Raspberry Pi","summary":"\n

Handy tricks when playing with raspberry pi. You should know them.

\n","body":"\n

Get the Temperature

\n
\n
\n \n shell\n
cat /sys/class/thermal/thermal_zone0/temp
\n

Outputs:

\n
29482\n
\n

And here is bash script to create temperature PS1

\n
\n
\n \n shell\n
if [ -e /sys/class/thermal/thermal_zone0/temp ]; then\n    temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n    if [ $temp -lt 30000 ]; then color=2\n    elif [ $temp -lt 50000 ]; then color=3\n    else color=1\n    fi\n    prompt_section $color \"${temp::${#temp}-3}.${temp:${#temp}-3}\"\nfi
\n

Adding a dot is just mixing bash substrings (${string:offset[:length]}) with bash string lengths (${#string})

\n

vcgencmd

\n

From https://www.raspberrypi.org/documentation/raspbian/applications/vcgencmd.md

\n

To get all commands, use:

\n
\n
\n \n shell\n
vcgencmd commands
\n

Voltage

\n
\n
\n \n shell\n
vcgencmd get_throttled
\n

If voltage is okay, you will get 0x0

\n

Else, say 0x50000, convert it to binary, and see each bit:

\n
19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0\n 0  1  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
BitMeaning
0Under-voltage detected
1Arm frequency capped
2Currently throttled
3Soft temperature limit active
16Under-voltage has occurred
17Arm frequency capping has occurred
18Throttling has occurred
19Soft temperature limit has occurred
\n

Generally, the 3 0s in the middle do not have specific meaning. Just need to care about the first 5 and the last 0.

\n

Screen on / off

\n

When you start the Raspberry Pi with display / monitor / screen on, it will automatically turn on display power. If not, display power will never turn on even if you later connect it to a screen. So it is necessary to control the display power.

\n
\n
\n \n shell\n
vcgencmd display_power 1
\n

1 for on and 0 for off. bare vcgencmd display_power shows current state.

\n

It is possible to specify a certain display ID. For more info, visit the doc above.

","createdAt":"2020-01-21T08:29:47.000Z","lastEditedAt":"2020-06-24T06:04:38.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/showcase/index.json b/assets/data/post/showcase/index.json index 4efcacd2e..a9ffa010d 100644 --- a/assets/data/post/showcase/index.json +++ b/assets/data/post/showcase/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"18","title":"Showcase","summary":"\n

测试一下 Markdown 渲染的效果 网页的 CSS

\n","body":"\n

Hello!

\n
Yes!\n

😂 🐶

\n
\n
\n \n python\n
print('hello world!')
\n
\n

really awesome!

\n\n
\n
\n \n js\n
even('write').code ? ah : ha
\n
\n
\n

code in head of course!

\n
code with unspecified language\n
\n
or code with indent\n
\n

More complex heading level!

\n
A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code\n
\n

And more!

\n

Paragraph

\n
Small Headers
\n

Paragraph

\n
Should Be Visible
\n

Another paragraph

\n

Inline $\\frac{1}{2 \\pi i}$

\n

$$
\n\\int_{- \\infty}^{\\infty} \\frac{1}{x} \\mathrm d x
\n$$

","createdAt":"2019-12-28T09:22:09.000Z","lastEditedAt":"2020-07-22T07:54:13.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]},{"emoji":"🎉","count":1,"users":["AllanChain"]},{"emoji":"❤","count":1,"users":["AllanChain"]},{"emoji":"🚀","count":1,"users":["AllanChain"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-639432138","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Awesome!

","createdAt":"2020-06-05T11:47:13.000Z","reactions":[{"emoji":"👍","count":1,"users":["fangtu"]},{"emoji":"😄","count":2,"users":["AllanChain","fangtu"]},{"emoji":"❤","count":2,"users":["AllanChain","fangtu"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-648726240","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

More

\n
\n

comment

\n
\n
with\n
\n

rich style

","createdAt":"2020-06-24T10:04:23.000Z","reactions":[{"emoji":"🚀","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-649517566","author":{"id":"fangtu","avatarUrl":"https://avatars.githubusercontent.com/u/18296978?s=64&v=4"},"bodyHTML":"

nice

","createdAt":"2020-06-25T12:44:32.000Z","reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-657232119","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

\n
\n

\n blog/github.data.js\n

\n

\n Lines 15 to 27\n in\n cdaf1f5\n

\n
\n
\n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n
/**
* Add slug to HTML headers
* @param {string} html html to add slug
*/
const slugPlugin = html => {
const slugger = new GithubSlugger()
return html.replace(/<h(\\d)>(.*?)<\\/h\\1>/gs, (_, level, title) => {
const slug = slugger.slug(title.replace(/<.*?>/g, ''))
return `<h${level}>
<a id=\"article-${slug}\" class=\"anchor-hover\" href=\"#${slug}\">
#</a> ${title}</h${level}>`
})
}
\n
\n
\n

","createdAt":"2020-07-12T14:49:54.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-662301785","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Link to other post like this vue-pwa-1#本地测试 vue-pwa-2

\n

Write math! $\\frac{1}{2}$

\n

Inline display $\\dfrac 1 2$, $\\displaystyle \\int x \\mathrm d x$

\n

Custom macro $\\ds \\int x \\rm d x$

","createdAt":"2020-07-22T07:51:03.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"18","title":"Showcase","summary":"\n

测试一下 Markdown 渲染的效果 网页的 CSS

\n","body":"\n

Hello!

\n
Yes!\n

😂 🐶

\n
\n
\n \n python\n
print('hello world!')
\n
\n

really awesome!

\n\n
\n
\n \n js\n
even('write').code ? ah : ha
\n
\n
\n

code in head of course!

\n
code with unspecified language\n
\n
or code with indent\n
\n

More complex heading level!

\n
A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code\n
\n

And more!

\n

Paragraph

\n
Small Headers
\n

Paragraph

\n
Should Be Visible
\n

Another paragraph

\n

Inline $\\frac{1}{2 \\pi i}$

\n

$$\n\n\\int_{- \\infty}^{\\infty} \\frac{1}{x} \\mathrm d x\n\n$$

","createdAt":"2019-12-28T09:22:09.000Z","lastEditedAt":"2020-07-22T07:54:13.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]},{"emoji":"🎉","count":1,"users":["AllanChain"]},{"emoji":"❤","count":1,"users":["AllanChain"]},{"emoji":"🚀","count":1,"users":["AllanChain"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-639432138","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Awesome!

","createdAt":"2020-06-05T11:47:13.000Z","reactions":[{"emoji":"👍","count":1,"users":["fangtu"]},{"emoji":"😄","count":2,"users":["AllanChain","fangtu"]},{"emoji":"❤","count":2,"users":["AllanChain","fangtu"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-648726240","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

More

\n
\n

comment

\n
\n
with\n
\n

rich style

","createdAt":"2020-06-24T10:04:23.000Z","reactions":[{"emoji":"🚀","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-649517566","author":{"id":"fangtu","avatarUrl":"https://avatars.githubusercontent.com/u/18296978?s=64&v=4"},"bodyHTML":"

nice

","createdAt":"2020-06-25T12:44:32.000Z","reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-657232119","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

\n
\n

\n blog/github.data.js\n

\n

\n Lines 15 to 27\n in\n cdaf1f5\n

\n
\n
\n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n
/**
* Add slug to HTML headers
* @param {string} html html to add slug
*/
const slugPlugin = html => {
const slugger = new GithubSlugger()
return html.replace(/<h(\\d)>(.*?)<\\/h\\1>/gs, (_, level, title) => {
const slug = slugger.slug(title.replace(/<.*?>/g, ''))
return `<h${level}>
<a id=\"article-${slug}\" class=\"anchor-hover\" href=\"#${slug}\">
#</a> ${title}</h${level}>`
})
}
\n
\n
\n

","createdAt":"2020-07-12T14:49:54.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-662301785","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Link to other post like this vue-pwa-1#本地测试 vue-pwa-2

\n

Write math! $\\frac{1}{2}$

\n

Inline display $\\dfrac 1 2$, $\\displaystyle \\int x \\mathrm d x$

\n

Custom macro $\\ds \\int x \\rm d x$

","createdAt":"2020-07-22T07:51:03.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/sql-query-perf/index.json b/assets/data/post/sql-query-perf/index.json index 7170fefa7..d2196d4ac 100644 --- a/assets/data/post/sql-query-perf/index.json +++ b/assets/data/post/sql-query-perf/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"164","title":"数据库查询性能优化手记","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","body":"\n

尝试 1:使用 SQLAlchemy 的 Dynamic Relationship Loaders 功能:

\n
\n
\n \n python\n
class BasicInfo(Base):\n    __tablename__ = \"basic_info\"\n    day_infos = relationship(\n        \"DayInfo\", order_by=\"desc(DayInfo.date)\", lazy=\"dynamic\"\n    )\n\n\nclass DayInfo(Base):\n    __tablename__ = \"day_info\"\n    code = Column(String(8), ForeignKey(\"basic_info.code\"), primary_key=True)
\n

并在读取中使用 limit 控制读取的个数。处理 3 天的数据查找指定条件,用时约 1 分钟。

\n

尝试 2:一次性读取所有的记录

\n

按照尝试 1 的做法数据库请求的个数会很多,数千次请求不会很快。如果改用所有数据合并到一次请求当中,则大大加快。用时达到 8 秒。

\n

问题 1:启动时间

\n

由于需要导入大量的库,启动时间约有 4 秒钟,不容乐观。

\n

使用 PYTHONPROFILEIMPORTTIME=1 运行,并将 stderr 导出至文件,使用 tuna 查看,发现大头是 pandas 和 SQLAlchemy。由于 pandas 并不是每次运行都需要使用,故将其从 top level import 挪到的相应的函数中。时间减少了 2 秒。

\n

此时用时达到 6 秒。

\n

尝试 3:使用 List Comprehension 运行匹配算法

\n

时间几乎没有变化。后来知道是匹配算法本来运行时间就短的原因。

\n

尝试 4:使用 SQLAlchemy 自带的分页功能读取数据

\n
\n
\n \n python\n
for data in session.execute(\n    session.query(BasicInfo, DayInfo)\n    .join(BasicInfo.day_infos)\n    .where(DayInfo.date > date_after)\n    .execution_options(yield_per=1000)\n).partitions():
\n

时间并没有显著变化,但是内存的占用从大约 9% 降至 2%。可以肯定是 Python 的对象数量减少带来的。

\n

尝试 5:更换 Python 的 MySQL 连接库

\n

使用 python -m cProfile -o program.prof -m myprog ,再使用 snakeviz 可视化,发现很多时间被用在了 parse string, int 等上。而且使用 top 命令也可以看到 CPU 占满了一个核。将 PyMySQL 更换为 mysqlclient 后,处理 40 天数据从 22 秒降至 12 到 13 秒。

\n

问题 2:分页

\n

由于股票数据很多,尽管尝试 4 中使用了分页,但是在 MySQL 中,数据仍是一次读取。

\n

参考了网上的资料:

\n\n

看到应该结合所选的条件进行分页而不是暴力地 LIMIT OFFSET. 如:

\n
\n
\n \n sql\n
SELECT day_info.*, basic_info.data FROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-03-01'\nAND (\n    basic_info.code > 'sh600011'\n    OR (basic_info.code='sh600011' AND day_info.date > '2021-03-03')\n)\nLIMIT 30
\n

尝试 6:不使用 SQLAlchemy

\n

进一步 profile 看到有很多运行时间是花在 SQLAlchemy 上的。直接跳过 ORM 层操作,时间减少了约 1 秒(可能就是不 import SQLAlchemy 带来的),但是在运行时间中,CPU 占用比率((user + sys)/real) 从 96% 降至 50% - 60%。这意味着性能瓶颈由原来的 CPU 转化为 CPU 和 IO

\n

问题 3:Query Time

\n

过程中发现 MariaDB 突然表现出了缓存功能,再次运行后 IO 时间显著减小至可以忽略。所以 MySQL 的 query 速度还有很大的优化空间。

\n

使用

\n
\n
\n \n sql\n
SHOW GlOBAL VARIABLES LIKE 'slow%log%'
\n

发现并没有开启对慢查询的日志,使用以下命令开启:

\n
\n
\n \n sql\n
SET GLOBAL slow_query_log='on';\nSET GLOBAL slow_query_log_file='/var/log/mysql/sql-slow.log';\nSET GLOBAL long_query_time = 1;
\n

查阅日志可以看到,一条一万数据的 select 花了 7 秒钟才完成。

\n
Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799\n
\n

为方便调试性能,需要关闭缓存功能,可使用 SELECT SQL_NO_CACHE ... 实现。

\n

如问题 2 中的 query:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND (\n basic_info.code > 'specificode' OR (\n basic_info.code='specificode' AND day_info.date > '2021-02-09'\n)\n)\nLIMIT 5000;
\n

单独运行需要 3 秒钟。但是如果拆成 2 个:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND  basic_info.code > 'specificode'\nLIMIT 5000;
\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09';
\n

则半秒钟内即可完成。

\n

有没有将两个合成一个但又不影响性能的方法呢?是有的。使用 UNION 合并两个 query,并注意要用 UNION ALL 跳过去重步骤,并用括号第把二个语句括起来。如果不使用括号,则 LIMIT 将被识别为整个大语句的,严重影响速度。

\n
\n
\n \n sql\n
SELECT SQL_NO_CACHE day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09'\nUNION ALL\n(\n  SELECT day_info.*, basic_info.data\n  FROM basic_info\n  JOIN day_info ON basic_info.code=day_info.code\n  WHERE DATE > '2021-01-25' AND (\n   basic_info.code > 'specificode'\n  )\n  LIMIT 5000\n);
\n

处理 40 天数据降至 7 秒。

\n

尝试 7:use_result vs store_result

\n

在 mysqlclient 中,默认的 cursor 类在底层使用 store_result,即将每一次查询的结果一股脑存下来备用。相比之下 use_result 就人性化很多,先放在 server 里有需要再取,也符合 fetchmany 的初衷。

\n

使用默认 Cursor 类的 profile 结果:

\n

\"before\"

\n

使用 SSCursor 类的 profile 结果(橘色那块是 execute,剩下那块是另一个与 SQL 无关的函数,也就是上图较深灰的部分):

\n

\"after\"

","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"164","title":"数据库查询性能优化手记","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","body":"\n

尝试 1:使用 SQLAlchemy 的 Dynamic Relationship Loaders 功能:

\n
\n
\n \n python\n
class BasicInfo(Base):\n    __tablename__ = \"basic_info\"\n    day_infos = relationship(\n        \"DayInfo\", order_by=\"desc(DayInfo.date)\", lazy=\"dynamic\"\n    )\n\n\nclass DayInfo(Base):\n    __tablename__ = \"day_info\"\n    code = Column(String(8), ForeignKey(\"basic_info.code\"), primary_key=True)
\n

并在读取中使用 limit 控制读取的个数。处理 3 天的数据查找指定条件,用时约 1 分钟。

\n

尝试 2:一次性读取所有的记录

\n

按照尝试 1 的做法数据库请求的个数会很多,数千次请求不会很快。如果改用所有数据合并到一次请求当中,则大大加快。用时达到 8 秒。

\n

问题 1:启动时间

\n

由于需要导入大量的库,启动时间约有 4 秒钟,不容乐观。

\n

使用 PYTHONPROFILEIMPORTTIME=1 运行,并将 stderr 导出至文件,使用 tuna 查看,发现大头是 pandas 和 SQLAlchemy。由于 pandas 并不是每次运行都需要使用,故将其从 top level import 挪到的相应的函数中。时间减少了 2 秒。

\n

此时用时达到 6 秒。

\n

尝试 3:使用 List Comprehension 运行匹配算法

\n

时间几乎没有变化。后来知道是匹配算法本来运行时间就短的原因。

\n

尝试 4:使用 SQLAlchemy 自带的分页功能读取数据

\n
\n
\n \n python\n
for data in session.execute(\n    session.query(BasicInfo, DayInfo)\n    .join(BasicInfo.day_infos)\n    .where(DayInfo.date > date_after)\n    .execution_options(yield_per=1000)\n).partitions():
\n

时间并没有显著变化,但是内存的占用从大约 9% 降至 2%。可以肯定是 Python 的对象数量减少带来的。

\n

尝试 5:更换 Python 的 MySQL 连接库

\n

使用 python -m cProfile -o program.prof -m myprog ,再使用 snakeviz 可视化,发现很多时间被用在了 parse string, int 等上。而且使用 top 命令也可以看到 CPU 占满了一个核。将 PyMySQL 更换为 mysqlclient 后,处理 40 天数据从 22 秒降至 12 到 13 秒。

\n

问题 2:分页

\n

由于股票数据很多,尽管尝试 4 中使用了分页,但是在 MySQL 中,数据仍是一次读取。

\n

参考了网上的资料:

\n\n

看到应该结合所选的条件进行分页而不是暴力地 LIMIT OFFSET. 如:

\n
\n
\n \n sql\n
SELECT day_info.*, basic_info.data FROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-03-01'\nAND (\n    basic_info.code > 'sh600011'\n    OR (basic_info.code='sh600011' AND day_info.date > '2021-03-03')\n)\nLIMIT 30
\n

尝试 6:不使用 SQLAlchemy

\n

进一步 profile 看到有很多运行时间是花在 SQLAlchemy 上的。直接跳过 ORM 层操作,时间减少了约 1 秒(可能就是不 import SQLAlchemy 带来的),但是在运行时间中,CPU 占用比率((user + sys)/real) 从 96% 降至 50% - 60%。这意味着性能瓶颈由原来的 CPU 转化为 CPU 和 IO

\n

问题 3:Query Time

\n

过程中发现 MariaDB 突然表现出了缓存功能,再次运行后 IO 时间显著减小至可以忽略。所以 MySQL 的 query 速度还有很大的优化空间。

\n

使用

\n
\n
\n \n sql\n
SHOW GlOBAL VARIABLES LIKE 'slow%log%'
\n

发现并没有开启对慢查询的日志,使用以下命令开启:

\n
\n
\n \n sql\n
SET GLOBAL slow_query_log='on';\nSET GLOBAL slow_query_log_file='/var/log/mysql/sql-slow.log';\nSET GLOBAL long_query_time = 1;
\n

查阅日志可以看到,一条一万数据的 select 花了 7 秒钟才完成。

\n
Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799\n
\n

为方便调试性能,需要关闭缓存功能,可使用 SELECT SQL_NO_CACHE ... 实现。

\n

如问题 2 中的 query:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND (\n basic_info.code > 'specificode' OR (\n basic_info.code='specificode' AND day_info.date > '2021-02-09'\n)\n)\nLIMIT 5000;
\n

单独运行需要 3 秒钟。但是如果拆成 2 个:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND  basic_info.code > 'specificode'\nLIMIT 5000;
\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09';
\n

则半秒钟内即可完成。

\n

有没有将两个合成一个但又不影响性能的方法呢?是有的。使用 UNION 合并两个 query,并注意要用 UNION ALL 跳过去重步骤,并用括号第把二个语句括起来。如果不使用括号,则 LIMIT 将被识别为整个大语句的,严重影响速度。

\n
\n
\n \n sql\n
SELECT SQL_NO_CACHE day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09'\nUNION ALL\n(\n  SELECT day_info.*, basic_info.data\n  FROM basic_info\n  JOIN day_info ON basic_info.code=day_info.code\n  WHERE DATE > '2021-01-25' AND (\n   basic_info.code > 'specificode'\n  )\n  LIMIT 5000\n);
\n

处理 40 天数据降至 7 秒。

\n

尝试 7:use_result vs store_result

\n

在 mysqlclient 中,默认的 cursor 类在底层使用 store_result,即将每一次查询的结果一股脑存下来备用。相比之下 use_result 就人性化很多,先放在 server 里有需要再取,也符合 fetchmany 的初衷。

\n

使用默认 Cursor 类的 profile 结果:

\n

\"before\"

\n

使用 SSCursor 类的 profile 结果(橘色那块是 execute,剩下那块是另一个与 SQL 无关的函数,也就是上图较深灰的部分):

\n

\"after\"

","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/tmux-setup/index.json b/assets/data/post/tmux-setup/index.json index 756548082..67b7d738c 100644 --- a/assets/data/post/tmux-setup/index.json +++ b/assets/data/post/tmux-setup/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"67","title":"Introduce and Setup Tmux","summary":"\n

Setup and introduce Tmux so that I can use it

\n","body":"\n

Cheat Sheet

\n

\"\"

\n

Image from https://linuxacademy.com

\n

Enable 256 color vim

\n

From https://superuser.com/a/1459904

\n

First, outside tmux, you need:

\n
\n
\n \n shell\n
export TERM=xterm-256color
\n

in .tmux.conf:

\n
\n
\n \n shell\n
set -g default-terminal \"screen-256color\"
\n

And don't forget to source config manually:

\n
\n
\n \n shell\n
tmux source-file ~/.tmux.conf
\n

All above make sure tmux have full 256 colors support.

\n

And in your vimrc, add:

\n
\n
\n \n viml\n
if exists(\"$TMUX\")\n    set t_Co=256\n    set notermguicolors\nelse\n    set termguicolors\nendif
\n

Enable True Color for vim

\n

From tmux/tmux#1246 and https://github.com/lifepillar/vim-solarized8#troubleshooting

\n

Basically,

\n
\n
\n \n viml\n
let &t_8f = \"\\<Esc>[38;2;%lu;%lu;%lum\"\nlet &t_8b = \"\\<Esc>[48;2;%lu;%lu;%lum\"\nset termguicolors
\n

will do the trick.

\n

Resize Panes on Keyboard

\n
\n

You may just use mouse though

\n
\n

This assumes that you've hit ctrl + b and : to get to the command prompt

\n
:resize-pane -D (Resizes the current pane down)\n:resize-pane -U (Resizes the current pane upward)\n:resize-pane -L (Resizes the current pane left)\n:resize-pane -R (Resizes the current pane right)\n:resize-pane -D 10 (Resizes the current pane down by 10 cells)\n:resize-pane -U 10 (Resizes the current pane upward by 10 cells)\n:resize-pane -L 10 (Resizes the current pane left by 10 cells)\n:resize-pane -R 10 (Resizes the current pane right by 10 cells)\n
\n

Border Chaos

\n

If I split the panes vertically, the first column of the right pane is overwritten by the border.

\n

\"chaos\"

\n

It turns out to be the old problem with MinTTY. As you can see on the top-middle of the screenshot, the border is displayed in full width (2 chars), with introduced the chaos.

\n

To solve it, inspired by mintty/mintty#615, all you need to do is Options → Text → Locale: C (Or any language with sane character width). And the broken vim airline is solved too!

\n

\"fixed\"

","createdAt":"2020-01-21T13:55:42.000Z","lastEditedAt":"2020-07-10T12:46:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"67","title":"Introduce and Setup Tmux","summary":"\n

Setup and introduce Tmux so that I can use it

\n","body":"\n

Cheat Sheet

\n

\"\"

\n

Image from https://linuxacademy.com

\n

Enable 256 color vim

\n

From https://superuser.com/a/1459904

\n

First, outside tmux, you need:

\n
\n
\n \n shell\n
export TERM=xterm-256color
\n

in .tmux.conf:

\n
\n
\n \n shell\n
set -g default-terminal \"screen-256color\"
\n

And don't forget to source config manually:

\n
\n
\n \n shell\n
tmux source-file ~/.tmux.conf
\n

All above make sure tmux have full 256 colors support.

\n

And in your vimrc, add:

\n
\n
\n \n viml\n
if exists(\"$TMUX\")\n    set t_Co=256\n    set notermguicolors\nelse\n    set termguicolors\nendif
\n

Enable True Color for vim

\n

From tmux/tmux#1246 and https://github.com/lifepillar/vim-solarized8#troubleshooting

\n

Basically,

\n
\n
\n \n viml\n
let &t_8f = \"\\<Esc>[38;2;%lu;%lu;%lum\"\nlet &t_8b = \"\\<Esc>[48;2;%lu;%lu;%lum\"\nset termguicolors
\n

will do the trick.

\n

Resize Panes on Keyboard

\n
\n

You may just use mouse though

\n
\n

This assumes that you've hit ctrl + b and : to get to the command prompt

\n
:resize-pane -D (Resizes the current pane down)\n:resize-pane -U (Resizes the current pane upward)\n:resize-pane -L (Resizes the current pane left)\n:resize-pane -R (Resizes the current pane right)\n:resize-pane -D 10 (Resizes the current pane down by 10 cells)\n:resize-pane -U 10 (Resizes the current pane upward by 10 cells)\n:resize-pane -L 10 (Resizes the current pane left by 10 cells)\n:resize-pane -R 10 (Resizes the current pane right by 10 cells)\n
\n

Border Chaos

\n

If I split the panes vertically, the first column of the right pane is overwritten by the border.

\n

\"chaos\"

\n

It turns out to be the old problem with MinTTY. As you can see on the top-middle of the screenshot, the border is displayed in full width (2 chars), with introduced the chaos.

\n

To solve it, inspired by mintty/mintty#615, all you need to do is Options → Text → Locale: C (Or any language with sane character width). And the broken vim airline is solved too!

\n

\"fixed\"

","createdAt":"2020-01-21T13:55:42.000Z","lastEditedAt":"2020-07-10T12:46:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vim-cht/index.json b/assets/data/post/vim-cht/index.json index 1fb86406e..6ee28defe 100644 --- a/assets/data/post/vim-cht/index.json +++ b/assets/data/post/vim-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"85","title":"Vim Cheatsheet","summary":"","body":"\n

Getting help

\n

ctrl+] to follow link, ctrl+t to trace back.

\n

Quit all

\n
\n
\n \n viml\n
:qa
\n

Scroll the terminal

\n

From https://stackoverflow.com/a/50545253/8810271

\n

ctrl+w N (notice the capital N) to enter terminal normal mode. You can even search in the terminal output!

\n

And hit either i or a to enter insert mode.

\n

Open terminal / help page verically

\n

Use the :vert[ical] command modifier:

\n
\n
\n \n viml\n
:vert term\n:vert help ex
\n

Enter normal mode for command history

\n

CTRL-F q: q/ q?

\n

Paste yanked text into the Vim command line

\n

From https://stackoverflow.com/a/3997110/8810271

\n

Hit Ctrl-R then \". If you have literal control characters in what you have yanked, use Ctrl-R, Ctrl-O, \".

\n

PS: this Stack Overflow answer is excellent, maybe I will translate it into Chinese later.

\n

Excute command on matched line

\n

:h global for more information

\n
:[range]g[lobal]/{pattern}/[cmd]\n
\n

Execute the Ex command [cmd] (default \":p\") on the lines within [range] where {pattern} matches.

\n

For pattern not match, use :g! or :v instead. You can use another charater as delimiter or even nest g and v.

\n

If you want to excute normal commands, just :g/This line/norm 3dd.

\n

What is Ex mode?

\n

Switch to \"Ex\" mode. This is a bit like typing \":\" commands one after another, except:

\n\n

Therefore, Ex command can simply be considered as command.

\n

Measure startup time

\n
\n
\n \n shell\n
vim --startuptime vim.log
","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"85","title":"Vim Cheatsheet","summary":"","body":"\n

Getting help

\n

ctrl+] to follow link, ctrl+t to trace back.

\n

Quit all

\n
\n
\n \n viml\n
:qa
\n

Scroll the terminal

\n

From https://stackoverflow.com/a/50545253/8810271

\n

ctrl+w N (notice the capital N) to enter terminal normal mode. You can even search in the terminal output!

\n

And hit either i or a to enter insert mode.

\n

Open terminal / help page verically

\n

Use the :vert[ical] command modifier:

\n
\n
\n \n viml\n
:vert term\n:vert help ex
\n

Enter normal mode for command history

\n

CTRL-F q: q/ q?

\n

Paste yanked text into the Vim command line

\n

From https://stackoverflow.com/a/3997110/8810271

\n

Hit Ctrl-R then \". If you have literal control characters in what you have yanked, use Ctrl-R, Ctrl-O, \".

\n

PS: this Stack Overflow answer is excellent, maybe I will translate it into Chinese later.

\n

Excute command on matched line

\n

:h global for more information

\n
:[range]g[lobal]/{pattern}/[cmd]\n
\n

Execute the Ex command [cmd] (default \":p\") on the lines within [range] where {pattern} matches.

\n

For pattern not match, use :g! or :v instead. You can use another charater as delimiter or even nest g and v.

\n

If you want to excute normal commands, just :g/This line/norm 3dd.

\n

What is Ex mode?

\n

Switch to \"Ex\" mode. This is a bit like typing \":\" commands one after another, except:

\n\n

Therefore, Ex command can simply be considered as command.

\n

Measure startup time

\n
\n
\n \n shell\n
vim --startuptime vim.log
","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vue-pwa-1/index.json b/assets/data/post/vue-pwa-1/index.json index 93a408d70..20a01b9ab 100644 --- a/assets/data/post/vue-pwa-1/index.json +++ b/assets/data/post/vue-pwa-1/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"68","title":"用 Vue 做 PWA(一):开始","summary":"\n

Vue 党快速上手 PWA

\n","body":"\n

什么是 PWA

\n

其全称为 Progressive Web Apps,可赋予网页原生 App 的各种优点。本人水平有限,不再赘述。对我而言最重要的是本地存储 + 离线可看,还有消息推送。

\n

为什么 PWA

\n

当然是不想学 Android 和 iOS 啦!学会前端一下搞定桌面、Android、iOS 三端,岂不美哉!

\n

如何开始

\n

这里就不说怎么安装 vue-cli 了。这里以 vue-cli-4 为例。

\n

如果是已有的项目,如下,记得先 commit,Vue 会改动代码

\n
\n
\n \n shell\n
# 创建 名为 test 的项目,一般默认即可\nvue create test\n\n# 添加 PWA 功能\nvue add pwa
\n

直接新建 PWA 项目(该方法已过时):

\n
\n
\n \n shell\n
npm install -g @vue/cli-init # 使用 init 需要安装\nvue init pwa test
\n

看看加了什么

\n

运行vue add pwa后,会输出修改的文件。如果直接新建项目并且 Git 配置正确,Vue 会自动初始化提交。这时再添加 PWA,可使用git status查看。输出略有不同

\n

输出:

\n
The following files have been updated / added:\n\n public/img/icons/android-chrome-192x192.png\n public/img/icons/android-chrome-512x512.png\n public/img/icons/apple-touch-icon-120x120.png\n ...\n public/img/icons/favicon-32x32.png\n public/img/icons/msapplication-icon-144x144.png\n public/img/icons/mstile-150x150.png\n public/img/icons/safari-pinned-tab.svg\n public/robots.txt\n src/registerServiceWorker.js\n package-lock.json\n package.json\n src/main.js\n
\n

git status输出:

\n
On branch master\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   package-lock.json\n        modified:   package.json\n        modified:   src/main.js\n\nUntracked files:\n  (use \"git add <file>...\" to include in what will be committed)\n\n        public/img/\n        public/robots.txt\n        src/registerServiceWorker.js\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n
\n\n

配置 PWA

\n
\n

😜 TL;DR

\n

一切默认也可,你可以不用做任何配置。

\n
\n

在项目根目录下新建vue.config.js进行配置

\n

配置以文档为准

\n\n

最简单的情况下,可考虑如下配置:

\n
\n
\n \n js\n
module.exports = {\n  pwa: {\n    name: 'test',\n    themeColor: '#4c89fe',\n    msTileColor: '#4c89fe',\n    manifestOptions: {\n      start_url: '.',\n      background_color: '#4c89fe'\n    },\n    workboxPluginMode: 'GenerateSW',\n    workboxOptions: {\n    }\n  }\n}
\n

名字和颜色涉及添加至桌面的应用名,及桌面进入的启动页面的长相。

\n

workboxPluginMode: 'GenerateSW'就是自动生成 Service Worker,也是默认操作。具体要求就如workboxOptions。这里根据默认,一股脑 precache 了所有东西,可以达到离线可看的目的。

\n

如果部署环境不在网站根目录,还需加上:

\n
\n
\n \n js\n
publicPath: process.env.PUB_PATH || '/'
\n
\n

部署时要:

\n
\n
\n \n batchfile\n
set PUB_PATH=\"/test/\"\nnpm run build
\n
\n
\n \n shell\n
PUB_PATH='/test/' npm run build
\n
\n

比下面的更优(马上看到为什么):

\n
\n
\n \n js\n
publicPath: process.env.NODE_ENV === 'production' ? '/URLPrefix/' : '/'
\n

本地测试

\n
\n

⚠️ 避免踩坑

\n

使用 devserver 是看不到想要的结果的,Service Worker 也显示不能正常工作。因为 dev 环境下使用的是“傻子” Service Worker,啥都不存(不然开发是时候不得心态崩)。

\n

但是npm run build再开本地服务器,或使用 GitHub Pages 部署后,Android Chrome 成功跳出安装至桌面的提示。

\n
\n
\n
\n \n shell\n
npm run build
\n
\n

提示

\n

如果如上第二种配置了publicPath,此时要设置NODE_ENV 不为production,但是非production不能成功注册 Service Worker。所以 publicPath依赖的其实不是NODE_ENV

\n
\n

可以使用browser-sync作为本地服务器。

\n
\n
\n \n shell\n
npm install -g browser-sync\nbrowser-sync dist
\n

如果成功配置,Chrome 导航栏会出现$\\oplus$字样,console 会输出成功缓存。注意仅在 localhost 会有哦

\n
Service worker has been registered.\nregisterServiceWorker.js:20 New content is downloading.\nlogger.mjs:44 workbox Precaching 7 files.\nlogger.mjs:44 View newly precached URLs.\nregisterServiceWorker.js:17 Content has been cached for offline use.\nregisterServiceWorker.js:8 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\n
\n

再次打开,会输出成功加载

\n
workbox Precaching is responding to: /css/app.b706d8fd.css\nlogger.mjs:44 workbox Precaching is responding to: /css/chunk-vendors.bb30aab5.css\nlogger.mjs:44 workbox Precaching is responding to: /js/app.23a81c05.js\nlogger.mjs:44 workbox Precaching is responding to: /js/chunk-vendors.57affefc.js\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\nlogger.mjs:44 workbox Precaching is responding to: /manifest.json\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 Service worker has been registered.\n
\n

然后呢

\n

恭喜你有了 PWA 的基本配置,像正常一样开发 Vue 应用,初级阶段也没什么问题。

","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"68","title":"用 Vue 做 PWA(一):开始","summary":"\n

Vue 党快速上手 PWA

\n","body":"\n

什么是 PWA

\n

其全称为 Progressive Web Apps,可赋予网页原生 App 的各种优点。本人水平有限,不再赘述。对我而言最重要的是本地存储 + 离线可看,还有消息推送。

\n

为什么 PWA

\n

当然是不想学 Android 和 iOS 啦!学会前端一下搞定桌面、Android、iOS 三端,岂不美哉!

\n

如何开始

\n

这里就不说怎么安装 vue-cli 了。这里以 vue-cli-4 为例。

\n

如果是已有的项目,如下,记得先 commit,Vue 会改动代码

\n
\n
\n \n shell\n
# 创建 名为 test 的项目,一般默认即可\nvue create test\n\n# 添加 PWA 功能\nvue add pwa
\n

直接新建 PWA 项目(该方法已过时):

\n
\n
\n \n shell\n
npm install -g @vue/cli-init # 使用 init 需要安装\nvue init pwa test
\n

看看加了什么

\n

运行vue add pwa后,会输出修改的文件。如果直接新建项目并且 Git 配置正确,Vue 会自动初始化提交。这时再添加 PWA,可使用git status查看。输出略有不同

\n

输出:

\n
The following files have been updated / added:\n\n public/img/icons/android-chrome-192x192.png\n public/img/icons/android-chrome-512x512.png\n public/img/icons/apple-touch-icon-120x120.png\n ...\n public/img/icons/favicon-32x32.png\n public/img/icons/msapplication-icon-144x144.png\n public/img/icons/mstile-150x150.png\n public/img/icons/safari-pinned-tab.svg\n public/robots.txt\n src/registerServiceWorker.js\n package-lock.json\n package.json\n src/main.js\n
\n

git status输出:

\n
On branch master\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   package-lock.json\n        modified:   package.json\n        modified:   src/main.js\n\nUntracked files:\n  (use \"git add <file>...\" to include in what will be committed)\n\n        public/img/\n        public/robots.txt\n        src/registerServiceWorker.js\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n
\n\n

配置 PWA

\n
\n

😜 TL;DR

\n

一切默认也可,你可以不用做任何配置。

\n
\n

在项目根目录下新建vue.config.js进行配置

\n

配置以文档为准

\n\n

最简单的情况下,可考虑如下配置:

\n
\n
\n \n js\n
module.exports = {\n  pwa: {\n    name: 'test',\n    themeColor: '#4c89fe',\n    msTileColor: '#4c89fe',\n    manifestOptions: {\n      start_url: '.',\n      background_color: '#4c89fe'\n    },\n    workboxPluginMode: 'GenerateSW',\n    workboxOptions: {\n    }\n  }\n}
\n

名字和颜色涉及添加至桌面的应用名,及桌面进入的启动页面的长相。

\n

workboxPluginMode: 'GenerateSW'就是自动生成 Service Worker,也是默认操作。具体要求就如workboxOptions。这里根据默认,一股脑 precache 了所有东西,可以达到离线可看的目的。

\n

如果部署环境不在网站根目录,还需加上:

\n
\n
\n \n js\n
publicPath: process.env.PUB_PATH || '/'
\n
\n

部署时要:

\n
\n
\n \n batchfile\n
set PUB_PATH=\"/test/\"\nnpm run build
\n
\n
\n \n shell\n
PUB_PATH='/test/' npm run build
\n
\n

比下面的更优(马上看到为什么):

\n
\n
\n \n js\n
publicPath: process.env.NODE_ENV === 'production' ? '/URLPrefix/' : '/'
\n

本地测试

\n
\n

⚠️ 避免踩坑

\n

使用 devserver 是看不到想要的结果的,Service Worker 也显示不能正常工作。因为 dev 环境下使用的是“傻子” Service Worker,啥都不存(不然开发是时候不得心态崩)。

\n

但是npm run build再开本地服务器,或使用 GitHub Pages 部署后,Android Chrome 成功跳出安装至桌面的提示。

\n
\n
\n
\n \n shell\n
npm run build
\n
\n

提示

\n

如果如上第二种配置了publicPath,此时要设置NODE_ENV 不为production,但是非production不能成功注册 Service Worker。所以 publicPath依赖的其实不是NODE_ENV

\n
\n

可以使用browser-sync作为本地服务器。

\n
\n
\n \n shell\n
npm install -g browser-sync\nbrowser-sync dist
\n

如果成功配置,Chrome 导航栏会出现$\\oplus$字样,console 会输出成功缓存。注意仅在 localhost 会有哦

\n
Service worker has been registered.\nregisterServiceWorker.js:20 New content is downloading.\nlogger.mjs:44 workbox Precaching 7 files.\nlogger.mjs:44 View newly precached URLs.\nregisterServiceWorker.js:17 Content has been cached for offline use.\nregisterServiceWorker.js:8 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\n
\n

再次打开,会输出成功加载

\n
workbox Precaching is responding to: /css/app.b706d8fd.css\nlogger.mjs:44 workbox Precaching is responding to: /css/chunk-vendors.bb30aab5.css\nlogger.mjs:44 workbox Precaching is responding to: /js/app.23a81c05.js\nlogger.mjs:44 workbox Precaching is responding to: /js/chunk-vendors.57affefc.js\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\nlogger.mjs:44 workbox Precaching is responding to: /manifest.json\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 Service worker has been registered.\n
\n

然后呢

\n

恭喜你有了 PWA 的基本配置,像正常一样开发 Vue 应用,初级阶段也没什么问题。

","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vue-pwa-3/index.json b/assets/data/post/vue-pwa-3/index.json index cf7b1758c..d553d225f 100644 --- a/assets/data/post/vue-pwa-3/index.json +++ b/assets/data/post/vue-pwa-3/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","body":"\n

理解 service worker 的生命周期,可以帮助掌握其行为,以达到更好的体验。

\n

本文主要概括 https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle

\n

理解 service worker 的“哲学”

\n

概括来讲就是离线优先和一致性。离线优先没说的,一致性体现在:

\n
    \n
  1. 页面打开是什么(用不用 service worker,用哪个版本的)就一直是什么
  2. \n
  3. 所有(同一个作用域内的)标签页用同一个版本的 service worker
  4. \n
  5. 当然新的不能影响正在运行的旧的 service worker
  6. \n
\n

知识补充:precache 机制

\n

Precache 简单讲就是在 service worker 安装的时候预先下载需要的文件( JS, CSS 之类),保证安装成功的时候这些文件都已经缓存完毕,一旦 service worker 激活即可直接从 cache 中获取。

\n

cli-plugin-pwa 用到了 workbox-webpack-plugin。之所以成为 webpack-plugin,就是为了自动将打包出来的文件 precache。

\n

安装成功不代表可用

\n

按照第一条,由于第一次打开时是无 service worker 的,所以即使安装成功了,激活了,请求也不会走 service worker。必须刷新页面才行。

\n

\"sw-install\"

\n

不过可以用选项 clientsClaim 可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。

\n

Scope

\n

当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js,那默认作用域就是 /assets/js/。解决方法有二:

\n
    \n
  1. sw.js 放到应用根目录下
  2. \n
  3. 注册的时候说明 register('/assets/js/sw.js', { scope: '/' })\n
    \n
    \n \n js\n
    // 或者翻译成 Vue 的\nregister('/service-worker.js', {\n  registrationOptions: { scope: './' },\n   // ...\n})
    \n
  4. \n
\n

注意如果你的应用在 /app/ 下,注册了 /app/sw.js,但是某一次以 /app (没有末尾斜杠)访问主页,那么 service worker 是不会起作用的。

\n

什么时候更新

\n

这个简单情况很简单,只要访问到作用域内的页面即可触发浏览器查看更新。

\n

不过 pushsync 的事件在一定条件下也可触发,但这是直接写 InjectManifest 的大佬的事情了。

\n

还可以手动更新:

\n
\n
\n \n js\n
navigator.serviceWorker.register('/sw.js').then(reg => {\n  // sometime later…\n  reg.update();\n})
\n

或者翻译成 Vue 的:

\n
\n
\n \n js\n
import { register } from 'register-service-worker'\n\nregister('/service-worker.js', {\n  registered (registration) {\n    console.log('Service worker has been registered.')\n\n    registration.update()\n    // Or\n    setTimeout(registration.update.bind(registration), 10000)\n  }\n})
\n

更新完要重开才有效果

\n

为什么安装是刷新就可以,但是更新是重开?因为在刷新的时候,旧页面和新页面是有交叠的,也就是说旧的 service worker 没法放手,要一直抓着,直到页面关闭。

\n

\"sw-update\"

\n

所以结合起来,要(正常)更新 service worker(也就是更新应用版本,因为 precache 机制),需要打开页面,稍等一会,等待新的 service worker 安装完成后

\n

不过(在新的 service worker 上)用 skipWaiting 选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。

\n

更新这么麻烦,开发测试的时候等不起啊

\n

勾上 Update on reload

\n

这样每次刷新或访问新的页面都会触发更新,且无论 service worker 是否有变化都会重新安装并立即生效。

\n

\"update-on-reload\"

\n

手动 Skip waiting

\n

\"skip-waiting\"

\n

强制刷新

\n

不仅在开发时有用,还可以作为一个选项提供给用户,方便解决设计出错或用不上新功能给用户带来的烦恼。

\n
\n
\n \n js\n
location.reload(true)
","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","body":"\n

理解 service worker 的生命周期,可以帮助掌握其行为,以达到更好的体验。

\n

本文主要概括 https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle

\n

理解 service worker 的“哲学”

\n

概括来讲就是离线优先和一致性。离线优先没说的,一致性体现在:

\n
    \n
  1. 页面打开是什么(用不用 service worker,用哪个版本的)就一直是什么
  2. \n
  3. 所有(同一个作用域内的)标签页用同一个版本的 service worker
  4. \n
  5. 当然新的不能影响正在运行的旧的 service worker
  6. \n
\n

知识补充:precache 机制

\n

Precache 简单讲就是在 service worker 安装的时候预先下载需要的文件( JS, CSS 之类),保证安装成功的时候这些文件都已经缓存完毕,一旦 service worker 激活即可直接从 cache 中获取。

\n

cli-plugin-pwa 用到了 workbox-webpack-plugin。之所以成为 webpack-plugin,就是为了自动将打包出来的文件 precache。

\n

安装成功不代表可用

\n

按照第一条,由于第一次打开时是无 service worker 的,所以即使安装成功了,激活了,请求也不会走 service worker。必须刷新页面才行。

\n

\"sw-install\"

\n

不过可以用选项 clientsClaim 可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。

\n

Scope

\n

当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js,那默认作用域就是 /assets/js/。解决方法有二:

\n
    \n
  1. sw.js 放到应用根目录下
  2. \n
  3. 注册的时候说明 register('/assets/js/sw.js', { scope: '/' })\n
    \n
    \n \n js\n
    // 或者翻译成 Vue 的\nregister('/service-worker.js', {\n  registrationOptions: { scope: './' },\n   // ...\n})
    \n
  4. \n
\n

注意如果你的应用在 /app/ 下,注册了 /app/sw.js,但是某一次以 /app (没有末尾斜杠)访问主页,那么 service worker 是不会起作用的。

\n

什么时候更新

\n

这个简单情况很简单,只要访问到作用域内的页面即可触发浏览器查看更新。

\n

不过 pushsync 的事件在一定条件下也可触发,但这是直接写 InjectManifest 的大佬的事情了。

\n

还可以手动更新:

\n
\n
\n \n js\n
navigator.serviceWorker.register('/sw.js').then(reg => {\n  // sometime later…\n  reg.update();\n})
\n

或者翻译成 Vue 的:

\n
\n
\n \n js\n
import { register } from 'register-service-worker'\n\nregister('/service-worker.js', {\n  registered (registration) {\n    console.log('Service worker has been registered.')\n\n    registration.update()\n    // Or\n    setTimeout(registration.update.bind(registration), 10000)\n  }\n})
\n

更新完要重开才有效果

\n

为什么安装是刷新就可以,但是更新是重开?因为在刷新的时候,旧页面和新页面是有交叠的,也就是说旧的 service worker 没法放手,要一直抓着,直到页面关闭。

\n

\"sw-update\"

\n

所以结合起来,要(正常)更新 service worker(也就是更新应用版本,因为 precache 机制),需要打开页面,稍等一会,等待新的 service worker 安装完成后

\n

不过(在新的 service worker 上)用 skipWaiting 选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。

\n

更新这么麻烦,开发测试的时候等不起啊

\n

勾上 Update on reload

\n

这样每次刷新或访问新的页面都会触发更新,且无论 service worker 是否有变化都会重新安装并立即生效。

\n

\"update-on-reload\"

\n

手动 Skip waiting

\n

\"skip-waiting\"

\n

强制刷新

\n

不仅在开发时有用,还可以作为一个选项提供给用户,方便解决设计出错或用不上新功能给用户带来的烦恼。

\n
\n
\n \n js\n
location.reload(true)
","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/wsl-cht/index.json b/assets/data/post/wsl-cht/index.json index 01666f1e5..cc3770854 100644 --- a/assets/data/post/wsl-cht/index.json +++ b/assets/data/post/wsl-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"175","title":"WSL Cheatsheet","summary":"\n

WSL issues or tricks I had to search for again and again

\n","body":"\n

No Network

\n
ping: socket: Operation not permitted\nTemporary failure in name resolution\n
\n

Solution 1: resolv.conf

\n

microsoft/WSL#6404 (comment)

\n
\n

It seems lounching VSCode daemon messes things up

\n

Setup below helped me, thanks guys:

\n
    \n
  1. /etc/wsl.conf
  2. \n
\n
[network]\ngenerateResolvConf = false\n
\n
    \n
  1. Shutdown wsl
  2. \n
\n
wsl --shutdown\n
\n
    \n
  1. Start wsl, delete /etc/resolv.conf symbolic link and create a file instead:
    \n/etc/resolv.conf
  2. \n
\n
nameserver 8.8.8.8\n
\n
\n

But this solution ruins mDNS.

\n

Solution 2: Check Firewall

\n

If the DNS works after disabling Firewall, try allowing 172.16.0.0/12 inbound in Windows Defender.

\n

Run Linux GUI apps on the Windows Subsystem for Linux

\n

https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps

\n

Hyper-V & Reserved Ports

\n

https://hungyi.net/posts/wsl2-reserved-ports/

\n

Run netsh int ipv4 show excludedportrange protocol=tcp to show reserved ports.

\n
    \n
  1. Run netsh int ipv4 set dynamic tcp start=51001 num=5000 to reset the dynamic port range.
  2. \n
  3. Run reg add HKLM\\SYSTEM\\CurrentControlSet\\Services\\hns\\State /v EnableExcludedPortRange /d 0 /f to disable the HNS port exclusion behavior.
  4. \n
  5. Reboot
  6. \n
","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"175","title":"WSL Cheatsheet","summary":"\n

WSL issues or tricks I had to search for again and again

\n","body":"\n

No Network

\n
ping: socket: Operation not permitted\nTemporary failure in name resolution\n
\n

Solution 1: resolv.conf

\n

microsoft/WSL#6404 (comment)

\n
\n

It seems lounching VSCode daemon messes things up

\n

Setup below helped me, thanks guys:

\n
    \n
  1. /etc/wsl.conf
  2. \n
\n
[network]\ngenerateResolvConf = false\n
\n
    \n
  1. Shutdown wsl
  2. \n
\n
wsl --shutdown\n
\n
    \n
  1. Start wsl, delete /etc/resolv.conf symbolic link and create a file instead:
    \n/etc/resolv.conf
  2. \n
\n
nameserver 8.8.8.8\n
\n
\n

But this solution ruins mDNS.

\n

Solution 2: Check Firewall

\n

If the DNS works after disabling Firewall, try allowing 172.16.0.0/12 inbound in Windows Defender.

\n

Run Linux GUI apps on the Windows Subsystem for Linux

\n

https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps

\n

Hyper-V & Reserved Ports

\n

https://hungyi.net/posts/wsl2-reserved-ports/

\n

Run netsh int ipv4 show excludedportrange protocol=tcp to show reserved ports.

\n
    \n
  1. Run netsh int ipv4 set dynamic tcp start=51001 num=5000 to reset the dynamic port range.
  2. \n
  3. Run reg add HKLM\\SYSTEM\\CurrentControlSet\\Services\\hns\\State /v EnableExcludedPortRange /d 0 /f to disable the HNS port exclusion behavior.
  4. \n
  5. Reboot
  6. \n
","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/wtf-c/index.json b/assets/data/post/wtf-c/index.json index cda5e76d3..6eb2106da 100644 --- a/assets/data/post/wtf-c/index.json +++ b/assets/data/post/wtf-c/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"26","title":"WTFs in C","summary":"\n

为了备战变态的计算概论考试, 不得已要研究一下 C 的奇奇怪怪的东西

\n","body":"\n

UPDATE: 看起来今年的计概考试并没有类似往年题一样的未定义行为出现,可以说今年收敛了一些。故这里说,zy 和 chj 比起来,还是排雷 chj 😁

\n

除非有说明,均在 gcc 下测试

\n

Nice websites

\n\n

往年题?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(x) -x*x+\nint main()\n{\n    int i,j,x,n,k;\n    x=1;\n    n=2;\n    k=3;\n    i=-f(-x+n)-k;\n    j=--x+n*-x+n+-k;\n    \n    printf(\"%d\\n%d\\n\",i,j);\n}
\n

其中,gcc -E认为的是

\n
\n
\n \n c\n
i=- - -x+n*-x+n+-k;
\n

注意到 C 的运算符处理和空格有关,故结果为

\n
-4\n-1\n
\n

而 VC 认为的是

\n
\n
\n \n c\n
i=- --x+n*-x+n+-k;
\n

注意到第二句时 x 已自减,故结果为

\n
-1\n0\n
\n

h(x) != g(x) ?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(a,b) a##b\n#define g(a)   #a\n#define h(a) g(a)\n\nint main()\n{\n  printf(\"%s\\n\",h(f(1,2)));\n  printf(\"%s\\n\",g(f(1,2)));\n  return 0;\n}
\n
\n

https://stackoverflow.com/a/4368983

\n

A single '#' will create a string from the given argument, regardless of what that argument contains, while the double '##' will create a new token by concatenating the arguments.

\n
\n

An occurrence of a parameter in a function-like macro, unless it is the operand of # or ##, is expanded before substituting it and rescanning the whole for further expansion. Because g's parameter is the operand of #, the argument is not expanded but instead immediately stringified (\"f(1,2)\"). Because h's parameter is not the operand of # nor ##, the argument is first expanded (12), then substituted (g(12)), then rescanning and further expansion occurs (\"12\").

\n
\n

也就是说,碰到###后,宏就不会递归展开

\n

No print?

\n
\n
\n \n c\n
#include<stdio.h>\n\n#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))\nint array[] = {23,34,12,17,204,99,16};\n\nint main()\n{\n  int d;\n\n  for(d=-1;d <= (TOTAL_ELEMENTS-2);d++)\n      printf(\"%d\\n\",array[d+1]);\n\n  return 0;\n}
\n

这是非常神奇的, 但是和宏没什么关系。

\n

首先,sizeof返回的是 unsigned long int, dint 类型,这两个比较的时候会比较迷,现行的是 unsigned preserving 策略,即把 int 变成 unsigned int,再行比较。

\n

于是TOTAL_ELEMENTS-2还是unsigned long int,而 -1 就变成巨大无比,条件自然就不可能成立了。

\n

所以呢,把TOTAL_ELEMENTS换成由unsigned int a=7;定义的a,还是一样的。

\n

还有一点有意思的是:

\n
\n

https://stackoverflow.com/questions/2084949

\n

On a platform with 32bit int with e.g.

\n
\n
\n \n c\n
int x = -1;\nunsigned y = 0xffffffff;
\n

the expression x == y would yield 1 because through the \"usual arithmetic conversions\" the value of x is converted to unsigned and thus to 0xffffffff.

\n

The expression (unsigned int)x == y is 1 as well. The only difference is that you do the conversion explicitly with a cast.

\n

The expression x == (int)y will most likely be 1 as well because converting 0xffffffff to int yields -1 on most platforms (two's complement negatives). Strictly speaking this is implementation-defined behavior and thus might vary on different platforms.

\n
\n

Safest is to check that the number is in range before casting:

\n
\n
\n \n c\n
if (x >= 0 && ((unsigned int)x) == y)
\n
\n

continue in do...while

\n
\n
\n \n c\n
#include<stdio.h>\n\nenum {false,true};\n\nint main()\n{\n    int i=1;\n    do {\n        printf(\"%d\\n\",i);\n        i++;\n        if(i < 15)\n            continue;\n    }while(false);\n    return 0;\n}
\n

跟在后面的while也是循环控制的一种,所以continue不会跳过while

\n

拓展知识:how for equals while?

\n
\n

Why does it tend to get into an infinite loop if I use continue in a while loop, but works fine in a for loop?
\nThe loop-counter increment i++ gets ignored in while loop if I use it after continue, but it works if it is in for loop.

\n

If continue ignores subsequent statements, then why doesn't it ignore the third statement of the for loop then, which contains the counter increment i++? Isn't the third statement of for loop subsequent to continue as well and should be ignored, given the third statement of for loop is executed after the loop body?

\n
\n
\n \n c\n
while(i<10)   //causes infinite loop\n{\n    ...\n    continue\n    i++\n    ...\n}\n\nfor(i=0;i<10;i++)  //works fine and exits after 10 iterations\n{\n    ...\n    continue\n    ...\n}
\n
\n

The reason is because the continue statement will short-circuit the statements that follow it in the loop body. Since the way you wrote the while loop has the increment statement following the continue statement, it gets short-circuited. You can solve this by changing your while loop.

\n

A lot of text books claim that:

\n
\n
\n \n c\n
for (i = 0; i < N; ++i) {\n    /*...*/\n}
\n

is equivalent to:

\n
\n
\n \n c\n
i = 0;\nwhile (i < N) {\n    /*...*/\n    ++i;\n}
\n

But, in reality, it is really like:

\n
\n
\n \n c\n
j = 0;\nwhile ((i = j++) < N) {\n    /*...*/\n}
\n

Or, to be a little more pedantic:

\n
\n
\n \n c\n
i = 0;\nif (i < 10) do {\n    /*...*/\n} while (++i, (i < 10));
\n

These are more equivalent, since now if the body of the while has a continue, the increment still occurs, just like in a for. The latter alternative only executes the increment after the iteration has completed, just like for (the former executes the increment before the iteration, deferring to save it in i until after the iteration).

\n
\n

著名的变态题

\n
\n
\n \n c\n
#include<stdio.h>\n\nint main() {\n    int i = 1;\n    printf(\"%d\", ++i+ ++i+ ++i);\n}
\n

著名的 ALE(bushi) 如是说:

\n
\n

operation on 'i' may be undefined

\n
\n

Stackoverflow 如是说

\n
\n

https://stackoverflow.com/a/2989771/8810271

\n

Modifying the value of i more than once without a sequence point in between the modifications results in undefined behavior. So, the results of your code are undefined.

\n
\n

简单翻译成人话就是,想一口气多次改变变量值的行为是未定义的。所以考试考一个未定义的东西有什么用呢?

\n

所以“往年题”部分提到的例子也是未定义的。

\n

讲道理说,不同主流编译器在同样的计算机上,使用相同的合法的程序,产生不同的结果,要么是编译器的附加功能,要么就是未定义的运算了吧!

\n

这只是普通的标签

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=10;\n    switch(a)\n    {\n        case '1':\n            printf(\"ONE\\n\");\n            break;\n        case '2':\n            printf(\"TWO\\n\");\n            break;\n        defau1t:\n            printf(\"NONE\\n\");\n    }\n    return 0;\n}
\n

坑就在deefaultl打成了1😂

\n

居然编译器给过了!?

\n

原来假的defau1t被处理成了一个标签供goto使用,casedefault也类似于 C 里的标签,共用类似的语法,毕竟case本身就可以直接跳到任何地方,包括if里面。

\n

case真的可以随便跳吗?

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=1;\n    switch(a)\n    {   int b=20;\n        case 1: printf(\"b is %d\\n\",b);\n                break;\n        default:printf(\"b is %d\\n\",b);\n                break;\n    }\n    return 0;\n}
\n

跳过了变量初始化就会报错了,编译都过不了

","createdAt":"2020-01-01T08:59:08.000Z","lastEditedAt":"2020-06-30T07:43:45.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"26","title":"WTFs in C","summary":"\n

为了备战变态的计算概论考试, 不得已要研究一下 C 的奇奇怪怪的东西

\n","body":"\n

UPDATE: 看起来今年的计概考试并没有类似往年题一样的未定义行为出现,可以说今年收敛了一些。故这里说,zy 和 chj 比起来,还是排雷 chj 😁

\n

除非有说明,均在 gcc 下测试

\n

Nice websites

\n\n

往年题?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(x) -x*x+\nint main()\n{\n    int i,j,x,n,k;\n    x=1;\n    n=2;\n    k=3;\n    i=-f(-x+n)-k;\n    j=--x+n*-x+n+-k;\n    \n    printf(\"%d\\n%d\\n\",i,j);\n}
\n

其中,gcc -E认为的是

\n
\n
\n \n c\n
i=- - -x+n*-x+n+-k;
\n

注意到 C 的运算符处理和空格有关,故结果为

\n
-4\n-1\n
\n

而 VC 认为的是

\n
\n
\n \n c\n
i=- --x+n*-x+n+-k;
\n

注意到第二句时 x 已自减,故结果为

\n
-1\n0\n
\n

h(x) != g(x) ?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(a,b) a##b\n#define g(a)   #a\n#define h(a) g(a)\n\nint main()\n{\n  printf(\"%s\\n\",h(f(1,2)));\n  printf(\"%s\\n\",g(f(1,2)));\n  return 0;\n}
\n
\n

https://stackoverflow.com/a/4368983

\n

A single '#' will create a string from the given argument, regardless of what that argument contains, while the double '##' will create a new token by concatenating the arguments.

\n
\n

An occurrence of a parameter in a function-like macro, unless it is the operand of # or ##, is expanded before substituting it and rescanning the whole for further expansion. Because g's parameter is the operand of #, the argument is not expanded but instead immediately stringified (\"f(1,2)\"). Because h's parameter is not the operand of # nor ##, the argument is first expanded (12), then substituted (g(12)), then rescanning and further expansion occurs (\"12\").

\n
\n

也就是说,碰到###后,宏就不会递归展开

\n

No print?

\n
\n
\n \n c\n
#include<stdio.h>\n\n#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))\nint array[] = {23,34,12,17,204,99,16};\n\nint main()\n{\n  int d;\n\n  for(d=-1;d <= (TOTAL_ELEMENTS-2);d++)\n      printf(\"%d\\n\",array[d+1]);\n\n  return 0;\n}
\n

这是非常神奇的, 但是和宏没什么关系。

\n

首先,sizeof返回的是 unsigned long int, dint 类型,这两个比较的时候会比较迷,现行的是 unsigned preserving 策略,即把 int 变成 unsigned int,再行比较。

\n

于是TOTAL_ELEMENTS-2还是unsigned long int,而 -1 就变成巨大无比,条件自然就不可能成立了。

\n

所以呢,把TOTAL_ELEMENTS换成由unsigned int a=7;定义的a,还是一样的。

\n

还有一点有意思的是:

\n
\n

https://stackoverflow.com/questions/2084949

\n

On a platform with 32bit int with e.g.

\n
\n
\n \n c\n
int x = -1;\nunsigned y = 0xffffffff;
\n

the expression x == y would yield 1 because through the \"usual arithmetic conversions\" the value of x is converted to unsigned and thus to 0xffffffff.

\n

The expression (unsigned int)x == y is 1 as well. The only difference is that you do the conversion explicitly with a cast.

\n

The expression x == (int)y will most likely be 1 as well because converting 0xffffffff to int yields -1 on most platforms (two's complement negatives). Strictly speaking this is implementation-defined behavior and thus might vary on different platforms.

\n
\n

Safest is to check that the number is in range before casting:

\n
\n
\n \n c\n
if (x >= 0 && ((unsigned int)x) == y)
\n
\n

continue in do...while

\n
\n
\n \n c\n
#include<stdio.h>\n\nenum {false,true};\n\nint main()\n{\n    int i=1;\n    do {\n        printf(\"%d\\n\",i);\n        i++;\n        if(i < 15)\n            continue;\n    }while(false);\n    return 0;\n}
\n

跟在后面的while也是循环控制的一种,所以continue不会跳过while

\n

拓展知识:how for equals while?

\n
\n

Why does it tend to get into an infinite loop if I use continue in a while loop, but works fine in a for loop?
\nThe loop-counter increment i++ gets ignored in while loop if I use it after continue, but it works if it is in for loop.

\n

If continue ignores subsequent statements, then why doesn't it ignore the third statement of the for loop then, which contains the counter increment i++? Isn't the third statement of for loop subsequent to continue as well and should be ignored, given the third statement of for loop is executed after the loop body?

\n
\n
\n \n c\n
while(i<10)   //causes infinite loop\n{\n    ...\n    continue\n    i++\n    ...\n}\n\nfor(i=0;i<10;i++)  //works fine and exits after 10 iterations\n{\n    ...\n    continue\n    ...\n}
\n
\n

The reason is because the continue statement will short-circuit the statements that follow it in the loop body. Since the way you wrote the while loop has the increment statement following the continue statement, it gets short-circuited. You can solve this by changing your while loop.

\n

A lot of text books claim that:

\n
\n
\n \n c\n
for (i = 0; i < N; ++i) {\n    /*...*/\n}
\n

is equivalent to:

\n
\n
\n \n c\n
i = 0;\nwhile (i < N) {\n    /*...*/\n    ++i;\n}
\n

But, in reality, it is really like:

\n
\n
\n \n c\n
j = 0;\nwhile ((i = j++) < N) {\n    /*...*/\n}
\n

Or, to be a little more pedantic:

\n
\n
\n \n c\n
i = 0;\nif (i < 10) do {\n    /*...*/\n} while (++i, (i < 10));
\n

These are more equivalent, since now if the body of the while has a continue, the increment still occurs, just like in a for. The latter alternative only executes the increment after the iteration has completed, just like for (the former executes the increment before the iteration, deferring to save it in i until after the iteration).

\n
\n

著名的变态题

\n
\n
\n \n c\n
#include<stdio.h>\n\nint main() {\n    int i = 1;\n    printf(\"%d\", ++i+ ++i+ ++i);\n}
\n

著名的 ALE(bushi) 如是说:

\n
\n

operation on 'i' may be undefined

\n
\n

Stackoverflow 如是说

\n
\n

https://stackoverflow.com/a/2989771/8810271

\n

Modifying the value of i more than once without a sequence point in between the modifications results in undefined behavior. So, the results of your code are undefined.

\n
\n

简单翻译成人话就是,想一口气多次改变变量值的行为是未定义的。所以考试考一个未定义的东西有什么用呢?

\n

所以“往年题”部分提到的例子也是未定义的。

\n

讲道理说,不同主流编译器在同样的计算机上,使用相同的合法的程序,产生不同的结果,要么是编译器的附加功能,要么就是未定义的运算了吧!

\n

这只是普通的标签

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=10;\n    switch(a)\n    {\n        case '1':\n            printf(\"ONE\\n\");\n            break;\n        case '2':\n            printf(\"TWO\\n\");\n            break;\n        defau1t:\n            printf(\"NONE\\n\");\n    }\n    return 0;\n}
\n

坑就在deefaultl打成了1😂

\n

居然编译器给过了!?

\n

原来假的defau1t被处理成了一个标签供goto使用,casedefault也类似于 C 里的标签,共用类似的语法,毕竟case本身就可以直接跳到任何地方,包括if里面。

\n

case真的可以随便跳吗?

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=1;\n    switch(a)\n    {   int b=20;\n        case 1: printf(\"b is %d\\n\",b);\n                break;\n        default:printf(\"b is %d\\n\",b);\n                break;\n    }\n    return 0;\n}
\n

跳过了变量初始化就会报错了,编译都过不了

","createdAt":"2020-01-01T08:59:08.000Z","lastEditedAt":"2020-06-30T07:43:45.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/wtf-hugo/index.json b/assets/data/post/wtf-hugo/index.json index 952e22d9e..20c736f89 100644 --- a/assets/data/post/wtf-hugo/index.json +++ b/assets/data/post/wtf-hugo/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"28","title":"谜之 Hugo | AC's Blog","summary":"","body":"\n

UPDATE: Hugo 是什么垃圾?现已弃坑。

\n

对新手不友好的 Hugo

\n

在之前 PKUCard 操作之后加了几个 Github 好友,发现很多(显然是主观感受)都在用 Hugo,也感觉之前用的 Mkdocs 不是像样的博客,早有换掉的意思。于是近期就开始上手试了一下。

\n

说 Hugo 之前先说一下 Python 的 Pelican。Pelican 也是一个博客写作应用,但是因为用户量不够,主题样式太少,并且主题的功能也不健全,(说白了就是不合胃口,)于是就很难受。

\n

不用 Hugo 不知道,用了才发现原来 Markdown 渲染可以有这么快。但是当我按照教程,选了一个主题,按照提示一步一步来的时候。。?!为什么是白页面?怎么还是白的?把配置文件全部照抄还不行?错误提示都是虚假的!令人抓狂

\n

无奈只好变成全部复制下来,再进行改动。。

\n

从 v0.55 到 v0.59 一直有 Bug 的 Hugo

\n
\n

Source block following a plain list ends up in the last plain list item #556

\n

kaushalmodi commented [on 2 Aug]
\nHello,

\n

Here's a minimal example to reproduce the issue:

\n\n
\n
This block should be out of <ul>...</ul>\nAnd also out of blockquote\n
\n
\n

Ref: https://discourse.gohugo.io/t/possible-regression-in-v0-55-5-regarding-lists-containing-code-blocks/18502/4?u=kaushalmodi

\n

/cc @aignas as you seem to be the last person to work on this Blackfriday plain-list/code block issue :)

\n
\n

来,你瞅瞅,现在还是这个样子的😭

\n

v0.60.0 UPDATE: 见“新的引擎”一节

\n

这么不稳定的 Markdown 引擎,没法和 Python 比 😏

\n

仿佛急冰的 toc 功能

\n

Hugo 自带的目录功能简直了,因为只能从 h1 开始,否则:

\n\n

自带的功能,老兄!这种事情都做得出来!NOT PYTHONIC AT ALL!

\n

最后我找到了https://gist.github.com/skyzyx/a796d66f6a124f057f3374eff0b3f99a @looeee 的代码,基本可以满足需求。

\n

修改找到的 TOC 代码

\n

如果你要更改标题级别的范围,可以把[2-4]替换成你想要的。

\n

而且这位老兄的代码就是会把标题里的一些诸如don't的标点干掉,也不会保留加粗的格式。

\n

解决办法就是去掉planify | htmlEscape, 并在

\n
\n
\n \n html\n
{{ $cleanedID := replace (replace $id \"id=\\\"\" \"\") \"\\\"\" \"\" }}
\n

后加入

\n
\n
\n \n html\n
{{- $header := replaceRE \"<h[2-4].*?>((.|\\n])+?)</h[2-4]>\" \"$1\" $header -}}
\n

谜之 Template 语法

\n

看了 Hugo 的之后这才发现 Jinja 原来是真的漂亮简洁

\n

就问你and (ConditionA) (ConditionB) 是人话吗?没有not是什么?没有括号也叫 Function?怕是 Shell 用多了?

\n

not的正确打开方式:

\n
\n
\n \n html\n
{{ if eq (reflect.IsMap site.Params.address) false }}
\n

的确。。反人类

\n

奇奇怪怪的 Toml

\n

放着好好的 YAML 和 JSON 不用,突然冒出来一个 TOML。好像 Markdown 前面加 YAML 已经很常用了,然后跟我说用 TOML,不觉得等于号 有点 丑?不觉得 引号 有点 丑?虽然在做配置文件方面可能有独到之处,对不起,Vim 原生不支持,一键秒杀颜值。

\n

新的引擎

\n

突然,Hugo 给我换了一个引擎 https://github.com/gohugoio/hugo/releases/tag/v0.60.0

\n

使用了新的引擎之后之前的问题消失了,但是变成了默认忽略 HTML,需要在config.toml里做修改

\n
\n
\n \n toml\n
[markup.goldmark.renderer]\n    unsafe = true
\n

或者config.yml

\n
\n
\n \n yaml\n
markup:\n  goldmark:\n    renderer:\n      unsafe: true
\n

新引擎,新bug

\n

headerID

\n

Goldmark 在设置 headerID 的时候,默认是仅保留字母和数字,但是这对 CJK 及其不友好。“更有甚者”,该项目的作者是日本人,竟然不考虑对本国文字的支持?!还说,仅保留字母和数字是合适的默认行为。Excuse me? 不应该原封不动保留 Unicode 才是好的默认行为吗?

\n

据说会在 Hugo v0.63.0 解决,那就拭目以待吧。。

\n

render_link

\n

本来说好可以支持 link 的 Markdown hook 的,但是呢,只支持[text](url)型,不支持<url>型。。[摊手.jpg]

\n

而且,说好引用其他 Markdown 文档的 render_link 实现居然还有点复杂。。还好找到了官方实现,不然又是大坑。。

\n

Hugo theme,注定又是一个面向 GitHub 编程。

\n

TOC 新回复

\n

我又针对 toc 写了一篇博客 hugo-toc,然后有去原来的 gist 上评论了,受到了@looeee 的回复。他说。。。他已经弃坑 Hugo 了,使用 js 解决问题。

\n

我。。。

","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"28","title":"谜之 Hugo | AC's Blog","summary":"","body":"\n

UPDATE: Hugo 是什么垃圾?现已弃坑。

\n

对新手不友好的 Hugo

\n

在之前 PKUCard 操作之后加了几个 Github 好友,发现很多(显然是主观感受)都在用 Hugo,也感觉之前用的 Mkdocs 不是像样的博客,早有换掉的意思。于是近期就开始上手试了一下。

\n

说 Hugo 之前先说一下 Python 的 Pelican。Pelican 也是一个博客写作应用,但是因为用户量不够,主题样式太少,并且主题的功能也不健全,(说白了就是不合胃口,)于是就很难受。

\n

不用 Hugo 不知道,用了才发现原来 Markdown 渲染可以有这么快。但是当我按照教程,选了一个主题,按照提示一步一步来的时候。。?!为什么是白页面?怎么还是白的?把配置文件全部照抄还不行?错误提示都是虚假的!令人抓狂

\n

无奈只好变成全部复制下来,再进行改动。。

\n

从 v0.55 到 v0.59 一直有 Bug 的 Hugo

\n
\n

Source block following a plain list ends up in the last plain list item #556

\n

kaushalmodi commented [on 2 Aug]
\nHello,

\n

Here's a minimal example to reproduce the issue:

\n\n
\n
This block should be out of <ul>...</ul>\nAnd also out of blockquote\n
\n
\n

Ref: https://discourse.gohugo.io/t/possible-regression-in-v0-55-5-regarding-lists-containing-code-blocks/18502/4?u=kaushalmodi

\n

/cc @aignas as you seem to be the last person to work on this Blackfriday plain-list/code block issue :)

\n
\n

来,你瞅瞅,现在还是这个样子的😭

\n

v0.60.0 UPDATE: 见“新的引擎”一节

\n

这么不稳定的 Markdown 引擎,没法和 Python 比 😏

\n

仿佛急冰的 toc 功能

\n

Hugo 自带的目录功能简直了,因为只能从 h1 开始,否则:

\n\n

自带的功能,老兄!这种事情都做得出来!NOT PYTHONIC AT ALL!

\n

最后我找到了https://gist.github.com/skyzyx/a796d66f6a124f057f3374eff0b3f99a @looeee 的代码,基本可以满足需求。

\n

修改找到的 TOC 代码

\n

如果你要更改标题级别的范围,可以把[2-4]替换成你想要的。

\n

而且这位老兄的代码就是会把标题里的一些诸如don't的标点干掉,也不会保留加粗的格式。

\n

解决办法就是去掉planify | htmlEscape, 并在

\n
\n
\n \n html\n
{{ $cleanedID := replace (replace $id \"id=\\\"\" \"\") \"\\\"\" \"\" }}
\n

后加入

\n
\n
\n \n html\n
{{- $header := replaceRE \"<h[2-4].*?>((.|\\n])+?)</h[2-4]>\" \"$1\" $header -}}
\n

谜之 Template 语法

\n

看了 Hugo 的之后这才发现 Jinja 原来是真的漂亮简洁

\n

就问你and (ConditionA) (ConditionB) 是人话吗?没有not是什么?没有括号也叫 Function?怕是 Shell 用多了?

\n

not的正确打开方式:

\n
\n
\n \n html\n
{{ if eq (reflect.IsMap site.Params.address) false }}
\n

的确。。反人类

\n

奇奇怪怪的 Toml

\n

放着好好的 YAML 和 JSON 不用,突然冒出来一个 TOML。好像 Markdown 前面加 YAML 已经很常用了,然后跟我说用 TOML,不觉得等于号 有点 丑?不觉得 引号 有点 丑?虽然在做配置文件方面可能有独到之处,对不起,Vim 原生不支持,一键秒杀颜值。

\n

新的引擎

\n

突然,Hugo 给我换了一个引擎 https://github.com/gohugoio/hugo/releases/tag/v0.60.0

\n

使用了新的引擎之后之前的问题消失了,但是变成了默认忽略 HTML,需要在config.toml里做修改

\n
\n
\n \n toml\n
[markup.goldmark.renderer]\n    unsafe = true
\n

或者config.yml

\n
\n
\n \n yaml\n
markup:\n  goldmark:\n    renderer:\n      unsafe: true
\n

新引擎,新bug

\n

headerID

\n

Goldmark 在设置 headerID 的时候,默认是仅保留字母和数字,但是这对 CJK 及其不友好。“更有甚者”,该项目的作者是日本人,竟然不考虑对本国文字的支持?!还说,仅保留字母和数字是合适的默认行为。Excuse me? 不应该原封不动保留 Unicode 才是好的默认行为吗?

\n

据说会在 Hugo v0.63.0 解决,那就拭目以待吧。。

\n

render_link

\n

本来说好可以支持 link 的 Markdown hook 的,但是呢,只支持[text](url)型,不支持<url>型。。[摊手.jpg]

\n

而且,说好引用其他 Markdown 文档的 render_link 实现居然还有点复杂。。还好找到了官方实现,不然又是大坑。。

\n

Hugo theme,注定又是一个面向 GitHub 编程。

\n

TOC 新回复

\n

我又针对 toc 写了一篇博客 hugo-toc,然后有去原来的 gist 上评论了,受到了@looeee 的回复。他说。。。他已经弃坑 Hugo 了,使用 js 解决问题。

\n

我。。。

","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/blog/cheatsheet/index.html b/blog/cheatsheet/index.html index e11751916..450cab7c0 100644 --- a/blog/cheatsheet/index.html +++ b/blog/cheatsheet/index.html @@ -16,7 +16,9 @@ } -
AC Dustbin
Hit Enter to do fulltext search on GitHub
WSL Cheatsheet
2021-10-05 10:49 2021-10-14 02:17
+
AC Dustbin
Hit Enter to do fulltext search on GitHub
Physics Knowledge Sharing
2022-05-21 11:23 2022-05-21 11:23
+

Sharing some of my physics notes / slides

+
WSL Cheatsheet
2021-10-05 10:49 2021-10-14 02:17

WSL issues or tricks I had to search for again and again

Docker Cheatsheet
2021-07-31 09:58 2022-05-01 13:49

Docker commands are easily forgotten

@@ -33,6 +35,6 @@
FFmpeg Cheatsheet
2020-03-21 23:56 2020-06-30 09:14
换源集合
2020-02-28 14:29 2021-12-09 02:49

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

- + diff --git a/blog/moments/index.html b/blog/moments/index.html index 34e208170..20821e3ae 100644 --- a/blog/moments/index.html +++ b/blog/moments/index.html @@ -16,7 +16,7 @@ } -
AC Dustbin
Hit Enter to do fulltext search on GitHub
免费下网易云会员歌曲 MP3 竟如此简单?
2022-05-01 08:50 2022-05-01 08:50
+
AC Dustbin
Hit Enter to do fulltext search on GitHub
免费下网易云会员歌曲 MP3 竟如此简单?
2022-05-01 08:50 2022-05-01 08:50

我坦白,我是标题党

腾讯会议 Linux 客户端无法正常结束共享的 Workaround
2022-02-27 09:02 2022-02-27 09:02

就改下窗口状态的事儿

diff --git a/blog/programming/index.html b/blog/programming/index.html index ed10a4d92..f7d7cc2a3 100644 --- a/blog/programming/index.html +++ b/blog/programming/index.html @@ -24,7 +24,7 @@ } -
AC Dustbin
Hit Enter to do fulltext search on GitHub
Another Move of My Blog?
2022-04-06 04:00 2022-05-01 08:55
+
AC Dustbin
Hit Enter to do fulltext search on GitHub
Another Move of My Blog?
2022-04-06 04:00 2022-05-01 08:55

It really needs a serious discussion.

`skipWaiting()` with `StaleWhileRevalidate` the right way
2021-06-28 04:31 2021-06-28 10:11

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

diff --git a/index.html b/index.html index a6aad76d9..8b978d009 100644 --- a/index.html +++ b/index.html @@ -20,14 +20,14 @@ } -
AC Dustbin

Hi! Thanks for your interest in my blog!

I'm Allan Chain, a programming hobbyist and open source lover. My primary programming languages are Python and JavaScript. I'm also having (or had) "fun" with

This blog mainly hosts my notes when programming, as well as some other interesting bits.

Blog Entries

Moments
听说我不发朋友圈?
Cheatsheet
One sheet to get them all!
Programming
Exciting or strange things for programming which worth noting

Recent Updates

Docker Cheatsheet
2021-07-31 09:58 2022-05-01 13:49
+
AC Dustbin

Hi! Thanks for your interest in my blog!

I'm Allan Chain, a programming hobbyist and open source lover. My primary programming languages are Python and JavaScript. I'm also having (or had) "fun" with

This blog mainly hosts my notes when programming, as well as some other interesting bits.

Blog Entries

Moments
听说我不发朋友圈?
Cheatsheet
One sheet to get them all!
Programming
Exciting or strange things for programming which worth noting

Recent Updates

Physics Knowledge Sharing
2022-05-21 11:23 2022-05-21 11:23
+

Sharing some of my physics notes / slides

+
Docker Cheatsheet
2021-07-31 09:58 2022-05-01 13:49

Docker commands are easily forgotten

Above image from scmagazine.com

Another Move of My Blog?
2022-04-06 04:00 2022-05-01 08:55

It really needs a serious discussion.

-
免费下网易云会员歌曲 MP3 竟如此简单?
2022-05-01 08:50 2022-05-01 08:50
-

我坦白,我是标题党

-

Friends

ᴍɪᴍɪ
做了一点微小的工作
txtyb
The quieter you be, the more you can hear.
Yixuan-Wang
This is a blоg, not a blοg.
- +

Friends

ᴍɪᴍɪ
做了一点微小的工作
txtyb
The quieter you be, the more you can hear.
Yixuan-Wang
This is a blоg, not a blοg.
+ diff --git a/labels/index.html b/labels/index.html index d3a6fceff..77e935a90 100644 --- a/labels/index.html +++ b/labels/index.html @@ -12,7 +12,7 @@ } - - + + diff --git a/offline/index.html b/offline/index.html index e153d36b6..6927296c4 100644 --- a/offline/index.html +++ b/offline/index.html @@ -12,7 +12,7 @@ } -
AC Dustbin
+
AC Dustbin
diff --git a/post/damn-gpg/index.html b/post/damn-gpg/index.html index 8caa7f287..7970078c5 100644 --- a/post/damn-gpg/index.html +++ b/post/damn-gpg/index.html @@ -68,7 +68,7 @@

And today, when I have succeeded in signing many commits in different repos, I failed to sign this repo...

Type:

-
git config -l
+
git config -l
 

And I saw two user.signingkey there... Interesting ...

One is global and one is local, the local one is introduced in the early age when I configure the GPG key generated by windows locally and forgot to remove it...

@@ -86,7 +86,7 @@ gpg: signing failed: Inappropriate ioctl for device gpg: [stdin]: clear-sign failed: Inappropriate ioctl for device

GPG NEEDS A FOLLISH TTY?!

-
export GPG_TTY=$(tty)
+
export GPG_TTY=$(tty)
 

That solved the problem


@@ -97,12 +97,12 @@ shell
echo "test" | gpg2 --clearsign

again and it shows:

-
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'
+gpg: [stdin]: clear-sign failed: No agent running">
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'
 gpg: can't connect to the agent: IPC connect call failed
 gpg: can't connect to the agent: IPC connect call failed
 gpg: keydb_search failed: No agent running
@@ -128,6 +128,6 @@
 
  • https://stackoverflow.com/questions/41525221
  • https://askubuntu.com/questions/1167333
  • - + diff --git a/post/docker-tag/index.html b/post/docker-tag/index.html index 5f3deccb6..7fa2bba4c 100644 --- a/post/docker-tag/index.html +++ b/post/docker-tag/index.html @@ -53,10 +53,10 @@

    但是天朝。。

    网易和阿里的官方 API 里都要求有 API Key 签名认证之类,比较繁琐

    故先尝试使用 USTC 的镜像,缺点是速度慢、标签不全。。根据一波猜测,得到了 USTC 标签的 API:

    -
    https://docker.mirrors.ustc.edu.cn/v2/library/nginx/tags/list
    +
    https://docker.mirrors.ustc.edu.cn/v2/library/nginx/tags/list
     

    同理又意外获得了网易的,标签全!

    -
    https://hub-mirror.c.163.com/v2/library/nginx/tags/list
    +
    https://hub-mirror.c.163.com/v2/library/nginx/tags/list
     

    于是根据 https://stackoverflow.com/a/39454426/8810271 代码改编:

    @@ -91,6 +91,6 @@ fi echo "${tags}"
    - + diff --git a/post/ffmpeg-cht/index.html b/post/ffmpeg-cht/index.html index 145590949..e4e94c6ce 100644 --- a/post/ffmpeg-cht/index.html +++ b/post/ffmpeg-cht/index.html @@ -42,7 +42,7 @@

    Get help from command line

    ffmpeg -h
    Some useful help here -
    Get help from command line -an disable audio -acodec codec force audio codec ('copy' to copy stream) -vol volume change audio volume (256=normal) --af filter_graph set audio filters">
    Global options (affect whole program instead of just one file:
    +-af filter_graph    set audio filters">
    Global options (affect whole program instead of just one file:
     -loglevel loglevel  set logging level
     -v loglevel         set logging level
     -y                  overwrite output files
    @@ -117,8 +117,8 @@ 

    拼接

    shell
    ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4

    list.txt 内容为:

    -
    file 1.mp4
    +
    file 1.mp4
     file 2.mp4
     

    生成静音的音频

    @@ -166,6 +166,6 @@

    直接倍速

    shell
    ffmpeg -itsscale 0.01666 -i input.mkv -c copy output.mkv
    - + diff --git a/post/freq-formula/index.html b/post/freq-formula/index.html index aa007f627..9ecc50c4b 100644 --- a/post/freq-formula/index.html +++ b/post/freq-formula/index.html @@ -37,25 +37,37 @@

    东西慢慢加进来好了,顺便看看公式渲染的情况。

    柱坐标系下的梯度散度旋度公式
    -

    $$
    -(\nabla u)_r = \frac{\partial u}{\partial r},\ (\nabla u)_{\theta} = \frac 1 r \frac{\partial u}{\partial \theta},\ (\nabla u)_z = \frac{\partial u}{\partial z}
    -$$

    -

    $$
    -\nabla \cdot \mathbf{A} = \frac 1 r \frac{\partial (r A_r)}{\partial r} + \frac 1 r \frac{\partial A_{\theta}}{\partial \theta} + \frac{\partial A_z}{\partial z}
    -$$

    -

    $$
    -\nabla \times \mathbf{A} = \frac 1 r \left[\frac{\partial A_z}{\partial \theta} - \frac{\partial (rA_{\theta})}{\partial z}\right]\mathbf{e_r} + \left[\frac{\partial A_r}{\partial z} - \frac{\partial A_z}{\partial r}\right]\mathbf{e_{\theta}} + \frac 1 r \left[\frac{\partial (rA_{\theta})}{\partial r} - \frac{\partial A_r}{\partial \theta}\right]\mathbf{e_z}
    -$$

    +

    $$ + +(\nabla u)_r = \frac{\partial u}{\partial r},\ (\nabla u)_{\theta} = \frac 1 r \frac{\partial u}{\partial \theta},\ (\nabla u)_z = \frac{\partial u}{\partial z} + +$$

    +

    $$ + +\nabla \cdot \mathbf{A} = \frac 1 r \frac{\partial (r A_r)}{\partial r} + \frac 1 r \frac{\partial A_{\theta}}{\partial \theta} + \frac{\partial A_z}{\partial z} + +$$

    +

    $$ + +\nabla \times \mathbf{A} = \frac 1 r \left[\frac{\partial A_z}{\partial \theta} - \frac{\partial (rA_{\theta})}{\partial z}\right]\mathbf{e_r} + \left[\frac{\partial A_r}{\partial z} - \frac{\partial A_z}{\partial r}\right]\mathbf{e_{\theta}} + \frac 1 r \left[\frac{\partial (rA_{\theta})}{\partial r} - \frac{\partial A_r}{\partial \theta}\right]\mathbf{e_z} + +$$

    球坐标系下的梯度散度旋度公式
    -

    $$
    -(\nabla u)_r = \frac{\partial u}{\partial r},\ (\nabla u)_{\theta} = \frac 1 r \frac{\partial u}{\partial \theta},\ (\nabla u)_{\phi} = \frac{1}{r \sin \theta} \frac{\partial u}{\partial \phi}
    -$$

    -

    $$
    -\nabla \cdot \mathbf{A} = \frac{1}{r^2} \frac{\partial (r^2 A_r)}{\partial r} + \frac{1}{r \sin \theta} \frac{\partial (\sin \theta A_{\theta})}{\partial \theta} + \frac{1}{r \sin \theta} \frac{\partial A_{\phi}}{\partial \phi}
    -$$

    -

    $$
    -\nabla \times \mathbf{A} = \frac{1}{r \sin \theta} \left[\frac{\partial (\sin \theta A_{\phi})}{\partial \theta} - \frac{\partial A_{\theta}}{\partial \phi}\right]\mathbf{e_r} + \frac 1 r \left[\frac{1}{\sin \theta}\frac{\partial A_r}{\partial \phi} - \frac{\partial (r A_{\phi})}{\partial r}\right]\mathbf{e_{\theta}} + \frac 1 r \left[\frac{\partial (rA_{\theta})}{\partial r} - \frac{\partial A_r}{\partial \theta}\right]\mathbf{e_{\phi}}
    -$$

    - +

    $$ + +(\nabla u)_r = \frac{\partial u}{\partial r},\ (\nabla u)_{\theta} = \frac 1 r \frac{\partial u}{\partial \theta},\ (\nabla u)_{\phi} = \frac{1}{r \sin \theta} \frac{\partial u}{\partial \phi} + +$$

    +

    $$ + +\nabla \cdot \mathbf{A} = \frac{1}{r^2} \frac{\partial (r^2 A_r)}{\partial r} + \frac{1}{r \sin \theta} \frac{\partial (\sin \theta A_{\theta})}{\partial \theta} + \frac{1}{r \sin \theta} \frac{\partial A_{\phi}}{\partial \phi} + +$$

    +

    $$ + +\nabla \times \mathbf{A} = \frac{1}{r \sin \theta} \left[\frac{\partial (\sin \theta A_{\phi})}{\partial \theta} - \frac{\partial A_{\theta}}{\partial \phi}\right]\mathbf{e_r} + \frac 1 r \left[\frac{1}{\sin \theta}\frac{\partial A_r}{\partial \phi} - \frac{\partial (r A_{\phi})}{\partial r}\right]\mathbf{e_{\theta}} + \frac 1 r \left[\frac{\partial (rA_{\theta})}{\partial r} - \frac{\partial A_r}{\partial \theta}\right]\mathbf{e_{\phi}} + +$$

    + diff --git a/post/from-python-to-js/index.html b/post/from-python-to-js/index.html index 43d3742c5..56c0a4c79 100644 --- a/post/from-python-to-js/index.html +++ b/post/from-python-to-js/index.html @@ -189,7 +189,7 @@

    parseInt v.s. Well, that makes sense. And I'm going to specify a radix when using parseInt.

    And note that Python is more friendly:

    -
    parseInt v.s.
    class int(object)
    + |  4">
    class int(object)
      |  int([x]) -> integer
      |  int(x, base=10) -> integer
      |
    diff --git a/post/get-wechat-emoji/index.html b/post/get-wechat-emoji/index.html
    index e32069df4..11b437fde 100644
    --- a/post/get-wechat-emoji/index.html
    +++ b/post/get-wechat-emoji/index.html
    @@ -35,7 +35,7 @@
         
    AC Dustbin

    我是如何获得微信内置表情的

    2020-12-28 09:46 2020-12-28 09:58
    -

    在 GitHub 上直接搜微信表情,搜出来的多是 https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/0.gif 系列,但这些表情不仅已经过时,而且不清晰(如 emoji demo),甚至有白底。很不方便。

    +

    在 GitHub 上直接搜微信表情,搜出来的多是 https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/0.gif 系列,但这些表情不仅已经过时,而且不清晰(如 emoji demo),甚至有白底。很不方便。

    微信 APK

    很自然会想到直接从官方网站下载的 APK 提取。但是会发现 APK 里只有在 assets/newemoji 里有一些新的 emoji。分辨率是 64x64 的,很令人满意。

    继续搜索

    @@ -44,6 +44,6 @@

    继续搜索

    光有雪碧图还不够,难道要手动建立对应关系?继续在该项目下搜索就发现 src/emoji/emoji_positon.less 文件里有相应样式。

    注意事项

    使用表情应当遵守微信相应许可。

    - + diff --git a/post/gh-action-cache/index.html b/post/gh-action-cache/index.html index b9d9baf19..59955492f 100644 --- a/post/gh-action-cache/index.html +++ b/post/gh-action-cache/index.html @@ -55,6 +55,6 @@

    Be careful with cache scope

    Caches between two parallel branches are not shared. And tags only have access to caches created in default branch. If you want to share cache between build on tags, you would like to keep cache in master scope. For example, use a update-cache.yml action to keep track of cache.

    My Solution

    Checkout https://github.com/AllanChain/webnav/tree/3ddd12f707fc374f907f5a2ac26aaf98a7c9a3d2/.github/workflows

    - + diff --git a/post/jupyter-raspi/index.html b/post/jupyter-raspi/index.html index f79e163b0..b093774a3 100644 --- a/post/jupyter-raspi/index.html +++ b/post/jupyter-raspi/index.html @@ -143,7 +143,7 @@

    Configuration

    shell
    jt -t oceans16 -T -N -fs 16 -nfs 16 -cellw 90%

    Explanation:

    -
    Configuration

    -dfs DFFONTSIZE pandas dataframe fontsize -ofs OUTFONTSIZE output area fontsize -mathfs MATHFONTSIZE mathjax fontsize (in %) --cellw CELLWIDTH set cell width (px or %)">
    -T, --toolbar         make toolbar visible
    +-cellw CELLWIDTH      set cell width (px or %)">
    -T, --toolbar         make toolbar visible
     -N, --nbname          nb name/logo visible
     -t THEME              theme name to install
     -fs MONOSIZE          code font-size
    @@ -200,6 +200,6 @@ 

    Behind Nginx

    proxy_set_header Connection "upgrade"; proxy_set_header Origin ""; }
    - + diff --git a/post/jupyter-wolfram/index.html b/post/jupyter-wolfram/index.html index f24483206..39a53fe98 100644 --- a/post/jupyter-wolfram/index.html +++ b/post/jupyter-wolfram/index.html @@ -56,7 +56,7 @@

    WTF PATH

    shell
    ./configure-jupyter.wls add

    Error occurred:

    -
    configure-jupyter.wls: Jupyter installation on Environment["PATH"] not found.
    +
    configure-jupyter.wls: Jupyter installation on Environment["PATH"] not found.
     

    But jupyter is in PATH indeed:

    @@ -81,8 +81,8 @@

    WTF PATH

    > ystemFiles/Graphics/Binaries/Linux-ARM:/home/pi/.local/bin:/usr/bin:/bin

    Well, PATH is different...

    As said in ./configure-jupyter.wls help, you can specify jupyter path by

    -
    configure-jupyter.wls add "/absolute/path/to/Wolfram-Engine-binary--not-wolframscript" "path/to/Jupyter-binary"
    +
    configure-jupyter.wls add "/absolute/path/to/Wolfram-Engine-binary--not-wolframscript" "path/to/Jupyter-binary"
             adds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path
     

    If you try the second, you can avoid providing path to Wolfram Engine binary:

    @@ -94,6 +94,6 @@

    WTF PATH

    WTF GFW

    However, it seems to update the paclet from server, which is very slow here...

    Luckily, success.

    - + diff --git a/post/linux-cht/index.html b/post/linux-cht/index.html index cc07ed357..5f3afc760 100644 --- a/post/linux-cht/index.html +++ b/post/linux-cht/index.html @@ -37,7 +37,7 @@

    systemctl

    Where are service files?

    -
    /etc/systemd/system/service-name.service
    +
    /etc/systemd/system/service-name.service
     

    Start at Boot

    @@ -47,8 +47,8 @@

    Start at Boot

    sudo systemctl enable sshd.service

    disable is the opposite.

    Use Environment in Service Unit File

    -
    [Service]
    +
    [Service]
     Environment=PATH=/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
     
    @@ -59,7 +59,7 @@

    Use Environment in Service Unit File

    Unit File Template

    Click to expand -
    Unit File Template #KillMode=mixed [Install] -WantedBy=multi-user.target">
    [Unit]
    +WantedBy=multi-user.target">
    [Unit]
     Description=Jupyter Notebook
     
     [Service]
    @@ -142,7 +142,7 @@ 

    List All Users

    lsof

    LiSt Open Files. A very powerful tool.

    For network checking:

    -
    ls -i [46][protocol][@hostname|hostaddr][:service|port]
    +
    ls -i [46][protocol][@hostname|hostaddr][:service|port]
     

    e.g.

    @@ -164,6 +164,6 @@

    No Wireless Connection After Sleep

    shell
    sudo service network-manager restart
    - + diff --git a/post/markdown-cht/index.html b/post/markdown-cht/index.html index b17070d07..f54f8b3e9 100644 --- a/post/markdown-cht/index.html +++ b/post/markdown-cht/index.html @@ -39,20 +39,20 @@

    Escaping `

    The general principle is, escape ` with more `s.

    For example, if you want to escape a single `, just wrap it with two on each side, and add spaces, that is, `` ` ``.

    If you want to escape three back quotes in a code block, use 4 for the code fence:

    -
    ````
    +````">
    ````
     ```python
     print('hello')
     ```
     ````
     

    Produces:

    -
    ```python
    +```">
    ```python
     print('hello')
     ```
     
    @@ -97,6 +97,6 @@

    Center a Image in GFM

    <img src="https://github.com/favicon.ico"> </p>

    - + diff --git a/post/mysql-emoji/index.html b/post/mysql-emoji/index.html index 2e92466ff..6135ec19e 100644 --- a/post/mysql-emoji/index.html +++ b/post/mysql-emoji/index.html @@ -38,7 +38,7 @@

    UPDATE: See https://docs.sqlalchemy.org/en/13/dialects/mysql.html#unicode for more info

    utf8_bin?

    一开始就套用存储中文姓名的那一套,使用utf8_bin的 collation,觉得 utf8 这种万能的东西直接用就行了。可谁知给我报错:

    -
    mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\xE8\x86\x9C' for column 'text' at row 1
    +
    mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\xE8\x86\x9C' for column 'text' at row 1
     

    蛤?竟有如此操作?

    utf8mb4_unicode_ci?

    @@ -126,6 +126,6 @@

    版本大坑

    MySQL Connector/Python 8.0 is highly recommended for use with MySQL Server 8.0, 5.7, 5.6, and 5.5. Please upgrade to MySQL Connector/Python 8.0.

    好一个recommended,就给我挖这种坑?版本问题害死人!

    - + diff --git a/post/mysql-in-wsl/index.html b/post/mysql-in-wsl/index.html index c39f26398..d9f83f0fe 100644 --- a/post/mysql-in-wsl/index.html +++ b/post/mysql-in-wsl/index.html @@ -50,11 +50,11 @@

    在使用 Windows Subsystem for Linux 时,可能因为缺少对 MySQL 是预置安装,会遇到一些问题

    如果说

    -
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
    +
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
     

    可以试试建立 /var/run/mysqld 目录,并给 mysql 权限

    如果说

    -
    No directory, logging in with HOME=/
    +
    No directory, logging in with HOME=/
     

    可以试试 sudo usermod -d /var/lib/mysql/ mysql


    @@ -81,6 +81,6 @@ sudo rm -rf /etc/mysql /var/lib/mysql sudo apt autoremove sudo apt autoclean
    - + diff --git a/post/physics-share/index.html b/post/physics-share/index.html new file mode 100644 index 000000000..b769b6936 --- /dev/null +++ b/post/physics-share/index.html @@ -0,0 +1,49 @@ + + + + Physics Knowledge Sharing - AC Dustbin + + +
    AC Dustbin

    Physics Knowledge Sharing

    2022-05-21 11:23 2022-05-21 11:23
    +

    All files are available here

    +

    Hartree-Fock method

    +

    Language: 🇨🇳

    +

    My presentation at Quantum Mechanics Seminar

    +

    PDF link

    +

    Quantum Optics and Quantum Computation

    +

    Language: 🇨🇳

    +

    My presentation at Optics Seminar

    +

    PDF link

    + + + diff --git a/post/pkg-source/index.html b/post/pkg-source/index.html index 0699e5dec..7896df4ff 100644 --- a/post/pkg-source/index.html +++ b/post/pkg-source/index.html @@ -58,7 +58,7 @@

    npm

    shell
    yarn config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"

    electron 源

    -
    npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
    +
    npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
     

    docker

    /etc/docker/daemon.json 或者直接 Docker Desktop 的 GUI 界面里修改:

    @@ -84,6 +84,6 @@

    maven

    mavenCentral() } }
    - + diff --git a/post/pwa-skipwaiting/index.html b/post/pwa-skipwaiting/index.html index 091c58582..00850b610 100644 --- a/post/pwa-skipwaiting/index.html +++ b/post/pwa-skipwaiting/index.html @@ -166,21 +166,21 @@

    Complete code for demonstration

  • change service-worker.js, manually reload page and test again
  • You may found something like this in console:

    -
    (index):16 installed 1624852106984
    +activated 1624852110393">
    (index):16 installed 1624852106984
     service-worker.js:7 trigger skipWaiting at 1624852110391
     (index):16 activating 1624852110391
     activated 1624852110393
     

    Then page reloads, the button stay disabled because the new service worker is active and no service worker waiting.

    Open Network tab, you can see slow.json is canceled because of page refresh. Or in Firefox it's directly logged into console:

    -
    installed 1624852139119 normal:16:17
    +Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.">
    installed 1624852139119 normal:16:17
     trigger skipWaiting at 1624852141791 service-worker.js:7:13
     activating 1624852141795 normal:16:17
     activated 1624852141797 normal:16:17
    @@ -225,7 +225,7 @@ 

    Still waiting after skipWaiting, self.addEventListener('message', (event) => { const message = event.data

    open http://localhost:8345/stuck/, repeat the above steps. Here is the example output:

    -
    Still waiting after skipWaiting, workbox-core.dev.js:45 View the final response here. (index):16 activating 1624853116470 (index):16 activated 1624853117482 -Navigated to http://localhost:8345/stuck/">
    (index):16 installed 1624853106784
    +Navigated to http://localhost:8345/stuck/">
    (index):16 installed 1624853106784
     workbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json
     service-worker.js:17 trigger skipWaiting at 1624853112301
     workbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'
    @@ -244,12 +244,12 @@ 

    Still waiting after skipWaiting, Navigated to http://localhost:8345/stuck/

    The new service worker is not activated until the request completes. Even if resource is cached:

    -
    (index):16 installed 1624853346141
    +(index):16 activated 1624853353871">
    (index):16 installed 1624853346141
     workbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json
     workbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'
     service-worker.js:17 trigger skipWaiting at 1624853348949
    @@ -334,14 +334,14 @@ 

    The solution

    } })

    And the (truncated) output:

    -
    (index):16 installed 1624853994162
    +Navigated to http://localhost:8345/solution/">
    (index):16 installed 1624853994162
     workbox-core.dev.js:45 workbox Router is responding to: /solution/slow.json
     service-worker.js:21 trigger skipWaiting at 1624854001606
     workbox-strategies.dev.js:1005 Uncaught (in promise) no-response: The strategy could not generate a response for 'http://localhost:8345/solution/slow.json'. The underlying error is AbortError: The user aborted a request..
    diff --git a/post/python-async-subprocess/index.html b/post/python-async-subprocess/index.html
    index 81f8c70ee..0b65ac328 100644
    --- a/post/python-async-subprocess/index.html
    +++ b/post/python-async-subprocess/index.html
    @@ -92,7 +92,7 @@ 

    Get Your Hands Dirty!

    asyncio.run(main()) elapsed = time.perf_counter() - s print(f"{__file__} executed in {elapsed:0.2f} seconds.")
    -
    One One One Two Two Two aioplay.py executed in 1.00 seconds.
    +
    One One One Two Two Two aioplay.py executed in 1.00 seconds.
     

    只是直接调用count(),会返回<coroutine object count at ...>,而并不会立即执行,这和 JS 不一样。插播 JS:

    @@ -101,11 +101,11 @@

    Get Your Hands Dirty!

    js
    const a = async () => console.log(1)
     a()
    -
    1
    +   ... }">
    1
     Promise {
       undefined,
       domain:
    @@ -132,7 +132,7 @@ 

    Get Your Hands Dirty!

    这又和 JS 不一样。


    如果不使用await asyncio.sleep,而是使用time.sleep来模拟一个阻塞的操作:

    -
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
    +
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
     

    没有切入点,只好顺序执行


    @@ -144,7 +144,7 @@

    Get Your Hands Dirty!

    async def main():
         for _ in range(3):
             await count()
    -
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
    +
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
     

    因为await会等到函数结束后才进行下一步操作。asyncio.gather(count(), count(), count())正是为了让它们一起运行。

    获取命令输出

    @@ -211,7 +211,7 @@

    异步获取命令输出

    # asyncio.set_event_loop(loop) # loop.run_until_complete(get_date()) asyncio.run(main())
    -
    异步获取命令输出 2> 2> --- www.a.shifen.com ping statistics --- 2> 4 packets transmitted, 4 received, 0% packet loss, time 3004ms -2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms">
    1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.
    +2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms">
    1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.
     1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=34.2 ms
     2> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.
     2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=46.6 ms
    @@ -303,10 +303,10 @@ 

    async for?

    asyncio.run(main()) elapsed = time.perf_counter() - s print(f"{__file__} executed in {elapsed:0.2f} seconds.")
    -
    One Two Yeah!
    +aioplay.py executed in 3.00 seconds.">
    One Two Yeah!
     One Two Yeah!
     One Two Yeah!
     aioplay.py executed in 3.00 seconds.
    diff --git a/post/raspi-tricks/index.html b/post/raspi-tricks/index.html
    index 177900e33..b6c3327b3 100644
    --- a/post/raspi-tricks/index.html
    +++ b/post/raspi-tricks/index.html
    @@ -42,7 +42,7 @@ 

    Get the Temperature

    shell
    cat /sys/class/thermal/thermal_zone0/temp

    Outputs:

    -
    29482
    +
    29482
     

    And here is bash script to create temperature PS1

    @@ -74,8 +74,8 @@

    Voltage

    vcgencmd get_throttled

    If voltage is okay, you will get 0x0

    Else, say 0x50000, convert it to binary, and see each bit:

    -
    19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
    +
    19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
      0  1  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
     
    @@ -130,6 +130,6 @@

    Screen on / off

    vcgencmd display_power 1

    1 for on and 0 for off. bare vcgencmd display_power shows current state.

    It is possible to specify a certain display ID. For more info, visit the doc above.

    - + diff --git a/post/showcase/index.html b/post/showcase/index.html index 7d27508ea..e52536562 100644 --- a/post/showcase/index.html +++ b/post/showcase/index.html @@ -58,12 +58,12 @@

    Hello!

    code in head of course!

    -
    code with unspecified language
    +
    code with unspecified language
     
    -
    or code with indent
    +
    or code with indent
     

    More complex heading level!

    -
    A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code
    +
    A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code
     

    And more!

    Paragraph

    @@ -71,10 +71,12 @@
    Small Headers

    Paragraph

    Should Be Visible

    Another paragraph

    -

    Inline $\frac{1}{2 \pi i}$

    -

    $$
    -\int_{- \infty}^{\infty} \frac{1}{x} \mathrm d x
    -$$

    - +

    Inline $\frac{1}{2 \pi i}$

    +

    $$ + +\int_{- \infty}^{\infty} \frac{1}{x} \mathrm d x + +$$

    + diff --git a/post/sql-query-perf/index.html b/post/sql-query-perf/index.html index 3782e1ffb..2c58d6b16 100644 --- a/post/sql-query-perf/index.html +++ b/post/sql-query-perf/index.html @@ -112,7 +112,7 @@

    问题 3:Query Time

    SET GLOBAL slow_query_log_file='/var/log/mysql/sql-slow.log'; SET GLOBAL long_query_time = 1;

    查阅日志可以看到,一条一万数据的 select 花了 7 秒钟才完成。

    -
    Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799
    +
    Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799
     

    为方便调试性能,需要关闭缓存功能,可使用 SELECT SQL_NO_CACHE ... 实现。

    如问题 2 中的 query:

    @@ -174,6 +174,6 @@

    尝试 7:use_result vs before

    使用 SSCursor 类的 profile 结果(橘色那块是 execute,剩下那块是另一个与 SQL 无关的函数,也就是上图较深灰的部分):

    after

    - + diff --git a/post/tmux-setup/index.html b/post/tmux-setup/index.html index 4f5b19e5b..88af129bf 100644 --- a/post/tmux-setup/index.html +++ b/post/tmux-setup/index.html @@ -86,14 +86,14 @@

    Resize Panes on Keyboard

    You may just use mouse though

    This assumes that you've hit ctrl + b and : to get to the command prompt

    -
    :resize-pane -D (Resizes the current pane down)
    +:resize-pane -R 10 (Resizes the current pane right by 10 cells)">
    :resize-pane -D (Resizes the current pane down)
     :resize-pane -U (Resizes the current pane upward)
     :resize-pane -L (Resizes the current pane left)
     :resize-pane -R (Resizes the current pane right)
    @@ -108,6 +108,6 @@ 

    Border Chaos

    It turns out to be the old problem with MinTTY. As you can see on the top-middle of the screenshot, the border is displayed in full width (2 chars), with introduced the chaos.

    To solve it, inspired by mintty/mintty#615, all you need to do is Options → Text → Locale: C (Or any language with sane character width). And the broken vim airline is solved too!

    fixed

    - + diff --git a/post/vim-cht/index.html b/post/vim-cht/index.html index fef519b62..0dec64c52 100644 --- a/post/vim-cht/index.html +++ b/post/vim-cht/index.html @@ -61,7 +61,7 @@

    Paste yanked text into the Vim command line

    PS: this Stack Overflow answer is excellent, maybe I will translate it into Chinese later.

    Excute command on matched line

    :h global for more information

    -
    :[range]g[lobal]/{pattern}/[cmd]
    +
    :[range]g[lobal]/{pattern}/[cmd]
     

    Execute the Ex command [cmd] (default ":p") on the lines within [range] where {pattern} matches.

    For pattern not match, use :g! or :v instead. You can use another charater as delimiter or even nest g and v.

    @@ -81,6 +81,6 @@

    Measure startup time

    shell
    vim --startuptime vim.log
    - + diff --git a/post/vue-pwa-1/index.html b/post/vue-pwa-1/index.html index d7bec9176..4737a830f 100644 --- a/post/vue-pwa-1/index.html +++ b/post/vue-pwa-1/index.html @@ -73,7 +73,7 @@

    如何开始

    看看加了什么

    运行vue add pwa后,会输出修改的文件。如果直接新建项目并且 Git 配置正确,Vue 会自动初始化提交。这时再添加 PWA,可使用git status查看。输出略有不同

    输出:

    -
    看看加了什么 src/registerServiceWorker.js package-lock.json package.json - src/main.js">
    The following files have been updated / added:
    + src/main.js">
    The following files have been updated / added:
     
      public/img/icons/android-chrome-192x192.png
      public/img/icons/android-chrome-512x512.png
    @@ -104,7 +104,7 @@ 

    看看加了什么

    src/main.js

    git status输出:

    -
    看看加了什么 public/robots.txt src/registerServiceWorker.js -no changes added to commit (use "git add" and/or "git commit -a")">
    On branch master
    +no changes added to commit (use "git add" and/or "git commit -a")">
    On branch master
     Changes not staged for commit:
       (use "git add <file>..." to update what will be committed)
       (use "git checkout -- <file>..." to discard changes in working directory)
    @@ -240,14 +240,14 @@ 

    本地测试

    shell
    npm install -g browser-sync
     browser-sync dist
    -

    如果成功配置,Chrome 导航栏会出现$\oplus$字样,console 会输出成功缓存。注意仅在 localhost 会有哦

    -
    如果成功配置,Chrome 导航栏会出现$\oplus$字样,console 会输出成功缓存。注意仅在 localhost 会有哦

    +
    Service worker has been registered.
    +For more details, visit https://goo.gl/AFskqB">
    Service worker has been registered.
     registerServiceWorker.js:20 New content is downloading.
     logger.mjs:44 workbox Precaching 7 files.
     logger.mjs:44 View newly precached URLs.
    @@ -256,14 +256,14 @@ 

    本地测试

    For more details, visit https://goo.gl/AFskqB

    再次打开,会输出成功加载

    -
    workbox Precaching is responding to: /css/app.b706d8fd.css
    +app.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 Service worker has been registered.">
    workbox Precaching is responding to: /css/app.b706d8fd.css
     logger.mjs:44 workbox Precaching is responding to: /css/chunk-vendors.bb30aab5.css
     logger.mjs:44 workbox Precaching is responding to: /js/app.23a81c05.js
     logger.mjs:44 workbox Precaching is responding to: /js/chunk-vendors.57affefc.js
    @@ -274,6 +274,6 @@ 

    本地测试

    然后呢

    恭喜你有了 PWA 的基本配置,像正常一样开发 Vue 应用,初级阶段也没什么问题。

    - + diff --git a/post/vue-pwa-3/index.html b/post/vue-pwa-3/index.html index e916d6b46..331873e52 100644 --- a/post/vue-pwa-3/index.html +++ b/post/vue-pwa-3/index.html @@ -49,7 +49,7 @@

    知识补充:precache 机制

    cli-plugin-pwa 用到了 workbox-webpack-plugin。之所以成为 webpack-plugin,就是为了自动将打包出来的文件 precache。

    安装成功不代表可用

    按照第一条,由于第一次打开时是无 service worker 的,所以即使安装成功了,激活了,请求也不会走 service worker。必须刷新页面才行。

    -

    sw-install

    +

    sw-install

    不过可以用选项 clientsClaim 可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。

    Scope

    当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js,那默认作用域就是 /assets/js/。解决方法有二:

    @@ -98,7 +98,7 @@

    什么时候更新

    })

    更新完要重开才有效果

    为什么安装是刷新就可以,但是更新是重开?因为在刷新的时候,旧页面和新页面是有交叠的,也就是说旧的 service worker 没法放手,要一直抓着,直到页面关闭。

    -

    sw-update

    +

    sw-update

    所以结合起来,要(正常)更新 service worker(也就是更新应用版本,因为 precache 机制),需要打开页面,稍等一会,等待新的 service worker 安装完成后

    不过(在新的 service worker 上)用 skipWaiting 选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。

    更新这么麻烦,开发测试的时候等不起啊

    @@ -114,6 +114,6 @@

    强制刷新

    js
    location.reload(true)
    - + diff --git a/post/wsl-cht/index.html b/post/wsl-cht/index.html index 1089fdece..23208ddb9 100644 --- a/post/wsl-cht/index.html +++ b/post/wsl-cht/index.html @@ -36,8 +36,8 @@

    WSL issues or tricks I had to search for again and again

    No Network

    -
    ping: socket: Operation not permitted
    +
    ping: socket: Operation not permitted
     Temporary failure in name resolution
     

    Solution 1: resolv.conf

    @@ -48,20 +48,20 @@

    Solution 1: resolv.conf

    1. /etc/wsl.conf
    -
    [network]
    +
    [network]
     generateResolvConf = false
     
    1. Shutdown wsl
    -
    wsl --shutdown
    +
    wsl --shutdown
     
    1. Start wsl, delete /etc/resolv.conf symbolic link and create a file instead:
      /etc/resolv.conf
    -
    nameserver 8.8.8.8
    +
    nameserver 8.8.8.8
     

    But this solution ruins mDNS.

    @@ -77,6 +77,6 @@

    Hyper-V & Reserved Ports

  • Run reg add HKLM\SYSTEM\CurrentControlSet\Services\hns\State /v EnableExcludedPortRange /d 0 /f to disable the HNS port exclusion behavior.
  • Reboot
  • - + diff --git a/post/wtf-c/index.html b/post/wtf-c/index.html index b528cc39e..fc8cb9755 100644 --- a/post/wtf-c/index.html +++ b/post/wtf-c/index.html @@ -67,8 +67,8 @@

    往年题?

    c
    i=- - -x+n*-x+n+-k;

    注意到 C 的运算符处理和空格有关,故结果为

    -
    -4
    +
    -4
     -1
     

    而 VC 认为的是

    @@ -78,8 +78,8 @@

    往年题?

    c
    i=- --x+n*-x+n+-k;

    注意到第二句时 x 已自减,故结果为

    -
    -1
    +
    -1
     0
     

    h(x) != g(x) ?

    @@ -300,6 +300,6 @@

    case真的可以随便跳吗?< return 0; }

    跳过了变量初始化就会报错了,编译都过不了

    - + diff --git a/post/wtf-hugo/index.html b/post/wtf-hugo/index.html index 2b0975f42..79d4d9056 100644 --- a/post/wtf-hugo/index.html +++ b/post/wtf-hugo/index.html @@ -50,8 +50,8 @@

    从 v0.55 到 v0.59 一直有 Bug 的 Hugo

  • item 2
  • -
    This block should be out of <ul>...</ul>
    +
    This block should be out of <ul>...</ul>
     And also out of blockquote
     
    @@ -122,6 +122,6 @@

    render_link

    TOC 新回复

    我又针对 toc 写了一篇博客 hugo-toc,然后有去原来的 gist 上评论了,受到了@looeee 的回复。他说。。。他已经弃坑 Hugo 了,使用 js 解决问题。

    我。。。

    - + diff --git a/series/vue-pwa/index.html b/series/vue-pwa/index.html index 7cd25629e..8ba6183a7 100644 --- a/series/vue-pwa/index.html +++ b/series/vue-pwa/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    用 Vue 做 PWA (三):理解生命周期
    2020-09-03 02:40 2020-09-08 03:09
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    用 Vue 做 PWA (三):理解生命周期
    2020-09-03 02:40 2020-09-08 03:09

    虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

    用 Vue 做 PWA(二):Runtime Caching
    2020-04-18 04:49 2021-03-13 00:27

    本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

    diff --git a/service-worker.js b/service-worker.js index db6a045c5..09c22b6af 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1 +1 @@ -if(!self.define){const e=e=>{"require"!==e&&(e+=".js");let s=Promise.resolve();return a[e]||(s=new Promise((async s=>{if("document"in self){const a=document.createElement("script");a.src=e,document.head.appendChild(a),a.onload=s}else importScripts(e),s()}))),s.then((()=>{if(!a[e])throw new Error(`Module ${e} didn’t register its module`);return a[e]}))},s=(s,a)=>{Promise.all(s.map(e)).then((e=>a(1===e.length?e[0]:e)))},a={require:Promise.resolve(s)};self.define=(s,r,i)=>{a[s]||(a[s]=Promise.resolve().then((()=>{let a={};const o={uri:location.origin+s.slice(1)};return Promise.all(r.map((s=>{switch(s){case"exports":return a;case"module":return o;default:return e(s)}}))).then((e=>{const s=i(...e);return a.default||(a.default=s),a}))})))}}define("./service-worker.js",["./workbox-3cd600db"],(function(e){"use strict";e.setCacheNameDetails({prefix:"AC Dustbin"}),self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.precacheAndRoute([{url:"/blog/assets/css/styles.952165db.css",revision:"06fc2195c8d2c3fbfe26534b1261d739"},{url:"/blog/assets/js/196.75ff2e4b.js",revision:"96e826c495ecb90d2cad41b8a204b725"},{url:"/blog/assets/js/416.2edeb43c.js",revision:"725a88db2d2940aa5b41ee355b2558e3"},{url:"/blog/assets/js/46.c41f61e7.js",revision:"9e64bdd4f76787ea3a822bd1ae0a54be"},{url:"/blog/assets/js/app.5017c2a6.js",revision:"bbc44690bd86c3e3b6df9a264b756c9c"},{url:"/blog/assets/js/axios.3232deb7.js",revision:"2f85f60eea893ff722a8844829919fa5"},{url:"/blog/assets/js/core-js.365248a2.js",revision:"a4925fbb1b63decb21a4dfba91f5ac0c"},{url:"/blog/assets/js/gridsome-vendors.a4d1a31b.js",revision:"589f9c7be58ed1099c666949bf2c4a79"},{url:"/blog/assets/js/page--src--pages--index-vue.40826d72.js",revision:"3cedcfb83803971ebde5c4e7ebab7d5e"},{url:"/blog/assets/js/page--src--pages--labels-vue.30c3d8ab.js",revision:"7a02dbad31ac20eb2ced95aca0b1c5d9"},{url:"/blog/assets/js/page--src--pages--offline-vue.e4806600.js",revision:"96eebaac89fca2eaeb307910c5face07"},{url:"/blog/assets/js/page--src--templates--label-vue.46dcc102.js",revision:"9a98f972be22fafa5b8194339d7baf54"},{url:"/blog/assets/js/page--src--templates--post-vue.64a2bae6.js",revision:"1537034e87d56b694fbfd573e8ff57a2"},{url:"/blog/assets/js/runtime.ba56f071.js",revision:"1efd46be3c79d8feabcbbe335df553ba"},{url:"/blog/assets/js/vue-vendors.4d0a8523.js",revision:"58794d543f2edea0b8fa3e4c49dc5010"},{url:"/blog/offline/index.html",revision:"997d4fb6733fa0d5d3ad36b40af3f66a"}],{}),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/blog/offline/index.html"),{allowlist:[/\/$/]})),e.registerRoute(/\/img\/.*/,new e.CacheFirst({cacheName:"GithHub",plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/\/index.json$/,new e.NetworkFirst({cacheName:"Post-Data",networkTimeoutSeconds:3,plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/https:\/\/(cdn\.jsdelivr\.net|fonts\.(gstatic|googleapis)\.com)\/.*/,new e.CacheFirst({cacheName:"CDN",plugins:[new e.ExpirationPlugin({maxEntries:5})]}),"GET")})); +if(!self.define){const e=e=>{"require"!==e&&(e+=".js");let s=Promise.resolve();return a[e]||(s=new Promise((async s=>{if("document"in self){const a=document.createElement("script");a.src=e,document.head.appendChild(a),a.onload=s}else importScripts(e),s()}))),s.then((()=>{if(!a[e])throw new Error(`Module ${e} didn’t register its module`);return a[e]}))},s=(s,a)=>{Promise.all(s.map(e)).then((e=>a(1===e.length?e[0]:e)))},a={require:Promise.resolve(s)};self.define=(s,r,i)=>{a[s]||(a[s]=Promise.resolve().then((()=>{let a={};const b={uri:location.origin+s.slice(1)};return Promise.all(r.map((s=>{switch(s){case"exports":return a;case"module":return b;default:return e(s)}}))).then((e=>{const s=i(...e);return a.default||(a.default=s),a}))})))}}define("./service-worker.js",["./workbox-3cd600db"],(function(e){"use strict";e.setCacheNameDetails({prefix:"AC Dustbin"}),self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.precacheAndRoute([{url:"/blog/assets/css/styles.952165db.css",revision:"06fc2195c8d2c3fbfe26534b1261d739"},{url:"/blog/assets/js/196.75ff2e4b.js",revision:"96e826c495ecb90d2cad41b8a204b725"},{url:"/blog/assets/js/416.2edeb43c.js",revision:"725a88db2d2940aa5b41ee355b2558e3"},{url:"/blog/assets/js/46.c41f61e7.js",revision:"9e64bdd4f76787ea3a822bd1ae0a54be"},{url:"/blog/assets/js/app.5017c2a6.js",revision:"bbc44690bd86c3e3b6df9a264b756c9c"},{url:"/blog/assets/js/axios.3232deb7.js",revision:"2f85f60eea893ff722a8844829919fa5"},{url:"/blog/assets/js/core-js.365248a2.js",revision:"a4925fbb1b63decb21a4dfba91f5ac0c"},{url:"/blog/assets/js/gridsome-vendors.a4d1a31b.js",revision:"589f9c7be58ed1099c666949bf2c4a79"},{url:"/blog/assets/js/page--src--pages--index-vue.40826d72.js",revision:"3cedcfb83803971ebde5c4e7ebab7d5e"},{url:"/blog/assets/js/page--src--pages--labels-vue.30c3d8ab.js",revision:"7a02dbad31ac20eb2ced95aca0b1c5d9"},{url:"/blog/assets/js/page--src--pages--offline-vue.e4806600.js",revision:"96eebaac89fca2eaeb307910c5face07"},{url:"/blog/assets/js/page--src--templates--label-vue.46dcc102.js",revision:"9a98f972be22fafa5b8194339d7baf54"},{url:"/blog/assets/js/page--src--templates--post-vue.64a2bae6.js",revision:"1537034e87d56b694fbfd573e8ff57a2"},{url:"/blog/assets/js/runtime.ba56f071.js",revision:"1efd46be3c79d8feabcbbe335df553ba"},{url:"/blog/assets/js/vue-vendors.4d0a8523.js",revision:"58794d543f2edea0b8fa3e4c49dc5010"},{url:"/blog/offline/index.html",revision:"a373beabd9fcba7653de8ad3179008b1"}],{}),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/blog/offline/index.html"),{allowlist:[/\/$/]})),e.registerRoute(/\/img\/.*/,new e.CacheFirst({cacheName:"GithHub",plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/\/index.json$/,new e.NetworkFirst({cacheName:"Post-Data",networkTimeoutSeconds:3,plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/https:\/\/(cdn\.jsdelivr\.net|fonts\.(gstatic|googleapis)\.com)\/.*/,new e.CacheFirst({cacheName:"CDN",plugins:[new e.ExpirationPlugin({maxEntries:5})]}),"GET")})); diff --git a/tag/android/index.html b/tag/android/index.html index 4049edff7..b3c8da47e 100644 --- a/tag/android/index.html +++ b/tag/android/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)
    2021-03-07 09:34 2022-01-27 12:29
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)
    2021-03-07 09:34 2022-01-27 12:29

    Yes, very simple and... violent. It's about compiling your own copy of Fenix.

    我是如何获得微信内置表情的
    2020-12-28 09:46 2020-12-28 09:58

    授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

    diff --git a/tag/c/index.html b/tag/c/index.html index 3155ed9ec..de3af8109 100644 --- a/tag/c/index.html +++ b/tag/c/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数算环境准备
    2020-02-19 08:50 2020-07-01 13:00
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数算环境准备
    2020-02-19 08:50 2020-07-01 13:00

    g++ & VS Code 党对《数据结构与算法》的环境准备

    有必要解释一下名称的由来:Data Structure and Algorithm

    WTFs in C
    2020-01-01 08:59 2020-06-30 07:43
    diff --git a/tag/docker/index.html b/tag/docker/index.html index 5f55511f2..14ad8cde6 100644 --- a/tag/docker/index.html +++ b/tag/docker/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Docker Cheatsheet
    2021-07-31 09:58 2022-05-01 13:49
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Docker Cheatsheet
    2021-07-31 09:58 2022-05-01 13:49

    Docker commands are easily forgotten

    Above image from scmagazine.com

    列出 USTC Docker 镜像的标签
    2020-02-28 13:38 2020-06-21 12:56
    diff --git a/tag/flask/index.html b/tag/flask/index.html index 68679ec18..260fcde5e 100644 --- a/tag/flask/index.html +++ b/tag/flask/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    我为什么要选 Flask?
    2020-01-03 13:00 2021-02-22 08:02
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    我为什么要选 Flask?
    2020-01-03 13:00 2021-02-22 08:02

    注意,这里不是说 Flask 有多好,而是。。用 Flask 用到怀疑人生!

    diff --git a/tag/gh-action/index.html b/tag/gh-action/index.html index 8be475ab2..b1915f3fa 100644 --- a/tag/gh-action/index.html +++ b/tag/gh-action/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Setup GitHub Action Cache the Right Way
    2020-06-21 09:16 2020-07-06 10:52
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Setup GitHub Action Cache the Right Way
    2020-06-21 09:16 2020-07-06 10:52

    What on earth is happening? Why my cache never hits?

    diff --git a/tag/gridsome/index.html b/tag/gridsome/index.html index c868b6969..deebe3074 100644 --- a/tag/gridsome/index.html +++ b/tag/gridsome/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06

    How I am tring to build a nice gridsome-generated site with PWA support

    虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

    diff --git a/tag/hugo/index.html b/tag/hugo/index.html index 0ee1d645d..01056a393 100644 --- a/tag/hugo/index.html +++ b/tag/hugo/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    谜之 Hugo | AC's Blog
    2020-01-03 13:19 2020-11-02 04:31
    TOC in Hugo
    2019-12-31 15:17 2020-06-30 07:33
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    谜之 Hugo | AC's Blog
    2020-01-03 13:19 2020-11-02 04:31
    TOC in Hugo
    2019-12-31 15:17 2020-06-30 07:33

    What a challenge to build toc in hugo!

    Deploying Hugo with CircleCI
    2019-10-27 12:12 2020-06-30 07:28

    Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

    diff --git a/tag/javascript/index.html b/tag/javascript/index.html index 6c6c876de..14f414dad 100644 --- a/tag/javascript/index.html +++ b/tag/javascript/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    聊一聊 React 的 virtual scroll
    2020-07-10 09:58 2020-07-11 01:08
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    聊一聊 React 的 virtual scroll
    2020-07-10 09:58 2020-07-11 01:08

    也没啥高见,重复一下网上现有的资料而已

    JS 无依赖获取阴历
    2020-02-08 05:46 2020-07-03 02:55

    Intl 是个好东西,但是还不是很强大,浏览器支持上也有 Node 和 Android 的小缺憾。

    diff --git a/tag/jupyter/index.html b/tag/jupyter/index.html index 110eb214c..1077642be 100644 --- a/tag/jupyter/index.html +++ b/tag/jupyter/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter etc. Cheatsheet
    2020-12-05 01:25 2021-09-20 13:32
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter etc. Cheatsheet
    2020-12-05 01:25 2021-09-20 13:32

    主要是一些数据与绘图处理相关

    Jupyter on Raspberry Pi
    2020-02-26 12:20 2020-07-01 12:33
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    diff --git a/tag/linux/index.html b/tag/linux/index.html index 211322573..1c29c0836 100644 --- a/tag/linux/index.html +++ b/tag/linux/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Manjaro Linux 初体验
    2021-04-19 13:50 2021-04-19 14:31
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Manjaro Linux 初体验
    2021-04-19 13:50 2021-04-19 14:31

    偏随笔性质的文章,也没什么新东西,就随便谈谈,直接用母语好了

    Two Points to Notice when Upgrading Ubuntu
    2020-04-26 00:00 2020-06-21 12:55

    Ever feel frustated when a new release is available but it keeps saying no release found?

    diff --git a/tag/minecraft/index.html b/tag/minecraft/index.html index 9369a2914..dd638f9c0 100644 --- a/tag/minecraft/index.html +++ b/tag/minecraft/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Getting Started with Minecraft Fabric Without Any Java Knowledge
    2020-08-08 03:38 2021-02-22 08:40
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Getting Started with Minecraft Fabric Without Any Java Knowledge
    2020-08-08 03:38 2021-02-22 08:40

    Thats challenging but possible

    diff --git a/tag/mysql/index.html b/tag/mysql/index.html index ab1c0fab8..72defd70d 100644 --- a/tag/mysql/index.html +++ b/tag/mysql/index.html @@ -24,7 +24,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数据库查询性能优化手记
    2021-03-09 10:08 2021-08-12 11:59
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数据库查询性能优化手记
    2021-03-09 10:08 2021-08-12 11:59

    简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

    Mysql in WSL
    2020-02-07 08:59 2020-06-24 05:55

    😜 TL;DR

    diff --git a/tag/network/index.html b/tag/network/index.html index 9cb3c1ae0..2c80b2f4c 100644 --- a/tag/network/index.html +++ b/tag/network/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    NPM Registry: What If I Cannot Reach It?
    2021-02-01 08:18 2021-02-01 08:18
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    NPM Registry: What If I Cannot Reach It?
    2021-02-01 08:18 2021-02-01 08:18

    The story of mirrors, ipv6, and yarn 2

    diff --git a/tag/pwa/index.html b/tag/pwa/index.html index 2ea808351..149662655 100644 --- a/tag/pwa/index.html +++ b/tag/pwa/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    `skipWaiting()` with `StaleWhileRevalidate` the right way
    2021-06-28 04:31 2021-06-28 10:11
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    `skipWaiting()` with `StaleWhileRevalidate` the right way
    2021-06-28 04:31 2021-06-28 10:11

    It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06

    How I am tring to build a nice gridsome-generated site with PWA support

    diff --git a/tag/python/index.html b/tag/python/index.html index f3d8ea90b..deb98c527 100644 --- a/tag/python/index.html +++ b/tag/python/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Use pipenv and poetry in a way that works
    2020-08-23 07:29 2020-09-02 01:59
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Use pipenv and poetry in a way that works
    2020-08-23 07:29 2020-09-02 01:59

    Strange as the title is, they just don't work conveniently "out of box"

    异步 & 异步获取命令输出
    2020-02-05 10:53 2020-06-30 08:58

    与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

    diff --git a/tag/raspi/index.html b/tag/raspi/index.html index e630d9bfc..26ba07c67 100644 --- a/tag/raspi/index.html +++ b/tag/raspi/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter on Raspberry Pi
    2020-02-26 12:20 2020-07-01 12:33
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    Tricks about Raspberry Pi
    2020-01-21 08:29 2020-06-24 06:04
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter on Raspberry Pi
    2020-02-26 12:20 2020-07-01 12:33
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    Tricks about Raspberry Pi
    2020-01-21 08:29 2020-06-24 06:04

    Handy tricks when playing with raspberry pi. You should know them.

    diff --git a/tag/tmux/index.html b/tag/tmux/index.html index aae6ca09d..ed5d48d62 100644 --- a/tag/tmux/index.html +++ b/tag/tmux/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Introduce and Setup Tmux
    2020-01-21 13:55 2020-07-10 12:46
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Introduce and Setup Tmux
    2020-01-21 13:55 2020-07-10 12:46

    Setup and introduce Tmux so that I can use it

    Working with tmux and SSH
    2020-01-21 13:55 2020-07-03 01:54

    Useful tips when SSH in remote device which uses tmux

    diff --git a/tag/vim/index.html b/tag/vim/index.html index 5a0fc55e9..a102286a3 100644 --- a/tag/vim/index.html +++ b/tag/vim/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    How I compile and install Vim with +python3
    2020-11-15 06:36 2020-11-15 06:53
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    How I compile and install Vim with +python3
    2020-11-15 06:36 2020-11-15 06:53

    It's known to all that compiling is hard. Just record it.

    Vim Cheatsheet
    2020-04-18 04:48 2020-07-01 13:01
    Setup Vim Airline (in Termux)
    2019-02-09 00:00 2021-02-22 01:59
    diff --git a/tag/vscode/index.html b/tag/vscode/index.html index c158e0aba..d27a1649f 100644 --- a/tag/vscode/index.html +++ b/tag/vscode/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    VSCode 与如何配置 VSCode
    2021-02-14 02:30 2021-02-14 02:45
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    VSCode 与如何配置 VSCode
    2021-02-14 02:30 2021-02-14 02:45

    这是一篇(尽量)新手向的文章,以 VSCode, Python, JavaScript (Vue) 为例,介绍编辑器的功能和配置。

    当然,没说必须选择 VSCode,大可选择 JetBrains 那一套。但是由于笔者平日折腾跨越的语言、领域较多(且前端居多),VSCode 是更好的选择,故只对 VSCode 有少量研究。

    diff --git a/tag/vue/index.html b/tag/vue/index.html index de52d7ff6..0a719a13d 100644 --- a/tag/vue/index.html +++ b/tag/vue/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06

    How I am tring to build a nice gridsome-generated site with PWA support

    虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

    用 Vue 做 PWA (三):理解生命周期
    2020-09-03 02:40 2020-09-08 03:09
    diff --git a/tag/wolfram/index.html b/tag/wolfram/index.html index 75d73bcd2..dccbc4ab0 100644 --- a/tag/wolfram/index.html +++ b/tag/wolfram/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28