/** * @param string $path * * @param WriteMode $writeMode * What to do if there's already a file at the given path (UTF-8). * * @param resource $inStream * The source of data to upload. * * @param int|null $numBytes * You can pass in <code>null</code>. But if you know how many bytes you expect, pass in * that value and this function will do a sanity check at the end to make sure the number of * bytes read from $inStream matches up. * * @param int $chunkSize * * @return array * The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata * object</a> for the newly-added file. */ private function _uploadFileChunked($path, $writeMode, $inStream, $numBytes, $chunkSize) { Path::checkArg("path", $path); WriteMode::checkArg("writeMode", $writeMode); Checker::argResource("inStream", $inStream); Checker::argNatOrNull("numBytes", $numBytes); Checker::argNat("chunkSize", $chunkSize); // NOTE: This function performs 3 retries on every call. This is maybe not the right // layer to make retry decisions. It's also awkward because none of the other calls // perform retries. assert($chunkSize > 0); $data = self::readFully($inStream, $chunkSize); $len = strlen($data); $client = $this; $uploadId = RequestUtil::runWithRetry(3, function () use($data, $client) { return $client->chunkedUploadStart($data); }); $byteOffset = $len; while (!feof($inStream)) { $data = self::readFully($inStream, $chunkSize); $len = strlen($data); while (true) { $r = RequestUtil::runWithRetry(3, function () use($client, $uploadId, $byteOffset, $data) { return $client->chunkedUploadContinue($uploadId, $byteOffset, $data); }); if ($r === true) { // Chunk got uploaded! $byteOffset += $len; break; } if ($r === false) { // Server didn't recognize our upload ID // This is very unlikely since we're uploading all the chunks in sequence. throw new Exception_BadResponse("Server forgot our uploadId"); } // Otherwise, the server is at a different byte offset from us. $serverByteOffset = $r; assert($serverByteOffset !== $byteOffset); // chunkedUploadContinue ensures this. // An earlier byte offset means the server has lost data we sent earlier. if ($serverByteOffset < $byteOffset) { throw new Exception_BadResponse("Server is at an ealier byte offset: us={$byteOffset}, server={$serverByteOffset}"); } $diff = $serverByteOffset - $byteOffset; // If the server is past where we think it could possibly be, something went wrong. if ($diff > $len) { throw new Exception_BadResponse("Server is more than a chunk ahead: us={$byteOffset}, server={$serverByteOffset}"); } // The normal case is that the server is a bit further along than us because of a // partially-uploaded chunk. Finish it off. $byteOffset += $diff; if ($diff === $len) { break; } // If the server is at the end, we're done. $data = substr($data, $diff); } } if ($numBytes !== null && $byteOffset !== $numBytes) { throw new \InvalidArgumentException("You passed numBytes={$numBytes} but the stream had {$byteOffset} bytes."); } $metadata = RequestUtil::runWithRetry(3, function () use($client, $uploadId, $path, $writeMode) { return $client->chunkedUploadFinish($uploadId, $path, $writeMode); }); return $metadata; }