ろうとるがPythonを扱う、、（その13：回線速度checkプログラムその２）

# ろうとるがもっと頭がよくなるために
こちらの記事「[ろうとるがPythonを扱う、、（その12：回線速度checkプログラム）](https://qiita.com/infinite1oop/items/5b8da721decfa3d57e8a)」の続きである。少しでもスマートになろうとしたときのメモ。

## すること
- ダウンロード時の進行状況表示
- コード簡略化

## 先に実行状況
![実行状況のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/816ccfee-9eb0-765b-d36c-0cece25ef52d.png)
各サイズに相当するボタンをクリックすると、指定回数繰り返しダウンロードし、速度などを表示するとともに、CSVファイルを作成。青枠で囲った部分で示すように、ダウンロード状況（％）を表示する。

## ソースコード
いつものように、ポイントとなる箇所を中心に説明。最後にコード全体を記載。

### import
```python
import logging
import logging.handlers
from datetime import datetime
import time
import urllib.request　　　　　　　　　　　　　　　　　 # ダウンロード用URLライブラリ
import tkinter
from tkinter import *
import tkinter.ttk as ttk
import threading              # スレッド
from functools import partial # 複数ボタンをまとめて定義（後述）
```
説明略。

### 定義
```python
w_width = 750
w_height = 400

speed_list = [1, 5, 10, 30, 100]
url_list = [
    'https://www.4fgh.dr.jp/data/1MByte',
    'https://www.4fgh.dr.jp/data/5MByte',
    'https://www.4fgh.dr.jp/data/10MByte',
    'https://www.4fgh.dr.jp/data/30MByte',
    'https://www.4fgh.dr.jp/data/100MByte']
file_list = ['1Mbyte', '5Mbyte', '10Mbyte', '30Mbyte', '100Mbyte']
p_flag = 0 # for progress
```
- Windowサイズの定義。
- ダウンロードするファイルのURL（仮のもの）、サイズ定義など。
- ダウンロード状況表示に関連するフラグ（p_flag）。

### メイン
```python
class NetworkCheck(ttk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.create_widgets()
        self.pack()
...(略)

## End of NetworkCheck()

if __name__ == '__main__':
    master = Tk()
    master.title("Repeat Download 2")
    master.geometry("750x400")
    # Call main part
    NetworkCheck(master)
    master.mainloop()
```
- tkinterフレームワークにて、NetworkCheckというメイン関数をCall。
- create_widgetsにて、各要素を配置。

```python
    def create_widgets(self):
...（略）

        ## Start of create_widgets()
        # Log
        log = logging.getLogger('Download')
        log.setLevel(logging.DEBUG)
        fh = logging.FileHandler('{:Download_Log-%Y%m%d-%H%M%S}.csv'.format(datetime.now()))
        log.addHandler(fh)
        header = 'Start Time, Size(Mbyte), Elapsed Time(ms), Speed(Mbps), Failure,'
        log.debug(header)
        # Tab
        note = ttk.Notebook(self)
        note.pack()
        note0 = ttk.Frame(note, width=w_width, height=w_height)
        note.add(note0, text=" Click Download File Size ")
        cr_tab(note0)
    ## End of createwidgets()
```
- CSV化するログファイルの定義。
- タブの定義（１つしか存在しないが）。

### タブ
```python
        def cr_tab(frm):
...（略）

        ## Start of cr_tab()
            frm1 = ttk.Frame(frm, width=w_width, height=w_height)
            frm1.Btn = [ttk.Button(frm1, text=file_list[i], style='W.TButton', command=partial(clk_btn, i)) \
                        for i in range(5)]
            [frm1.Btn[i].grid(row = 0, column = i) for i in range(5)]

            frm1.label = ttk.Label(frm1, text=' Repetition Number ')
            frm1.label.grid(row = 0, column = 5)
            frm1.text = ttk.Entry(frm1, width = 10)
            frm1.text.grid(row = 0, column = 6)

            frm2 = ttk.Frame(frm, width=w_width, height=w_height)
            frm2.label = ttk.Label(frm2, text = 'Start Time, Size(Mbyte), Elapsed Time(ms), Speed(Mbps), Failure')
            frm2.label.grid(row = 0, column = 0)
            frm2.dummy = ttk.Label(frm2, text = '                                                    	  ')
            frm2.dummy.grid(row = 0, column = 1)
            frm2.progress = ttk.Label(frm2, text = '  ')
            frm2.progress.grid(row = 0, column = 2)

            frm3 = ttk.Frame(frm, width=w_width, height=w_height)
            frm3.prt = Text(frm3, font=("Courier New", 15), width=120, height=33)
            frm3.ysc = tkinter.Scrollbar(frm3, orient=tkinter.VERTICAL, command=frm3.prt.yview)
            frm3.ysc.pack(side=tkinter.RIGHT, fill="y")
            frm3.prt["yscrollcommand"] = frm3.ysc.set
            frm3.prt.config(state='disabled')
            frm3.prt.pack()

            frm1.pack()
            frm2.pack()
            frm3.pack()
        ## End of cr_tab()
```
- タブ内に３つのフレームを構成。
- ５つのボタン（frm1.Btn）の定義および配置。
    - for文による繰り返しについては、こちらのサイト「[Tkinterでオブジェクトを複数、変数に依存して作る（覚え）](https://qiita.com/omossan7182t/items/a3ffb3788667de937c59)」参照。
    - 複数ボタンクリック時のコマンド（関数）については、こちらのサイト「[Python Tkinterの大量のボタンをpartialを使って楽に設置](https://qiita.com/yuzukaki/items/9dd3a4481e1f6e6fa0a8)」参照。
- 繰り返し回数指定用ボックス・変数（frm1.text）の定義。
- ダウンロード進行状況表示用テキスト（frm2.progress）の定義。
- 結果表示用フィールド（frm3.prt）の定義。

### ボタンクリック
```python
            ## Start of clk_btn()
            def clk_btn(size):
                th = threading.Thread(target=download, args=(size, ))
                th.start()
```
ダウンロード進行状況や結果をリアルタイムに表示するには、ボタンクリック時にCallされる部分をスレッド化する必要がある。これについては、こちらのサイト「[tkinterで遅い処理を別スレッドに投げ画面が固まらないようにする](https://www.haya-programming.com/entry/2019/01/20/012926)」を参考にした。Call元にて定義された変数は、Call元に戻らないと更新されないようだ。

### ダウンロード
```python
            ## Start of download()
            def download(arg):
                num0 = frm1.text.get()
                if num0 == "":
                    num = 1
                else:
                    num = int(num0)
                for i in range(num):
                    global p_flag
                    start_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
                    start_time = time.perf_counter_ns()
                    failure = 0
                    try:
                        urllib.request.urlretrieve(url_list[arg], file_list[arg], progress)
                    except:
                        frm2.progress["text"] = 'Error'
                        failure = 1
                        time.sleep(1)
                    p_flag = 0
                    frm2.progress["text"] = 'End'
                    end_time = time.perf_counter_ns()
                    elapsed_time = (end_time - start_time) / 1000000
                    speed = speed_list[arg]*8*1024/elapsed_time
                    output = start_datetime + ', ' + str(speed_list[arg]) + ', ' + str(elapsed_time) + ', ' + str(speed) \
                        + ', ' + str(failure)
                    log.debug(output)
                    output = output + '\n'
                    frm3.prt.config(state='normal')
                    frm3.prt.insert(END, output)
                    frm3.prt.config(state='disabled')
                    frm3.prt.see('end')
                    time.sleep(1)
                    frm2.progress["text"] = ''
            ## End of download()
```
- ダウンロード繰り返し数（num0）の取得（未入力時は"1"とする）。
- ログ用の開始時刻（start_datetime）の取得。
- 開始時間（start_time）の記録。
- URLLib（urllib.request.urlretrieve）によるファイルダウンロード。
    - 途中経過処理行うCallback関数はprogress（後述）。
    - ダウンロードエラー時に進行状況表示を”Error”とする。
- 終了時間（end_time）の記録。
- ダウンロード時間（elapsed_time）および速度（speed）の計算。
- 結果をバッファ（output）に格納、ログに記録（log.debug）、画面表示（frm3.prt.insert）。

### 進行状況表示
```python
            ## Start of progress()
            def progress(block_count, block_size, total_size):
                global p_flag
                percent = 100.0 * block_count * block_size / total_size
                about = round(percent)
                curr = about % 5
                if curr == 0 and p_flag == 0:
                    frm2.progress["text"] = str(about)
                    p_flag = 1
                if curr >= 1:
                    p_flag = 0
            ## End of progress()
```
こちらの記事「[Pythonでファイルをダウンロード＆プログレスバーを表示させる](https://qiita.com/jesus_isao/items/ffa63778e7d3952537db)」を参考に作成。
- 進行状況を%(percent)で取得。
- 5%で表示（frm2.progressのText置き換え）するため、制御用フラグ（p_flag）を設ける。
    - 表示したタイミングで、p_flagを１にセット。
    - 進行状況(percent)が５で割り切れないときやダウンロード終了時に、p_flagを0にセット（リセット）。

### 全体
```python:
import logging
import logging.handlers
from datetime import datetime
import time
import urllib.request
import tkinter
from tkinter import *
import tkinter.ttk as ttk
import threading
from functools import partial

w_width = 750
w_height = 400

speed_list = [1, 5, 10, 30, 100]
url_list = [
    'https://www.4fgh.dr.jp/data/1MByte',
    'https://www.4fgh.dr.jp/data/5MByte',
    'https://www.4fgh.dr.jp/data/10MByte',
    'https://www.4fgh.dr.jp/data/30MByte',
    'https://www.4fgh.dr.jp/data/100MByte']
file_list = ['1Mbyte', '5Mbyte', '10Mbyte', '30Mbyte', '100Mbyte']
p_flag = 0 # for progress

class NetworkCheck(ttk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.create_widgets()
        self.pack()

    def create_widgets(self):
        def cr_tab(frm):
            ## Start of progress()
            def progress(block_count, block_size, total_size):
                global p_flag
                percent = 100.0 * block_count * block_size / total_size
                about = round(percent)
                curr = about % 5
                if curr == 0 and p_flag == 0:
                    frm2.progress["text"] = str(about)
                    p_flag = 1
                if curr >= 1:
                    p_flag = 0
            ## End of progress()

            ## Start of download()
            def download(arg):
                num0 = frm1.text.get()
                if num0 == "":
                    num = 1
                else:
                    num = int(num0)
                for i in range(num):
                    global p_flag
                    start_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
                    start_time = time.perf_counter_ns()
                    failure = 0
                    try:
                        urllib.request.urlretrieve(url_list[arg], file_list[arg], progress)
                    except:
                        frm2.progress["text"] = 'Error'
                        failure = 1
                        time.sleep(1)
                    p_flag = 0
                    frm2.progress["text"] = 'End'
                    end_time = time.perf_counter_ns()
                    elapsed_time = (end_time - start_time) / 1000000
                    speed = speed_list[arg]*8*1024/elapsed_time
                    output = start_datetime + ', ' + str(speed_list[arg]) + ', ' + str(elapsed_time) + ', ' + str(speed) \
                        + ', ' + str(failure)
                    log.debug(output)
                    output = output + '\n'
                    frm3.prt.config(state='normal')
                    frm3.prt.insert(END, output)
                    frm3.prt.config(state='disabled')
                    frm3.prt.see('end')
                    time.sleep(1)
                    frm2.progress["text"] = ''
            ## End of download()

            ## Start of clk_btn()
            # https://www.haya-programming.com/entry/2019/01/20/012926
            def clk_btn(size):
                th = threading.Thread(target=download, args=(size, ))
                th.start()

        ## Start of cr_tab()
            frm1 = ttk.Frame(frm, width=w_width, height=w_height)
            # list     https://qiita.com/omossan7182t/items/a3ffb3788667de937c59
            # partial  https://qiita.com/yuzukaki/items/9dd3a4481e1f6e6fa0a8
            frm1.Btn = [ttk.Button(frm1, text=file_list[i], style='W.TButton', command=partial(clk_btn, i)) \
                        for i in range(5)]
            [frm1.Btn[i].grid(row = 0, column = i) for i in range(5)]

            frm1.label = ttk.Label(frm1, text=' Repetition Number ')
            frm1.label.grid(row = 0, column = 5)
            frm1.text = ttk.Entry(frm1, width = 10)
            frm1.text.grid(row = 0, column = 6)

            frm2 = ttk.Frame(frm, width=w_width, height=w_height)
            frm2.label = ttk.Label(frm2, text = 'Start Time, Size(Mbyte), Elapsed Time(ms), Speed(Mbps), Failure')
            frm2.label.grid(row = 0, column = 0)
            frm2.dummy = ttk.Label(frm2, text = '                                                    	  ')
            frm2.dummy.grid(row = 0, column = 1)
            frm2.progress = ttk.Label(frm2, text = '  ')
            frm2.progress.grid(row = 0, column = 2)

            frm3 = ttk.Frame(frm, width=w_width, height=w_height)
            frm3.prt = Text(frm3, font=("Courier New", 15), width=120, height=33)
            frm3.ysc = tkinter.Scrollbar(frm3, orient=tkinter.VERTICAL, command=frm3.prt.yview)
            frm3.ysc.pack(side=tkinter.RIGHT, fill="y")
            frm3.prt["yscrollcommand"] = frm3.ysc.set
            frm3.prt.config(state='disabled')
            frm3.prt.pack()

            frm1.pack()
            frm2.pack()
            frm3.pack()
        ## End of cr_tab()

        ## Start of create_widgets()
        # Log
        log = logging.getLogger('Download')
        log.setLevel(logging.DEBUG)
        fh = logging.FileHandler('{:Download_Log-%Y%m%d-%H%M%S}.csv'.format(datetime.now()))
        log.addHandler(fh)
        header = 'Start Time, Size(Mbyte), Elapsed Time(ms), Speed(Mbps), Failure,'
        log.debug(header)
        # Tab
        note = ttk.Notebook(self)
        note.pack()
        note0 = ttk.Frame(note, width=w_width, height=w_height)
        note.add(note0, text=" Click Download File Size ")
        cr_tab(note0)
    ## End of createwidgets()
## End of NetworkCheck()

if __name__ == '__main__':
    master = Tk()
    master.title("Repeat Download 2")
    master.geometry("750x400")
    # Call main part
    NetworkCheck(master)
    master.mainloop()
```

EOF