# getlantern/systray で学ぶ並列処理 1日目(全8日) ## はじめに Web 開発部もそろそろネタ切れとなってきてしまい何をやろうかなと考えたところ **†並 列 処 理†** が浮かびました。考えてみれば gopher の最大の特徴は何よりも並列処理の手軽さですし(goroutine)、普段 go で http サーバー書かせている割にはどのようにリクエストを捌いているかを説明していませんでした。 しかし、普通に並列処理の説明をするのもつまらないですし、そもそも私の並列処理の知識も粗がかなりありそうです。なので、ここは開発部らしく何かを作りながら並列処理のお気持ちと goroutine のありがたみを知ることにしましょう。 ## 想定環境 OS: Ubuntu >=20.04, macOS >= big sur(windows での検証ができません><) DE: !regolith(i3wm で動くかわからないので) ## 作るもの ![](https://i.imgur.com/S69hSzA.png) これらのようなタスクトレイ上のアプリを作ります。具体的に何を作ろうかはまだ考えていませんが、未提出課題の数とかを表示するようなアプリが楽かつ便利じゃないかなーとは思っています。作りたいものがあったら言ってください。 ## 並列処理とは 並列処理とは、読んで字の如く並列で行う処理のことです。 ## getlantern/systray とは getlantern/systray とは、Windows/Linux/macOS それぞれで動作するタスクトレイ上のアプリケーションを作成することができる go のライブラリです。メニューからボタンまで簡単に作ることができてすごいです。 ### 動かしてみよう systray のサンプルがあるので動かしてみましょう。 ```console $ git clone https://github.com/getlantern/systray $ cd systray/example $ go get github.com/skratchdot/open- golang/open $ go get github.com/getlantern/systray $ GO111MODULE=on go build $ ./example ``` ![](https://i.imgur.com/H9KOpwm.png) こんなのがタスクトレイに出てきたら ok です。 ### コーディングの時間 #### プロジェクトの作成 ```console $ mkdir gakujo-taskapp $ cd gakujo-taskapp $ go mod init gakujo-taskapp $ touch main.go ``` #### 必要なパッケージのインストール ```console $ go get -u "github.com/getlantern/systray" ``` #### タスクアプリのアイコンだけ表示してみる `favicon.ico` を `gakujo-taskapp/` に置く。 ここでは <https://gakujo.shizuoka.ac.jp/portal/liveapps/livecampus/images/favicon/favicon.ico> を使う。 ```go package main import ( "log" "os" "github.com/getlantern/systray" ) func main() { // onReady ... 実行の準備ができた時に実行する関数 // リストアイテムやアイコンなどは全部この中で set する // onExit ... 終了するときに実行する関数 systray.Run(func() { b, err := os.ReadFile("favicon.ico") if err != nil { log.Fatal(err) } systray.SetIcon(b) }, func() {}) } ``` mac だったら上、ubuntu(i3wm) だったら右下にアイコンがあれば ok! #### リストアイテムを追加してみる ```diff= package main import ( "log" "os" "github.com/getlantern/systray" ) func main() { // onReady ... 実行の準備ができた時に実行する関数 // onExit ... 終了するときに実行する関数 systray.Run(func() { b, err := os.ReadFile("favicon.ico") if err != nil { log.Fatal(err) } systray.SetIcon(b) + systray.AddMenuItem("login as: 鈴木太郎", "") }, func() {}) } ``` #### クリックのハンドラを追加してみる ```diff= package main import ( "fmt" "log" "os" "github.com/getlantern/systray" ) func main() { // onReady ... 実行の準備ができた時に実行する関数 // onExit ... 終了するときに実行する関数 systray.Run(func() { b, err := os.ReadFile("favicon.ico") if err != nil { log.Fatal(err) } systray.SetIcon(b) systray.AddMenuItem("login as: 鈴木太郎", "") + clickMeItem := systray.AddMenuItem("Click me!", "") + for { + select { + case <-clickMeItem.ClickedCh: + fmt.Println("Clicked!") + } + } }, func() {}) } ``` > select って? > ```go > select { > case <- clickMeItem.ClickedCh: > fmt.Println("Clicked!") >} #### 1秒ごとにリストアイテムのタイトルを変更してみる ```diff= package main import ( "fmt" "log" "os" "time" "github.com/getlantern/systray" ) func main() { // onReady ... 実行の準備ができた時に実行する関数 // onExit ... 終了するときに実行する関数 systray.Run(func() { b, err := os.ReadFile("favicon.ico") if err != nil { log.Fatal(err) } systray.SetIcon(b) systray.AddMenuItem("login as: 鈴木太郎", "") clickMeItem := systray.AddMenuItem("Click me!", "") + count := 0 + countItemTitle := "0 sec elapsed" + countItem := systray.AddMenuItem(countItemTitle, "") + go func() { + for { + time.Sleep(time.Second) + count += 1 + countItemTitle = fmt.Sprintf("%d sec elapsed", count) + countItem.SetTitle(countItemTitle) + } + }() for { select { case <-clickMeItem.ClickedCh: fmt.Println("Clicked!") } } }, func() {}) } ``` > goって? > func(){} を別スレッドで実行するよ! #### gakujo-api を使ってみる > 何かしらの方法で学情の username と password を取得できるようにしておいてください。 ##### gakujo-api のインストール ```console $ go get -u "github.com/szpp-dev-team/gakujo-api/gakujo" ``` ##### .env の準備 ```console $ touch .env ``` .env ```env GAKUJO_USERNAME= GAKUJO_PASSWORD= ``` ##### 未読の授業連絡の数を表示してみる ```diff= package main import ( "fmt" "log" "os" "time" "github.com/getlantern/systray" "github.com/joho/godotenv" "github.com/szpp-dev-team/gakujo-api/gakujo" "github.com/szpp-dev-team/gakujo-api/model" ) func init() { if err := godotenv.Load(".env"); err != nil { log.Fatal(err) } } func main() { username := os.Getenv("GAKUJO_USERNAME") password := os.Getenv("GAKUJO_PASSWORD") // onReady ... 実行の準備ができた時に実行する関数 // onExit ... 終了するときに実行する関数 systray.Run(func() { b, err := os.ReadFile("favicon.ico") if err != nil { log.Fatal(err) } systray.SetIcon(b) + systray.AddMenuItem("login as: "+username, "") - clickMeItem := systray.AddMenuItem("Click me!", "") + classNoticeCountTitle := "未読授業連絡数: -1" + classNoticeItem := systray.AddMenuItem(classNoticeCountTitle, "") + client := gakujo.NewClient() + if err := client.Login(username, password); err != nil { + log.Println(err) + } + go func() { + for { + opt := model.BasicClassNoticeSearchOpt(2021, model.LaterPeriod, time.Now().AddDate(0, -1, 0)) + rows, err := client.ClassNoticeRows(opt) + if err != nil { + log.Fatal(err) + } + classNoticeCountTitle = fmt.Sprintf("未読授業連絡数: %d", len(rows)) + classNoticeItem.SetTitle(classNoticeCountTitle) + time.Sleep(time.Minute * 20) + } + }() - for { - select { - case <-clickMeItem.ClickedCh: - fmt.Println("Clicked!") - } - } }, func() {}) } ```