[["path" => "postcards/a.png", "size" => 812, "type" => "image/png"]]]); // // $r["files"][0]: {id, url, type, size, expires}; PUT the bytes to url with that Content-Type // storage_call("commit", ["ids" => [$id, ...]]); // one B2 listing for the whole begin // storage_call("urls", ["ids" => [$id, ...]]); // presigned GETs, five minutes each // storage_call("list", ["after" => 0, "limit" => 1000]); // what the zoo saved for this person function storage_call(string $op, array $in = []): array { [$s, $out] = call_app("storage", "/app/$op", true, "POST", json_encode($in), ["Content-Type: application/json"], 55); $j = json_decode($out, true); return [$s, is_array($j) ? $j : ["error" => "storage answered $s: " . substr($out, 0, 160)]]; } // PUT many bodies to their presigned URLs, 16 at a time: [status, ...] in order. // $jobs: [[url, bytes, content type], ...] function put_many(array $jobs): array { return many(array_map(function ($j) { $c = curl_init($j[0]); curl_setopt_array($c, [CURLOPT_CUSTOMREQUEST => "PUT", CURLOPT_POSTFIELDS => $j[1], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => ["Content-Type: $j[2]", "Expect:"]]); return $c; }, $jobs)); } // GET many presigned URLs: [[status, body], ...] in order. function get_many(array $urls): array { return many(array_map(function ($u) { $c = curl_init($u); curl_setopt_array($c, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30]); return $c; }, $urls), true); } function many(array $handles, bool $bodies = false): array { $mh = curl_multi_init(); $out = []; foreach (array_chunk($handles, 16, true) as $chunk) { foreach ($chunk as $c) curl_multi_add_handle($mh, $c); do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.2); } while ($running); foreach ($chunk as $i => $c) { $code = (int)curl_getinfo($c, CURLINFO_HTTP_CODE); $out[$i] = $bodies ? [$code, (string)curl_multi_getcontent($c)] : $code; curl_multi_remove_handle($mh, $c); } } return $out; }