(io.Reader).Read を「フック」することで、Read の進捗状況を監視する方法の紹介です。
例えば、HTTP でのファイルダウンロードの進捗状況を画面に表示したいという場合に使えます。
方法
(段階的に解説します)
手始めに io.Reader
をラッピングするような構造体を定義します。
Go では、構造体のフィールド(無名)として構造体やインターフェイスを埋め込むと、埋め込んだ側の構造体が埋め込まれた側のメソッドの実装を引き継ぐことができます。
io.Reader
を埋め込んだ構造体は、自身が io.Reader
を実装したかのように振る舞います。
無名フィールドとして埋め込む
type MyReader struct {
io.Reader
}
func a() {
file, _ := os.Open("hoge.txt")
defer file.Close()
r := MyReader {
Reader: file,
}
content, _ := ioutil.ReadAll(r) // r.Read が実装されていることになっている
}
MyReader.Read
というメソッドは実装していません。
しかし MyReader.Reader
として io.Reader
を埋め込んでいるため、MyReader
に対して ioutil.ReadAll
を呼び出せます。
MyReader.Reader
として埋め込まれた *os.File
のメソッド Read
が呼び出されます。
埋め込み先でフックする
ここで、メソッド MyReader.Read
を定義してみます。
func (r *MyReader) Read(p []byte) (n int, err error) {
return r.Reader.Read(p)
}
ここでは、埋め込んだ Reader
のメソッド Read
を明示的に呼び出しているだけです。
当然、この状態の MyReader
も ioutil.ReadAll
に渡せます。
そして、r.Reader.Read(p)
の前後に処理を挟むことで、加工やらフィルタリングやら、進捗状況の更新やらができるようになるわけです。
作ってみた
例によってパッケージを作ってみました。
progreader := progio.NewReader(
response.Body,
func(p int64) {
fmt.Printf("%d%% ", p)
},
progio.Percent(response.ContentLength, 5/* % ごとに上のfuncを呼び出す */),
)
ちなみに Writer もあります。