class

Update::GitHubRepository

Inherits Update::Repository < Reference < Object

GitHub Releases API から取る実装。

Constants

BASE = "https://api.github.com/repos/limit7412/KxNotifyUtils"
BUFFER_SIZE = 64 * 1024

読み出しの単位。

CONNECT_TIMEOUT = 5.seconds

常駐アプリの片手間の確認であり、待たされてまで通す価値は無い。 主ループを止めないよう別のファイバで呼ぶが、それでも延々と待たせない。

DOWNLOAD_READ_TIMEOUT = 60.seconds

アセットの取得は数 MB を運ぶ。一覧の確認と同じ待ち時間では短すぎる。 なお読み取りの待ち時間は 1 回の read ごとに数えるもので、全体の上限ではない。

Log = ::Log.for("update")
MAX_ASSET_SIZE = (64_i64 * 1024) * 1024

受け取るアセットの上限。実物は 8 MB 前後である。 宣言された大きさがこれを超えるものは、取りに行く前に断る。

MAX_PAGES = 10

一覧をたどるページ数の上限。

一覧は作成の新しい順に返るが、版の順序とは食い違いうる。 自動のプレリリースは最新の安定版の次のパッチとして作られる一方、 メジャーやマイナーのプレリリースや保守版の安定版は手で作れるためである。 そのため作成順の先頭だけを見るわけにいかず、ある程度の範囲を集めて版で比べる。

この上限は、積まれている数がこれより少ないリポジトリの費用を下げない(issue #30)。 fetch_all は満たない件数のページが返った時点で止めるため、 リリースが 10 件なら上限がいくつでも要求は 1 回である。 効くのは上限を超えて積まれたときだけで、そこは効いてほしくない場面でもある。 超えた範囲に版番号のより大きい安定版が残ると、それを見つけられなくなる。

それでも上限は要る。最悪の場合を縛るものが他に無い。 未認証の上限は IP あたり 60 回/時であり、本体は SteamVR の自動起動で立ち上がるため VR を始めるたびに確認が走る。際限なくたどると、そこへ近づくほど確認自体が失敗しやすくなる。

10 ページとすると、最悪でも 1 回の確認で 11 要求(一覧 10 と latest 1)に収まる。 1000 件を超えるまで届かず、超えても 60 回/時に対して 5 回ぶんの余地が残る。 上限に達したらログに残す。黙って切ると「全部見た」と読めてしまう。

MAX_REDIRECTS = 5

追うリダイレクトの上限。 browser_download_url は署名付きの配布元へ 302 で渡す作りであり、 Crystal の HTTP::Client は自動では追わない。

PER_PAGE = 100

一覧の 1 ページあたりの件数。100 が API の上限である。

READ_TIMEOUT = 5.seconds

Constructors

new(user_agent : String)

GitHub API は User-Agent の無い要求を拒む。

Source

Class methods

build(payload : Payload) : Release | Nil

ページごとの読み取りからも使うため、クラス内に公開しておく。

Source
catalog(listed : Array(Release), complete : Bool, & : -> Array(Release)) : Catalog

一覧の結果から目録を作る。latest を取りに行くかどうかもここで決める。

一覧を最後まで取れたなら、latest はその中に必ず含まれる。 /releases が返すのは下書き以外のすべてであり、latest はそのうちの 1 件だからである。 取りに行っても同じタグが merge で落ちるだけで、要求を 1 つ無駄にする。

取りに行くのは切れていたときだけとする。 そのときだけは、押し出された範囲に安定版が残っていて latest から拾えることがある。

普段の確認が 1 要求ぶん軽くなる。MAX_PAGES を広げられるのはこの分による。

取りに行くかどうかを spec で確かめられるよう、latest はブロックで受け取る。

Source
merge(releases : Array(Release), extra : Array(Release)) : Array(Release)

タグで重複を除く。一覧と latest は同じリリースを返しうる。 通信から切り離して spec で確かめられるようにしてある。

Source
parse(body : String) : Array(Release)

版として読めないタグと下書きは黙って捨てる。 確認の目的は新しい版を見つけることであり、 運用しているタグの綴りから外れたものを見つけられなくても実害が無い。

応答の読み取りだけを spec で確かめられるよう、通信から切り離してある。

Source
parse_one(body : String) : Array(Release)

/releases/latest は配列ではなく 1 件を返す。

Source
redirect_target(response : HTTP::Client::Response, from : URI) : String | Nil

リダイレクトなら次に当たる先を返す。そうでなければ nil を返す。 相対の Location も受け付ける。仕様では絶対でなくてよい。

Source
store(source : IO, asset : Asset, path : String) : Nil

応答を読みながら digest を取り、通ったときだけ path へ残す。

読みながら数えるのは、宣言より大きいものを最後まで受け取らないためである。 通信から切り離してあり、spec では IO を直に渡して確かめる。

Source

Instance methods

download(asset : Asset, path : String) : Nil

アセットを取り、digest と照合してから path へ残す。

照合を通らなければ書きかけを消して例外を投げる。 置き換えるのは実行ファイルであり、途中まで落ちたものを残しておく意味が無い。

Source
fetch_releases(channel : String) : Catalog

一覧と /releases/latest の両方を見て、重複を除いて返す。 チャンネルによる絞り込みは呼び出し側が行う。ここは候補を集めるだけである。

一覧だけでは足りない。作成の新しい順に返るため、安定版を出した後に プレリリースが積まれ続けると、その安定版はいずれ取得の範囲から落ちる。

/releases/latest だけでも足りない。これが返すのは GitHub が latest とした 1 件、 すなわち下書きとプレリリースを除いて最も新しく作られたものであり、 版番号が最大のものとは限らない。2.0.0 の後に保守版の 1.5.1 を出すと、 返るのは 1.5.1 になり、1.6.0 の利用者には 2.0.0 が見えなくなる。

両方を合わせれば、版番号で比べる候補が一覧から得られ、 一覧の範囲から押し出された安定版も latest から拾える。 一覧が上限で切れた場合は、集めきれていないことを添えて返す。 押し出された範囲に、版番号がより大きい安定版が残っている可能性があるためである。 保守版を後から出すと、作成順では新しいのに版としては古いものが latest になる。

Source

Nested types