OmniAICreator/Qiita-1.07M huggingface dataset:
ReactNative 前提知識

#ReactNative 前提知識と.js .ts .tsx 拡張子の違い

**ReactNative**
・Reactでアプリ開発できるようにしたもの
・iOS、andoroidに対応したJavaScriptエンジンを使用
→一つの成果物でどちらのアプリにも対応
　従来はそれぞれ作成（iOS→Swift、Object-C/android→Kotlin、Java）

**React**
・Facebook社が開発したJavaScriptライブラリ
・フレームワーク、言語ではなくライブラリ
・Webサイト上のUIパーツ構築に長けている


**.js**
・JavaScriptが使用できる
・TypeScriptは使用できない
TypeScript→JavaScriptの拡張言語で可読性に優れる

**.ts**
・TypeScriptが使用できる
・マイクロソフト社が開発したJavaScriptを拡張したプログラミング言語

**.tsx**
・TypeScriptが使用できる
・TypeScriptの中でJSX構文を扱うことができる
JSX：JavaScriptでhtmlを書くようにDOMを記述できる

**JSX**
React.createElement(component,props, ... childen)の糖衣構文
コンパイル後はJavaScriptとして読み込まれる
糖衣構文：プログラミング言語で読み書きしやすさのために導入する。シンプルでわかりやすいコードを書くためのもの
音声認識【８．CTC】

- CTC: Connectionist temporal classification
- DNNだけで音響モデルを作成
- HMMなしで直接音素列や文字列の確率を出力
- 一般には言語モデルと併用
- 前向き・後ろ向きアルゴリズムを用いてDNNを学習
- ブランクラベルの導入
- 射影（Projection）層：RNNの出力層の後に線形層を加えてパラメータを削減
- サブサンプリング：入力音声のフレーム数は出力テキストのトークン数に比べて非常に多く，またRNNは他の時刻の情報も保持するため，RNNの計算処理を数フレーム単位で間引いても性能に大差ない．
- Colab: （なぜかリンクが共有できないので，後日更新します．）
音声認識【７．DPマッチング】

### 時間的ゆらぎ
音声の長さや途中のスピードの変動

### アライメント
各音声と各フレームの対応関係

# DPマッチング
- Colab: https://colab.research.google.com/drive/1uLG_I2fY4Ox2YRA-laCGugitj6P4BsYV?usp=sharing
- アライメントを推定しながら音声同士の距離を計算する
- DP: Dynamic Programming = 動的計画法
    - 順番に問題を解いていく．問題の一部を解いたらその答えを使って残りの問題を解く．

### Dynamic Time Warping (DTW)
- 時間的ゆらぎを吸収するように音声を時間方向に伸縮させる処理のこと
- DTW距離

### バックトラック
- 経路を記録しておいて，ゴールから逆にたどること．
音声認識【６．メル周波数ケプストラム】

# メル周波数ケプストラム
- 対数メルフィルタバンク特徴量は，人間の**聴覚**のメカニズムに基づいて情報を圧縮
- メル周波数ケプストラムは，聴覚だけでなく，**発声**のメカニズムも考慮してさらに情報を圧縮
- 声道の共振特性: 声道内で声帯振動の特定の周波数が共鳴し，音声となる
    - フォルマント：共振周波数

## ケプストラム分析
- この声道の共振特性の成分を抽出
- 音声スペクトルを声帯振動の成分と声道共振特性の成分に分離する
- ケプストラム：対数パワースペクトルをさらに逆フーリエ変換を行って得たもの
    - 横軸：ケフレンシ（秒）
    - 低ケフレンシ帯域：パワースペクトルの包絡部分（声道共振特性の成分）
    - 高ケフレンシ帯域：パワースペクトルの微細構造の部分（声帯振動の成分）
    - リフタリング：ケプストラム領域において高ケフレンシ帯域をカットすること
    - リフタ：ケプストラム領域におけるフィルタ
- Colab: https://colab.research.google.com/drive/1fa3bHBJfLQLfhR8fycP5fjh8tdAB28D7?usp=sharing

## メル周波数ケプストラム（MFCC）
- ケプストラムをメルフィルタバンク特徴量に対して計算したもの．
- メルフィルタバンク分析によって圧縮したパワースペクトル情報に対して，ケプストラム分析によってスペクトル包絡（声道共振特性）の成分だけを取り出して情報をさらに圧縮する．
- 対数メルフィルタバンク特徴量に対して，離散コサイン変換を行い，低次の値だけを取りだす．
- Colab: https://colab.research.google.com/drive/1r6-gB97lfYv6u9kail9lBY7Tj5Re2shE?usp=sharing
音声認識【５．対数メルフィルタバンク特徴量】

## ディザリング処理
- 無声区間における波形に対してスペクトログラムを計算しようとすると，振幅スペクトルの対数を計算したときに値がマイナス無限大となってしまう．
- そこで，波形全体に対して極小さいノイズを加える．

## 直流成分除去
- 振幅値の平均は0になるはずだが，環境の影響によって平均値が0からずれることがある．
- この影響を除くために短時間フーリエ変換の際にフレームごとにその振幅値の平均で引く．

## 高域強調（プリエンファシス）
- 音声は高周波数帯域になるほど，マイクに到達するまでに減衰してしまう．
- 減衰分だけ増加させるためのフィルタを畳み込む．

## フィルタバンク
- そのままでは周波数成分それぞれが次元となり，次元が多すぎて分析には適していない．
- そこで，一定範囲ごとに周波数成分の値を集約して一つにまとめる．

## メルフィルタバンク
- フィルタバンクの中でも，人間の聴覚特性に基づいて集約を行う方法．
- 人間は周波数が低い音に敏感で，高い音に鈍感．
- よって，低周波数帯域では細かい単位で集約し，高周波帯域になるほど大きい単位で集約する．
- メル尺度：低周波数帯域ほど急で，高周波数帯域ほど緩やか
- メル尺度に従ってフィルタバンクを作成したものが，メルフィルタバンク．

<img title='image.png' alt='image' src='/attachments/d827769f-0b17-44be-8812-224a939eff14' width="720" data-meta='{"width":720,"height":360}'>

## 対数メルフィルタバンク特徴量（FBANK）
- メルフィルタバンク特徴量の対数を取ったもの．


## 実装
- Colab: https://colab.research.google.com/drive/17RlHdTqDVQfG39MjysJ745VmQOqFH5wL?usp=sharing
音声認識【４．短時間フーリエ変換】

# 復習
- サンプリング周波数：1秒間にサンプリングする音圧値の数（サンプル数）．通常は16,000Hz.
- サンプルサイズ：音圧値を離散値に変換する際の目盛りの数．
- 全サンプル数 / サンプリング周波数 = 音声データの秒数
- フーリエ変換：時間信号を周波数軸に変換する．蝸牛内の基底膜と同様の働き．
- スペクトル：フーリエ変換の結果得られたもの．
- 振幅スペクトル：スペクトルの絶対値．複素数から振幅成分を取り出す．
- パワースペクトル：振幅スペクトルの二乗．
- 対数振幅スペクトル：振幅スペクトルのままだと値の振れ幅が大きく見づらいため，対数を取る．

# 短時間フーリエ変換
- 記事３．フーリエ変換では，音声波形をフーリエ変換してスペクトルを可視化した．一方で，横軸が周波数のせいで時間情報が分からなくなってしまった．
- そこで，時間変化の情報も維持するために，短い時間ごとに音声を区切り，短時間ごとにフーリエ変換を行う．（**短時間フーリエ変換**）
- フレーム：短時間ごとに取り出されたデータそれぞれ
- オーバーラップ：隣のフレームとの被り．ある程度被らないと音声の変化を捉えられない．
- フレームサイズ：フレームの幅．
- フレームシフト：フレームの間隔．

## 窓関数
<img title='image.png' alt='image' src='/attachments/11bfc224-f087-4032-9bf5-f8414f7fc7ed' width="550" data-meta='{"width":550,"height":740}'>

- あまり知られていない（？）が，これ重要．就活でも理解を問われたことがある．
- フーリエ変換では分析区間を1周期分とみなす．すなわち，分析区間の波形がその前後に連続すると解釈．
- ゆえに分析区間の始端と終端が一致している必要がある．
- しかし，現実には波の周期と分析区間は一致していない．
- そのため，波形の不連続な結合がノイズを生じる
- そこで，窓関数の出番．

### ハミング窓
<img title='image.png' alt='image' src='/attachments/f9a3a99d-d745-4767-ac07-1a66a7634b12' width="700" data-meta='{"width":700,"height":500}'>

- 最もよく使われる窓関数．
- 始端，終端に近付くにつれて０に近付く．
- よって，波の継ぎ目の不連続部分が0に収束する．
- 故に，不連続部分が目立たなくなり，ノイズが生じにくくなる．

