Overview

Classes

  • Dropbox\AccessToken
  • Dropbox\AccessType
  • Dropbox\AppInfo
  • Dropbox\AuthInfo
  • Dropbox\Client
  • Dropbox\Config
  • Dropbox\Path
  • Dropbox\RequestToken
  • Dropbox\Token
  • Dropbox\WebAuth
  • Dropbox\WriteMode

Exceptions

  • Dropbox\AppInfoLoadException
  • Dropbox\AuthInfoLoadException
  • Dropbox\DeserializeException
  • Dropbox\Exception
  • Dropbox\Exception_BadRequest
  • Dropbox\Exception_BadResponse
  • Dropbox\Exception_BadResponseCode
  • Dropbox\Exception_InvalidAccessToken
  • Dropbox\Exception_NetworkIO
  • Dropbox\Exception_ProtocolError
  • Dropbox\Exception_RetryLater
  • Dropbox\Exception_ServerError
  • Overview
  • Class
  • Tree
   1: <?php
   2: namespace Dropbox;
   3: 
   4: /**
   5:  * The class used to make most Dropbox API calls.  You can use this once you've gotten an
   6:  * {@link AccessToken} via {@link WebAuth}.
   7:  *
   8:  * This class is stateless so it can be shared/reused.
   9:  */
  10: final class Client
  11: {
  12:     /**
  13:      * The config used when making requests to the Dropbox server.
  14:      *
  15:      * @return Config
  16:      */
  17:     function getConfig() { return $this->config; }
  18: 
  19:     /** @var Config */
  20:     private $config;
  21: 
  22:     /**
  23:      * The access token used by this client to make authenticated API calls.  You can get an
  24:      * access token via {@link WebAuth}.
  25:      *
  26:      * @return AccessToken
  27:      */
  28:     function getAccessToken() { return $this->accessToken; }
  29: 
  30:     /** @var AccessToken */
  31:     private $accessToken;
  32: 
  33:     /**
  34:      * Constructor.
  35:      *
  36:      * <code>
  37:      * use \Dropbox as dbx;
  38:      * $config = new Config(...);
  39:      * list($accessToken, $dropboxUserId) = $webAuth->finish(...);
  40:      * $client = new dbx\Client($config, $accessToken);
  41:      * </code>
  42:      *
  43:      * @param Config $config
  44:      *     See {@link getConfig()}
  45:      * @param AccessToken $accessToken
  46:      *     See {@link getAccessToken()}
  47:      */
  48:     function __construct($config, $accessToken)
  49:     {
  50:         Config::checkArg("config", $config);
  51:         AccessToken::checkArg("accessToken", $accessToken);
  52: 
  53:         $this->config = $config;
  54:         $this->accessToken = $accessToken;
  55: 
  56:         // These fields are redundant, but it makes these values a little more convenient
  57:         // to access.
  58:         $this->apiHost = $config->getAppInfo()->getHost()->getApi();
  59:         $this->contentHost = $config->getAppInfo()->getHost()->getContent();
  60:         $this->root = $config->getAppInfo()->getAccessType()->getUrlPart();
  61:     }
  62: 
  63:     /** @var string */
  64:     private $apiHost;
  65:     /** @var string */
  66:     private $contentHost;
  67:     /** @var string */
  68:     private $root;
  69: 
  70:     private function appendFilePath($base, $path)
  71:     {
  72:         return $base . "/" . $this->root . "/" . rawurlencode(substr($path, 1));
  73:     }
  74: 
  75:     /**
  76:      * Returns a basic account and quota information.
  77:      *
  78:      * <code>
  79:      * $client = ...
  80:      * $accountInfo = $client->getAccountInfo();
  81:      * print_r($accountInfo);
  82:      * </code>
  83:      *
  84:      * @return array
  85:      *    See <a href="https://www.dropbox.com/developers/core/api#account-info">/account/info</a>.
  86:      *
  87:      * @throws Exception
  88:      */
  89:     function getAccountInfo()
  90:     {
  91:         $response = $this->doGet($this->apiHost, "1/account/info");
  92:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
  93:         return RequestUtil::parseResponseJson($response->body);
  94:     }
  95: 
  96:     /**
  97:      * Downloads a file from Dropbox.  The file's contents are written to the
  98:      * given <code>$outStream</code> and the file's metadata is returned.
  99:      *
 100:      * <code>
 101:      * $client = ...;
 102:      * $metadata = $client->getFile("/Photos/Frog.jpeg",
 103:      *                              fopen("./Frog.jpeg", "wb"));
 104:      * print_r($metadata);
 105:      * </code>
 106:      *
 107:      * @param string $path
 108:      *   The path to the file on Dropbox (UTF-8).
 109:      *
 110:      * @param resource $outStream
 111:      *   If the file exists, the file contents will be written to this stream.
 112:      *
 113:      * @param string|null $rev
 114:      *   If you want the latest revision of the file at the given path, pass in <code>null</code>.
 115:      *   If you want a specific version of a file, pass in value of the file metadata's "rev" field.
 116:      *
 117:      * @return null|array
 118:      *   The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
 119:      *   object</a> for the file at the given $path and $rev, or <code>null</code> if the file
 120:      *   doesn't exist,
 121:      *
 122:      * @throws Exception
 123:      */
 124:     function getFile($path, $outStream, $rev = null)
 125:     {
 126:         Path::checkArgNonRoot("path", $path);
 127:         Checker::argResource("outStream", $outStream);
 128:         Checker::argStringNonEmptyOrNull("rev", $rev);
 129: 
 130:         $url = RequestUtil::buildUrl(
 131:             $this->config,
 132:             $this->contentHost,
 133:             $this->appendFilePath("1/files", $path),
 134:             array("rev" => $rev));
 135: 
 136:         $curl = self::mkCurl($url);
 137:         $metadataCatcher = new DropboxMetadataHeaderCatcher($curl->handle);
 138:         $streamRelay = new CurlStreamRelay($curl->handle, $outStream);
 139: 
 140:         $response = $curl->exec();
 141: 
 142:         if ($response->statusCode === 404) return null;
 143: 
 144:         if ($response->statusCode !== 200) {
 145:             $response->body = $streamRelay->getErrorBody();
 146:             throw RequestUtil::unexpectedStatus($response);
 147:         }
 148: 
 149:         return $metadataCatcher->getMetadata();
 150:     }
 151: 
 152:     /**
 153:      * Calling 'uploadFile' with <code>$numBytes</code> less than this value, will cause this SDK
 154:      * to use the standard /files_put endpoint.  When <code>$numBytes</code> is greater than this
 155:      * value, we'll use the /chunked_upload endpoint.
 156:      *
 157:      * @var int
 158:      */
 159:     private static $AUTO_CHUNKED_UPLOAD_THRESHOLD = 9863168;  // 8 MB
 160: 
 161:     /**
 162:      * @var int
 163:      */
 164:     private static $DEFAULT_CHUNK_SIZE = 4194304;  // 4 MB
 165: 
 166:     /**
 167:      * Creates a file on Dropbox, using the data from <code>$inStream</code> for the file contents.
 168:      *
 169:      * <code>
 170:      * use \Dropbox as dbx;
 171:      * $client = ...;
 172:      * $md1 = $client->uploadFile("/Photos/Frog.jpeg",
 173:      *                            dbx\WriteMode::add(), 
 174:      *                            fopen("./frog.jpeg", "rb"));
 175:      * print_r($md1);
 176:      *
 177:      * // Re-upload with WriteMode::update(...), which will overwrite the
 178:      * // file if it hasn't been modified from our original upload.
 179:      * $md2 = $client->uploadFile("/Photos/Frog.jpeg",
 180:      *                            dbx\WriteMode::update($md1["rev"]),
 181:      *                            fopen("./frog-new.jpeg", "rb"));
 182:      * print_r($md2);
 183:      * </code>
 184:      *
 185:      * @param string $path
 186:      *    The Dropbox path to save the file to (UTF-8).
 187:      *
 188:      * @param WriteMode $writeMode
 189:      *    What to do if there's already a file at the given path.
 190:      *
 191:      * @param resource $inStream
 192:      *
 193:      * @param int|null $numBytes
 194:      *    You can pass in <code>null</code> if you don't know.  If you do provide the size, we can
 195:      *    perform a slightly more efficient upload (fewer network round-trips) for files smaller
 196:      *    than 8 MB.
 197:      *
 198:      * @return mixed
 199:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
 200:      *    object</a> for the newly-added file.
 201:      *
 202:      * @throws Exception
 203:      */
 204:     function uploadFile($path, $writeMode, $inStream, $numBytes = null)
 205:     {
 206:         try {
 207:             Path::checkArgNonRoot("path", $path);
 208:             WriteMode::checkArg("writeMode", $writeMode);
 209:             Checker::argResource("inStream", $inStream);
 210:             Checker::argNatOrNull("numBytes", $numBytes);
 211: 
 212:             // If we don't know how many bytes are coming, we have to use chunked upload.
 213:             // If $numBytes is large, we elect to use chunked upload.
 214:             // In all other cases, use regular upload.
 215:             if ($numBytes === null || $numBytes > self::$AUTO_CHUNKED_UPLOAD_THRESHOLD) {
 216:                 $metadata = $this->_uploadFileChunked($path, $writeMode, $inStream, $numBytes,
 217:                                                       self::$DEFAULT_CHUNK_SIZE);
 218:             } else {
 219:                 $metadata = $this->_uploadFile($path, $writeMode,
 220:                     function(Curl $curl) use ($inStream, $numBytes) {
 221:                         $curl->set(CURLOPT_PUT, true);
 222:                         $curl->set(CURLOPT_INFILE, $inStream);
 223:                         $curl->set(CURLOPT_INFILESIZE, $numBytes);
 224:                     });
 225:             }
 226:         }
 227:         catch (\Exception $ex) {
 228:             fclose($inStream);
 229:             throw $ex;
 230:         }
 231:         fclose($inStream);
 232: 
 233:         return $metadata;
 234:     }
 235: 
 236:     /**
 237:      * Creates a file on Dropbox, using the given $data string as the file contents.
 238:      *
 239:      * <code>
 240:      * use \Dropbox as dbx;
 241:      * $client = ...;
 242:      * $md = $client->uploadFile("/Grocery List.txt",
 243:      *                           dbx\WriteMode::add(), 
 244:      *                           "1. Coke\n2. Popcorn\n3. Toothpaste\n");
 245:      * print_r($md);
 246:      * </code>
 247:      *
 248:      * @param string $path
 249:      *    The Dropbox path to save the file to (UTF-8).
 250:      *
 251:      * @param WriteMode $writeMode
 252:      *    What to do if there's already a file at the given path.
 253:      *
 254:      * @param string $data
 255:      *    The data to use for the contents of the file.
 256:      *
 257:      * @return mixed
 258:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
 259:      *    object</a> for the newly-added file.
 260:      *
 261:      * @throws Exception
 262:      */
 263:     function uploadFileFromString($path, $writeMode, $data)
 264:     {
 265:         Path::checkArgNonRoot("path", $path);
 266:         WriteMode::checkArg("writeMode", $writeMode);
 267:         Checker::argString("data", $data);
 268: 
 269:         return $this->_uploadFile($path, $writeMode, function(Curl $curl) use ($data) {
 270:             $curl->set(CURLOPT_CUSTOMREQUEST, "PUT");
 271:             $curl->set(CURLOPT_POSTFIELDS, $data);
 272:             $curl->addHeader("Content-Type: application/octet-stream");
 273:         });
 274:     }
 275: 
 276:     /**
 277:      * Creates a file on Dropbox, using the data from $inStream as the file contents.
 278:      *
 279:      * This version of <code>uploadFile</code> splits uploads the file ~4MB chunks at a time and
 280:      * will retry a few times if one chunk fails to upload.  Uses {@link chunkedUploadStart()},
 281:      * {@link chunkedUploadContinue()}, and {@link chunkedUploadFinish()}.
 282:      *
 283:      * @param string $path
 284:      *    The Dropbox path to save the file to (UTF-8).
 285:      *
 286:      * @param WriteMode $writeMode
 287:      *    What to do if there's already a file at the given path.
 288:      *
 289:      * @param resource $inStream
 290:      *
 291:      * @param int|null $numBytes
 292:      *    The number of bytes available from $inStream.
 293:      *    You can pass in <code>null</code> if you don't know.
 294:      *
 295:      * @param int|null $chunkSize
 296:      *    The number of bytes to upload in each chunk.  You can omit this (or pass in
 297:      *    <code>null</code> and the library will use a reasonable default.
 298:      *
 299:      * @return mixed
 300:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
 301:      *    object</a> for the newly-added file.
 302:      *
 303:      * @throws Exception
 304:      */
 305:     function uploadFileChunked($path, $writeMode, $inStream, $numBytes = null, $chunkSize = null)
 306:     {
 307:         try {
 308:             if ($chunkSize === null) {
 309:                 $chunkSize = self::$DEFAULT_CHUNK_SIZE;
 310:             }
 311: 
 312:             Path::checkArgNonRoot("path", $path);
 313:             WriteMode::checkArg("writeMode", $writeMode);
 314:             Checker::argResource("inStream", $inStream);
 315:             Checker::argNatOrNull("numBytes", $numBytes);
 316:             Checker::argIntPositive("chunkSize", $chunkSize);
 317: 
 318:             $metadata = $this->_uploadFileChunked($path, $writeMode, $inStream, $numBytes,
 319:                                                   $chunkSize);
 320:         }
 321:         catch (\Exception $ex) {
 322:             fclose($inStream);
 323:             throw $ex;
 324:         }
 325:         fclose($inStream);
 326: 
 327:         return $metadata;
 328:     }
 329: 
 330:     /**
 331:      * @param string $path
 332:      *
 333:      * @param WriteMode $writeMode
 334:      *    What to do if there's already a file at the given path (UTF-8).
 335:      *
 336:      * @param resource $inStream
 337:      *    The source of data to upload.
 338:      *
 339:      * @param int|null $numBytes
 340:      *    You can pass in <code>null</code>.  But if you know how many bytes you expect, pass in
 341:      *    that value and this function will do a sanity check at the end to make sure the number of
 342:      *    bytes read from $inStream matches up.
 343:      *
 344:      * @param int $chunkSize
 345:      *
 346:      * @return array
 347:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
 348:      *    object</a> for the newly-added file.
 349:      */
 350:     private function _uploadFileChunked($path, $writeMode, $inStream, $numBytes, $chunkSize)
 351:     {
 352:         Path::checkArg("path", $path);
 353:         WriteMode::checkArg("writeMode", $writeMode);
 354:         Checker::argResource("inStream", $inStream);
 355:         Checker::argNatOrNull("numBytes", $numBytes);
 356:         Checker::argNat("chunkSize", $chunkSize);
 357: 
 358:         // NOTE: This function performs 3 retries on every call.  This is maybe not the right
 359:         // layer to make retry decisions.  It's also awkward because none of the other calls
 360:         // perform retries.
 361: 
 362:         assert($chunkSize > 0);
 363: 
 364:         $data = fread($inStream, $chunkSize);
 365:         $len = strlen($data);
 366: 
 367:         $client = $this;
 368:         $uploadId = RequestUtil::runWithRetry(3, function() use ($data, $client) {
 369:             return $client->chunkedUploadStart($data);
 370:         });
 371: 
 372:         $byteOffset = $len;
 373: 
 374:         while (!feof($inStream)) {
 375:             $data = fread($inStream, $chunkSize);
 376:             $len = strlen($data);
 377: 
 378:             while (true) {
 379:                 $r = RequestUtil::runWithRetry(3,
 380:                     function() use ($client, $uploadId, $byteOffset, $data) {
 381:                         return $client->chunkedUploadContinue($uploadId, $byteOffset, $data);
 382:                     });
 383: 
 384:                 if ($r === true) {  // Chunk got uploaded!
 385:                     $byteOffset += $len;
 386:                     break;
 387:                 }
 388:                 if ($r === false) {  // Server didn't recognize our upload ID
 389:                     // This is very unlikely since we're uploading all the chunks in sequence.
 390:                     throw new Exception_BadResponse("Server forgot our uploadId");
 391:                 }
 392: 
 393:                 // Otherwise, the server is at a different byte offset from us.
 394:                 $serverByteOffset = $r;
 395:                 assert($serverByteOffset !== $byteOffset);  // chunkedUploadContinue ensures this.
 396:                 // An earlier byte offset means the server has lost data we sent earlier.
 397:                 if ($r < $byteOffset) throw new Exception_BadResponse(
 398:                     "Server is at an ealier byte offset: us=$byteOffset, server=$serverByteOffset");
 399:                 // The normal case is that the server is a bit further along than us because of a
 400:                 // partially-uploaded chunk.
 401:                 $diff = $serverByteOffset - $byteOffset;
 402:                 if ($diff > $len) throw new Exception_BadResponse(
 403:                     "Server is more than a chunk ahead: us=$byteOffset, server=$serverByteOffset");
 404: 
 405:                 // Finish the rest of this chunk.
 406:                 $byteOffset += $diff;
 407:                 $data = substr($data, $diff);
 408:             }
 409:         }
 410: 
 411:         if ($numBytes !== null && $byteOffset !== $numBytes) throw new \InvalidArgumentException(
 412:             "You passed numBytes=$numBytes but the stream had $byteOffset bytes.");
 413: 
 414:         $metadata = RequestUtil::runWithRetry(3,
 415:             function() use ($client, $uploadId, $path, $writeMode) {
 416:                 return $client->chunkedUploadFinish($uploadId, $path, $writeMode);
 417:             });
 418: 
 419:         return $metadata;
 420:     }
 421: 
 422:     /**
 423:      * @param string $path
 424:      * @param WriteMode $writeMode
 425:      * @param callable $curlConfigClosure
 426:      * @return array
 427:      */
 428:     private function _uploadFile($path, $writeMode, $curlConfigClosure)
 429:     {
 430:         Path::checkArg("path", $path);
 431:         WriteMode::checkArg("writeMode", $writeMode);
 432:         Checker::argCallable("curlConfigClosure", $curlConfigClosure);
 433: 
 434:         $url = RequestUtil::buildUrl(
 435:             $this->config,
 436:             $this->contentHost,
 437:             $this->appendFilePath("1/files_put", $path),
 438:             $writeMode->getExtraParams());
 439: 
 440:         $curl = $this->mkCurl($url);
 441: 
 442:         $curlConfigClosure($curl);
 443: 
 444:         $curl->set(CURLOPT_RETURNTRANSFER, true);
 445:         $response = $curl->exec();
 446: 
 447:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 448: 
 449:         return RequestUtil::parseResponseJson($response->body);
 450:     }
 451: 
 452:     /**
 453:      * Start a new chunked upload session and upload the first chunk of data.
 454:      *
 455:      * @param string $data
 456:      *     The data to start off the chunked upload session.
 457:      *
 458:      * @return array
 459:      *     A pair of <code>(string $uploadId, int $byteOffset)</code>.  <code>$uploadId</code>
 460:      *     is a unique identifier for this chunked upload session.  You pass this in to
 461:      *     {@link chunkedUploadContinue} and {@link chuunkedUploadFinish}.  <code>$byteOffset</code>
 462:      *     is the number of bytes that were successfully uploaded.
 463:      *
 464:      * @throws Exception
 465:      */
 466:     function chunkedUploadStart($data)
 467:     {
 468:         Checker::argString("data", $data);
 469: 
 470:         $response = $this->_chunkedUpload(array(), $data);
 471: 
 472:         if ($response->statusCode === 404) {
 473:             throw new Exception_BadResponse("Got a 404, but we didn't send up an 'upload_id'");
 474:         }
 475: 
 476:         $correction = self::_chunkedUploadCheckForOffsetCorrection($response);
 477:         if ($correction !== null) throw new Exception_BadResponse(
 478:             "Got an offset-correcting 400 response, but we didn't send an offset");
 479: 
 480:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 481: 
 482:         list($uploadId, $byteOffset) = self::_chunkedUploadParse200Response($response->body);
 483:         $len = strlen($data);
 484:         if ($byteOffset !== $len) throw new Exception_BadResponse(
 485:             "We sent $len bytes, but server returned an offset of $byteOffset");
 486: 
 487:         return $uploadId;
 488:     }
 489: 
 490:     /**
 491:      * Append another chunk data to a previously-started chunked upload session.
 492:      *
 493:      * @param string $uploadId
 494:      *     The unique identifier for the chunked upload session.  This is obtained via
 495:      *     {@link chunkedUploadStart}.
 496:      *
 497:      * @param int $byteOffset
 498:      *     The number of bytes you think you've already uploaded to the given chunked upload
 499:      *     session.  The server will append the new chunk of data after that point.
 500:      *
 501:      * @param string $data
 502:      *     The data to append to the existing chunked upload session.
 503:      *
 504:      * @return int|bool
 505:      *     If <code>false</code>, it means the server didn't know about the given
 506:      *     <code>$uploadId</code>.  This may be because the chunked upload session has expired
 507:      *     (they last around 24 hours).
 508:      *     If <code>true</code>, the chunk was successfully uploaded.  If an integer, it means
 509:      *     you and the server don't agree on the current <code>$byteOffset</code>.  The returned
 510:      *     integer is the server's internal byte offset for the chunked upload session.  You need
 511:      *     to adjust your input to match.
 512:      *
 513:      * @throws Exception
 514:      */
 515:     function chunkedUploadContinue($uploadId, $byteOffset, $data)
 516:     {
 517:         Checker::argStringNonEmpty("uploadId", $uploadId);
 518:         Checker::argNat("byteOffset", $byteOffset);
 519:         Checker::argString("data", $data);
 520: 
 521:         $response = $this->_chunkedUpload(
 522:             array("upload_id" => $uploadId, "offset" => $byteOffset), $data);
 523: 
 524:         if ($response->statusCode === 404) {
 525:             // The server doesn't know our upload ID.  Maybe it expired?
 526:             return false;
 527:         }
 528: 
 529:         $correction = self::_chunkedUploadCheckForOffsetCorrection($response);
 530:         if ($correction !== null) {
 531:             list($correctedUploadId, $correctedByteOffset) = $correction;
 532:             if ($correctedUploadId !== $uploadId) throw new Exception_BadResponse(
 533:                 "Corrective 400 upload_id mismatch: us=".
 534:                 self::q($uploadId)." server=".self::q($correctedUploadId));
 535:             if ($correctedByteOffset === $byteOffset) throw new Exception_BadResponse(
 536:                 "Corrective 400 offset is the same as ours: $byteOffset");
 537:             return $correctedByteOffset;
 538:         }
 539: 
 540:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 541:         list($retUploadId, $retByteOffset) = self::_chunkedUploadParse200Response($response->body);
 542: 
 543:         $nextByteOffset = $byteOffset + strlen($data);
 544:         if ($uploadId !== $retUploadId) throw new Exception_BadResponse(
 545:                 "upload_id mismatch: us=".self::q($uploadId).", server=".self::q($uploadId));
 546:         if ($nextByteOffset !== $retByteOffset) throw new Exception_BadResponse(
 547:                 "next-offset mismatch: us=$nextByteOffset, server=$retByteOffset");
 548: 
 549:         return true;
 550:     }
 551: 
 552:     /**
 553:      * @param string $body
 554:      * @return array
 555:      */
 556:     private static function _chunkedUploadParse200Response($body)
 557:     {
 558:         $j = RequestUtil::parseResponseJson($body);
 559:         $uploadId = self::getField($j, "upload_id");
 560:         $byteOffset = self::getField($j, "offset");
 561:         return array($uploadId, $byteOffset);
 562:     }
 563: 
 564:     /**
 565:      * @param HttpResponse $response
 566:      * @return array|null
 567:      */
 568:     private static function _chunkedUploadCheckForOffsetCorrection($response)
 569:     {
 570:         if ($response->statusCode !== 400) return null;
 571:         $j = json_decode($response->body, true);
 572:         if ($j === null) return null;
 573:         if (!array_key_exists("upload_id", $j) || !array_key_exists("offset", $j)) return null;
 574:         $uploadId = $j["upload_id"];
 575:         $byteOffset = $j["offset"];
 576:         return array($uploadId, $byteOffset);
 577:     }
 578: 
 579:     /**
 580:      * Creates a file on Dropbox using the accumulated contents of the given chunked upload session.
 581:      *
 582:      * @param string $uploadId
 583:      *     The unique identifier for the chunked upload session.  This is obtained via
 584:      *     {@link chunkedUploadStart}.
 585:      *
 586:      * @param string $path
 587:      *    The Dropbox path to save the file to ($path).
 588:      *
 589:      * @param WriteMode $writeMode
 590:      *    What to do if there's already a file at the given path.
 591:      *
 592:      * @return array|null
 593:      *    If <code>null</code>, it means the Dropbox server wasn't aware of the
 594:      *    <code>$uploadId</code> you gave it.
 595:      *    Otherwise, you get back the
 596:      *    <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata object</a>
 597:      *    for the newly-created file.
 598:      *
 599:      * @throws Exception
 600:      */
 601:     function chunkedUploadFinish($uploadId, $path, $writeMode)
 602:     {
 603:         Checker::argStringNonEmpty("uploadId", $uploadId);
 604:         Path::checkArgNonRoot("path", $path);
 605:         WriteMode::checkArg("writeMode", $writeMode);
 606: 
 607:         $params = array_merge(array("upload_id" => $uploadId), $writeMode->getExtraParams());
 608: 
 609:         $response = $this->doPost(
 610:             $this->contentHost,
 611:             $this->appendFilePath("1/commit_chunked_upload", $path),
 612:             $params);
 613: 
 614:         if ($response->statusCode === 404) return null;
 615:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 616: 
 617:         return RequestUtil::parseResponseJson($response->body);
 618:     }
 619: 
 620:     /**
 621:      * @param array $params
 622:      * @param string $data
 623:      * @return HttpResponse
 624:      */
 625:     private function _chunkedUpload($params, $data)
 626:     {
 627:         $url = RequestUtil::buildUrl(
 628:             $this->config, $this->contentHost, "1/chunked_upload", $params);
 629: 
 630:         $curl = $this->mkCurl($url);
 631: 
 632:         // We can't use CURLOPT_PUT because it wants a stream, but we already have $data in memory.
 633:         $curl->set(CURLOPT_CUSTOMREQUEST, "PUT");
 634:         $curl->set(CURLOPT_POSTFIELDS, $data);
 635:         $curl->addHeader("Content-Type: application/octet-stream");
 636: 
 637:         $curl->set(CURLOPT_RETURNTRANSFER, true);
 638:         return $curl->exec();
 639:     }
 640: 
 641:     /**
 642:      * Returns the metadata for whatever file or folder is at the given path.
 643:      *
 644:      * <code>
 645:      * $client = ...;
 646:      * $md = $client->getMetadata("/Photos/Frog.jpeg");
 647:      * print_r($md);
 648:      * </code>
 649:      *
 650:      * @param string $path
 651:      *    The Dropbox path to a file or folder (UTF-8).
 652:      *
 653:      * @return array|null
 654:      *    If there is a file or folder at the given path, you'll get back the
 655:      *    <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata object</a>
 656:      *    for that file or folder.  If not, you'll get back <code>null</code>.
 657:      *
 658:      * @throws Exception
 659:      */
 660:     function getMetadata($path)
 661:     {
 662:         Path::checkArg("path", $path);
 663: 
 664:         return $this->_getMetadata($path, array("list" => "false"));
 665:     }
 666: 
 667:     /**
 668:      * Returns the metadata for whatever file or folder is at the given path and, if it's a folder,
 669:      * also include the metadata for all the immediate children of that folder.
 670:      *
 671:      * <code>
 672:      * $client = ...;
 673:      * $md = $client->getMetadataWithChildren("/Photos");
 674:      * print_r($md);
 675:      * </code>
 676:      *
 677:      * @param string $path
 678:      *    The Dropbox path to a file or folder (UTF-8).
 679:      *
 680:      * @return array|null
 681:      *    If there is a file or folder at the given path, you'll get back the
 682:      *    <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata object</a>
 683:      *    for that file or folder, along with all immediate children if it's a folder.  If not,
 684:      *    you'll get back <code>null</code>.
 685:      *
 686:      * @throws Exception
 687:      */
 688:     function getMetadataWithChildren($path)
 689:     {
 690:         Path::checkArg("path", $path);
 691: 
 692:         return $this->_getMetadata($path, array("list" => "true", "file_limit" => "25000"));
 693:     }
 694: 
 695:     /**
 696:      * @param string $path
 697:      * @param array $params
 698:      * @return array
 699:      */
 700:     private function _getMetadata($path, $params)
 701:     {
 702:         $response = $this->doGet(
 703:             $this->apiHost,
 704:             $this->appendFilePath("1/metadata", $path),
 705:             $params);
 706: 
 707:         if ($response->statusCode === 404) return null;
 708:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 709: 
 710:         $metadata = RequestUtil::parseResponseJson($response->body);
 711:         if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) return null;
 712:         return $metadata;
 713:     }
 714: 
 715:     /**
 716:      * If you've previously retrieved the metadata for a folder and its children, this method will
 717:      * retrieve updated metadata only if something has changed.  This is more efficient than
 718:      * calling {@link getMetadataWithChildren} if you have a cache of previous results.
 719:      *
 720:      * <code>
 721:      * $client = ...;
 722:      * $md = $client->getMetadataWithChildren("/Photos");
 723:      * print_r($md);
 724:      * assert($md["is_dir"], "expecting \"/Photos\" to be a folder");
 725:      *
 726:      * sleep(10);
 727:      *
 728:      * // Now see if anything changed...
 729:      * list($changed, $new_md) = $client->getMetadataWithChildrenIfChanged(
 730:      *                                    "/Photos", $md["hash"]);
 731:      * if ($changed) {
 732:      *     echo "Folder changed.\n";
 733:      *     print_r($new_md);
 734:      * } else {
 735:      *     echo "Folder didn't change.\n";
 736:      * }
 737:      * </code>
 738:      *
 739:      * @param string $path
 740:      *    The Dropbox path to a folder (UTF-8).
 741:      *
 742:      * @param string $previousFolderHash
 743:      *    The "hash" field from the previously retrieved folder metadata.
 744:      *
 745:      * @return array
 746:      *    A <code>list(boolean $changed, array $metadata)</code>.  If the metadata hasn't changed,
 747:      *    you'll get <code>list(false, null)</code>.  If the metadata of the folder or any of its
 748:      *    children has changed, you'll get <code>list(true, $newMetadata)</code>.  $metadata is a
 749:      *    <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata object</a>.
 750:      *
 751:      * @throws Exception
 752:      */
 753:     function getMetadataWithChildrenIfChanged($path, $previousFolderHash)
 754:     {
 755:         Path::checkArg("path", $path);
 756:         Checker::argStringNonEmpty("previousFolderHash", $previousFolderHash);
 757: 
 758:         $params = array("list" => "true", "limit" => "25000", "hash" => $previousFolderHash);
 759: 
 760:         $response = $this->doGet(
 761:             $this->apiHost, "1/metadata",
 762:             $this->appendFilePath("1/metadata", $path),
 763:             $params);
 764: 
 765:         if ($response->statusCode === 304) return array(false, null);
 766:         if ($response->statusCode === 404) return array(true, null);
 767:         if ($response->statusCode !== 404) throw RequestUtil::unexpectedStatus($response);
 768: 
 769:         $metadata = RequestUtil::parseResponseJson($response->body);
 770:         if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) {
 771:             return array(true, null);
 772:         }
 773:         return array(true, $metadata);
 774:     }
 775: 
 776:     /**
 777:      * A way of letting you keep up with changes to files and folders in a user's Dropbox.
 778:      *
 779:      * @param string|null $cursor
 780:      *    If this is the first time you're calling this, pass in <code>null</code>.  Otherwise,
 781:      *    pass in whatever cursor was returned by the previous call.
 782:      *
 783:      * @return array
 784:      *    A <a href="https://www.dropbox.com/developers/core/api#delta">delta page</a>, which
 785:      *    contains a list of changes to apply along with a new "cursor" that should be passed into
 786:      *    future <code>getDelta</code> calls.  If the "reset" field is <code>true</code>, you
 787:      *    should clear your local state before applying the changes.  If the "has_more" field is
 788:      *    <code>true</code>, call <code>getDelta</code> immediately to get more results, otherwise
 789:      *    wait a while (at least 5 minutes) before calling <code>getDelta</code> again.
 790:      *
 791:      * @throws Exception
 792:      */
 793:     function getDelta($cursor = null)
 794:     {
 795:         Checker::argStringNonEmptyOrNull("cursor", $cursor);
 796: 
 797:         $response = $this->doPost($this->apiHost, "1/delta", array("cursor" => $cursor));
 798: 
 799:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 800: 
 801:         return RequestUtil::parseResponseJson($response->body);
 802:     }
 803: 
 804:     /**
 805:      * Gets the metadata for all the file revisions (up to a limit) for a given path.
 806:      *
 807:      * See <a href="https://www.dropbox.com/developers/core/api#revisions">/revisions</a>.
 808:      *
 809:      * @param string path
 810:      *    The Dropbox path that you want file revision metadata for (UTF-8).
 811:      *
 812:      * @param int|null limit
 813:      *    The maximum number of revisions to return.
 814:      *
 815:      * @return array|null
 816:      *    A list of <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
 817:      *    objects</a>, one for each file revision.  The later revisions appear first in the list.
 818:      *    If <code>null</code>, then there were too many revisions at that path.
 819:      *
 820:      * @throws Exception
 821:      */
 822:     function getRevisions($path, $limit = null)
 823:     {
 824:         Path::checkArgNonRoot("path", $path);
 825:         Checker::argIntPositiveOrNull("limit", $limit);
 826: 
 827:         $response = $this->doGet(
 828:             $this->apiHost,
 829:             $this->appendFilePath("1/revisions", $path),
 830:             array("rev_limit" => $limit));
 831: 
 832:         if ($response->statusCode === 406) return null;
 833:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 834: 
 835:         return RequestUtil::parseResponseJson($response->body);
 836:     }
 837: 
 838:     /**
 839:      * Takes a copy of the file at the given revision and saves it over the current copy.  This
 840:      * will create a new revision, but the file contents will match the revision you specified.
 841:      *
 842:      * See <a href="https://www.dropbox.com/developers/core/api#restore">/restore</a>.
 843:      *
 844:      * @param string $path
 845:      *    The Dropbox path of the file to restore (UTF-8).
 846:      *
 847:      * @param string $rev
 848:      *    The revision to restore the contents to.
 849:      *
 850:      * @return mixed
 851:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
 852:      *    object</a>
 853:      *
 854:      * @throws Exception
 855:      */
 856:     function restoreFile($path, $rev)
 857:     {
 858:         Path::checkArgNonRoot("path", $path);
 859:         Checker::argStringNonEmpty("rev", $rev);
 860: 
 861:         $response = $this->doPost(
 862:             $this->apiHost,
 863:             $this->appendFilePath("1/restore", $path),
 864:             array("rev" => $rev));
 865: 
 866:         if ($response->statusCode === 404) return null;
 867:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 868: 
 869:         return RequestUtil::parseResponseJson($response->body);
 870:     }
 871: 
 872:     /**
 873:      * Returns metadata for all files and folders whose filename matches the query string.
 874:      *
 875:      * @param string $basePath
 876:      *    The path to limit the search to (UTF-8).  Pass in "/" to search everything.
 877:      *
 878:      * @param string $query
 879:      *    A space-separated list of substrings to search for.  A file matches only if it contains
 880:      *    all the substrings.
 881:      *
 882:      * @param int|null $limit
 883:      *    The maximum number of results to return.
 884:      *
 885:      * @param bool $includeDeleted
 886:      *    Whether to include deleted files in the results.
 887:      *
 888:      * @return mixed
 889:      *    A list of <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata
 890:      *    objects</a> of files that match the search query.
 891:      *
 892:      * @throws Exception
 893:      */
 894:     function searchFileNames($basePath, $query, $limit = null, $includeDeleted = false)
 895:     {
 896:         Path::checkArg("basePath", $basePath);
 897:         Checker::argStringNonEmpty("query", $query);
 898:         Checker::argNatOrNull("limit", $limit);
 899:         Checker::argBool("includeDeleted", $includeDeleted);
 900: 
 901:         $response = $this->doPost(
 902:             $this->apiHost,
 903:             $this->appendFilePath("1/search", $basePath),
 904:             array(
 905:                 "query" => $query,
 906:                 "file_limit" => $limit,
 907:                 "include_deleted" => $includeDeleted,
 908:             ));
 909: 
 910:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 911: 
 912:         return RequestUtil::parseResponseJson($response->body);
 913:     }
 914: 
 915:     /**
 916:      * Creates and returns a public link to a file or folder's "preview page".  This link can be
 917:      * used without authentication.  The preview page may contain a thumbnail or some other
 918:      * preview of the file, along with a download link to download the actual file.
 919:      *
 920:      * See <a href="https://www.dropbox.com/developers/core/api#shares">/shares</a>.
 921:      *
 922:      * @param string $path
 923:      *    The Dropbox path to the file or folder you want to create a shareable link to (UTF-8).
 924:      *
 925:      * @return string
 926:      *    The URL of the preview page.
 927:      *
 928:      * @throws Exception
 929:      */
 930:     function createShareableLink($path)
 931:     {
 932:         Path::checkArg("path", $path);
 933: 
 934:         $response = $this->doPost(
 935:             $this->apiHost,
 936:             $this->appendFilePath("1/shares", $path),
 937:             array(
 938:                 "short_url" => "false",
 939:             ));
 940: 
 941:         if ($response->statusCode === 404) return null;
 942:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 943: 
 944:         $j = RequestUtil::parseResponseJson($response->body);
 945:         return self::getField($j, "url");
 946:     }
 947: 
 948:     /**
 949:      * Creates and returns a direct link to a file.  This link can be used without authentication.
 950:      * This link will expire in a few hours.
 951:      *
 952:      * @param string $path
 953:      *    The Dropbox path to a file or folder (UTF-8).
 954:      *
 955:      * @return array
 956:      *    A <code>list(string $url, \DateTime $expires) where <code>$url</code> is a direct link to
 957:      *    the requested file and <code>$expires</code> is a standard PHP <code>\DateTime</code>
 958:      *    representing when <code>$url</code> will stop working.
 959:      *
 960:      * @throws Exception
 961:      */
 962:     function createTemporaryDirectLink($path)
 963:     {
 964:         Path::checkArgNonRoot("path", $path);
 965: 
 966:         $response = $this->doPost(
 967:             $this->apiHost,
 968:             $this->appendFilePath("1/media", $path));
 969: 
 970:         if ($response->statusCode === 404) return null;
 971:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
 972: 
 973:         $j = RequestUtil::parseResponseJson($response->body);
 974:         $url = self::getField($j, "url");
 975:         $expires = self::parseDateTime(self::getField($j, "expires"));
 976:         return array($url, $expires);
 977:     }
 978: 
 979:     /**
 980:      * Creates and returns a "copy ref" to a file.  A copy ref can be used to copy a file across
 981:      * different Dropbox accounts without downloading and re-uploading.
 982:      *
 983:      * For example: Create a <code>Client</code> using the access token from one account and call
 984:      * <code>createCopyRef</code>.  Then, create a <code>Client</code> using the access token for
 985:      * another account and call <code>copyFromCopyRef</code> using the copy ref.  (You need to use
 986:      * the same app key both times.)
 987:      *
 988:      * @param string path
 989:      *    The Dropbox path of the file or folder you want to create a copy ref for (UTF-8).
 990:      *
 991:      * @return string
 992:      *    The copy ref (just a string that you keep track of).
 993:      *
 994:      * @throws Exception
 995:      */
 996:     function createCopyRef($path)
 997:     {
 998:         Path::checkArg("path", $path);
 999: 
1000:         $response = $this->doGet(
1001:             $this->apiHost,
1002:             $this->appendFilePath("1/copy_ref", $path));
1003: 
1004:         if ($response->statusCode === 404) return null;
1005:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1006: 
1007:         $j = RequestUtil::parseResponseJson($response->body);
1008:         return self::getField($j, "copy_ref");
1009:     }
1010: 
1011:     /**
1012:      * Gets a thumbnail image representation of the file at the given path.
1013:      *
1014:      * @param string $path
1015:      *    The path to the file you want a thumbnail for (UTF-8).
1016:      *
1017:      * @param string $format
1018:      *    One of the two image formats: "jpeg" or "png".
1019:      *
1020:      * @param string $size
1021:      *    One of the predefined image size names, as a string:
1022:      *    <ul>
1023:      *    <li>"xs" - 32x32</li>
1024:      *    <li>"s" - 64x64</li>
1025:      *    <li>"m" - 128x128</li>
1026:      *    <li>"l" - 640x480</li>
1027:      *    <li>"xl" - 1024x768</li>
1028:      *    </ul>
1029:      *
1030:      * @return array|null
1031:      *    If the file exists, you'll get <code>list(array $metadata, string $data)</code> where
1032:      *    <code>$metadata</code> is the file's
1033:      *    <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata object</a>
1034:      *    and $data is the raw data for the thumbnail image.  If the file doesn't exist, you'll
1035:      *    get <code>null</code>.
1036:      *
1037:      * @throws Exception
1038:      */
1039:     function getThumbnail($path, $format, $size)
1040:     {
1041:         Path::checkArgNonRoot("path", $path);
1042:         Checker::argString("format", $format);
1043:         Checker::argString("size", $size);
1044:         if (!in_array($format, array("jpeg", "png"))) {
1045:             throw new \InvalidArgumentException("Invalid 'format': ".self::q($format));
1046:         }
1047:         if (!in_array($size, array("xs", "s", "m", "l", "xl"))) {
1048:             throw new \InvalidArgumentException("Invalid 'size': ".self::q($format));
1049:         }
1050: 
1051:         $url = RequestUtil::buildUrl(
1052:             $this->config,
1053:             $this->contentHost,
1054:             $this->appendFilePath("1/thumbnails", $path),
1055:             array("size" => $size, "format" => $format));
1056: 
1057:         $curl = self::mkCurl($url);
1058:         $metadataCatcher = new DropboxMetadataHeaderCatcher($curl->handle);
1059: 
1060:         $curl->set(CURLOPT_RETURNTRANSFER, true);
1061:         $response = $curl->exec();
1062: 
1063:         if ($response->statusCode === 404) return null;
1064:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1065: 
1066:         $metadata = $metadataCatcher->getMetadata();
1067:         return array($metadata, $response->body);
1068:     }
1069: 
1070:     /**
1071:      * Copies a file or folder to a new location
1072:      *
1073:      * @param string $fromPath
1074:      *    The Dropbox path of the file or folder you want to copy (UTF-8).
1075:      *
1076:      * @param string $toPath
1077:      *    The destination Dropbox path (UTF-8).
1078:      *
1079:      * @return mixed
1080:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
1081:      *    object</a> for the new file or folder.
1082:      *
1083:      * @throws Exception
1084:      */
1085:     function copy($fromPath, $toPath)
1086:     {
1087:         Path::checkArg("fromPath", $fromPath);
1088:         Path::checkArgNonRoot("toPath", $toPath);
1089: 
1090:         $response = $this->doPost(
1091:             $this->apiHost,
1092:             "1/fileops/copy",
1093:             array(
1094:                 "root" => $this->root,
1095:                 "from_path" => $fromPath,
1096:                 "to_path" => $toPath,
1097:             ));
1098: 
1099:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1100: 
1101:         return RequestUtil::parseResponseJson($response->body);
1102:     }
1103: 
1104:     /**
1105:      * Creates a file or folder based on an existing copy ref (possibly from a different Dropbox
1106:      * account).
1107:      *
1108:      * @param string $copyRef
1109:      *    A copy ref obtained via the {@link createCopyRef()} call.
1110:      *
1111:      * @param string $toPath
1112:      *    The Dropbox path you want to copy the file or folder to (UTF-8).
1113:      *
1114:      * @return mixed
1115:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
1116:      *    object</a> for the new file or folder.
1117:      *
1118:      * @throws Exception
1119:      */
1120:     function copyFromCopyRef($copyRef, $toPath)
1121:     {
1122:         Checker::argStringNonEmpty("copyRef", $copyRef);
1123:         Path::checkArgNonRoot("toPath", $toPath);
1124: 
1125:         $response = $this->doPost(
1126:             $this->apiHost,
1127:             "1/fileops/copy",
1128:             array(
1129:                 "root" => $this->root,
1130:                 "from_copy_ref" => $copyRef,
1131:                 "to_path" => $toPath,
1132:             )
1133:         );
1134: 
1135:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1136: 
1137:         return RequestUtil::parseResponseJson($response->body);
1138:     }
1139: 
1140:     /**
1141:      * Creates a folder.
1142:      *
1143:      * @param string $path
1144:      *    The Dropbox path at which to create the folder (UTF-8).
1145:      *
1146:      * @return array|null
1147:      *    If successful, you'll get back the
1148:      *    <a href="https://www.dropbox.com/developers/core/api#metadata-details>metadata object</a>
1149:      *    for the newly-created folder.  If not successful, you'll get <code>null</code>.
1150:      *
1151:      * @throws Exception
1152:      */
1153:     function createFolder($path)
1154:     {
1155:         Path::checkArgNonRoot("path", $path);
1156: 
1157:         $response = $this->doPost(
1158:             $this->apiHost,
1159:             "1/fileops/create_folder",
1160:             array(
1161:                 "root" => $this->root,
1162:                 "path" => $path,
1163:             ));
1164: 
1165:         if ($response->statusCode === 403) return null;
1166:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1167: 
1168:         return RequestUtil::parseResponseJson($response->body);
1169:     }
1170: 
1171:     /**
1172:      * Deletes a file or folder
1173:      *
1174:      * See <a href="https://www.dropbox.com/developers/core/api#fileops-delete">/fileops/delete</a>.
1175:      *
1176:      * @param string $path
1177:      *    The Dropbox path of the file or folder to delete (UTF-8).
1178:      *
1179:      * @return mixed
1180:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
1181:      *    object</a> for the deleted file or folder.
1182:      *
1183:      * @throws Exception
1184:      */
1185:     function delete($path)
1186:     {
1187:         Path::checkArgNonRoot("path", $path);
1188: 
1189:         $response = $this->doPost(
1190:             $this->apiHost,
1191:             "1/fileops/delete",
1192:             array(
1193:                 "root" => $this->root,
1194:                 "path" => $path,
1195:             ));
1196: 
1197:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1198: 
1199:         return RequestUtil::parseResponseJson($response->body);
1200:     }
1201: 
1202:     /**
1203:      * Moves a file or folder to a new location.
1204:      *
1205:      * See <a href="https://www.dropbox.com/developers/core/api#fileops-move">/fileops/move</a>.
1206:      *
1207:      * @param string $fromPath
1208:      *    The source Dropbox path (UTF-8).
1209:      *
1210:      * @param string $toPath
1211:      *    The destination Dropbox path (UTF-8).
1212:      *
1213:      * @return mixed
1214:      *    The <a href="https://www.dropbox.com/developers/core/api#metadata-details">metadata
1215:      *    object</a> for the destination file or folder.
1216:      *
1217:      * @throws Exception
1218:      */
1219:     function move($fromPath, $toPath)
1220:     {
1221:         Path::checkArgNonRoot("fromPath", $fromPath);
1222:         Path::checkArgNonRoot("toPath", $toPath);
1223: 
1224:         $response = $this->doPost(
1225:             $this->apiHost,
1226:             "1/fileops/move",
1227:             array(
1228:                 "root" => $this->root,
1229:                 "from_path" => $fromPath,
1230:                 "to_path" => $toPath,
1231:             ));
1232: 
1233:         if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
1234: 
1235:         return RequestUtil::parseResponseJson($response->body);
1236:     }
1237: 
1238:     /**
1239:      * @param string $host
1240:      * @param string $path
1241:      * @param array|null $params
1242:      * @return HttpResponse
1243:      *
1244:      * @throws Exception
1245:      */
1246:     private function doGet($host, $path, $params = null)
1247:     {
1248:         Checker::argString("host", $host);
1249:         Checker::argString("path", $path);
1250:         return RequestUtil::doGet($this->config, $this->accessToken, $host, $path, $params);
1251:     }
1252: 
1253:     /**
1254:      * @param string $host
1255:      * @param string $path
1256:      * @param array|null $params
1257:      * @return HttpResponse
1258:      *
1259:      * @throws Exception
1260:      */
1261:     private function doPost($host, $path, $params = null)
1262:     {
1263:         Checker::argString("host", $host);
1264:         Checker::argString("path", $path);
1265:         return RequestUtil::doPost($this->config, $this->accessToken, $host, $path, $params);
1266:     }
1267: 
1268:     /**
1269:      * @param string $url
1270:      * @return Curl
1271:      */
1272:     private function mkCurl($url)
1273:     {
1274:         return RequestUtil::mkCurl($this->config, $url, $this->accessToken);
1275:     }
1276: 
1277:     /**
1278:      * Parses date/time strings returned by the Dropbox API.  The Dropbox API returns date/times
1279:      * formatted like: <code>"Sat, 21 Aug 2010 22:31:20 +0000"</code>.
1280:      *
1281:      * @param string $apiDateTimeString
1282:      *    A date/time string returned by the API.
1283:      *
1284:      * @return \DateTime
1285:      *    A standard PHP <code>\DateTime</code> instance.
1286:      *
1287:      * @throws Exception_BadResponse
1288:      *    Thrown if <code>$apiDateTimeString</code> isn't correctly formatted.
1289:      */
1290:     static function parseDateTime($apiDateTimeString)
1291:     {
1292:         $dt = \DateTime::createFromFormat(self::$dateTimeFormat, $apiDateTimeString);
1293:         if ($dt === false) throw new Exception_BadResponse(
1294:             "Bad date/time from server: ".self::q($apiDateTimeString));
1295:         return $dt;
1296:     }
1297: 
1298:     private static $dateTimeFormat = "D, d M Y H:i:s T";
1299: 
1300:     private static function q($object) { return var_export($object, true); }
1301: 
1302:     private static function getField($j, $fieldName)
1303:     {
1304:         if (!array_key_exists($fieldName, $j)) throw new Exception_BadResponse(
1305:             "missing field \"$fieldName\": $body");
1306:         return $j[$fieldName];
1307:     }
1308: }
1309: 
Dropbox SDK for PHP API documentation generated by ApiGen 2.8.0