|
- #!/usr/bin/env bash
- set -ueo pipefail
-
- api='https://icerbox.com/api/v1/'
- jwt=~/.icerbox.token
- cfg=~/.icerbox.conf
- ct='Content-Type: application/json'
-
- err() {
- echo "err: ${1}" >/dev/stderr
- exit 1
- }
-
- warn() {
- echo "warn: ${1}" >/dev/stderr
- }
-
- nfo() {
- echo "info: ${1}" >/dev/stdout
- }
-
- join_by() {
- local d=${1-} f=${2-}
- if shift 2; then
- printf %s "$f" "${@/#/$d}"
- fi
- }
-
- icerbox_handle_response() {
- test -n "$1" || err "empty http status code"
- test -s "$2" || err "empty json reponse body"
- if [ "$1" -ne 200 ]; then
- jq '.' "${2}" >/dev/stderr || true
- err "request failed with http ${1}"
- fi
- }
-
- icerbox_auth_login() {
- local out='/dev/shm/icerbox.auth.login.json'
- test -f "$out" && rm "$out"
- local code=$(curl -s -w '%{http_code}' -H "$ct" -o "$out" -d "email=${1}" -d "password=${2}" "${api}auth/login")
- test $? -eq 0 || err "auth/login: curl request failed"
- icerbox_handle_response "$code" "$out"
- token=$(jq -r '.token?' "$out")
- test -n "$token" || err "auth/login: empty jwt token"
- }
-
- icerbox_auth_refresh() {
- local out='/dev/shm/icerbox.auth_refresh.json'
- test -f "$out" && rm "$out"
- # force POST with empty -d
- local code=$(curl -s -w '%{http_code}' --oauth2-bearer "$token" -H "$ct" -o "$out" -d "" "${api}auth/refresh")
- test $? -eq 0 || err "auth/refresh: curl request failed"
- icerbox_handle_response "$code" "$out"
- token=$(jq -r '.token?' "$out")
- test -n "$token" || err "auth/refresh: empty jwt token"
- }
-
- icerbox_user_account() {
- local out='/dev/shm/icerbox.user.account.json'
- test -f "$out" && rm "$out"
- local code=$(curl -s -w '%{http_code}' --oauth2-bearer "$token" -H "$ct" -o "$out" "${api}user/account")
- test $? -eq 0 || err "user/account: curl request failed"
- icerbox_handle_response "$code" "$out"
- }
-
- icerbox_download_quota() {
- local in='/dev/shm/icerbox.user.account.json'
- test -f "$in"
- jq -r '.data.package.volume - .data.downloaded_today' "$in"
- }
-
- icerbox_file() {
- local out='/dev/shm/icerbox.file.json'
- test -f "$out" && rm "$out"
- local code=$(curl -s -w '%{http_code}' --oauth2-bearer "$token" -H "$ct" -o "$out" -d id="$2" -G "${api}file")
- test $? -eq 0 || err "file: curl request failed"
- icerbox_handle_response "$code" "$out"
- local status=$(jq -r '.data?.status?' "$out")
- test $? -eq 0 || err "file: failed parsing json"
- test -n "$status" || err "file: empty status"
- if [ "$status" != "active" ]; then
- warn "skip: file id ${2} is not active"
- return 1
- fi
- local name=$(jq -r '.data?.name?' "$out")
- test $? -eq 0 || err "file: failed parsing json"
- test -n "$name" || err "file: empty name"
- if [ -f "$name" ]; then
- warn "skip: file exists: ${name}"
- return 1
- fi
- return 0
- }
-
- icerbox_files() {
- local out='/dev/shm/icerbox.files.json'
- test -f "$out" && rm "$out"
- local code=$(curl -s -w '%{http_code}' --oauth2-bearer "$token" -H "$ct" -o "$out" -d ids="$1" -G "${api}files")
- test $? -eq 0 || err "files: curl request failed"
- icerbox_handle_response "$code" "$out"
- }
-
- icerbox_filter_files() {
- local in='/dev/shm/icerbox.files.json'
- test -f "$in" || err "filter files: no input file"
- local size_sum=0
- local IFS=$'\t'
- while read -a arr; do
- if [ -f "${arr[1]}" ]; then
- local local_size=$(stat --printf '%s' "${arr[1]}")
- test -n "$local_size" || err "filter_files: empty file size"
- test "$local_size" -eq 0 && err "abort: file exists and is empty: ${arr[1]}"
- if [ $local_size -eq ${arr[2]} ]; then
- warn "skip: file of same size exists: ${arr[0]}: ${arr[1]}"
- continue
- elif [ $local_size -lt ${arr[2]} ]; then
- warn "skip: file with smaller size exists: ${arr[0]}: ${arr[1]}"
- continue
- elif [ $local_size -gt ${arr[2]} ]; then
- err "abort: local file is larger than remote: ${arr[0]}: ${arr[1]}"
- fi
- fi
- size_sum=$(( $size_sum + ${arr[2]} ))
- if [ $size_sum -gt $download_quota ]; then
- warn "break: ${arr[0]}: above download quota"
- break
- fi
- to_download+=("${arr[0]}")
- done < <(jq -r '.data[] | select(.status="active") | [.id,.name,.size] | @tsv' $in)
- numfmt --format='download size: %f' --to=iec $size_sum
- }
-
- icerbox_dl_ticket() {
- local out='/dev/shm/icerbox.dl.ticket.json'
- test -f "$out" && rm "$out"
- # endpoint does not accept file parameter with json as content type
- local code=$(curl -s -w '%{http_code}' --oauth2-bearer "$token" -o "$out" -d file="$1" "${api}dl/ticket")
- test $? -eq 0 || err "curl request failed"
- if [ "$code" -eq 422 ]; then
- warn "skip: file is unavailable: $1"
- return
- fi
- icerbox_handle_response "$code" "$out"
- local url=$(jq -r '.url?' "$out")
- test $? -eq 0 || err "failed parsing json"
- test -n "$url" || err "empty ticket url"
- tickets+=(-OJL "$url")
- }
-
- icerbox_file_id() {
- test -n "$1" || err "empty url"
- test "${1:0:20}" = "https://icerbox.com/" || err "not a icerbox url: $1"
- test "${#1}" -ge 28 || err "url $1 does not contain valid file id"
- file_ids+=("${1:20:8}")
- }
-
- test "$#" -eq 0 && err "usage $0 https://icerbox.com/abcdefgh https://icerbox.com/abcdefgh ..."
- test "$#" -gt 666 && err "too many arguments. http request will fail with 414 Request-URI Too Large"
- test -f "$cfg" && source "$cfg" || err "please set 'email' and 'password' in ${cfg}"
- test -n "$email" || err "email not set"
- test -n "$password" || err "password not set"
- type curl >/dev/null || err "need curl"
- type jq >/dev/null || err "need jq"
-
- if [ -f $jwt ] && [ -s $jwt ]; then
- token=$(< $jwt)
- token_age=$(stat --format=%Y "$jwt")
- now=$(date +%s)
- if [ $token_age -le $(( $now - 2700 )) ]; then
- icerbox_auth_refresh
- echo "$token" > "${jwt}.tmp"
- mv "${jwt}.tmp" "$jwt"
- nfo "session refreshed"
- else
- nfo "using active session"
- fi
- else
- icerbox_auth_login "$email" "$password"
- echo "$token" > "${jwt}.tmp"
- mv "${jwt}.tmp" "${jwt}"
- nfo "new session acquired"
- fi
-
- test -n "$token" || err "no session token"
-
- icerbox_user_account
- download_quota=$(icerbox_download_quota)
-
- numfmt --format='daily download limit: %f' --to=iec "$download_quota"
-
- # globals filled by functions
- file_ids=()
- to_download=()
- tickets=()
-
- nfo "requested: $#"
- for url in $@; do
- icerbox_file_id "$url"
- done
- icerbox_files "$(join_by , "${file_ids[@]}")"
- icerbox_filter_files
- test ${#to_download[@]} -gt 0 || (nfo "nothing to download" ; exit 1)
-
- nfo "to download: ${#to_download[@]}"
- for file_id in ${to_download[@]}; do
- icerbox_dl_ticket "$file_id"
- done
- test ${#tickets[@]} -gt 0 || (nfo "no download tickets" ; exit 1)
-
- nfo "download tickets: $(( ${#tickets[@]} / 2 ))"
- curl -s -w '%{filename_effective}\t%{size_download} bytes\t%{speed_download} bytes/sec\n' "${tickets[@]}"
- test $? -eq 0 || warn "curl exit error code $?"
|