class BackupMigrateDropboxAPI in Backup and Migrate Dropbox 7.2
Same name and namespace in other branches
- 7.3 backup_migrate_dropbox.dropbox_api.inc \BackupMigrateDropboxAPI
- 7 backup_migrate_dropbox.dropbox_api.inc \BackupMigrateDropboxAPI
@file backup_migrate_dropbox.api.inc
Dropbox api.
Hierarchy
- class \BackupMigrateDropboxAPI
Expanded class hierarchy of BackupMigrateDropboxAPI
File
- ./
backup_migrate_dropbox.dropbox_api.inc, line 8 - backup_migrate_dropbox.api.inc
View source
class BackupMigrateDropboxAPI {
private $token;
private $upload_session = array();
public function setToken($token) {
$this->token = $token;
}
/**
* Returns the contents of a Dropbox folder.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder Dropbox API /list_folder}
*
* @param string $folder
*
* @return object[]
* A list of file metadata of the files in the folder.
*
* @throws \RuntimeException
*/
public function list_folder($folder) {
// Note, I once got this message: Dropbox error: Error in call to API
// function "files/list_folder": request body: path: Specify the root folder
// as an empty string rather than as "/". So we'handle that case here.
if ($folder === '/') {
$folder = '';
}
// Simple listing: using Dropbox defaults:
// - Not recursive.
// - No deleted files.
// - Include mounted files (the app folder is a mounted folder).
$parameters = array(
'path' => $folder,
'include_non_downloadable_files' => TRUE,
);
$response = $this
->sendMessage('api', 'files/list_folder', $parameters);
$files = $response->entries;
while ($response->has_more) {
$parameters = [
'cursor' => $response->cursor,
];
$response = $this
->sendMessage('api', 'files/list_folder/continue', $parameters);
$files = array_merge($files, $response->entries);
}
return $files;
}
/**
* Creates a folder on Dropbox.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder}
*
* @param string $folder
* The folder to create.
*
* @return object[]
* A list of folder metadata for the created folder.
*
* @throws \RuntimeException
* The folder could not be created. If that is because it already exists,
* the exception message will contain something like
* '... path/conflict/folder/.. ...'.
*/
public function create_folder($folder) {
if ($folder[0] !== '/') {
$folder = '/' . $folder;
}
$parameters = array(
'path' => $folder,
'autorename' => FALSE,
);
return $this
->sendMessage('api', 'files/create_folder', $parameters);
}
/**
* Downloads the file from the given Dropbox $path.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-download Dropbox API /download}
*
* @param string $path
* Path of file to download.
* @return string
* The contents of the requested file.
*
* @throws \RuntimeException
*/
public function file_download($path) {
$parameters = array(
'path' => $path,
);
return $this
->sendMessage('content', 'files/download', $parameters);
}
/**
* Uploads a file to the given path.
*
* If the upload is larger then:
* - what Dropbox can handle per request.
* - or the internal memory available to PHP
* The upload is split into multiple smaller chunks, otherwise it is uploaded
* in 1 part.
*
* @param string $file
* Name of local file to upload its contents from.
* @param string $path
* Path on Dropbox (including the file name) to upload the file contents to.
*
* @return object
* The json decoded response.
*
* @throws \RuntimeException
*/
public function file_upload($file, $path) {
// Cut PHP memory limit by 10% to allow for other in memory data.
$php_memory_limit = intval($this
->byte_size(ini_get('memory_limit')) * 0.9);
// Dropbox currently has a 150M upload limit per transaction.
$dropbox_upload_limit = $this
->byte_size('150M');
// For testing or in case the 10% leeway isn't enough allow a smaller upload
// limit as an advanced setting. This variable has no ui but can be set with
// drush or through the variable module.
$manual_upload_limit = $this
->byte_size(variable_get('backup_migrate_dropbox_upload_limit', '150M'));
// Use the smallest value for the max file size.
$max_file_size = min($php_memory_limit, $dropbox_upload_limit, $manual_upload_limit);
// File.
$file_size = filesize($file);
// If the file size is greater than
if ($file_size > $max_file_size) {
// Open file.
$file_handle = fopen($file, 'rb');
if (!$file_handle) {
throw new RuntimeException('Cannot open backup file (1).');
}
// Start.
$content = fread($file_handle, $max_file_size);
if (!$content) {
throw new RuntimeException('Cannot read backup file (2).');
}
$this
->_file_upload_session_start($content);
// Append.
while (!feof($file_handle)) {
// Get content.
$content = fread($file_handle, $max_file_size);
if (!$content) {
throw new RuntimeException('Cannot read backup file (3).');
}
$this
->_file_upload_session_append($content);
}
// Finish.
$result = $this
->_file_upload_session_finish($path);
}
else {
$content = file_get_contents($file);
if (!$content) {
throw new RuntimeException('Cannot open backup file (4).');
}
$result = $this
->_file_upload_upload($path, $content);
}
return $result;
}
/**
* Starts a multi-request upload.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start Dropbox API /upload_session/start}
*
* @param string $content
*
* @return object
* The json decoded response.
*
* @throws \RuntimeException
*/
private function _file_upload_session_start($content) {
$result = $this
->sendMessage('content', 'files/upload_session/start', array(), $content);
if (!isset($result->session_id)) {
throw new RuntimeException('No session id returned.');
}
$this->upload_session['session_id'] = $result->session_id;
$this->upload_session['offset'] = strlen($content);
return $result;
}
/**
* Uploads 1 part of a multi-request upload.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append Dropbox API /upload_session/append}
* @param string $content
*
* @return object
* The json decoded response.
*
* @throws \RuntimeException
*/
private function _file_upload_session_append($content) {
$parameters = array(
'cursor' => $this->upload_session,
);
$result = $this
->sendMessage('content', 'files/upload_session/append_v2', $parameters, $content);
$this->upload_session['offset'] += strlen($content);
return $result;
}
/**
* Ends a multi-request upload.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish Dropbox API /upload_session/finish}
*
* @param string $path
*
* @return object
* The json decoded response.
*
* @throws \RuntimeException
*/
private function _file_upload_session_finish($path) {
$parameters = array(
'cursor' => $this->upload_session,
'commit' => array(
'path' => $path,
'mode' => 'add',
'autorename' => TRUE,
'mute' => TRUE,
),
);
return $this
->sendMessage('content', 'files/upload_session/finish', $parameters);
}
/**
* Uploads the $contents of a file (with 1 request) to the indicated $path.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-upload Dropbox API /upload}
*
* @param string $path
* @param string $content
*
* @return object
* The json decoded response.
*
* @throws \RuntimeException
*/
private function _file_upload_upload($path, $content) {
// Simple upload.
$parameters = array(
'path' => $path,
'mode' => 'add',
'autorename' => TRUE,
'mute' => FALSE,
);
return $this
->sendMessage('content', 'files/upload', $parameters, $content);
}
/**
* Deletes the file at the given $path.
*
* {@link https://www.dropbox.com/developers/documentation/http/documentation#files-delete Dropbox API /delete}
*
* @param string $path
*
* @return object
* The json decoded response.
*
* @throws \RuntimeException
*/
public function file_delete($path) {
$parameters = array(
'path' => $path,
);
return $this
->sendMessage('api', 'files/delete_v2', $parameters);
}
/**
* Sends a request to Dropbox and returns the response.
*
* @param string $endpointType
* This type determines the url to use and how to process parameters. It can
* be one of:
* - 'api'
* - 'content'
* More info about end points can be found at:
* https://www.dropbox.com/developers/documentation/http/documentation#formats
* @param string $command
* @param array|null $parameters
* @param string|null $content
* File contents for the request.
*
* @return object
* The json decoded response.
*
* @throws \RuntimeException
*/
private function sendMessage($endpointType, $command, $parameters = null, $content = null) {
// Prepare the request: url, headers and the body
$url = "https://{$endpointType}.dropboxapi.com/2/{$command}";
$headers = array();
$headers[] = 'Content-type: ' . ($endpointType === 'api' ? 'application/json; charset=utf-8' : 'application/octet-stream');
$headers[] = 'Authorization: Bearer ' . $this->token;
$headers[] = 'Accept: application/json, application/octet-stream';
// Content end points may have real content in the body and therefore expect
// the parameters in the 'Dropbox-API-Arg' header.
if ($endpointType === 'content' && !empty($parameters)) {
$headers[] = 'Dropbox-API-Arg: ' . json_encode($parameters);
}
// Api endpoints expect the parameters in the body as a json encoded string,
// otherwise any passed in $contents is placed in the body.
$body = $endpointType === 'api' && !empty($parameters) ? json_encode($parameters) : $content;
$response = $this
->sendHttpRequest($url, $headers, $body);
if ($this
->isJsonResponse($endpointType, $command)) {
$result = json_decode($response, FALSE);
if ($result === NULL) {
// No json was returned, but (probably) a plain error message. E.g, I
// once got the string 'Incorrect host for API function
// "files/list_folder". You must issue the request to
// "api.dropboxapi.com".' as response.
throw new RuntimeException("Dropbox error: {$response}");
}
elseif (isset($result->error_summary)) {
throw new RuntimeException("Dropbox error: {$result->error_summary}");
}
}
else {
// Plain result: no decoding needed.
$result = $response;
}
return $result;
}
/**
* Executes a curl request.
*
* @param string $url
* @param array $headers
* @param string $body
*
* @return string
* The response.
*
* @throws \RuntimeException
* On any error at the curl level, an exception will be thrown.
*/
private function sendHttpRequest($url, $headers, $body) {
$options = [
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => TRUE,
CURLOPT_RETURNTRANSFER => TRUE,
];
if (!empty($body)) {
$options[CURLOPT_POSTFIELDS] = $body;
}
$request = curl_init();
curl_setopt_array($request, $options);
$response = curl_exec($request);
if ($response === FALSE) {
$response_code = curl_getinfo($request, CURLINFO_HTTP_CODE);
if (curl_error($request)) {
throw new RuntimeException('Curl error: ' . curl_error($request));
}
elseif ($response_code >= 500) {
throw new RuntimeException('Dropbox server error. Try later or check status.dropbox.com for outages.');
}
else {
throw new RuntimeException("Error: http response code: {$response_code}");
}
}
curl_close($request);
return $response;
}
/**
* Returns whether the response should be a json string or file contents.
*
* - 'api' endpoints always return json.
* - 'content' endpoints may return:
* - File contents (files/download).
* - A json encoded object.
* - Nothing (files/upload_session/append_v2).
*
* @param string $endpointType
* @param string $command
*
* @return bool
* True if the response is expected to be a json string, false otherwise.
*/
private function isJsonResponse($endpointType, $command) {
return $endpointType === 'api' || !in_array($command, [
'files/download',
'files/upload_session/append_v2',
]);
}
// Pulled from Stack Overflow: http://stackoverflow.com/q/1336581/819883.
private function byte_size($byteString) {
preg_match('/^\\s*([0-9.]+)\\s*([KMGT])B?\\s*$/i', $byteString, $matches);
if (!(count($matches) >= 3)) {
return 0;
}
$num = (double) $matches[1];
switch (strtoupper($matches[2])) {
/** @noinspection PhpMissingBreakStatementInspection */
case 'T':
$num *= DRUPAL_KILOBYTE;
/** @noinspection PhpMissingBreakStatementInspection */
case 'G':
$num *= DRUPAL_KILOBYTE;
/** @noinspection PhpMissingBreakStatementInspection */
case 'M':
$num *= DRUPAL_KILOBYTE;
case 'K':
$num *= DRUPAL_KILOBYTE;
}
return intval($num);
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
BackupMigrateDropboxAPI:: |
private | property | ||
BackupMigrateDropboxAPI:: |
private | property | ||
BackupMigrateDropboxAPI:: |
private | function | ||
BackupMigrateDropboxAPI:: |
public | function | Creates a folder on Dropbox. | |
BackupMigrateDropboxAPI:: |
public | function | Deletes the file at the given $path. | |
BackupMigrateDropboxAPI:: |
public | function | Downloads the file from the given Dropbox $path. | |
BackupMigrateDropboxAPI:: |
public | function | Uploads a file to the given path. | |
BackupMigrateDropboxAPI:: |
private | function | Returns whether the response should be a json string or file contents. | |
BackupMigrateDropboxAPI:: |
public | function | Returns the contents of a Dropbox folder. | |
BackupMigrateDropboxAPI:: |
private | function | Executes a curl request. | |
BackupMigrateDropboxAPI:: |
private | function | Sends a request to Dropbox and returns the response. | |
BackupMigrateDropboxAPI:: |
public | function | ||
BackupMigrateDropboxAPI:: |
private | function | Uploads 1 part of a multi-request upload. | |
BackupMigrateDropboxAPI:: |
private | function | Ends a multi-request upload. | |
BackupMigrateDropboxAPI:: |
private | function | Starts a multi-request upload. | |
BackupMigrateDropboxAPI:: |
private | function | Uploads the $contents of a file (with 1 request) to the indicated $path. |