## 短時間フーリエ変換・スペクトログラム作成
- Colab: https://colab.research.google.com/drive/1Q12VvZ9Qkzzln2L6ahxAWp__clChG3-g?usp=sharing
- ``int(fft_size/2)+1``：振幅スペクトルは左右対称であり，右半分は不要．
- ``np.fft.fft(frame, n=fft_size)``：サンプル数400に対して，500になるまで0を加えた後でfftを行う．
<img title='image.png' alt='image' src='/attachments/d51c6ef2-0537-428e-9f30-1dcac9697e3b' width="395" data-meta='{"width":395,"height":248}'>
音声認識【3. フーリエ変換】

# フーリエ変換
## 背景
- 自然界の音は複数の周波数の音が組み合わさってできている
- この組み合わさり方の違いが，音色の違いとして表れる
- 人間は蝸牛内の基底膜が，周波数ごとの振動に分解する
- 音声認識では，フーリエ変換によって，基底膜の働きを模倣する

## フーリエ変換とは
- 「あらゆる周期的な信号は，周波数の異なる三角関数の組み合わせで表現できる」
- 連続フーリエ変換：連続値の時間信号を連続値の周波数軸に分解
- 離散時間フーリエ変換：離散値の時間信号を連続値の周波数軸に分解
- **離散フーリエ変換（DFT）**：離散値の時間信号を離散値の周波数軸に分解
    - 音声認識では離散フーリエ変換
- 逆離散フーリエ変換（IDFT）：スペクトルから時間信号に戻す
- 高速フーリエ変換（FFT）：離散フーリエ変換を高速に処理する方法
- この辺りの理論はまた後程詳しくやり直す．

## スペクトル
- スペクトル：フーリエ変換の結果得られる．音圧、音の強さなどを周波数の関数として示したもの．
- 振幅スペクトル：スペクトルの絶対値．フーリエ変換の結果得られた複素数から絶対値を取って，振幅を取り出す．複素数平面における原点からの距離．
- 位相スペクトル：偏角．フーリエ変換の結果得られた複素数の偏角．
- 対数振幅スペクトル：振幅スペクトルの対数を取ったもの．振幅スペクトル自体は値の振れ幅が極端に大きく，プロットすると細部の構造が見づらく分析に向いていない．
- パワースペクトル：振幅スペクトルの二乗

## FFT 実装
- Colab: https://colab.research.google.com/drive/1DlMTv_XumEzoodqjfFES3-UvixSaKS-H?usp=sharing
- 時間0.58の時点における対数振幅スペクトル
<img title='image.png' alt='image' src='/attachments/6edd9b3f-dd94-4e2b-8ac5-f702195fe88f' width="368" data-meta='{"width":368,"height":248}'>


- 次回は短時間フーリエ変換．
音声認識【2. 特徴量抽出】

# 特徴量抽出
## 音声ファイル
- 圧縮音声
    - 可逆圧縮音声: flac
    - 非可逆圧縮音声: mp3
- 非圧縮音声: wav

**音声認識では普通，非圧縮音声であるwavが用いられる．**
<br>
- チャネル数: マイクの数．
    - モノラル音声
    - ステレオ音声
   
音声認識では普通，チャネル数は１．

## 音声のコンピュータでの保存
- チャネル数１のマイクで検知された音声は連続値．しかしコンピュータに保存する際には離散値に変換する必要がある．
- 横軸（時間）：一定の時間間隔でその時の音圧値をサンプリングする．
    - 音圧値: 空気の圧力変動の強さ
    - サンプリング周期: サンプリングする時間間隔
    - サンプリング周波数: サンプリング周期の逆数．1秒間に何個分の音圧値をサンプリングしたか．
        - CD: 44,100 Hz
        - 電話: 8,000 Hz
        - **通常の音声認識: 16,000 Hz**
- 縦軸（音圧）：一定の間隔で目盛りを刻み，各音圧値を最も近い目盛りの値に近似して記録（量子化）
    - サンプルサイズ: 刻んだ目盛りの総数
        - 2のべき乗
        - **通常の音声認識: 16ビット**

## wav波形プロット
チャネル数，サンプリング周波数，サンプルサイズはwavファイルのヘッダに記載されている．よくみる音の波形は，サンプリングした**音圧値**を時系列に並べてプロットしたもの．
- コード: https://colab.research.google.com/drive/17fZq9tNyHehPEnjXv4EChVl49cuyUbVP?usp=sharing
Amazon S3 Interface型エンドポイント（PrivateLink）のアクセス


### １．目的

本記事では、Interface型エンドポイントを用いて、Amazon S3にアクセスする方法を整理する。Interface型エンドポイントを作成するとエンドポイント固有のDNSが作成される。このエンドポイント固有のDNS名はパブリック DNS ドメインから解決できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482612/987dda13-7f99-a82e-3132-a61272c8a796.png)




#### ２．作成手順
S3エンドポイントをVPCサービスから作成。タイプをInterface型のものを選択する。セキュリティグループは、要件に合わせて設定する。送信元を絞り、プロトコルHTTPS:443を許可するイメージ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482612/1e8eecc1-54d6-11f2-240f-cb07ca5149a3.png)



#### ３．エンドポイントに割り当てられるDNS名の確認

・Interface型エンドポイントには「リージョン固有のDNSホスト名」と「ゾーンごとのDNSホスト名」が発行される。
・リージョンDNSホスト名は紐づいている全てのENIのプライベートIPアドレスを返す。ゾーンDNSホスト名は各AZ毎に生成され、そのゾーンにあるENIのプライベートIPアドレスを返す。


試しに、リージョンDNSをVPC内部のサーバから名前解決を試みると、、、


```bash:digコマンドの結果
dig vpce-XXXXXXXXXXXXXXXXXXX.s3.ap-northeast-1.vpce.amazonaws.com

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.68.rc1.87.amzn1 <<>> vpce-04fc227a85d14d7d7-8slelhjt.s3.ap-northeast-1.vpce.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2612
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;vpce-04fc227a85d14d7d7-8slelhjt.s3.ap-northeast-1.vpce.amazonaws.com. IN A

;; ANSWER SECTION:
vpce-04fc227a85d14d7d7-8slelhjt.s3.ap-northeast-1.vpce.amazonaws.com. 60 IN A 172.31.48.202
vpce-04fc227a85d14d7d7-8slelhjt.s3.ap-northeast-1.vpce.amazonaws.com. 60 IN A 172.31.49.113
vpce-04fc227a85d14d7d7-8slelhjt.s3.ap-northeast-1.vpce.amazonaws.com. 60 IN A 172.31.50.240

```


