OmniAICreator/Qiita-1.07M huggingface dataset:
ブラウザのレンダリングのサイクル

URLを打ち込むと、レンダリングサイクルの流れの後にサイト・アプリケーションが表示されます。

## ブラウザのレンダリングサイクルは4つ

**Loading　→　Scripting　→　Rendering　→　Painting**

### サイクル図

![スクリーンショット 2022-02-11 9.34.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1310301/cd6f1a56-65d1-63bf-9aa3-bdd7211bfcd9.png)

- Composite Layersが終わると、ユーザーが認知できる画面が表示される。

![スクリーンショット 2022-02-11 14.02.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1310301/4c38203a-af98-53c4-d3f0-88536058803f.png)

上記の画像は私が理解するために日本語化したものです。

本記事は以上となります。

***
書籍
[Webフロントエンド ハイパフォーマンス チューニング](https://www.amazon.co.jp/Web%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89-%E3%83%8F%E3%82%A4%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9-%E3%83%81%E3%83%A5%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0-%E4%B9%85%E4%BF%9D%E7%94%B0-%E5%85%89%E5%89%87/dp/4774189677/ref=pd_sim_14_2/355-8347036-3165354?_encoding=UTF8&pd_rd_i=4774189677&pd_rd_r=242f1b07-5d75-484b-831f-8e3186723bd4&pd_rd_w=SLj3f&pd_rd_wg=efHnd&pf_rd_p=51d7f721-ab31-4c99-8db8-c3f45803167a&pf_rd_r=639J7G22C2SMCZT0SF5B&psc=1&refRID=639J7G22C2SMCZT0SF5B)
C＃を使用してカラーPDFをグレースケールに変換する方法

この記事では、C＃コードを例として取り上げ、カラーPDFファイルをグレースケール（白黒）PDFファイルに変換する方法を紹介します。つまり、PdfGrayConverter.ToGrayPdf（）メソッドを呼び出すことによってドキュメントページ、PDFドキュメントのカラー画像またはテキストを色のないグレートーンドキュメントに変換します。次のプログラム実行環境とコード例。

##プログラム環境を構成
Visual Studio 2017
.Net Framework 4.6.1
Spire.PDF for .NET
##dllファイルをインポートする方法について：

**1. NuGetを介してdllをインストールします（2つの方法）**

1.1 Visual Studioで「ソリューションエクスプローラー」を開き、「参照」、「NuGetパッケージの管理」を右クリックして、Spire.PDFのインストールを検索できます。

1.2以下をPMコンソールのインストールにコピーします。
Install-Package Spire.PDF -Version 7.11.1

**2.dll参照を手動で追加する**

パッケージを手動で**[ダウンロード](https://www.e-iceblue.com/Download/download-pdf-for-net-now.html)**して解凍し、BINフォルダーでSpire.Pdf.dllを見つけることができます。 次に、Visual Studioで「ソリューションエクスプローラー」を開き、「参照」を右クリックし、「参照の追加」をクリックして、プログラムへのローカルパスのBINフォルダーにあるdllファイルへの参照を追加します。

##カラーPDFをグレースケールに変換する

ここでの変換は、主に次の手順を使用します。

1.PdfGrayConverterクラスを使用してPDFファイルをロードします。
2. PdfGrayConverter.ToGrayPdf（）メソッドを呼び出して、PDFをグレースケールに変換します。

###**C#**

```
using Spire.Pdf.Conversion;

namespace ToGrayPDF
{
    class Program
    {
        static void Main(string[] args)
        {
            //PdfGrayConverterインスタンスを作成し、ドキュメントをロードする
            PdfGrayConverter converter = new PdfGrayConverter("C:\\Users\\Administrator\\Desktop\\sample.pdf");

            //カラーPDFをグレースケールに変換する
            converter.ToGrayPdf("Grayscale.pdf");
            converter.Dispose();
        }
    }
}
```

###**VB.NET**

```
Imports Spire.Pdf.Conversion
 
Namespace ToGrayPDF
    Class Program
        Shared  Sub Main(ByVal args() As String)
            'PdfGrayConverterインスタンスを作成し、ドキュメントをロードする
            Dim converter As PdfGrayConverter =  
New PdfGrayConverter("C:\\Users\\Administrator\\Desktop\\sample.pdf") 
 
            'カラーPDFをグレースケールに変換する
            converter.ToGrayPdf("Grayscale.pdf")
            converter.Dispose()
        End Sub
    End Class
End Namespace
```

変換した結果：
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/52f9d4e4-d74e-f06f-a189-6f6f6540e62c.png)

以上は今回のカラーPDFをグレースケールに変換する方法でした、最後まで読んでいただきありがとうございます。
SystemdとmcrconでMinecraftをサービス化する

# はじめに

マイクラマルチサーバーを建てる際にScreenを使っていたのですが、うっかりScreenのターミナルを閉じてしまったりとなにかと問題がありました。
Systemdとmcrconで快適にサービス化してしまいましょう。

# 諸要素

* CentOS 8 Stream
* MinecraftサーバのJAR (今回はバニラサーバーを使用)
* Systemd
* mcrcon

# Minecraftの設定

```text:server.properties(追記/変更)
rcon.port=25575
enable-rcon=true
rcon.password=あなたのRCONパスワード
```

# mcrconのインストール

```shell
# 未導入であれば入れる
sudo dnf gcc make
# お好みの作業用ディレクトリ内で
wget https://github.com/Tiiffi/mcrcon/archive/refs/tags/v0.7.2.zip
unzip v0.7.2.zip
cd mcrcon-0.7.2
make
sudo make install
```

minecraft作業用ユーザーの`.bashrc`に下記内容を追記しておくと幸せになれます。

```text:.bashrc(追記)
export MCRCON_HOST=localhost
export MCRCON_PORT=25575
export MCRCON_PASS=あなたのRCONパスワード
```

# 起動/終了スクリプト、サービス定義ファイルの作成

サーバーのJARやスクリプトなどは`/home/minecraft/mc_dir/`に設置されている想定です。

```sh:start.sh
#!/bin/sh
export HOME=/home/minecraft
export USER=minecraft
cd ~/mc_dir
java -Xmx1024M -Xms1024M -jar ./server.jar nogui
```
※割当メモリ量は環境に合わせて調整してください。

```sh:stop.sh
export USER=minecraft
export MCRCON_HOST=localhost
export MCRCON_PORT=25575
export MCRCON_PASS=あなたのRCONパスワード
echo stop | /usr/local/bin/mcrcon -t
```
※`/usr/local/bin/mcrcon stop`とするとサーバーの終了を待たずにコマンドが終了するので不適当です。

```text:minecraft.service
[Unit]
Description=Minecraft Multiplay Server Service

[Service]
Type=simple
User=minecraft
ExecStart=/home/minecraft/mc_dir/start.sh
ExecStop=/home/minecraft/mc_dir/stop.sh
TimeoutStopSec=120
PrivateTmp=true
Restart=always

[Install]
WantedBy=multi-user.target
```

※ タイムアウト時間はサーバー規模に合わせて調整してください。

# サービス登録、起動/終了確認

```shell
cd /usr/lib/systemd/system/
ln -s /home/minecraft/mc_dir/minecraft.service
systemctl enable minecraft
systemctl start minecraft
journalctl -f -u minecraft
systemctl stop minecraft
```

# 最後に

楽しいマイクラ生活を！
WSLでのシェルスクリプト実行でWSL内のサーバーを外部から見れるようにする

公式では、

https://docs.microsoft.com/en-us/windows/wsl/networking

におおよそ情報が書かれている。

Windowsのファイアーウォールの対象ポートの設定はあらかじめ行っておく。

外部からWSL内のサーバーを見に行くには、WSLのIPアドレスにポートプロキシする設定をする必要がある。
また、WSLのIPアドレスは起動毎変わるので、起動毎に実行する必要がある。

複数のWSLの使い分けを考慮したいのと、自動で実行されることに抵抗感があるので、
利用するときにWSL内部からシェルスクリプトを実行する方式にしたかった。

```sh
#!/bin/bash
address=$(ip address show dev eth0 | sed -En 's/^.*inet ([.0-9]*).*$/\1/p')
echo address: $address
cmds_str=$(
  printf %s \
    "netsh interface portproxy delete v4tov4 listenport=8001 listenaddress=0.0.0.0 & " \
    "netsh interface portproxy add    v4tov4 listenport=8001 listenaddress=0.0.0.0 connectaddress=$address"
)
powershell.exe \
  start-process \
  cmd "'/c ${cmds_str}'" \
  -verb runas
sleep 1
netsh.exe interface portproxy show all
```

`portproxy` の `delete` と `add` は管理者権限が必要になる。
また、管理者権限を実行するコマンドは毎回ダイアログが表示されてしまうので、コマンドを1回にまとめている。

また、最後に `show all` することで、ちゃんと設定されたか、余計な設定が残ってないかを毎回確認できるようにしている。

設定が反映が間に合ってないように見えたことがあったので `sleep 1` で少し待っている。
Compose Material3におけるComponentの対応状況

Jetpack Compose の Material 3 の Component の対応表です。
Jetpack Compose のプロジェクトで Material ３ を導入を検討する時にでも活用ください。  



|Component|material-component(1.10.0)|Compose Material3(1.2.0-alpha03)|
|:--:|:--:|:--:|
| Backdrop | × | × |
| [Badge](https://m3.material.io/components/badges/overview) | ◯ | ◯ |
| [Bottom app bar](https://m3.material.io/components/bottom-app-bar/overview) | ◯ | ◯ |
| [Bottom sheet](https://m3.material.io/components/bottom-sheets/overview) | ◯ | ◯ |
| [Button](https://m3.material.io/components/buttons/overview) | ◯ | ◯ |
| [Card](https://m3.material.io/components/cards/overview) | ◯ | ◯ |
| [Carousel](https://m3.material.io/components/carousel/overview) | ◯ | × |
| [Checkbox](https://m3.material.io/components/checkbox/overview) | ◯ | ◯ |
| [Chip](https://m3.material.io/components/chips/overview) | ◯ | ◯ |
| [Divider](https://m3.material.io/components/divider/overview) | ◯ | ◯ |
| [Dialog](https://m3.material.io/components/dialogs/overview) | ◯ | ◯ |
| [Floating action button](https://m3.material.io/components/floating-action-button/overview) | ◯ | ◯ |
| [Menu](https://m3.material.io/components/menus/overview) | ◯ | ◯ |
| [Navigation bar](https://m3.material.io/components/navigation-bar/overview)<br />(Bottom navigation) | ◯ | ◯ |
| [Navigation drawer](https://m3.material.io/components/navigation-drawer/overview) | ◯ | ◯ |
| [Navigation rail](https://m3.material.io/components/navigation-rail/overview) | ◯ | ◯ |
| [Progress indicator](https://m3.material.io/components/progress-indicators/overview) | ◯ | ◯ |
| [Radio button](https://m3.material.io/components/radio-button/overview) | ◯ | ◯ |
| [Searchbar](https://m3.material.io/components/search/overview) | ◯ | ◯ |
| [Side sheet](https://m3.material.io/components/side-sheets/overview) | ◯ | × |
| [Slider](https://m3.material.io/components/sliders/overview) | ◯ | ◯ |
| [Snackbar](https://m3.material.io/components/snackbar/overview) | ◯ | ◯ |
| [Switch](https://m3.material.io/components/switch/overview) | ◯ | ◯ |
| [Tab](https://m3.material.io/components/tabs/overview) | ◯ | ◯ |
| [Text field](https://m3.material.io/components/text-fields/overview) | ◯ | ◯ |
| [Top app bar](https://m3.material.io/components/top-app-bar/overview) | ◯ | ◯ |
| Swipe Refresh | - <br />(Swiperefreshlayoutのライブラリで利用可能) | × |

### 参考

- https://m3.material.io
- https://github.com/material-components/material-components-android
- https://developer.android.com/jetpack/androidx/releases/compose-material3
- https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary
Golang (net/http)とFirestoreで簡単なREST APIを作ってみた

はてなブログに掲載しています。

https://moritomo7315.hatenablog.com/entry/go-user-rest-api

##  やったこと

* Golangで基本的なCRUDをするREST APIを作成
* net/http 使用
* EchoやGinなどのframeworkは使用しない
* DBはfirestoreを使用
* 簡単なエラーハンドリング

## やっていないこと

* test (user_test.goとか、firestore_test.goとか）


## github

https://github.com/MoriTomo7315/go-user-rest-api
え、この機能、デフォルトでUIKitにあったの？ 長押しメニュー、Context Menuでかんたんに。

#アプリでよく見る長押しして表示するメニュー機能をかんたんに作れる方法です
![Feb-11-2022 09-56-15](https://user-images.githubusercontent.com/23278992/153523539-6ce5756f-c9f0-464a-9d60-61c24560057b.gif)
#わかりやすい長押しポップアップハイライトを作りたいけど、１から作るのはたいへんそう。。。

アプリでよく見るこういう機能、使いたいけど、アニメーションの設定などたいへんそうです。

#すごい便利なUIKitの機能があった
Context Menuは、この機能を短いコードで追加できるものです。

#方法

###UICollectionViewに追加する
CollectionViewにデリゲートメソッドが元々あるので、こちらの方が単一のViewに追加するより楽です。

```swift
func collectionView(_ collectionView: UICollectionView,
                    contextMenuConfigurationForItemAt indexPath: IndexPath,
                    point: CGPoint) -> UIContextMenuConfiguration? {
    return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
        let deleteAction = self.deleteAction(indexPath)
        return UIMenu(title: "", children: [deleteAction])
    }
}
```

これだけでViewがアニメーションでハイライトされ、メニューがポップアップして、背景がぼやけます。
ハプティクスもついています。
もう一度タップすると、選択が解除されます。

メニューの表示位置も、きちんと見えるように自動で調整してくれます。


###UIViewに追加する

```Swift
let interaction = UIContextMenuInteraction(delegate: self)
view.addInteraction(interaction)
```

デリゲートメソッドでポップアップメニューの中身を追加します。

```swift
func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
                                configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
    return UIContextMenuConfiguration(identifier: nil,
                                      previewProvider: nil,
                                      actionProvider: { suggestedActions in
        let saveAction = UIAction(title: NSLocalizedString("Save", comment: ""),
                                  image: UIImage(systemName: "arrow.down.square")) { action in
                             self.performSave()
                         }
                
        let deleteAction = UIAction(title: NSLocalizedString("Delete", comment: ""),
                                    image: UIImage(systemName: "trash"),
                                    attributes: .destructive) { action in
                               self.performDelete()
                           }
                                            
        return UIMenu(title: "Select Action", children: [inspectAction, deleteAction])
    })
}
```
![Feb-11-2022 10-18-41](https://user-images.githubusercontent.com/23278992/153523639-68ca20e9-8f46-4039-95f5-f82ff9377d59.gif)


#アプリづくりのスピードアップ
こういった機能を使うことで、わかりやすいUIを手早く作れるといいですね。

🐣
****
フリーランスエンジニアです。
お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLやARKitを使ったアプリを作っています。
機械学習／AR関連の情報を発信しています。

[Twitter](https://twitter.com/JackdeS11)
[Medium](https://medium.com/@rockyshikoku)
Goで配列同士を比較する

普段Rubyを書いてるんですが、最近Goに興味が出てきました。
Goの配列同士を比較する時どうするのか調べたので、記事にする。

Rubyだったらこんな感じで配列同士を比較できます。

```rb
a = [1, 2, 3]
b = [1, 2, 3]

a == b
=> true
```

Goでも下記のように書くと比較できる。

```go
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}

fmt.Println(a == b)
=> true
```

ただこれだとエラーになる。

```go
a := []int{1, 2, 3}
b := []int{1, 2, 3}

fmt.Println(a == b)
=> invalid operation: a == b (slice can only be compared to nil)
```

調べてみると、

Goにおける配列(Array)は同じ型の値の集まりです。
さらに、配列の長さはその型の一部であるため配列のサイズを変更することはできません。

https://go.dev/ref/spec#Array_types

じゃあ自分が配列だと思っていた`a := []int{1, 2, 3}`はなんだったのか。
↓
`Slices`型だった。

配列は長さが固定されてるけど、`Slices`型は可変長でより柔軟な配列を表現できる。
`Slices`型の方がArray型よりもよく使われるみたい。

https://go.dev/ref/spec#Slice_types

じゃあなんで`Slices`型同士を比較するとエラーが出たのか？

リファレンスを確認すると、比較演算子は`Slices`同士を比較できないようだった。
https://go.dev/ref/spec#Comparison_operators

他に方法はないかと思って調べると、`reflect.DeepEqual`で`Slices`型同士の比較ができた。
`DeepEqual`は、ざっくり言うと`==`を緩くしたもの。

https://pkg.go.dev/reflect#DeepEqual

```
a := []int{1, 2, 3}
b := []int{1, 2, 3}

fmt.Println(reflect.DeepEqual(a, b))
=> true
```

配列同士を比較したいと思って、調べたらいろんな事知れてよかったです。
LeafletによるWEB地図をVSCodeの簡易ローカルサーバを利用して表示する方法

こんにちは。[Leaflet](https://leafletjs.com/)でgeojsonなどのファイルを参照、表示するWEB地図を作ろうと思って勉強していますが、index.htmlを直接実行しただけではうまく動きませんでした。この場合、ローカル―サーバーの導入などが必要となり、私のような初心者がつまづくポイントになると思うので（私のように）、以下にメモします。
ここでは、VSCodeの拡張機能【Live Server】を利用して簡単にローカルサーバを導入して、Leafletで作ったWEB地図の動作を確認する方法を紹介します。

## 動作環境
- windows10
- Visual Studio Code v1.64.2
- Live Server v5.7.4

# 素材
[dayjournal](https://day-journal.com/)さんの「[JavaScriptではじめるWebマップアプリケーション (PDF版)](https://dayjournal.booth.pm/items/1314906)」で勉強しています。サンプルコードは[こちら](https://github.com/dayjournal/JSWebMapApp)に公開してくださっています。
以下の章番号などは、このサンプルコードの番号を指します。

# VS Codeでのサンプルコードの表示
以下、上記資料の「05-2 外部GeoJSONフィル読み込み」のサンプルコード[05-02]を参照しています。

- 下記の画面は、VSCodeでLeaflet用のファイルセットがフォルダを開き（ここではLeafletフォルダ）、index.htmlを開いたところ。メニューの[実行]-[デバッグの開始]をクリックすると、ブラウザに地図が表示される。以下、VSCodeの画面と、ブラウザに表示された地図。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201444/886ee236-6869-e2f1-7cf1-5fa2fcb8e689.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201444/49b7ef3e-bd08-77d2-60b5-fc19e487b87a.png)

本地図では、フォルダ内のgeojsonファイルを参照するようにしているのだが、うまく表示されていない。そのため、下記のように、ローカルサーバを立ち上げる必要がある。

# Live Serverによるローカルサーバ―の構築
VSCodeの拡張機能であるLive Serverを使うと簡単にできる。
Live Serverの導入方法は以下を参照した。

- [【Live Server】Visual Studio Codeの便利な拡張機能　（基礎知識・使い方）](https://min-web.com/vscode-live-server/)
- [簡単なローカルサーバーの立ち上げ方](https://hothukurou.com/blog/post-2887)

- Live Serverをインストールしたら、下記の画面のように、VSCodeのフォルダで先ほどのindex.htmlを右クリックして、「Open with Live Server」をクリックする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201444/09d160f2-cf15-9845-7cda-067e9894d148.png)

- すると、ローカルサーバが作られて以下のようにブラウザが開き、geojsonファイルを反映した地図が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201444/f72b06f8-5303-bcad-3d31-6b5c5d4d0b71.png)

以上です。
## 参考
VSCodeによるWEBアプリの構築については、以下のjmoocの講座で勉強させてもらってます。
[Webで学ぶJavascript 2020](https://platjam.jmooc.jp/javascript)
ツールを使わないで React を使う方法

参考ページ
[既存のウェブサイトに React を追加する](https://ja.reactjs.org/docs/add-react-to-a-website.html)
こちらのサンプルを改造しました。
[Multiple React components on a single HTML page](https://gist.github.com/gaearon/faa67b76a6c47adbab04f739cba7ceda)

次のようなページを作成しました。
![react_feb11_aa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/21490956-a647-bb90-fa0f-db7f4c3b0872.png)

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>グリムの昔話</title>
  </head>
  <body>
    <h2>簡単な React</h2>
    <blockquote>
    <p>このページはツールを使わないで React を使う例です。</p>
    <p>React はスクリプトタグとしてロードされます。</p>
    </blockquote>
    <p>
    KHM 21 灰かぶり
      <!-- We will put our React component inside this div. -->
      <div class="like_button_container" data-commentid="11"></div>
    </p>

    <p>
    KHM 53 白雪姫
      <!-- We will put our React component inside this div. -->
      <div class="like_button_container" data-commentid="12"></div>
    </p>

    <p>
    KHM 15 ヘンゼルとグレーテル
      <!-- We will put our React component inside this div. -->
      <div class="like_button_container" data-commentid="13"></div>
    </p>

    <hr />
    <blockquote>
	    Feb/11/2022<br />
    </blockquote>
    <!-- Load React. -->
    <!-- Note: when deploying, replace "development.js" with "production.min.js". -->
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>

    <!-- Load our React component. -->
    <script src="like_button.js"></script>

  </body>
</html>
```

```js:like_button.js
'use strict';

const e = React.createElement;

class LikeButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = { liked: false };
  }

  render() {
    if (this.state.liked) {
	    const rvalue = 'あなたは ' + this.props.commentID + ' が好きです。';
      return rvalue
    }

    return e(
      'button',
      { onClick: () => this.setState({ liked: true }) },
      'すき'
    );
  }
}

// Find all DOM containers, and render Like buttons into them.
document.querySelectorAll('.like_button_container')
  .forEach(domContainer => {
    // Read the comment ID from a data-* attribute.
    const commentID = parseInt(domContainer.dataset.commentid, 10);
    ReactDOM.render(
      e(LikeButton, { commentID: commentID }),
      domContainer
    );
  });
```

## ローカルでテストする方法 ##

```text
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
```

ブラウザーで
http://0.0.0.0:8000/
にアクセス

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/6526932e-ebec-fa0f-bc25-8991cf709d38.png)
【備忘録】Docker-composeでコンテナが立ち上がらない

コンテナを立ち上げようとしたら以下のエラーが発生

```
ERROR: for xxxxxxx  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=60)

ERROR: xxxxxxx  UnixHTTPConnectionPool(host='localhost', port=None): Read timed out. (read timeout=60)
ERROR: An HTTP request took too long to complete. Retry with --verbose to obtain debug information.
If you encounter this issue regularly because of slow network conditions, consider setting COMPOSE_HTTP_TIMEOUT to a higher value (current value: 60).
````

とりあえずDockerデスクトップを再起動したら解決した。
laravel バリデーションエラーメッセージを日本語化する

# 概要

- laravelのバリデーションエラーメッセージを日本語化する方法を簡単にまとめる。

# 前提

- 説明で実行するコマンドは特筆しない限り、前回実行したコマンドと同じディレクトリで実行するものとする。

# 方法

1. アプリ名ディレクトリで　下記コマンドを実行して言語ファイルをコピーする。

    ```terminal
    $ cp -r resources/lang/en resources/lang/ja
    ```

1. `アプリ名ディレクトリ/ resources/lang/ja/validation.php`を開く。
1. 今回は試しに、「必須」と「日にちフォーマット」の２つのバリデーションメッセージを日本語化してみる。
1. 下記の二箇所の記載を修正した。
    1. 修正前

        ```resources/lang/ja/validation.php
            'date' => 'The :attribute is not a valid date.',
            'required' => 'The :attribute field is required.',
        ```

    1. 修正後

        ```resources/lang/ja/validation.php
            'date' => '日時フォーマットに誤りがあります 正しい値を入力してください',
            'required' => ':attribute は必須です',
        ```

1. 同クラスの最下部に記載されているattributesの配列に追記して項目名も日本語化する。

    ```resources/lang/ja/validation.php
    /*
    |--------------------------------------------------------------------------
    | Custom Validation Attributes
    |--------------------------------------------------------------------------
    |
    | The following language lines are used to swap our attribute placeholder
    | with something more reader friendly such as "E-Mail Address" instead
    | of "email". This simply helps us make our message more expressive.
    |
    */

    'attributes' => [
        'bladeから送っているときのnameの値'　=> '日本語での項目名'
    ],
    ```

1. config/app.phpを開きlocateの内容を確認する
    1. 下記のようになっていればOK

        ```config/app.php
        /*
        |--------------------------------------------------------------------------
        | Application Locale Configuration
        |--------------------------------------------------------------------------
        |
        | The application locale determines the default locale that will be used
        | by the translation service provider. You are free to set this value
        | to any of the locales which will be supported by the application.
        |
        */
    
        'locale' => 'ja',
        ```

1. 後はアプリ側でバリデーションエラーが出力されるように操作し、日本語化されていればOK
[wordpress]function.phpで正規表現を使って特定のディレクトリ以下へのアクセスをリダイレクト

WordPressで作っているサイトでリダイレクトをかけたい場合、.htaccessで行うことが多いかと思います。多いかと思います、と言いつつ私は今回初めて実装したのですが、数時間の格闘の上、stgでの実装に成功！
しめしめ…と本番反映しようとしたところ、なぜかstgでは効いた.htaccessが本番で機能しない。こんなことがあっていいのか、否、よくない。

*WordPressでの.htaccessで参考にしたのはこちらの記事。この記事は何も悪くないです。
[WordPressで.htaccessリダイレクトができない場合【解決】](https://wp-customize.net/wordpress/7626.html)


しかし急いでいたこともあり、ひとまず.htacceｓｓの検証は後回しにしてWordPressのfunction.phpでリダイレクトを実装することに。まあこちらも不慣れなもんでしてね。数時間の格闘の上、やっと実装に成功したってもんです。

特に正規表現！！難しいですねー備忘録を残しておきましょう。

やりたいこと：
- URI /blog/以下のページを302リダイレクト（一時的）させたい
- Englishページ(/en/blog/)もあるのでそれもリダイレクト
- /blogxxxxのようなURIは404に

まずはfunction.phpの頭にコードをプラス。

```php:function.php
<?php
$request_url = $_SERVER["REQUEST_URI"];
$redirect_match = preg_match("ここに正規表現が入るよ", $request_url);

if($redirect_match === 1){
  wp_redirect(home_url(), 302);
  exit;
}

```

$request_urlでアクセスしたページが正規表現とマッチするか判定して、マッチするならホーム画面に302リダイレクトで飛ばします。

preg_matchについては下記参考
> preg_matchでは、ある文字列から正規表現で指定したパターンにマッチした文字列を検索できます。
> パターンにマッチした場合は1を返し、マッチしなかったら0を返します。

[PHPのpreg_matchで正規表現によるマッチングする方法](https://techacademy.jp/magazine/11402#:~:text=%E3%81%8C%E6%B7%B1%E3%81%BE%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82-,preg_match%E3%81%A8%E3%81%AF,%E3%81%9F%E3%82%890%E3%82%92%E8%BF%94%E3%81%97%E3%81%BE%E3%81%99%E3%80%82&text=%E4%BB%96%E3%81%AB%E3%82%82%E5%A4%A7%E6%96%87%E5%AD%97%E3%83%BB%E5%B0%8F%E6%96%87%E5%AD%97,%E3%81%95%E3%81%9B%E3%82%8B%E3%81%93%E3%81%A8%E3%82%82%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82)より

そしてお次に、"ここに正規表現が入るよ"の箇所に正規表現を入力していきます。
正規表現ってなんぞ？という方は、いろいろなサイトが解説してくれているのでぜひ。
[正規表現入門](https://www.javadrive.jp/regex-basic/)

正規表現がなんたるかはわかっているものの、どの記号がなんだっけ？というのを見ながら自分で書きたい方は「regex cheatsheet」で検索するのがおすすめです。regexはregular expressionの略ですね。
私はこちらのサイトを使用しています。

[Quick-Start: Regex Cheat Sheet](https://www.rexegg.com/regex-quickstart.html)

チートシートを見ながら下記のサイトで正規表現の書き方が合っているか、確認してからファイルに書き込んでいます。めちゃ便利！

[PHP: preg_match() / JavaScript: match() 正規表現チェッカー ver3.1](http://okumocchi.jp/php/re.php)

まず最初に書いたのはこれ。

```php:function.php
 $redirect_match = preg_match("/^\/en\/blog\/|^\/en\/blog$|^\/blog\/|^\/blog$/", $request_url);

```
*正規表現は/で最初と最後を囲むよ

- /en/blog/
- /en/blog
- /blog/
- /blog
の4パターンをORでつないでいる形です。めちゃ長い！頭悪そうやんけ！！

blog＄のように「\＄」をつけているのは、URLが「blog」で終わることを示しています。
例えば/blogxxx　といったページにアクセスした際は、302リダイレクトではなく404を表示させたいからです。
blog/のようにblogの後に/がついている場合は、その最後の/がURLの最後の文字とは限らない（/blog/catや/blog/cat/foodなど）ので、$は入れていません。

余談ですが、このfunction.phpをサーバにアップした時の確認方法です。
1. Chrome developer toolを開く
2. /blog/にアクセス
3. NetworkタブからNameの`blog/`を選択し、header情報などを確認
↓この辺
Request URL: https://ｘｘｘ/blog/
Status Code: 302 
location: https://xxx(HOME)


それにしても待てよ？今/blog/がURIに入っているページで公開しても良いページなんてないのだから、そもそも/en/自体入れなくても良いのでは？
と気づき、下記に変更しました。

```php:function.php
$redirect_match = preg_match("/^(\/.*)?\/blog\/|^(\/.*)?\/blog$/", $request_url);

```

`(\/.*)?`で、/xx（何文字でもいい）が0回か1回以上ある時、という条件を追加しています。これで、/en/が入っていないURLを叩かれた場合でも/blog/ページをリダイレクトさせることができます。

ただ正直これでもええんちゃうん？と思わずにはいられない。

```php:function.php
$redirect_match = preg_match("/\/blog\/|\/blog$/", $request_url);
// URIに/blogが入っていたら全部リダイレクト
```

ただあまりにも縛りが緩いと思わぬ穴があるかもしれないと思って一応直上のコードは書いていません。
誰かお詳しい方おられたら、ご教示いただけますと幸いです…
Pony 試乗記(2) 複数アクターの通信

# はじめに

[Hello, world!](https://qiita.com/tadashi9e/items/9f5916676ed49f9355f3) の次は、アクター間の通信を使ったプログラムを書いてみようと思いました。

# 素数列生成

いかにも動作確認・お勉強のためのプログラムです、という感じで実用性皆無ですが、複数アクターを連携させて「エラトステネスのふるい」を書いてみることにします。

- 整数列（素数候補列）生成アクター: TwoAndOddsGenerator
- ふるいアクター: PrimeFilter
- 結果表示アクター: Report

の３つのアクターを作って、それらをメッセージ通信で繋げて動かしてみようという目論見です。

## メインアクター

メイン関数、というかメインアクターをまず用意します。
任意の数を上限とした素数列を生成できるように、コマンド引数を最大値として取得します。
コマンド引数が与えられなかったとき、与えられたコマンド引数が整数でなかった場合には例外を送出するので、それを捕捉して使用方法を表示します。

例外を送出するメソッドの呼び出しには "?" を付ける、というのが分かりやすくて良いと思いました。

~~~pony
actor Main
  new create(env: Env) =>
    try
      let max_n: U64 = env.args(1)?.u64()?
      TwoAndOddsGenerator(max_n, PrimeFilter(Report(env)))
    else
      // コマンド引数が与えられていない、あるいは整数でなかった場合
      try
        // env.args(0) は必ずあるはずなので例外は無視する
        env.out.print("usage: " + env.args(0)? + " n")
      end
    end
~~~

## 素数候補列生成アクター

素数候補となる数を生成するアクターを書いてみます。
コンストラクタの中で、最小の素数である 2, 続いて 3, 5, 7, ... と奇数を順次生成し、
素数判定を行うアクター PrimeFilter に送信します。

~~~pony
actor TwoAndOddsGenerator
  new create(max_value: U64, primeFilter: PrimeFilter) =>
    """
    素数の候補を生成し、PrimeFilter の check に順番に渡す。
    """
    // 2 は素数
    primeFilter.check(2)
    // 3 以降の奇数列を候補とする
    var i: U64 = 1
    while true do
      let odd = (2 * i) + 1
      if odd > max_value then
        break
      end
      primeFilter.check(odd)
      i = i + 1
    end
~~~

## 素数判定アクター

素数候補を受信して、それが素数かどうかをふるいにかけるアクターを書いてみます。

be は behaviour の略で、アクターがメッセージを受けて非同期に動作することを表します
(詳しくは https://tutorial.ponylang.io/types/actors.html 参照)。
class へのメソッド記述（同期動作）は fun を使います。

~~~pony
actor PrimeFilter
  """
  TwoAndOddsGenerator から受信した数が素数かどうかチェックする。
  """
  let report: Report  // 素数通知先のアクター
  var primes: Array[U64] = Array[U64]  // これまでに出現した素数
  new create(report': Report) =>
    report = report'
  be check(n: U64) =>
    var p': U64 = n
    for p in primes.values() do
      if (n % p) == 0 then
        p' = 0
        break
      end
    end
    if p' != 0 then
      report.report[U64](p')
      primes.push(p')
    end
~~~

## 素数判定結果表示アクター

素数と判定された値を受信し表示するアクターです。

report は単純に U64 を受ければいいだけなのですが、
ジェネリクス（ https://tutorial.ponylang.io/generics/index.html 参照）を使ってみたかったので敢えて Stringable にしました。

~~~pony
actor Report
  let env: Env
  new create(env': Env) =>
    env = env'
  be report[T: Stringable val](p: T val) =>
    env.out.write(p.string() + " ")
~~~

ここで Reference Capabilities の洗礼を受けました。上記の report を

~~~pony
  be report[T: Stringable](p: T) =>
    env.out.print(p.string())
~~~

と書くと以下のようなコンパイルエラーになります。

~~~
 this parameter must be sendable (iso, val or tag)
~~~

まだ Reference Capabilities を理解していないので困りましたが、当てずっぽうでアノテーション val を付けてコンパイルを通しました。

# コンパイルと実行

ディレクトリ primes に、上記のように作った primes.pony を置きました。
ディレクトリ primes 上で ponyc を実行すると、実行ファイル primes ができました。

~~~bash
$ ./primes 100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
~~~

また、引数に十分大きい値を与えて長時間実行させつつ、top コマンド（-H）でスレッドの一覧を見ることで３つのスレッドが動いていることが確認できました。
Pony 試乗記(1) コンパイラのインストール

# モチベーション

ムーアの法則の時代は終わった、これからはマルチコアの時代だ、と言われはじめてからもうずいぶん経ちました。
マルチコア CPU の性能を最大限使うには複数スレッドを使ったプログラミングが必要です。

一方で、C / C++ を含む古くからある一般的なプログラミング言語は、シングルスレッドの記述を前提としています。これらの言語ではあと付けのスレッドライブラリによってマルチスレッドプログラミングが実現されています。

C / C++ はそうでなくてもメモリエラーを引き起こしやすい言語で、マルチスレッド化するとさらに排他漏れやデッドロックといった障害も増えてきます。

マルチスレッディングが設計レベルで組み込まれた言語ならもっと安全にプログラミングが可能になるのではと考えました。

例えば、Erlang は独立したメモリを持つスレッド群が通信する形でのプログラミングが可能です。
Erlang も悪くない（というよりかなり理想に近い）のですが、あえて不満点を上げるなら動的型言語なのが気になります。コンパイル時に型チェックしてくれた方が安心です。また、大量にプロセスを作ることができる反面、不要化したプロセスを自動的には破棄してくれないようです。

Ocaml で Erlang コードを生成するコンパイラ、というのも見つけて若干心惹かれたのですが、アクター志向のプログラミング言語で Pony というのがありました。

https://www.ponylang.io/

触れ込みによると:

- 型安全
- メモリ安全
- データ競合安全
- デッドロックフリー
- ネイティブコード動作
- C ヘッダファイル生成可能で、C / C++ プログラムからの呼び出しが可能

これなら希望にかなっています。また、Pony のアクターは Erlang のプロセスと異なりガベージコレクション対象である、と書かれているのを読んでこれも気に入りました。

2022/2/22 時点で、まだバージョン 0.48.0 で、今後この言語の利用がどれだけ広まるかはわかりませんが、Reference Capabilities の考え方は学んでおくと役に立ちそうな気がします。

# ponyup インストール

なにはなくともコンパイラをインストールしなくてははじまりません。

Pony コンパイラのインストール、アップデートを自動化する ponyup というツールの利用が推奨されているようです。

https://github.com/ponylang/ponyup

手元にある Ubuntu マシンに 

~~~bash
$ sh -c "$(curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/ponylang/ponyup/latest-release/ponyup-init.sh)"
~~~

PATH に $HOME/.local/share/ponyup/bin を追加せよ、というメッセージが出るので .bashrc で追加するようにしました。

# ponyc インストール

ponyup を使ってコンパイラ ponyc をインストールします。

https://github.com/ponylang/ponyc/blob/main/INSTALL.md

~~~bash
$ ponyup update ponyc release
~~~

しばらく待たされてインストールが完了しました。

~~~bash
$ ponyup update ponyc nightly
~~~

で最新の nightly build を追いかけることもできます。

ちなみに、「pony up」は「決済する」という意味があるようです。

# まずは Hello, world! から

みんな大好き Hello, world! です。

ディレクトリ helloworld を作って、その下に helloworld.pony ファイルを作れ、と書いてあったのでそのとおりにします。

~~~ helloworld.pony
actor Main
  new create(env: Env) =>
    env.out.print("Hello, world!")
~~~

いざコンパイル、となって躓きました。ponyc に与えるオプションが分からないのです。
どうせコンパイラのオプションなんて gcc とかと一緒だろう、と

~~~bash
ponyc helloworld.pony -o helloworld
~~~

と投入したらよくわからないエラーメッセージが出てきました。正解は

~~~
ponyc
~~~

これだけでした。これで晴れて helloworld 実行ファイルが得られました。

面白いことに、生成される実行ファイルの名前には 〜.pony を置くディレクトリの名前が用いられるようです。試しにディレクトリ **hell_world** に helloworld.pony を置いて ponyc を実行すると、実行ファイル **hell_world** が得られました。

これから少しづつ Pony の書き方・考え方を身につけたいと思います。
GitHubを初めて使った…どういう仕組みなんだろ💦

#投稿の経緯#

- 私は、GitHubを利用したことがなかったのですが、プロジェクトで作成した成果物（ソースコード、ドキュメント）をGitHubで管理している
- 私は、開発ではなく、マニュアルを `Word` から `AsciiDoc` に置き換える作業をしました。
- プロジェクトの管理者からGitHubに登録しちゃいましょ！と言われあたふたしていた
    - 結果、言われるがまな操作してGitHubに登録を行いました

そこで、この時言われるがまま操作した内容（Command）をメモを頼りに思い返し、どういう動きになっているのか、備忘録としてまとめておこう！と思った次第です。

#やったこと#
- GitHub のインストール
    - PCにGitHub のインストールを行いました。（公式サイト：`https://gitforwindows.org/`） <br>
       基本的に初心者なので、default の設定でインスートしました。
- GitHub にリポジトリを新規に作成しました。
    名前は`docsample`としました。<br> その他の設定は、キャプチャの通り…
    
    | <img width="75%" alt="代替テキスト" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518581/e7129531-b5fa-9bc6-1e48-aeafc15b2789.png"> |
|:--|
- 新規にRepositoryが出来上がると、そのあとに実施するべきCommandがGitHub上に表示された

    | <img hight="55%" alt="代替テキスト" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518581/77f0aa43-7f1d-41a5-d4cd-b07dd3f64ad1.png"> |
|:--|
- この通りに実行するわけなのですが、実行はPowerShellを用いて行いました。
    - まずは、Visual Studio Codeを起動して、ツールバーの`ターミナル`から`新しいターミナル`を選択しました。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518581/a4e5503b-8128-c679-e38c-e5969d36b03a.png)
    - 選択すると、`ターミナル：PowerShell`が起動しました。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518581/ed03a68b-23ca-e1ba-2acb-a6688f5e47db.png)
    - ガイダンスに従って、以下のコマンドを打ちました。
         
``` 
　　　　git init
　　　　git status
　　　　git add .　　　　　　// ここでは、ガイダンスと異なり、Readmeだけではなく、カレントにあるファイルすべて登録したかったので"." を指定しました。
　　　　git status
　　　　git config --global user.email "eメールアドレス"   //"eメールアドレス"には、GitHubのアカウントとして登録しているアドレスを指定しました。
　　　　git config --global user.name "名前"               //"名前"には、GitHubのアカウントとして登録している名前を指定しました。
　　　　git commit -m "first commit"
　　　　git log
　　　　git remote add origin "Github リポジトリ URL"
　　　　git push origin master
```

- Pushまでが完了したら、次の結果が返ってきて、無事登録ができました。

```
info: please complete authentication in your browser...
Enumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 8 threads
Compressing objects: 100% (21/21), done.
Writing objects: 100% (23/23), 825.80 KiB | 27.53 MiB/s, done.
Total 23 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), done.
To https://github.com/Nogoooo/docsample.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.
```
--- 

###ここからが本題###
GitHubのサイトにRepositoryを作成したあと、WindowsPC の PowerShell を使って、`git init` から始まり`git log` までのコマンドを入力して実行したけど、最初に作成下Repositoryを指定したわけでない、むしろこの後、`git remote add origin "Github リポジトリ URL"` のコマンドを入力して実行したタイミングで接続されたのだと、なんとなくわかる。
ここまでやってきたリポジトリの作成からWindowsPC にインストールしたGitHub、そして一連のコマンド、これはそれぞれ何をしてたんだろう。
わからないときは、イメージ（絵）にしてみるのが一番！！！！！ということで、イメージしてみた。
さて、このイメージであってるかな？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518581/9da257dd-081b-aefd-3d93-3edd3f4d6d3a.png)
【Rails】erbファイルのコメントアウト方法

# はじめに
プログラミング初学者の[@kat_log](https://twitter.com/kat__log "Twitter")と申します。

railsの`.html.erb`形式のファイルのコメントアウト方法の共有です。

# 方法
**<%の直後に`#`を付ける。**
↓こんな感じです。
`<% コード %>`なら`<%# コード %>`
`<%= コード %>`なら`<%#= コード %>`

# おわりに
お読みいただきありがとうございました。
学習中の方、一緒に頑張っていきましょう〜😄

# 参考
http://rails.takayukikoyama.com/erb/erb-comment/
DeepL で洋ゲー翻訳

<iframe width="560" height="400" src="https://www.youtube.com/embed/Clx6KcS1nRc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
　
DeepL翻訳カスタマイズ Google Apps Script
上記動画で紹介してるアプリに下記GASのデプロイIDを設定

```js:GAS
/*
//GAS翻訳
function doPost(e){
	if(e.postData) var s = LanguageApp.translate(e.postData.getDataAsString(),'en','ja')
	return ContentService.createTextOutput(s||'(本文なし)')
}
*/

function doPost(e){
	if(e.postData) var s = deepl(e.postData.getDataAsString())
	return ContentService.createTextOutput(s||'(本文なし)')
}

function deepl(text) {
	var data = {auth_key:'*** DeepL API の認証キー ***',text:text,source_lang:'en',target_lang:'ja'}
	var r = UrlFetchApp.fetch('https://api-free.deepl.com/v2/translate',{method:'POST',payload:data})
	var v = JSON.parse(r.getContentText())
	return v.translations[0].text
}

```
2022/2/11主にITとかセキュリティの記事

「Emotet」感染、2月第1週より急速に拡大 - ピーク時に迫る勢い
https://www.security-next.com/134075

2021年の悪質ECサイト報告数、前年の約1.7倍に
https://www.security-next.com/134056

HISベトナム法人で顧客情報流出の可能性 - パスポート情報なども
https://www.security-next.com/134038

国内主要企業のDMARC導入率は24％ - 欧米とギャップ
https://www.security-next.com/134066

北海道新聞を装ったメールが送信、「Emotet」感染か
https://www.security-next.com/134064

弁護士や職員装うメールが送信される - 三宅法律事務所
https://www.security-next.com/134083

携帯電話の未払料金請求を装うスミッシング攻撃に注意
https://www.security-next.com/134079

応用地質グループ会社がサイト改ざん被害、フィッシング行為に利用される(2/7)
https://cybersecurity-jp.com/news/63323

株式会社風流舎がエモテット感染、取引顧客に不審メールの可能性(2/8)
https://cybersecurity-jp.com/news/63343

囲碁将棋チャンネルで不具合、別人の個人情報表示される(2/8)
https://cybersecurity-jp.com/news/63345

個人情報のビジネス利用　デジタル化の進展でじわり加速(2/9)
https://www.yomiuri.co.jp/column/economy03/20220207-OYT8T50060/

悪意ある偽サイトの削除件数、ロシアが世界一(2/9)
https://jp.sputniknews.com/20220209/10130102.html

Teamsの背景のぼかし方とは? デフォルトの設定方法やできないときの原因も(2/8)
https://news.mynavi.jp/article/20220208-2259243/

「銀行多すぎ」日本　デジタルバンクが風穴開けるはず(2/9)
https://www.nikkei.com/article/DGXZQOUC0159V0R00C22A2000000/

暗号資産5000億円を洗浄か　米で夫婦逮捕、押収額最高(2/9)
https://www.nikkei.com/article/DGXZQOGN090FG0Z00C22A2000000/

Androidスマホの容量足りない　無駄データ一掃で解消(2/9)
https://style.nikkei.com/article/DGXZQOUC056JM0V00C22A1000000/

ラック、ITで日本を守る職業を紹介する「サイバーセキュリティ仕事ファイル１」を公開(2/9)
https://securityinsight.jp/news/13-inbrief/4922-220209-2

グーグル、2段階認証でハッキング半減と報告(2/9)
https://japan.cnet.com/article/35183279/

米当局、盗まれた暗号通貨４千億円相当を差し押さえ　ＮＹの夫婦を逮捕(2/9)
https://www.cnn.co.jp/tech/35183289.html?ref=rss

Akamai脅威レポート：世界的な海賊版の需要、業界、地域の傾向が判明(2/9)
https://www.jiji.com/sp/article?k=000000134.000031697&g=prt

iPhoneで「テキスト認識表示」を使おう(2/9)
https://news.mynavi.jp/article/20220209-ipadiphonehacks/

金融犯罪の現状報告書を発表＝英コンプライアドバンテージ〔ＢＷ〕(2/9)
https://www.jiji.com/sp/article?k=2022020900599&g=bnw

政府、北京五輪選手らの端末検査　健康管理アプリに懸念〔五輪〕(2/9)
https://www.jiji.com/sp/article?k=2022020900544&g=pol

北朝鮮、サイバー攻撃巧妙に　暗号資産を不正奪取(2/8)
https://www.nikkei.com/article/DGXZQOGN080AD0Y2A200C2000000/

テンプレートで制作を簡単に ノーコードでWebARが作成できる「palanAR」とは(2/8)
https://creatorzine.jp/article/detail/2750

2月Webサイト向けCMSシェア、WordPressが増加(2/9)
https://news.mynavi.jp/techplus/article/20220209-2267657/

ウェブブラウザーは多機能化するほどハッカーに狙われる？ 「Safari」の脆弱性から見えたジレンマと、知っておくべき重要なこと(2/9)
https://wired.jp/article/safari-flaws-webcam-online-accounts-mic/

2021年12月のモバイルマルウェアレビュー ― Google Play上の新たな脅威等(2/9)
https://japan.cnet.com/release/30643992/?ref=rss

セキュリティー人材不足の対策に盲点、NRIセキュアの日米豪の比較調査で判明(2/10)
https://xtech.nikkei.com/atcl/nxt/column/18/00001/06549/

いまだ123456やdoraemonが上位、漏洩ランキングにみるパスワード認証の限界(2/10)
https://xtech.nikkei.com/atcl/nxt/column/18/00138/020700976/
レビューの心得

## 概要
開発を行う上で、レビューへの取り組み方が人それぞれであったので、
レビューの立ち位置と、うまく使うために留意すべきことをまとめています

## レビューはなぜおこなうのか？

- 大前提として承認を得るためではない
- よりよいプロダクトを迅速に開発するため

## よりよいプロダクト開発のために

- 第三者の目を通すことで、多角的な視点を取り入れた開発が可能となる
    - 自身のみの観点で素晴らしいものづくりをするのは非常に難しい
    - よりよいプロダクトへの改善は他人から得られる気付きであることが多い
- 有識者の目を通すことで、より洗練された開発が可能となる
    - ドメイン知識を深い有識者の意見を取り入れることで、よりユーザーに価値を提供できるプロダクトを開発ができる
    - 技術的に知識の深い有識者の意見を取り入れることで、より体験性の高いプロダクト開発ができる

## 迅速な開発を行うために

- 第三者の目を通すことで、単純な抜け漏れを防ぐことが可能となる
    - 自身だけでは気づけないミスや観点、ユースケースの漏れなどはどうしても発生する
    - できるだけ早期に気づくことで、後段での手戻りを防ぐことができる
- 有識者の目を通すことで、不具合の発生を事前に防ぐことが可能となる
    - そもそも不具合を完全に防ぐことはできないが、多くは知識不足や観点不足に起因する
    - 有識者を使って事前にチェックしておくことで、不具合の発生を防ぐことができる
    - リリース後に発覚した不具合の対応コストは、設計時点で発覚した場合と比較し、40倍~200倍に及ぶ
- 有識者の目を通すことで、最速でユーザーに価値提供することが可能となる
    - 特定の機能をユーザーに提供するための方法は複数存在する
    - 優先順位を適切に定め、価値提供のために必要な要素に注力することで、より低コスト早くユーザーに価値提供することができる

## レビューの役割

- よりよいプロダクトを迅速に開発するためのツール

## レビューを有効活用するために

(つまりは、よりよいプロダクトを迅速に開発できるようにするために)

- レビューしてほしいポイントは事前にまとめたうえで共有する
    - より効率的に有用な指摘をもらうことができる
    - ポイントがない場合、レビューアーは全体をまんべんなく確認する必要があり、結果的に議論すべきポイントに注力しきれない
    - ポイントを事前に整理する過程で、改善の方法や確認すべき観点に気づくこともある
- 背景 / 前提を含めたうえで自分の考えを説明する、必要に応じて図やホワイトボード等も用いる
    - 背景 / 前提、意図を伝えられていない状態で、有用な指摘をもらうことは不可能
    - 筋違いな指摘をもらい、その指摘が筋違いだと説明しなくてはならない
    - 背景 / 前提、 意図を伝えて始めて建設的な議論をすることができる
- 事前にレビューを受ける日時を計画しておく
    - 成果物が完成後にレビューを受けるようにしていると、遠回りをすることが多い
    - 時間かけて対応がレビューの結果、不要な対応だと判明することもある
    - レビューの日時と目的を事前に決めておくと、不確定要素排除のための最短距離を駆け抜けることができる
    - レビューアーの予定も事前に抑えておくと、依頼からレビュー完了までのリードタイムもなくすことができる
- 最後は必ず自分で納得したうえでどうするか決める
    - 成果物に責任をもつのはレビューアーではなく開発者自身
    - よい成果物に対して讃えられるべきは開発者自身
    - レビューを有効活用し、よい成果物にするのも開発者の必要な能力

## チームとしての生産性

- レビューのための準備は丁寧に
    - 多くの場合においてレビューアー時間は貴重で有ることが多い
    - 短いレビュー時間で有用な指摘事項をうけることは、チームの生産性向上に大きく寄与する
    - レビューのための準備は、レビューのためだけのコストだけでなく、自身の成果物を見直し改善するためのきっかけとなることも多い
- ラフな状態でレビューしてもらう選択肢も頭にいれておく
    - 時と場合によっては、成果物の完成度が低くレビューの準備もままならない状態でもレビューを実施したほうが良い場合もある
    - 期限が迫っている場合や、どこから手をつけてよいか検討もつかない場合など
    - その場合には状況を説明し、今レビューをうけることがチームとして生産性が高くなることを伝えた上でレビューをうける
- すべてはチームの生産性を最大化するために
    - あくまで生産性向上のためのツールの一つ
    - レビューアーのレビューコストもチームの生産性向上のために必要なもの
    - レビューアー / レビューイーのコストも勘案して全体の生産性を最大化することを全メンバーが意識する必要がある
Google Data PortalでGoogleAnalyticsのデータを可視化してみた

# 概要

社内で`Google Data Portal`について教えていただいたので知識の整理も兼ねて、QIita・Zennの`Google Analytics`のデータを可視化してみました

![スクリーンショット 2022-02-11 2.58.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555632/5c43f8ed-a0c3-afe7-a05a-d77b5dd06024.png)

![スクリーンショット 2022-02-11 2.59.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555632/13d633d2-91dc-52ac-ff72-272ef0a5ec54.png)

## Google Data Portalとは

- 完全無料
- Google系サービスを主体とした様々なデータソースと簡単接続
- データの可視化 etc

膨大なデータを分析、分かりやすく可視化することができるので、レポートや事業計画の資料等、さまざまな場面に応用できます

基本的にドキュメントのガイドに従い可視化したいデータを選択していきます

今回はどの記事がよく読まれているのか・日々の視聴回数等を可視化してみました。

![スクリーンショット 2022-02-11 2.59.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555632/13d633d2-91dc-52ac-ff72-272ef0a5ec54.png)

# 最後に
蓄積されたデータを有効活用する視点が薄かったので個人的にとても良いツールだと思います
みなさんも、個人のAnalyticsで試しに可視化してみてはいかがでしょうか？
PCのユーザー名に半角スペースを入れたばっかりにアプリ開発に支障を来たしているときの対処法 - PCユーザー名のエイリアス命名規則

React.jsを使用してフロントエンドの勉強をしようと思ってReactを導入するべくCreate-React-Appを実行したところエラーが発生し、初手から出鼻を挫かれてSAN値を削られたのですがその原因はどうやらPCのユーザー名に入れてしまっていた半角スペースでした

:::note alert
npx create-react-appを実行して以下のエラーが発生
"Error: EPERM: operation not permitted, mkdir 'C:\Users\〇〇 ' command not found: create-react-app"
:::


PCのユーザー名の（というか開発全般において）半角スペースは開発に支障を来たしうるから避けるというのは開発者的には知ってて当たり前の知識だと思いますが、Windowsの初期設定をしている当時の自分はPCを買ってウキウキだったというのもあってかなんとミドルネームまで入れるというとってもややこしいことをしていました
PCのユーザー名に半角スペースが二つも入ってる人はそうはいないと思いますが、もし私と同じような愚かな過ちを犯して今現在も苦しんでいる人はこの知識を知っておきましょう

:::note info
「PCのユーザー名には"エイリアス"が使用できます!」
:::

さて半角スペースをユーザ名に入れてしまう自分のような人間はおそらくエイリアスという単語を聞いてもいまいちピンと来ないはずなので解説しておきますが、エイリアスとは別名とかあだ名とかいう意味です
コンピュータを扱う上でプログラムを実行するときなどにユーザー名などの変数名を入力する機会が往々にしてあると思いますが、ユーザー名に本名が含まれていてなるべく入力したくないケースがあったとします
こういったときにエイリアスというものを利用すると本名を入力したのと同じ扱いをしてもらえます
エイリアスとは要は別称を利用できるということです

さて話を元に戻しますが、エイリアスが使えると半角スペースの問題をクリアすることができます
というのも半角スペースを含めないエイリアス名を作ればよいからです
このようにエイリアスを使用すると半角スペースを使ったややこしいユーザー名を回避することができます

# エイリアス名の作り方1

さて肝心のエイリアス名の作り方ですが、Windows OSのPCユーザー名に関しては命名ルールがあり以下の規則になっています

:::note info
「FirstName[半角スペース]LastName」=「FirstName~1」
:::

「半角スペース手前の名称」に「~1」を加えるとそれがエイリアス名になります
例えばPCのユーザー名を「Kosei Daiki」にしていた場合は、「Kosei~1」がエイリアス名になります


# エイリアス名の作り方2

さて、上記の作成で全員が救われるのか......というとそうではないです
上記のエイリアス名で救われない人がいます
そうです、ミドルネームをつけてしまった人です（涙）

なんと上記の方法ではユーザー名に半角スペースが複数ある場合に非対応となっています

しかしあきらめないでください
道は残されています（涙目）

おそらくまさしくミドルネームなどの救済措置なんだと思いますが、半角スペースを複数含む場合のエイリアス名は以下の命名規則になっています

:::note info
「FirstName[半角スペース]MiddleName[半角スペース]LastName」=「FIRSTN~1」
「One[半角スペース]Second[半角スペース]Third」=「ONESEC~1」
:::

「全体のユーザー名の初めの6文字」に「~1」を加えるとエイリアス名になります
これが少しややこしくて、半角スペースをまたいで6文字であることに注意が必要です
例：
ユーザー名: Tim va Jiro
エイリアス名: TIMVAJ~1

全体で6文字もないユーザー名はどうやるかは知りません
というかミドルネーム含めてそんな名前を作れるんですかね
常識的に「Ad Bf C」みたいな名前作らないと思うので考えないことにします


# エラーの回避方法

さて、エイリアス名の命名方法はわかりましたがエラーをどうやって回避するかはその時々のケースによって違います
ググって調べるしかないです.....

Create-React-Appのユーザー名のエラーの場合は以下のコマンドを入力することで回避できました

:::note info
npm config set cache "C:\Users\[エイリアス名]\AppData\Roaming\npm-cache" --global
:::

パスが通っていないことが原因のようだったのでパスが通るように設定しています


ちなみにPCユーザーの半角エラーで一番頻繁に起こるものは、コマンドラインでファイルなど何らかのフルPathを通す場合にユーザー名の半角スペースがコマンドの意味の区切りとして勘違いされてエラーが起こるケースだと思います

例えばこういうものは半角スペースが引数の切れ目だと勘違いされてエラーが出るはずです

:::note alert
cd C:\Users\FirstName LastName
:::

こういう場合はダブルクォーテーションで回避することもできます

:::note info
cd "C:\Users\FirstName LastName" 
:::

これなら通るはずです


# 終わりに

次にPCを買ったときはユーザー名には半角スペースを絶対に入れない
Qiitaでめちゃイケ記事を書くためのTeXの埋め込み方

こんにちは。[@takutheking](https://twitter.com/takuk_ultimater)です。
n番煎じにはなりますが、Qiita上にTeX記法の埋め込み方を調べてみました。

# これだけ知っとくべき
:::note
インラインで数式を書く時は$で囲む。段落として書く時は$$で囲む。
:::
例えば、$e^x=1+x+\frac{1}{2}x^2+\cdots$はこんな感じで書きます。
> \$e^x=1+x+\frac{1}{2}x^2+\cdots$

:::note 
TeX形式で複数行の数式を書きたい時は```mathと```の間に挟む。改行は\\によって行う。
:::
```math 
\begin{align}
e^x&=1+x+\frac{1}{2}x^2+\cdots \\
  &=\sum_{i=0}^\infty \frac{x^n}{n!}
\end{align}
```
は、こんな感じです。

><div>
```math 
<br>\begin{align}
<br>e^x&=1+x+\frac{1}{2}x^2+\cdots \\
<br>&=\sum_{i=0}^\infty \frac{x^n}{n!}
\end{align}
<br>```
</div>

Texの表記法については、[Qiitaの数式チートシート](https://qiita.com/PlanetMeron/items/63ac58898541cbe81ada)、[Qiita Markdown 書き方 まとめ](https://qiita.com/shizuma/items/8616bbe3ebe8ab0b6ca1)がくわしいのでこちらをご覧ください。

# 参考文献
[Qiitaの数式チートシート](https://qiita.com/PlanetMeron/items/63ac58898541cbe81ada)
[Qiita Markdown 書き方 まとめ](https://qiita.com/shizuma/items/8616bbe3ebe8ab0b6ca1)
git push origin HEAD

# はじめに
この記事は、エンジニア経験1年の大学生が実務で勉強になったこと、詰まったことをまとめています。間違っている箇所などがございましたら、指摘していただけると幸いです。

# 背景
だいぶ前にあるタスクで先輩インターン生にモブプロのような形で教えていただくことがあり、その時に教えていただいたので、記事にしました。

# git push origin HEAD
```
$ git push origin <ブランチ名>
```
恥ずかしながら、これまで自分は`git push origin <ブランチ名>`で毎回プッシュを行なっていました。

```
$ git push origin HEAD
```
しかし、圧倒的にこちらのコマンドの方が楽にプッシュすることができます。では、なぜ`git push origin HEAD`でリモートブランチにプッシュできるのでしょうか?


# なぜgit push origin HEADで現在のブランチをプッシュできるのか

まず、`HEAD`が鍵になっています。`HEAD`を簡単に説明すると、「今自分が作業している場所を示すポインタ」になります。なので、`git push origin HEAD`でカレントブランチをプッシュすることができるのです。

# まとめ
今回改めて、ペアプロのメリットを感じることができました。実装の仕方や考え方、注目するポイントなど学ぶことがたくさんあり、とても貴重な時間でした。
【python初心者】matplotlib、seabornで箱ひげ図を描く

いつもわからなくなってしまうのでメモ。

```Python:
# 必要ライブラリのimport
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# matplotlib日本語化対応
import japanize_matplotlib

#今回はirisを使用
df_iris = sns.load_dataset("iris")
columns_i = ["がく片長","がく片幅","花弁長","花弁幅","種別"]
df_iris.columns = columns_i

display(df_iris.head())
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/47494113-c4b6-79ba-dacc-6719edf18887.png)

# 基本的な箱ひげ図
### matplotlib活用の場合
.boxplotを活用することで、数値列を自動で読み取り、各列の箱ひげ図を作成してくれる。

```Python:
df_iris.boxplot(patch_artist = True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/13205a41-ff78-346d-ee40-27f951717468.png)


patch_artistは箱の塗りつぶしを設定。デフォルトはFalseで、その場合塗りつぶしなしとなる。

```Python:
df_iris.boxplot(patch_artist = False)
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/408b958b-f13a-ab57-9ab2-7c8e716d10b0.png)

横に表示するときはvert=Falseを指定

```Python:
df_iris.boxplot(vert=False)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/773f8241-d0f1-0d44-62b3-3a0781a8ff8c.png)


### seabornの場合
seabornを使っても同じような結果を得られる

```Python:
sns.boxplot(data=df_iris)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/c09ee973-5dbf-1e67-0748-71db207f0524.png)

# グループ分けして箱ひげ図を描く
特定の列を選択し、その列の中でのグループ分けを基準にして箱ひげ図を作成する。
byで列を指定すると、そのグループ分けごとにグラフを作成する。

```Python:
df_iris.boxplot(by='種別', patch_artist = True)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/7792b767-0ed8-6da5-c3de-1a28ab1b6e3a.png)

seabornでは、xにグループ分けの基準とする列、yに対応する数値の列を指定する

```Python:
sns.boxplot(x = "種別", y = "がく片長", data = df_iris)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/28f3dfba-1ced-6bee-ed87-897758d2f837.png)

また、各x軸ごとの分類でさらに要素で分類する場合、元データを変換したうえでseabornを活用する。

```Python:
w = pd.melt(df_iris, id_vars = ["種別"])
display(w.head())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/ababd42f-584e-af3c-088e-b455ba339807.png)

```Python:
sns.boxplot(x = "variable", y = "value", data = w, hue = "種別")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307295/ab073812-cff9-5db4-f805-cf7a6f37a35d.png)
Ubuntu Server 20.04.3 LTS 構築#12 セキュリティ設定5-5

実施日:2022/02/08～2022/02/10

<<今回の内容>>
『定義ファイルを更新』⇒『ウイルススキャン』⇒『結果をメールで通知』をシェルスクリプトで行う

【イメージ】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516251/ffe2b667-d485-77ee-87fa-80b417664b48.png)

上記、イメージを実行するためにシェルスクリプトを作成しました。


##修正 2022/02/19##
当初、最初に設定したスクリプトだと隔離したディレクトリにファイルが存在しても、defected判定されてしまったりして
挙動がおかしいためスクリプト内を修正しました。修正箇所はif～の部分です。

:::note
SCAN_RESULTS=/home/ユーザー名/Documents/virus/scanlog.log

if [ ! -z \"\$(grep FOUNDS\$ ${SCAN_RESULTS})" ];then
{
cat \${SCAN_RESULTS} | mail -s 'Warning' ユーザー名<span>@</span>localhost
}
else
{
cat ${SCAN_RESULTS} | mail -s 'Safe' ユーザー名<span>@</span>localhost
}
fi
grep FOUNDS$はclamscan実行でウイルスとして検知したもののファイルの語尾にFOUNDSと付記されるため、
スキャン時にFOUNDSのものを拾ってinfectedで通知してもらうための設定です。
:::

```
#!/bin/bash

FILE=/home/ユーザー名/Documents/virus/

sudo systemctl stop clamav-freshclam
sudo freshclam
sudo systemctl start clamav-freshclam

if [ $?=0 ];then
{
echo 'success'
}
else
{
echo 'fail'
echo 'update NG!!' | mail -s 'freshclam NG!!' ユーザー名@localhost
}
fi

clamscan \
-i \
-r/ \
--log=/home/ユーザー名/Documetns/scanlog.log \
--exclude-dir=/sys/ \
--exclude-dir=/proc/ \
--exclude-dir=/home/ユーザー名/Documetns/virus/ \
--remove=/home/ユーザー名/Documetns/virus/ \
2>/dev/null

if [ -e ${FILE} ];then
{
ls -a /home/ユーザー名/Documents/virus/
echo 'virus infected' | mail -s 'warning' ユーザー名@localhost
echo 'Scan NG!!'
}
else
{
ls -a /home/ユーザー名/Documents/virus/
echo 'virus detected' | mail -s 'warning' ユーザー名@localhost
}
fi
```

最初は各コマンドをif [ \$?=0 ]で成功又は失敗でclamscanコマンドを条件分岐して実行しようとしましたが、
隔離先ディレクトリに無害ウイルスを格納しているにも関わらず、感染したファイルはありませんと表示されました。
exclude-dir=のオプションを入れてません。
途中でコマンド自体の成功か失敗かを判断して実行していることに気づき、変数を[ \$?=0 ]ではなく、
[ -e \${FILE} ]に修正して隔離先ディレクトリのファイルの有無で判定するようにしたら条件分岐するようになりました。

最後に/etc/cron.d/内にある実行ファイルに作成したシェルスクリプトを実行するように設定して、自動で実行が出来るのかを
確認します。ここでcronが動作してくれれば今回の作業は完了となります。
cronの結果も予定通りの挙動になってくれました。

参考にさせて頂きましたサイト様
[稲葉サーバーデザイン様](https://inaba-serverdesign.jp/blog/20170913/clamav_scan_virus_install.html)
[@kannkyo様](https://qiita.com/kannkyo/items/1cc32231afad88c11d8e)
Moonfire-NVRのすすめ

# 高解像度カメラの処理が重い…
いまや高画質のWifi監視カメラが、低価格で売られている時代です。
自宅のセキュリティのために、監視カメラシステムを自作している人も少なくないと思います。

いままでMotionやZoneminderを使ってきましたが、なんというか負荷が重い。
低解像度なカメラであれば十分な動きをするのですが、5MPとかの高解像度のカメラを使うとCPU負荷が半端ない。Raspiだとさらに…。
そして、録画された動画に遅延や音飛びやフレーム落ちも起きるので、困っていました。

# 組み合わせによる低負荷化
ほとんどの高解像度カメラには、高解像度の動画を送付する「メインストリーム」と、低解像度の動画を送付する「サブストリーム」が備わっています。

前述したMotionやZoneminderは、サブストリームの解析であればRaspiでも余裕があります。

そこで、動体検出はサブストリームで行うこととして、メインストリームを軽負荷で記録できるソフトはないかを探っていました。
そこでたどり着いたのがMoonfire-NVRです。

# 従来のシステムはなぜ重いか
大体のカメラは、H264で圧縮した動画をひたすら送り付けてきます。

MotionやZoneminderは、動体検出時に録画をするために動画をバッファリングしたりデコードしたり解析したり、というところにCPU/GPU/メモリを食います。重いわけです。
こう見るとConsだけですが、当然Prosはあります。
これらのProsは、なんの動きもない、いわゆる余分な録画を撮らないということです。
イベントを見返すのも楽ですし、記録容量が少なくてすみます。

# Moonfire-NVRはなぜ軽いか
一方、Moonfire-NVRは、HDDやSSDなどのストレージにカメラからのデータを垂れ流します。
常時録画をするのでストレージの容量は山ほど食いますが、CPU/GPU/メモリ余分な処理がない分非常に軽量です。
ストレージの容量にものを言わせるアプローチは**非常に魅力的**です。
例えば、8Mbpsの動画を記録するとして、一日丸っと録画しても100GB行きません。
> 8Mbps * 3600 sec * 24 hour = 約87GB

今や4TBのHDDは1万円しないので、非力なマシンでも常時高画質録画ができるって話ですね。素晴らしい。

Consは、常時録画ということ。
ただ、前述のとおりサブストリームで動体検出、メインストリームをMoonfire-NVRで録画しておけば大したことはありません。

あと、若いOSSですから機能はまだ限定的です。多分バグもあるでしょう。
それでも、ブラウザから録画一覧を見たり、時間指定してのプレビューができたり、
TUIで設定できたり、導入が簡単だったりと十分な機能を持っています。

# 導入方法
導入方法は以下を読め、です。

わかんね(^_-)-☆って人は、コメントもらえればある程度支援しますが、
ダウンロードの待ち時間など含めても１５分もかかりません。
https://github.com/scottlamb/moonfire-nvr

私はRaspi4にZoneminder+Moonfire-NVRの構成で、3台の5MP監視カメラを管理してます。ロードアベレージも平均2程度なので、すげー軽い。

# 終わりに
日本語で、Moonfire-NVRの記事が全く見つからなかったので書き起こしました。
興味あれば、一緒にいろいろ試してくれると嬉しいです。