たしかに、Private IPを返している。VPC外部（というかAWS外部）からnslookupを実施しても同様の結果が得られた。具体的には、[ここ](https://www.cman.jp/network/support/nslookup.html)のサイトを利用した。



```bash:nslookupの名前解決の結果
Server:         (当社Dnsサーバ)
Address:        157.65.26.10#53

Non-authoritative answer:
Name:   vpce-XXXXXXXXXXXXXX.s3.ap-northeast-1.vpce.amazonaws.com
Address: 172.31.50.240
Name:   vpce-XXXXXXXXXXXXXX.s3.ap-northeast-1.vpce.amazonaws.com
Address: 172.31.48.202
Name:   vpce-XXXXXXXXXXXXXX.s3.ap-northeast-1.vpce.amazonaws.com
Address: 172.31.49.113
```

ちなみに、VPC内部からの名前解決は、VPCを構築した際に払い出されるAmazonProvidedDNS（VPC のネットワークアドレス +2 の IPアドレスを持つ。）によって実行される。VPC内部のEC2インスタンスから、以下コマンドを叩くことで確認できる。

```bash:providedDNSの確認
cat /etc/resolv.conf 
options timeout:2 attempts:5
; generated by /sbin/dhclient-script
search ap-northeast-1.compute.internal
nameserver 172.31.0.2
```
#### ４．Interface型エンドポイントを経由したアクセス

endpoint-urlオプションでDNS名をつけることでアクセスすることができる。ここではBucket一覧を取得している。


```bash:Interface型エンドポイントを用いたアクセス
aws s3 --endpoint-url https://bucket.XXXXXXXXXXXX.s3.ap-northeast-1.vpce.amazonaws.com ls
2021-02-17 01:48:26 XXXXXXXXXXXX
2021-02-17 00:24:53 XXXXXXXXXXXX
```


以上

### 参考URL

- [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/privatelink-interface-endpoints.html)
- [クラスメソッドの記事](https://dev.classmethod.jp/articles/s3-privatelink-diagram/)
Could not find tools.jar. Please check that /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home contains a valid JDK installation.

しばらく開発していなかったAndroidアプリが以下のエラーでビルドできなくなってしまいました。

```
Could not find tools.jar. Please check that /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home contains a valid JDK installation.
```

[Stackoverflow](https://stackoverflow.com/questions/64968851/could-not-find-tools-jar-please-check-that-library-internet-plug-ins-javaapple) によると Big sur に更新後に発生する様子。

ちなみに私の環境は2番目の回答のような2つのJava環境がある(?はっきりわかってない)状態でした。

```
$ /usr/libexec/java_home -V | grep jdk
Matching Java Virtual Machines (2):
    1.8.181.13 (x86_64) "Oracle Corporation" - "Java" /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
    1.8.0_181 (x86_64) "Oracle Corporation" - "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home
```

対処方法は下の方のパスを`JAVA_HOME`として環境変数に設定する方法が有効でした。

```~/.bash_profile
# 以下追記
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home
```
音声認識【1. 基礎】

# 音声認識 基礎

## モチベーション
- 機械学習によって音声認識を行っていても，何を弄っているのかがよく分からない．そもそも音とは何なのだろうか．

## 人間による音の知覚
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/711751/6e217958-0e21-8812-38d8-b644c732eb73.png)


1. 音，すなわち空気の振動が外耳道を通って鼓膜を震わせる．
2. 耳小骨が鼓膜の振動を増幅させて内耳に伝える
3. 内耳の中の蝸牛の基底膜を振動させる
4. 振動の周波数によって，基底膜上の振動の位置が異なる -> **基底膜上の位置ごとの振動という形で周波数ごとの振動成分に分解される．**
5. 基底膜上の細胞が刺激されて脳へと伝わる

-> **フーリエ変換によって，蝸牛の基底膜と同様の働きを実現している．**

## 音声認識モデルの流れ
### 1. 音響モデル
音声特徴量と音素列の間の確率を計算するモデル．

### 2. 発音辞書
音素列と単語の対応を扱うモデル

### 3. 言語モデル
ある単語に対して，その単語が発話される確率を計算するモデル．
create-react-appでReact+Redux+Typescriptのアプリのひな形を作る

create-react-app で React + Redux + Typescript のアプリのひな形を作るときのメモ

環境は WSL2 + VSCode

追加で行う設定は以下
1. Prettier の設定を追加
2. import に絶対パスを使う

# create-react-app

`practice` のところに自分のアプリ名を入れる

```shell
npx create-react-app practice --template redux-typescript
cd practice
```

# Prettier

## Prettierを追加

```shell
yarn add -D prettier
```

## Prettierの設定を行う

設定は .prettierrc ではなく package.json に追加するのが好きなので、全部 package.json に書く。
あと VSCode とか使っていない人がチームにいたりするので、scripts にコマンドを追加しておく。

```json:package.json
{
  "scripts": {
    "format": "prettier --write src/**/*.{ts,tsx}"
  },
  "prettier": {
    "arrowParens": "avoid",
    "printWidth": 120,
    "trailingComma": "es5",
    "tabWidth": 2,
    "semi": false,
    "singleQuote": true
  }
}
```

# import の絶対パス指定

https://create-react-app.dev/docs/importing-a-component/#absolute-imports を参考に設定。
"include": ["src"] は最初から追加されていた

```json:tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src"
  }
}
```
C# PowerPoint プレゼンテーションに動画を追加

<p><strong>パワーポイントで作ったプレゼンテーションに音声付きの動画を挿入することで聞き手の注目を集めることができますし、プレゼンテーションを鮮やかな手段で、一層ビジュアル化させることができます。さあ、今日はFree Spire.Presentationという無料のライブラリを使って、プレゼンテーションに動画を追加する方法を紹介します。</strong></p>
<h4><strong> </strong><strong>下準備</strong></h4>
<p><strong>1.E-iceblueの公式サイトから<a href="https://www.e-iceblue.com/Download/download-presentation-for-net-free.html">Free Spire. Presentation</a>無料版をダウンロードしてください。</strong></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210728/20210728155920.png" alt="f:id:lendoris:20210728155920p:plain" width="554" height="205" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p>
<p><strong>2.Visual Studioを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire. Presentation.dllを参照に追加してください。</strong></p>
<p><strong>（Net 4.0を例としたら、デフォルトパスは“Bin→NET4.0→Presentation.dll”というようになります。）</strong></p>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210728/20210728155929.png" alt="f:id:lendoris:20210728155929p:plain" width="250" height="216" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p>
<h4><strong>ビデオ（動画）を挿入</strong></h4>
```C#
using System.Drawing;
using Spire.Presentation;

namespace ConsoleApp6
    {
        class Program
        {
            static void Main(string[] args)
            {
                //PPTファイルをロードします。
                Presentation ppt = new Presentation();
                ppt.LoadFromFile("サンプル.pptx");

                //スライドを取得します。           
                ISlide slide = ppt.Slides[0];
                //スライドに動画を挿入します。
                slide.Shapes.AppendVideoMedia(@"ビデオ.mp4", new RectangleF(300, 150, 300, 150));

                ppt.SaveToFile("動画追加.pptx", FileFormat.Pptx2010);

            }
        }
    }
```
<h4><strong>実行結果</strong></h4>
<p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/lendoris/20210728/20210728160008.png" alt="f:id:lendoris:20210728160008p:plain" width="554" height="300" loading="lazy" title="" class="hatena-fotolife" itemprop="image" /></p>

```C#
using System.Drawing;

using Spire.Presentation;

namespace ConsoleApp6

    {

        class Program

        {

            static void Main(string[] args)

            {

              //PPTファイルをロードします。

            Presentation ppt = new Presentation();

            ppt.LoadFromFile(@"動画追加.pptx");

            int i = 0;

           //スライドをループします。

            foreach (ISlide slide in ppt.Slides)

            {

           //スライド内のシェイプをループします。

            foreach (IShape shape in slide.Shapes)

            {

           //シェイプが動画なのか判断します。

            if (shape is IVideo)

           {

            //動画を保存します。

            (shape as IVideo).EmbeddedVideoData.SaveToFile(string.Format(@"動画{0}.mp4", i));

            i++;

        }

    }

}
```
<p> </p>
<p> </p>
<p><strong> </strong></p>
優しいGithub アップロード

##ダウンロード成功画面
![GIthubダウンロード完成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/b80734da-72a8-d435-d178-57eb9fa9d650.gif)
##手順
①githubでリモートリポジトリを作成する
②ローカルリポジトリを作成
③変更履歴を保存
④ローカルリポジトリとリモートリポジトリを紐づけ
⑤masterブランチをmainブランチに変更
⑥リモートリポジトリへPush
###①githubでリモートリポジトリを作成する
**title**というリモートリポジトリを作成
![newレポ時.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/2e84643f-763a-00b6-6aa3-392f1b22f84d.gif)
1. Githubで右上の**+**をクリックし、New repository画面に移動
2. **Repository name**に任意の名前を付ける。
3. 下の**Create repoitory**をクリック

リモートリポジトリができました。
###②ローカルリポジトリを作成
![ｃｄいにｔ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/97cc5dc5-03dd-7850-adb9-3cae1f100403.gif)

```ruby:terminal
$ cd file名
$ cd bookers2-5 #bookers2-5をGithubにアップロードします。
```
ターミナルを開き、アップロードしたいフォルダに移動します。

```ruby:terminal
$ git init
```

ディレクトリ（ファイル）にローカルリポジトリを作成する。
###③変更履歴を保存
```ruby:terminal
$ git add 変更したfile名
$ git add .  #.を入力すると全てのfileの変更履歴を保存します。
```

変更履歴を残します。

```ruby:terminal
$ git commit -m "何を変更したかわかりやすいコメント"
$ git commit -m "Initial Commit"　#Githubに初めてアップロードするときによく使われるコメントです
```
変更履歴を保存します。
###④ローカルリポジトリとリモートリポジトリを紐づけ
![Git紐づけ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/0f2dbc3a-be48-db92-2eb9-ae44a7c34fb9.gif)

リモートリポジトリURLをコピーする。
**※今回はSSHキーで紐づけします。事前にSSH接続を済ませておきましょう。**
>[動画付き　優しいGithub SSH接続](https://qiita.com/akachiryo/items/2e765220d6b2573f64d6)

```ruby:terminal
$ git remote add origin リモートリポジトリURL
$ git remote add origin git@github.com:akachiryo/title.git
```
ローカルリポジトリとリモートリポジトリを紐づけができました。
###⑤masterブランチをmainブランチに変更
```ruby:terminal
$ git branch -M main
```

ブランチの変更ができました。
###⑥リモートリポジトリへPush
```ruby:terminal
$ git push origin ブランチ名
$ git push origin main #今回のブランチ名はmain
```
これでGitHubにPushができました。
ダウンロード成功画面でアップロードできているか確認しましょう。
Python3: base64 変換して JSON になった画像を復元

次のような JSON を処理します。

```text
{"base64":"/9j//gAkWACmDwAAAAAAA **** "}
```

```py:decode.py
#! /usr/bin/python
#
#	decode.py
#
#						Feb/28/2021
# --------------------------------------------------------------------
import sys
import json
import base64
import numpy as np
import cv2
#
file_in = sys.argv[1]
file_image = sys.argv[2]
sys.stderr.write(file_in + "\n")
sys.stderr.write(file_image + "\n")
#
fp_in = open(file_in,encoding='utf-8')
json_str = fp_in.read()
fp_in.close()
#
dict_aa = json.loads(json_str)
data_64 = dict_aa["base64"]
#
sys.stderr.write("len(data) = %d\n" % len(data_64))
#
img_binary = base64.b64decode(data_64)
#
jpg=np.frombuffer(img_binary,dtype=np.uint8)
img = cv2.imdecode(jpg, cv2.IMREAD_COLOR)
cv2.imwrite(file_image,img)
#
# --------------------------------------------------------------------
```

実行コマンド

```bash
./decode.py in01.json out01.jpg
```

Ubuntu でのライブラリーのインストール

```bash
sudo apt install python3-numpy
sudo apt install python3-pip
sudo pip3 install opencv-python
```
pyenvの再インストール

# はじめに

[pyenv](https://github.com/pyenv/pyenv)でpython環境を整えていたが、正常に動作しないので再インストールすることにした。具体的には、新しく異なるバージョンのpythonをインストールして、global環境をそれに切り替えても、systemのpythonを参照してしまう。以下に状況を記録しておく。

```bash:terminal
$pyenv global X.Y.Z
$pyenv global
>> X.Y.Z
$which python
>> ~/.pyenv/shims/python
$python --version
>> python A.B.C
```
今まではきちんと動作していたし、元々インストールしていたpythonバージョンへの切り替えも問題なく行える。
色々調べてみたが記事が見当たらなかったので、TimeMachineで状態を保存しつつ、思い切って再インストールしてみることにした。


# 環境
```
macOS: Catalina 10.15.7
xCode: version 12.4
```

# 準備

```bash:terminal
$brew doctor
$softwareupdate --all --install --force
```

一応、`brew`を確認。`Warning: A newer Command Line Tools release is available.`と出たので、`softwareupdate`でアップデートする。
しかし、Command Line Toolはアップデートされなかったので、以下の手順でアップデートする。

```bash:terminal
$sudo rm -rf /Library/Developer/CommandLineTools
$sudo xcode-select --install
```

うまくアップデートされた。
さらに[ここ](https://github.com/pyenv/pyenv/wiki#suggested-build-environment)を参照してHomebrewの環境も整える。

```bash:terminal
$brew install openssl readline sqlite3 xz zlib
```
この時点で、zlibがinstallされていなかったことに気付く。
これが原因できちんと動作していないのかと思ったが（実際、新しく`pyenv install x.y.z`で正しくインストールされていなかった）、問題解決には至らず、結局systemのpythonを参照してしまう。

この時点で、timemachineで状態を保存しておく。

# アンインストール
[ここ](https://github.com/pyenv/pyenv#uninstalling-pyenv)を参照しつつアンインストール。

```bash:terminal
$rm -rf $(pyenv root)
$brew uninstall pyenv
```

# インストール
[ここ](https://github.com/pyenv/pyenv#installation)を参照してインストール。

```bash:terminal
$brew update
$brew install pyenv
```

# 環境設定
[ここ](https://github.com/pyenv/pyenv#basic-github-checkout)を参照して`.bash_profile`と`.bashrc`に以下を書き込む。

```bash:.bash_profile
#export PYENV_ROOT="$HOME/.pyenv"
#export PATH="$PYENV_ROOT/bin:$PATH" <- These two lines are not needed if pyenv is installed with Homebrew.
eval "$(pyenv init --path)"
```

```bash:.bash_rc
if command -v pyenv >/dev/null; then eval "$(pyenv init -)"; fi                                                                                                                                           
```

# 最終的に

残念ながら状況は改善されなかった。ただ、`python`コマンドが`python2`を参照にしているのが原因なのは分かった。例えば、`pyenv global 3.X.Y`と打った場合、`python3`はバージョン`3.X.Y`に変更されている。解決策が見つかるまで、`python3`とか`pip3`として、やることにする。
GCS コマンド一覧

・GCS間のファイル移動
　元フォルダA(from_dir)　→　移動先フォルダB(to_dir)

```
gsutil -m cp -r gs://from_dir gs://to_dir
```

・ファイルをローカルにダウンロードする

```
gsutil cp gs://ファイルpath 保存先path
```

・指定したbyteを抽出する

```
gsutil cat -r [開始ポジション]-[終了ポジション] [gs://パス]
```
Android: ActivityResultContract を使って画像選択アプリで画像を選択する

ActivityResultContract を使って、外部の画像選択アプリを起動し、ユーザーに画像を選択させます。

https://developer.android.com/training/basics/intents/result?hl=ja

Android 標準の画像選択アプリではなく Google Photos アプリを起動して画像選択させたい場合は以下の記事を参照してください。

https://qiita.com/irgaly/items/a1967f0fa4b505ab83e8

複数の画像選択に対応したい場合は以下の記事を参照してください。

https://qiita.com/irgaly/items/f65840e41888d67a07af

## 必要な権限

外部の画像選択アプリによって画像を選択し、その画像を読み取るためには **READ_EXTERNAL_STORAGE 権限は不要** です。

画像選択アプリが端末に保存されている画像を読み取る権限を取得しており、ユーザーが選択した画像は Intent 発行元のアプリが読み取れるパスとして返却されるためです。

## 前提

AndroidX や AndroidX Fragment が導入されている前提です。

## 実装

```kotlin
class MyFragment: Fragment() {
    private val getContentLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        if (uri != null) {
            // 画像が選択された
            // content://com.android.providers.media.documents/...
            // のような Content Provider 形式の URI を受け取ります
        } else {
            // 画像選択がキャンセルされた
        }
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val binding = /* ... */
        binding.button.setOnClickListener {
            getContentLauncher.launch("image/*")
        }
    }
// ...
```

## 解説

`Fragment.registerForActivityResult()` を実行しておくことで、Fragment が Activity に Attach されたときに、Activity の ActivityResultRegistry に GetContent Contract が登録されます。ActivityResultRegistry に登録されると、ActivityContract が onActivityResult() を受け取って処理できるようになります。

Contract を登録すると、任意のタイミングで `getContentLauncher.launch()` を実行することで、コンテンツ選択の Intent が発行されます。

GetContent Contract は以下の Intent を発行しています。

[ActivityResultContracts.java](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.java#468)

```kotlin
Intent(Intent.ACTION_GET_CONTENT)
    .addCategory(Intent.CATEGORY_OPENABLE)
    .setType(input) // ここでは input = "image/*"
```

`registerForActivityResult()` は内部で onActivityResult の RequestCode を連番として生成しています。 `registerForActivityResult()` を実行する順番は常に同じである必要があります。条件分岐などを使わないように気をつけながら Activity や Fragment のフィールドとして初期化してしまうのが安全です。

## 実行結果

以下のように画像選択アプリが起動します。Android 標準の画像選択アプリであれば、Google Drive や Google Photos からの選択もできるようになっています。インストールされていれば Dropbox なども表示されます。端末にインストールされている多数のストレージアプリと連携できるため、基本的には GetContent Contract で Android 標準の画像選択アプリを呼び出すことをお勧めします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/dd274451-4ae9-e723-6b9b-8db1e97d3be4.png)

## Intent Chooser で画像選択とカメラアプリでの写真撮影を選べるようにする

ActivityResultContract で Intent Chooser に画像選択とカメラアプリ起動での写真撮影の両方を表示する方法は以下の記事を参照してください。

https://qiita.com/irgaly/items/7b55c7f9e5996d5df924
【LIFF/ミニアプリ】LINE×AWS×PayPayでテーブルオーダーを作ってみよう！ハンズオン参加レポート

かなり遅くなってしまいましたが、2021年5月10日に開催された「【LIFF/ミニアプリ】LINE×AWS×PayPayでテーブルオーダーを作ってみよう！ハンズオン」の参加レポートです。
https://linedevelopercommunity.connpass.com/event/207867/

# 手順

https://zenn.dev/mochan_tk/books/line-aws-paypay-handson-doc

# GitHubリポジトリ

https://github.com/jaws-ug-kanazawa/line-api-use-case-table-order

# 構成図

手順書より引用

![スクリーンショット 2021-05-21 10.22.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/942208/aec914b3-e72e-5fd8-ee49-f1acb36ff56d.png)


# 動作内容

https://www.youtube.com/watch?v=e15n8fqvQqc&list=TLGGCNV5dCW_bJAyMTA1MjAyMQ


# 良かったポイント

- AWS SAMの対話式デプロイを初めて触った
  - Cloud Formationベースでリソースが作成されるため、最終的なリソースの全削除がやりやすい
![スクリーンショット 2021-05-21 11.09.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/942208/9821c34b-c5ca-6233-e133-8fe70756b8ac.png)
- PayPayの日本語ドキュメントが充実している
  - Webの支払い、アプリの支払い、継続課金など対応しているため、幅広いサービスでPayPay組み込みができそう
  - LINEもそうだが、日本語ドキュメントが充実しているのは非常に使いやすい
  - サンドボックスアカウントが10万円ぐらいお金が入った状態で作られるので、お金持ちになった気分になれる

![スクリーンショット 2021-05-21 11.05.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/942208/2ab9d3d6-4f1d-8218-fce2-c3a62436c7f8.png)

# 詰まりポイント

- LINEやPayPayから複数の設定用の変数(アカウント情報やシークレットキーなど)を持ってくる必要があるので、注意深く作業しないと疎通ができない場合がある
  - アカウントIDを設定する箇所が数字だけだと思ったら「数字-xxxxxxxxx」のような後ろのIDまで設定する形式だったので、LINE APIと繋がらない部分があり、結果Cloud Formationのスタックを全削除して手順をやり直す羽目になった
  - 普段使わないマイクロサービスだとエラーの見方(出力場所・エラーメッセージの内容)がよくわからなかったりして原因が特定できないので、この辺は慣れが必要
  - せっかくのマイクロサービスなので自分で一から構築する際は細かいチェックポイントやマイクロサービスごとのテスト項目を作ると原因究明がしやすいと感じた
- 特に今回はLINEのMessaging APIとLIFF(LINE Front-end Framework)の両方でLINEの設定を行う必要があり、どちらのシークレットキーを設定したのか混乱しやすかった

# 注意点

- ハンズオンの手順通り作業をするとLINEやPayPayの情報をconfigファイルに直書きすることになるので、誤ってGitHub等に上げないよう注意
- 今回の手順だと開発環境がCloud9なので、作業中微課金が発生する。また、放置しておくとEBSのストレージ料金がかかるので注意
  - 環境を壊してしまうと作業した内容が保存しないままなくなったりするので、必要であればバックアップを
  - Cloud9はAWS環境を直接叩けるので、IAMやネットワーク設定をかなり省けるのは良いポイントだが、自分のGitHubアカウントとの連携などは手間がかかるので、ローカルとCloud9のどちらを採用するかは一長一短
  - ソースコードをガリガリ編集するわけでなく、簡単なコマンド実行を行いたいだけの場合はAWS CloudShellという選択肢もあります(永続化には致命的に向かないので注意)
- 基本無音で動作するアプリなのに支払い時にいきなり「PayPay♪」と言い出すので、音量設定には注意
【Swift】mutatingとは


##
##概要
``mutating``は構造体のストアドプロパティの値を変更する為に、再代入することを示す明示的な``キーワード``です。その為``参照型``である``クラス``には使用せず、再代入を必要とする``値型``のみに使用します。

##使用例
前述した通り、``値型``である``構造体``の``ストアドプロパティ``に変更を加えるとエラーになります。``参照型``である``クラス``の場合はエラーにはなりません。

```swift
struct numberStruct {
    var number: Int

    init(number: Int){
        self.number = number
    }

     func plus() {
        self.number += 1 // コンパイルエラー
    }
}
```

```Swift
class numberClass {
    var number: Int

    init(number: Int) {
        self.number = number
    }

    func plus() {
        self.number += 1 // クラスの場合エラーにならない
    }
}
```
``mutating``を使用することで値を変更することができます。

```Swift
struct numberStruct {
    var number: Int

    init(number: Int){
        self.number = number
    }

    mutating func plus() {
        self.number += 1
    }
}
```
LinuxでSSL証明書のエラーが出たときに試したこと (wget, Python)

普段Ubuntu Serverでいろいろやっています。
ある日突然サーバーでwgetが使えなくなりました。

```
$ wget https://xxxxxx.jp/
--2021-07-28 15:11:11--  https://xxxxxx.jp/
Resolving xxxxxx.jp (xxxxxx.jp)... 
Connecting to xxxxxx.jp (xxxxxx.jp)|xxxxxxxx|:443... connected.
ERROR: cannot verify xxxxxx.jp's certificate, issued by ‘CN=GeoTrust RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US’:
  Unable to locally verify the issuer's authority.
To connect to xxxxxx.jp insecurely, use `--no-check-certificate'.
```

SSL証明書のエラーが出てしまったのです。
最初はこのサイトだけかと思って、いろいろなサイトへ試しに接続しようとしたらそれらもみんなダメ。

そうこうしているうちに、サーバー利用者から「PythonでSSLエラーが出る」という報告を受けました。
どうやらwget以外でも影響がでているようなのです。

# Pythonでの対処
とりあえずPythonの方を解決しようといろいろ調べると `SSL_CERT_FILE` という環境変数にCA証明書ファイルを指定するとよいという記載があったため、それに従って設定したところ、解決することができました。

PythonではCA証明書を持っているcertifiモジュールがあるため、そのモジュールが持っている証明書ファイルのパスを環境変数にセットしました。

ファイルパスは、`python3 -m certifi` で知ることができます。

```bash
$ python3 -m certifi
/usr/local/lib/python3.8/dist-packages/certifi/cacert.pem
$ echo SSL_CERT_FILE=/usr/local/lib/python3.8/dist-packages/certifi/cacert.pem >> $HOME/.bashrc
$ source $HOME/.bashrc # 環境変数読み込み
```

# wgetでの解決方法
wgetに至ってはなかなか解決ができず、試行錯誤しつつ調べを進めると、wgetがどこのCA証明書ファイルを読みに行っているかを知ると早いと言っている記事を見つけました。

[Cygwin - wget](https://seesaawiki.jp/w/kou1okada/d/Cygwin%20-%20wget)

Cygwinなので一見関係ないように見えましたが、この方法で解決しました。

まずstraceコマンドでwgetがどんなファイルを読んでいるのか調べます。
すると、このようなログが。

```bash
$ strace wget https://xxxxxx.jp/ | grep ssl
...
stat("/usr/local/ssl/certs/968d05c4.0", 0x7ffd21dd5a00) = -1 ENOENT (No such file or directory)
stat("/usr/local/ssl/certs/3513523f.0", 0x7ffd21dd5a00) = -1 ENOENT (No such file or directory)
stat("/usr/local/ssl/certs/3513523f.0", 0x7ffd21dd5a00) = -1 ENOENT (No such file or directory)
stat("/usr/local/ssl/certs/e4af4c8e.0", 0x7ffd21dd59b0) = -1 ENOENT (No such file or directory)
```

なんと /usr/local/ssl/certsのファイルを読みに行っていました。
そのディレクトリの中身を見ると空でした。

```bash
$ ls /usr/local/ssl/certs

```

これでモヤモヤが解決されました。

CA証明書は自分の環境では /etc/ssl/certs にあるため、シンボリックリンクを貼って対処します。

```bash
$ sudo rm -rf /usr/local/ssl/certs
$ sudo ln -s /etc/ssl/certs /usr/local/ssl/certs
```

再度wgetを試すと、エラーは解消できました。

同時にPythonでのエラーもなくなり、環境変数の設定が不要になりました。

# 最後に
ca-certificatesをインストールする等では解決できないレアな事象であったため、今回Qiitaにまとめました。
今後このようなことがあった場合に役立つことができれば幸いです。
ハードディスクについて〜応用情報技術者試験に向け〜

#HDDの構造

* セクタ　フォーマットされた作成された領域の最小範囲
* トラック　セクタをぐるりと集めた一周分の領域
* シリンダ　同心円状のトラックを複数まとめたもの
* クラスタ　　OSが複数のセレクタを一つのブロックと見なしたもの
* フラグメンテンショーンはデフラグしましょう

内容うすっ！！

#RAID
何回やってもこの部分は忘れすぎる。でもすごく聞かれる部分です。
複数のHDDを理論上どう運用していくか。

##RAID０
別名　ストライピング
一つのデータを２台以上のHDDに保存する。分散して書き込む。

##RAID１
別名　ミラーリング
２台以上に常に同じデータを書き込む。

##RAID10
なにこの順番ってなりますが。１＋０です。
RAID０とRAID１を組み合わせて使う。

##RAID２
分散して書き込む元データとは別に、メモリなどで使用される誤り訂正記号（ECC）を冗長コードとして専用のディスクに書き込む。製品として使われることはほとんどない。ストライピングはビット単位。

##RAID３、４
３台以上のHDDを使ってデータを分散して書くと同時にパリティと呼ばれる誤り訂正符号を専用のディスクに書き込む。RAID３はストライピングはビット単位。

##RAID５
HDD３台以上を使って、データとパリティを分散させて書き込む

##RAID６
RAID５を拡張している。２種類のパリティを分散させて書き込む。
Jetson Nano Mouseで「tegra-i2c 700c400.i2c: no acknowledge from address 0x3c」と出る

# 環境
Jetson Nano 4GB
Jetbot 0.4.3

# 原因
`jetbot_display`というDockerコンテナが動いているから

# 解決方法
`jetbot_display`の自動起動を無効にすればいい。
無効化して再起動してエラーが出なかったら解決！

## 無効化

```console
sudo docker update --restart=no jetbot_display
```

## 確認

```console
$ sudo docker inspect -f "{{.Name}} {{.HostConfig.RestartPolicy.Name}}" $(sudo docker ps -aq)
/jetbot_jupyter always
/jetbot_display no        # noになってればOK
```

## (有効化)

```console
sudo docker update --restart=always jetbot_display
```

# 調べて違かった方法

## https://github.com/NVIDIA-AI-IOT/jetcard/issues/27


`sudo systemctl disable jetcard_display`

jetcardのissueに載ってる`jetcard_display`というサービスを無効にする手法。
そもそもjetcardではないためサービスが存在せずだめ。

## https://github.com/NVIDIA-AI-IOT/jetbot/issues/343

jetbotのissueに載ってて期待が持てるがみんな困ってるだけでだめ。

## https://forums.developer.nvidia.com/t/105-180331-tegra-i2c-7000c400-no-acknowledge-from-address-0x3c/183717

英語力が皆無なのでGoogle先生に頼ったが、バージョン違いとかなんかいろいろあってさらによくわかんなくなる。
結局はDockerが関係してそうなことは分かった。
【Rails】アプリ 新規作成 サーバー起動 手順

**新規アプリ作成〜サーバー起動まで**を**最短**で行う方法を記述してます:pencil2:

# 環境
Ruby 3.0.2
Rails 6.1.4
MySQL 5.6

※環境を合わせると、エラー最小限で進められると思います:thumbsup:

#新規アプリ作成

### MySQLを使用する場合

```
$rails new <アプリ名を記入> -d mysql
```

### sqlite3を使用する場合

```
$rails new <アプリ名を記入>
```

# データベース設定
`config/database.yml`で以下を設定する

```database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root　←MySQLのユーザー名がrootでない場合は変更する
  password: "<MySQLのパスワードを記入>"
  socket: /tmp/mysql.sock

development:
  <<: *default
  database: <アプリ名>_development
```

# データベース作成
以下のコマンドを実行

```
$ rails db:create
```

以下が表示されればOK

```
Created database '<アプリ名>_development'
Created database '<アプリ名>_test'
```

# サーバー起動
①以下のコマンドを実行

```
$ rails s
```

②GoogleCrome等で、`localhost:3000`を開いて、以下の画面が表示されれば完璧:slight_smile:
![スクリーンショット 2021-07-28 15.05.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631023/455d0e0d-7314-12ab-2983-3cd0c1f4e440.png)

# 参考資料
https://qiita.com/Hal_mai/items/23c553e4af19bfc75c68

https://qiita.com/reeenapi/items/9fc38c4f2f8186c78288


# 備考
最短で起動したかったので、自分で記事を作成してみました。
不備などあれば教えていただけますと助かります:pray:
【Android】URLからファイルをダウンロードして表示

#はじめに

URLからpdfなどのファイルをダウンロードして、ダウンロード完了後に表示する方法について記載。
以下の2つの方法を紹介。

- **コルーチンを使用した方法**
- **DownloadManagerを使用した方法**

# コルーチンを使用した方法
事前にFileProviderを使うように設定する必要あり。
まず、コルーチンを使用するための設定。

```build.gradle
dependencies {
	implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1'
}
```

そして、ダウンロード用のメソッドを作成。

```Kotlin
fun download() {
	try {
		// linkにファイルが置かれたURLを指定
		val link = "https://example.com/example.pdf"
		//getExternalStoragePublicDirectoryはAndroid Qで非推奨になっているため、適宜変更。
		val dir = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS).absolutePath
		val path = dir + link.substring(link.lastIndexOf("/"))

		//ここでファイルをサーバからダウンロード
		URL(link).openStream().copyTo(FileOutputStream(File(path)))

		//intentによりファイルを開く
		val intent = Intent(Intent.ACTION_VIEW)
		val uri = FileProvider.getUriForFile(this@MainActivity, BuildConfig.APPLICATION_ID + ".provider", File(path))
		intent.setDataAndType(uri, contentResolver.getType(uri))
		intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
		startActivity(intent)

	} catch (e: Exception) {
		Log.e(localClassName, "Cancel", e)
	}
}

```

次に、ダウンロードしたいタイミングで以下を記述。

```Kotlin
CoroutineScope(Dispatchers.Default).launch {
	download()
}
```

# DownloadManagerを使用した方法
先ほどと同様、まずはダウンロード用のメソッドを作成。

```Kotlin
fun download() {
	try {
		// linkにファイルが置かれたURLを指定
		val link = "https://example.com/example.pdf"
		val fileName = link.substring(link.lastIndexOf("/") + 1)

		//DownloadManagerを使用してファイルをダウンロード
		val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
		val request = DownloadManager.Request(Uri.parse(link))
		request.setTitle(fileName)
		request.setDescription("Downloading")
		request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
		request.setDestinationInExternalPublicDir(DIRECTORY_DOWNLOADS, fileName)
		val downloadId = manager.enqueue(request)

		val receiver = object : BroadcastReceiver() {
			//ダウンロード完了後の処理
			override fun onReceive(context: Context, intent: Intent) {
				val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
				if (id == downloadId) {
					//intentによりファイルを開く
					val openFileIntent = Intent(Intent.ACTION_VIEW)
					val uri = manager.getUriForDownloadedFile(id)
					openFileIntent.setDataAndType(uri, contentResolver.getType(uri))
					openFileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
					startActivity(openFileIntent)
				}
			}
		}
		registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))

	} catch (e: Exception) {
		Log.e(localClassName, "Cancel", e)
	}
}
```

次に、ダウンロードしたいタイミングで以下を記述。こちらはコルーチンで囲わない。

```Kotlin
download()
```
SKStoreReviewController.requestReview()がiOS14から非推奨になった件について

## SKStoreReviewController.requestReview()
レビューをユーザーに促す場合に使われていたコード。
iOS14以降は非推奨となった。

```swift
SKStoreReviewController.requestReview()
```
一行で書けていたのに残念😕

## 公式によると
[requestReview(in:)](https://developer.apple.com/documentation/storekit/skstorereviewcontroller/3566727-requestreview)を使いなさいとのこと。

## iOS14以降の書き方
```swift
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
    SKStoreReviewController.requestReview(in: scene)
}
```

このように書けば良さそう！

## お知らせ
現在、iOS開発案件を業務委託で募集中です（副業）。[TwitterDM](https://twitter.com/ken_sasaki2)ご依頼をお待ちしています。
動画付き　優しいGithub SSH接続 

##SSH接続成功時
![aah接続完成.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/d973c7d7-a2cc-228b-e210-53732db7a609.gif)
##手順　　
①SSHキーの作成($ ssh-keygen)
②SSHをコピー($ cat ~/.ssh/id_rsa.pub)
③Githubで登録
④接続確認($ ssh git@github.com)
###①SSHキーの作成($ ssh-keygen)
**※gifにはキーが見えないようタブを設置しています**
![SSHコピーモザイク.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/6a9db300-d429-0416-96d3-f8d1de492737.gif)
1. ターミナルを開く
2. **ssh-keygen**をコピー、ターミナルにペーストし、Enterを押す
3. 画面に下記の表示が出るのでそれぞれ**Enter**を押す。（**計3回**）
　 Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa): 
　 Enter passphrase (empty for no passphrase): 
　 Enter same passphrase again: 

これでSSHキーの作成ができました。
###②SSHをコピー($ cat ~/.ssh/id_rsa.pub)
**※gifにはキーが見えないようタブを設置しています**
![SSHコピーモザイク２.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/88e4adfb-c8b7-9eae-60f0-8cc44dc4f41b.gif)

1. **cat ~/.ssh/id_rsa.pub**をコピー、ターミナルにペーストし、**Enter**を押す
2.表示をコピーする

これでSSHキーのコピーができました。
###③Githubで登録
**※gifにはキーが見えないようタブを設置しています**
![GIthubモザイク.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/040cd440-9ab4-d6dd-b0ed-a506add74227.gif)
1. Githubを開く
2. **settings**から**SSH and GPG keys**をクリック、右上の**New SSH key**をクリック
3. Title欄を入力し、**Key欄**に②でコピーしたキーをペースト、**Add SSH key**をクリック

これで接続ができました。画面を戻って作成できたか確認してみましょう。
###④接続確認
![GIthub接続確認.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798471/2a9e12b3-0939-c4de-0949-c7679409116d.gif)
1. ターミナルを開く
2. **ssh git@github.com**をコピー、ターミナルにペーストし、Enterを押す
3. Are you sure you want to continue connectingと表示されるので、**yseを入力してからEnterを押す**
  **※yesの入力は忘れがちなので注意（私はこれで1時間を無駄にしました）
※yesの入力は、接続先のホストが正しいか確認するためにあります。
 [GitHub の SSH キーフィンガープリント](https://docs.github.com/ja/github/authenticating-to-github/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints)にあるFingerprintと一致する事を確認してから yes と入力してください。**

4. You've successfully authenticatedと表示されているのを確認する

これで接続確認までできました。
API Gateway でコンソールからAuthorizationヘッダーを付与してリクエスト送る方法

## 書き方

API Gatewy　のコンソール、ヘッダーの入力欄から{}なしでヘッダーに下記のようにコロン区切りで記載する

```

Authorization:Bearer eyJhbGciOiJSU....aAWDvT6g

```

ヘッダーを付与するとこんな感じになります。

![スクリーンショット 2021-07-28 14.44.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274030/b1d829f9-ab84-0d57-ae46-691f9bd713ae.png)
条件分岐処理についてまとめてみた（つもり）

![E7XBlk2UYAkoWN_.jfif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293580/e6d043fc-fc58-d58a-2604-9d84d95c0a53.jpeg)
条件分岐処理についてまとめてみました
もう少し詳しく動画で解説してます
https://youtu.be/wkHddR-0KWo
ActiveRecord::NoDatabaseError

## 環境
Ruby 3.0.2
Rails 6.1.4

## 事象
以下を実行するとエラーが表示された。
①新規Railsアプリを作成（rails new）
②サーバーを起動（rails s）
③`config/database.yml`にMySQLの接続情報を追記
④localhost:3000を開く

```
ActiveRecord::NoDatabaseError
```

## 解決策
以下を実行。

```
rails db:create
```

「データベースがないよ」というエラーなので、データベースを作ってあげればOK
サーバーを再起動後にリロードしたら、Railsのデフォルト画面が表示されました:ok_hand:

## 参考資料
https://rails-ambassador.herokuapp.com/debugs/NoDatabaseError
XAMPPのMariaDBでエラー「InnoDB: Table `mysql`.`innodb_table_stats` not found.」を見つけたので修正した

## 概要

"XAMPP 8.0.7"の"MariaDB 10.4.19"にて次のエラーログを見つけた。MySQL5.6のバグのよう。

```
[ERROR] InnoDB: Table `mysql`.`innodb_table_stats` not found.
```

## 修正方法

### 1.rootでログインしてdrop tableをする

```
# mysql -h127.0.0.1 -uroot -pパスワード
mysql > use mysql;
mysql > drop table innodb_index_stats;
mysql > drop table innodb_table_stats;
mysql > drop table slave_master_info;
mysql > drop table slave_relay_log_info;
mysql > drop table slave_worker_info;
mysql > quit;
```

### 2.MySQLを停止させる

xampp-controlからポチポチ

### 3.ファイルを削除する

```
# cd C:\xampp\mysql\data\mysql
# rm -rf innodb_index_stats*
# rm -rf innodb_table_stats*
# rm -rf slave_master_info*
# rm -rf slave_relay_log_info*
# rm -rf slave_worker_info*
```

### 4.MySQLを起動させる

xampp-controlからポチポチ

### 5.rootでログインしてテーブル作成

[リンク先: https://bugs.mysql.com/file.php?id=19725&bug_id=67179&text=1](https://bugs.mysql.com/file.php?id=19725&bug_id=67179&text=1)

```
# mysql -h127.0.0.1 -uroot -pパスワード
mysql > use mysql;
mysql > リンク先のクエリを流す
mysql > quit;
```

### 6.mysql_upgradeをする

```
mysql_upgrade -u root -p
Enter password: ********
```

## 参考にしたサイト

[Error: Table "mysql"."innodb_table_stats" not found.のバグに対応する](https://www.bunkei-programmer.net/entry/2014/08/25/230357)
[MariaDB：MariaDB のログに「 InnoDB: Error: Table “mysql”.”innodb_table_stats” not found. 」と出てた時の対処方法](https://blog.offline-net.com/2017/11/19/mariadb-how-to-update-ticket-67179/)
[Qiita: MariaDBとMySQL のバージョン比較](https://qiita.com/katzueno/items/e735950c7440f232ef27)
ActiveRecord::ConnectionNotEstablished【MySQL】

##環境
Ruby 3.0.2
Rails 6.1.4

## 事象
以下を実行するとエラーが表示された。
①新規Railsアプリを作成（rails new）
②サーバーを起動（rails s）
③localhost:3000を開く。

```
ActiveRecord::ConnectionNotEstablished
```

## 解決策
`config/database.yml`でパスワードを設定

```database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: "<パスワード>"
  socket: /tmp/mysql.sock

development:
  <<: *default
  database: <アプリ名>_development

```

この後、`ActiveRecord::NoDatabaseError`が出ましたが、これは[こちら]()にまとめて置きます。

## 参考資料
https://teratail.com/questions/317758
jsonwebtokenの返り値がundefined

## 問題

ライブラリ`jsonwebtoken` を用いてトークンのデコードを行なっているが返り値が何も返ってこない。

```js:token-verify.js

const token= 'ey....'

jwt.verify(token, pem, { algorithms: ['RS256'] }, function(err:{}, decoded:{}) {
    decodedToken = decoded
    console.log(decodedToken)
});


```

```sh

% node token-verify.js
undefined


```

##  解決策

定数で設定したトークンを更新する。
期限が古い、トークンの署名が不正などあれば`undefined`が返される模様。
kintoneで同じレコード内のフィールド同士を比較した結果で一覧を絞り込みたいときの小技

タイトルが長くなってしまいましたが、どういうことをやりたいかというと・・・

動画をご覧ください。

<iframe width="480" height="360" src="https://www.youtube.com/embed/p1e0nmtRaW8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


・・・のように、
同じレコードのフィールド同士の比較をした結果で一覧を絞り込む小技です。


# アプリの準備

| フィールド種類 | フィールド名 | 備考|
|:-:|:-:|:-:|
|数値   | りんご  ||   
| 数値  | みかん  | |  
| 計算  | 計算  | りんご-みかん | 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/fabd7c43-3d76-fa7d-c145-fcbe92209653.png)

# 絞り込み方法

絞り込み条件の右辺にはフィールドコードを指定できないので、
計算フィールドの値を利用します。
計算フィールドの数式は`りんご-みかん`です。
すると

* りんご - みかん > 0 なら りんごのほうが多い
* りんご - みかん = 0 なら りんごとみかんは同数
* りんご - みかん < 0 なら みかんのほうが多い

となることは想像できるでしょうか👀！？

なので、絞り込み条件は以下のようにします

## りんごのほうが多い場合

純粋な「>」がないので、`計算≧1`とします。
これで、りんごのほうが多いという絞り込みができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/6f8283fb-804f-635e-43bb-16f449a3b953.png)

## みかんのほうが多い場合

りんごと同様の考え方で、`計算≦-1`とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/0488bce7-c530-1902-c1ab-24ed7e3aa82d.png)

## りんごとみかんが同数の場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536148/aa8d3a7a-e977-67a0-9623-3adba96ec5fc.png)

# まとめ
直接フィールドコード同士を比較できない場合は、計算フィールドで差分を条件にするとよさそうです👀✨
【AWS】rsync(lsyncd)によるサーバー間同期での冗長化はベストプラクティスではない？

# はじめに

- Webサーバー2台をrsyncで同期して冗長化
- ALBでWebサーバーへのアクセスは振り分け
- Webサーバー1にデプロイするとWebサーバー2にも同期される

という設計を見たことがあり、自分もこの設計でインフラを構築したことがあるのですが、色々勉強して、この設計は悪いとまではいかないもののベストプラクティスではないのではないか？
という考えに変わりました。
共有です。

# この構築の問題点

- rsync(lsyncd)の設定や動作状況確認が面倒、トラブルのもと
- デプロイ時にダウンタイムが生じる
- 同期の目的がgit管理されたソースコードと、ユーザーによって作られたgit管理されていないファイルの複製であるとするならば、前者はともかく後者の存在がかなり厄介
    - 片方向の同期(Webサーバー1→Webサーバー2へ同期される設定)だった場合、Webサーバー2にユーザーによって重要なファイルが作られてしまったら大変。ユーザーが次にアクセスした際にWebサーバー1を参照してしまうと……？
        - スティッキーセッション(ユーザーの参照先サーバーを一定期間固定する機能)で対応するのも限度がある。
    - 双方向同期設定はもっと厄介。

# じゃあどうするの

- Webサーバー1とWebサーバー2は同期しない
- アプリケーションは要するに「同じソースコードがWebサーバー1でもWebサーバー2でも動けば良い」という設計にする。ユーザーが生成するファイルがサーバー内で永続化するような設計はよろしくない。永続化するならDBやS3へ。
- デプロイは2台に順次デプロイするような設定にする。CodeDeployでローリングデプロイ等の設定をすれば良い。
    - ダウンタイムも避けられます

# まとめ

AWSの場合は「冗長化のためにrsyncを使う」という機会はかなり限られそう。少なくとも今の自分には思いつかないです。
何かご意見あればコメントください。

ではまた。
cakePHP4 複数絞り込み・検索機能実装

Every Qiita #23
のんびり独学初学者投稿 23日目
今回は・・・
cakePHP4の絞り込みと複数条件で検索機能を実装した時の備忘録です。

###完全一致を5つ・部分一致を1つ・空の場合は除外する
今回はポートフォリオで合同トレーニングのマッチングサービスを開発しており、絞り込みを実装している時のメモになります。
検索条件は以下の通りです。

| 検索項目 | テーブル.カラム |
| ---- | ---- |
| 日時 | Collects.time |
| ジム　| Gyms.id |
| ターゲット | Purposes.id |
| 住所 | Prefectures.id |
| トレーニング部位 | Parts.id |
| 店舗・施設（部分一致) | Collects.city |

###処理フローを考える
1. formから入力されたデータをコントローラ/indexアクションに送る
1. 検索データが送られたかを判定し、条件分岐する
 1. データ有：空を排除し、入力されたデータのみ格納
 1. データ無：find('all')で全件取得
1. whereは連想配列で設置するため、入力データを連想配列（キーに検索対象・条件、値に入力データ）として格納。
1. クエリを実行しセットする

上記のフローで実装していこうと思います。

```php:index.php
<?= $this->Form->create(null, ['type' => 'get', 'url' =>['action' => 'index','controller' => 'Collects']]); ?>
<?= $this->Form->hidden("filter",["value"=>"filter"]); ?>
<!-- 省略　-->
<?= $this->Form->submit(__("絞り込み")); ?>
<?= $this->Form->end();?>

```
inputのhiddenで検索された時のみに与える値を設置します。


```php:任意Controller.php
if ($this->request->getQuery('filter')) {
    // where文のデータを取得（空も含む）
    $getQuery = [];
    $getQuery["Collects.time"] = $this->request->getQuery("date");
    $getQuery["Gyms.id"] = $this->request->getQuery("gym_id");
    $getQuery["Purposes.id"] = $this->request->getQuery("purpose_id");
    $getQuery["Prefectures.id"]= $this->request->getQuery("prefecture_id");
    $getQuery["Parts.id"] = $this->request->getQuery("part_id");
    $getQuery["Collects.city LIKE"] = "%".$this->request->getQuery("city")."%";

    // 検索されたデータのみ形成しwhere実行
    $filter=[];
    foreach($getQuery as $key => $val){
        if(!empty($val)){
            $filter[$key] = $val;
        }
    }
    $query = $this->Collects->find();
    $query->where([$filter]);

    // クエリ実行
    $collects = $this->paginate($query);
}else{
    $collects = $this->paginate($this->Collects->find('all'));
}

```

1. hiddenの値が確認できれば連想配列に格納します
1. getQueryで送られたデータを取得できますが、空の状態もあるのでforeachで値が存在するもののみで新たに成形します。cityは部分一致なので、キーに`LIKE`を入れています
1. whereの中に連想配列を入れ、条件にあった募集のみを取得します。

あまり時間がない中で実装したので、微妙な感じでの実装になってしまっております・・・:cry:
他に最適な方法ご存知の方いらっしゃいましたらご教示頂ければと思います:smile:！
【初心者用】Linuxコマンド(2)

#linuxでよく使うコマンド

**ls(list)**
：ディレクトリリスト表示

|主なオプション|意味|
|---|---|
|-a|自分のディレクトリ（.）親ディレクトリ（..）表示|
|-l|長いフォーマット表示（属性など）|
|-t|時刻（降順）でソート|
|-r|ソート順番を逆にする|
|-h|K,M,Gなど人間が読みやすい単位で表示|


```bash:ls
root@localhost:/home/tmp# ls -altrh /usr/local
total 0
drwxr-xr-x 1 root root 512 Aug 22  2020 ..
drwxr-xr-x 1 root root 512 Aug 22  2020 games
drwxr-xr-x 1 root root 512 Aug 22  2020 sbin
drwxr-xr-x 1 root root 512 Aug 22  2020 include
drwxr-xr-x 1 root root 512 Aug 22  2020 src
lrwxrwxrwx 1 root root   9 Aug 22  2020 man -> share/man
drwxr-xr-x 1 root root 512 Aug 22  2020 etc
drwxr-xr-x 1 root root 512 Aug 22  2020 .
drwxr-xr-x 1 root root 512 Aug 22  2020 lib
drwxr-xr-x 1 root root 512 Aug 22  2020 share
drwxr-xr-x 1 root root 512 Mar 11 18:16 bin
```

**cd(change directory)**
：ディレクトリ変更

```bash:cd
root@localhost:/home/tmp# cd /tmp
```

**pwd(print working directory)**
：現在のディレクトリ表示

```bash:pwd
root@localhost:/home/tmp# pwd
/home/tmp
```

**mkdir(make directory)**
：ディレクトリ作成

|主なオプション|意味|
|---|---|
|-p|親ディレクトリがない場合、親ディレクトリを含めて作成|

```bash:mkdir
root@localhost:/home/tmp# pwd
/home/tmp
root@localhost:/home/tmp# ls -altrh
total 0
drwxr-xr-x 1 root root 512 Jul 27 18:41 ..
drwxr-xr-x 1 root root 512 Jul 27 18:41 .
root@localhost:/home/tmp# mkdir -p ./study/linux
root@localhost:/home/tmp# ls -altrh study/
total 0
drwxr-xr-x 1 root root 512 Jul 27 18:42 linux
drwxr-xr-x 1 root root 512 Jul 27 18:42 ..
drwxr-xr-x 1 root root 512 Jul 27 18:42 .
```

**rm(remove)**
：ファイルあるいはディレクトリ削除

|主なオプション|意味|
|---|---|
|-r|サブディレクトリを含め削除|
|-f|削除確認メッセージを表示せず、問答無用削除|
|-d|空のディレクトリ削除|

```bash:rm
root@localhost:/home/tmp# rm -d study/
rm: cannot remove 'study/': Directory not empty
root@localhost:/home/tmp# mkdir -p ./test/123
root@localhost:/home/tmp# ls -altrh ./test/
total 0
drwxr-xr-x 1 root root 512 Jul 27 19:08 ..
drwxr-xr-x 1 root root 512 Jul 27 19:08 123
drwxr-xr-x 1 root root 512 Jul 27 19:08 .
root@localhost:/home/tmp# rm -rf ./test/
root@localhost:/home/tmp# ls -altrh ./test/
ls: cannot access './test/': No such file or directory
```

**touch**
：timestamp変更。ファイルがない場合は新規作成

|主なオプション|意味|
|---|---|
|-t|指定された日付にファイルのtimestampを更新|


```bash:touch
root@localhost:/home/tmp# ls -altrh
total 0
drwxr-xr-x 1 root root 512 Jul 27 18:41 ..
drwxr-xr-x 1 root root 512 Jul 27 18:42 study
drwxr-xr-x 1 root root 512 Jul 27 18:50 .
root@localhost:/home/tmp# touch newfile.txt
root@localhost:/home/tmp# ls -altrh
total 0
drwxr-xr-x 1 root root 512 Jul 27 18:41 ..
drwxr-xr-x 1 root root 512 Jul 27 18:42 study
drwxr-xr-x 1 root root 512 Jul 27 18:50 .
-rw-r--r-- 1 root root   0 Jul 27 18:50 newfile.txt
root@localhost:/home/tmp# touch -t 202107121855 newfile.txt
root@localhost:/home/tmp# ls -altrh
total 0
-rw-r--r-- 1 root root   0 Jul 12 18:55 newfile.txt
drwxr-xr-x 1 root root 512 Jul 27 18:41 ..
drwxr-xr-x 1 root root 512 Jul 27 18:42 study
drwxr-xr-x 1 root root 512 Jul 27 18:50 .
```
コンセプトテスト

https://quest-research.co.jp/research/concept-test

コンセプトテストとは、新商品・サービスを企画して実際に消費者に対して提供する前に、このテストを実施することによってコンセプトへの反響を見ることができるそうです！！
コンセプトテストを実施しておくことで、本番で失敗した際のリスクを最小限にできる点で有用だと思われます！
【初心者向け】エックスサーバー使用時にURL正規化（301リダイレクト）を行う設定

##URLの正規化とは？
URLの正規化とは、同じ内容のページが複数URL存在する場合に、検索エンジンからの評価を向上させるために、評価を集めたいURLを統一することを指します。
##URLを正規化する方法はいくつかある
URLの正規化を行うためには、複数の方法がありますが、今回は301リダイレクトを用いた方法について書きます。
##.htaccessファイルにコードを追加
エックスサーバー内にFTPソフトなどで接続し、.htaccessファイルの5行目あたりにある、IfModule mod_rewrite.cタグ内に以下のコードを記載します。

```:変更前
<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteBase /
    RewriteRule ^wp-signup\.php 404-siteguard [L]
    RewriteRule ^wp-activate\.php 404-siteguard [L]
    RewriteRule ^login_*****(.*)$ wp-login.php$1 [L]
</IfModule>
```
```:変更後
<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteBase /
    RewriteRule ^wp-signup\.php 404-siteguard [L]
    RewriteRule ^wp-activate\.php 404-siteguard [L]
    RewriteRule ^login_*****(.*)$ wp-login.php$1 [L]
-------------------以下の2行を追加----------------------
    RewriteCond %{HTTPS} !on
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
```

```:※WordPressで子ページなどに設定が反映されない場合
【www.なしの場合】
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>

【www.ありの場合】
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
```

##最後に
私が運営するウェブグロースではWeb制作やSEOに関するお役立ち情報も発信してますので、興味がある方は是非御覧ください。
https://bit.ly/3ow9q94