Test Failed
Push — master ( 57b076...5497f6 )
by Raza
01:39
created

DropboxClient::isPipe()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Srmklive\Dropbox\Client;
4
5
use GuzzleHttp\Client as HttpClient;
6
use GuzzleHttp\Exception\ClientException as HttpClientException;
7
use GuzzleHttp\Psr7\StreamWrapper;
8
use Illuminate\Support\Collection;
9
use Srmklive\Dropbox\DropboxUploadCounter;
10
use Srmklive\Dropbox\Exceptions\BadRequest;
11
12
class DropboxClient
13
{
14
    const THUMBNAIL_FORMAT_JPEG = 'jpeg';
15
    const THUMBNAIL_FORMAT_PNG = 'png';
16
17
    const THUMBNAIL_SIZE_XS = 'w32h32';
18
    const THUMBNAIL_SIZE_S = 'w64h64';
19
    const THUMBNAIL_SIZE_M = 'w128h128';
20
    const THUMBNAIL_SIZE_L = 'w640h480';
21
    const THUMBNAIL_SIZE_XL = 'w1024h768';
22
23
    const MAX_CHUNK_SIZE = 150 * 1024 * 1024;
24
25
    /** @var \GuzzleHttp\Client */
26
    protected $client;
27
28
    /**
29
     * Dropbox OAuth access token.
30
     *
31
     * @var string
32
     */
33
    protected $accessToken;
34
35
    /**
36
     * Dropbox API v2 Url.
37
     *
38
     * @var string
39
     */
40
    protected $apiUrl;
41
42
    /**
43
     * Dropbox content API v2 url for uploading content.
44
     *
45
     * @var string
46
     */
47
    protected $apiContentUrl;
48
49
    /**
50
     * Dropbox API v2 endpoint.
51
     *
52
     * @var string
53
     */
54
    protected $apiEndpoint;
55
56
    /**
57
     * @var mixed
58
     */
59
    protected $content;
60
61
    /**
62
     * Collection containing Dropbox API request data.
63
     *
64
     * @var \Illuminate\Support\Collection
65
     */
66
    protected $request;
67
68
    /**
69
     * DropboxClient constructor.
70
     *
71 12
     * @param string             $token
72
     * @param \GuzzleHttp\Client $client
73 12
     */
74
    public function __construct($token, HttpClient $client = null)
75 12
    {
76
        $this->setAccessToken($token);
77 12
78 12
        $this->setClient($client);
79 12
80
        $this->apiUrl = 'https://api.dropboxapi.com/2/';
81
        $this->apiContentUrl = 'https://content.dropboxapi.com/2/';
82
    }
83
84
    /**
85
     * Set Http Client.
86 12
     *
87
     * @param \GuzzleHttp\Client $client
88 12
     */
89 11
    protected function setClient(HttpClient $client = null)
90 11
    {
91 1
        if ($client instanceof HttpClient) {
92
            $this->client = $client;
93 1
        } else {
94 1
            $this->client = new HttpClient([
95 1
                'headers' => [
96
                    'Authorization' => "Bearer {$this->accessToken}",
97 12
                ],
98
            ]);
99
        }
100
    }
101
102
    /**
103
     * Set Dropbox OAuth access token.
104 12
     *
105
     * @param string $token
106 12
     */
107 12
    protected function setAccessToken($token)
108
    {
109
        $this->accessToken = $token;
110
    }
111
112
    /**
113
     * Copy a file or folder to a different location in the user's Dropbox.
114
     *
115
     * If the source path is a folder all its contents will be copied.
116
     *
117
     * @param string $fromPath
118
     * @param string $toPath
119
     *
120
     * @return \Psr\Http\Message\ResponseInterface
121 1
     *
122
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy
123 1
     */
124 1 View Code Duplication
    public function copy($fromPath, $toPath)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125 1
    {
126 1
        $this->setupRequest([
127
            'from_path' => $this->normalizePath($fromPath),
128 1
            'to_path'   => $this->normalizePath($toPath),
129
        ]);
130 1
131
        $this->apiEndpoint = 'files/copy';
132
133
        return $this->doDropboxApiRequest();
134
    }
135
136
    /**
137
     * Create a folder at a given path.
138
     *
139
     * @param string $path
140
     *
141
     * @return \Psr\Http\Message\ResponseInterface
142 1
     *
143
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder
144 1
     */
145 1
    public function createFolder($path)
146 1
    {
147
        $this->setupRequest([
148 1
            'path' => $this->normalizePath($path),
149
        ]);
150 1
151 1
        $this->apiEndpoint = 'files/create_folder';
152
153 1
        $response = $this->doDropboxApiRequest();
154
        $response['.tag'] = 'folder';
155
156
        return $response;
157
    }
158
159
    /**
160
     * Delete the file or folder at a given path.
161
     *
162
     * If the path is a folder, all its contents will be deleted too.
163
     * A successful response indicates that the file or folder was deleted.
164
     *
165
     * @param string $path
166
     *
167
     * @return \Psr\Http\Message\ResponseInterface
168 1
     *
169
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-delete
170 1
     */
171 1
    public function delete($path)
172 1
    {
173
        $this->setupRequest([
174 1
            'path' => $this->normalizePath($path),
175
        ]);
176 1
177
        $this->apiEndpoint = 'files/delete';
178
179
        return $this->doDropboxApiRequest();
180
    }
181
182
    /**
183
     * Download a file from a user's Dropbox.
184
     *
185
     * @param string $path
186
     *
187
     * @return resource
188 1
     *
189
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download
190 1
     */
191 1
    public function download($path)
192 1
    {
193
        $this->setupRequest([
194 1
            'path' => $this->normalizePath($path),
195
        ]);
196 1
197
        $this->apiEndpoint = 'files/download';
198 1
199
        $response = $this->doDropboxApiContentRequest();
200
201
        return StreamWrapper::getResource($response->getBody());
202
    }
203
204
    /**
205
     * Returns the metadata for a file or folder.
206
     *
207
     * Note: Metadata for the root folder is unsupported.
208
     *
209
     * @param string $path
210
     *
211
     * @return \Psr\Http\Message\ResponseInterface
212 1
     *
213
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_metadata
214 1
     */
215 1
    public function getMetaData($path)
216 1
    {
217
        $this->setupRequest([
218 1
            'path' => $this->normalizePath($path),
219
        ]);
220 1
221
        $this->apiEndpoint = 'files/get_metadata';
222
223
        return $this->doDropboxApiRequest();
224
    }
225
226
    /**
227
     * Get a temporary link to stream content of a file.
228
     *
229
     * This link will expire in four hours and afterwards you will get 410 Gone.
230
     * Content-Type of the link is determined automatically by the file's mime type.
231
     *
232
     * @param string $path
233
     *
234
     * @return string
235 1
     *
236
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
237 1
     */
238 1 View Code Duplication
    public function getTemporaryLink($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
239 1
    {
240
        $this->setupRequest([
241 1
            'path' => $this->normalizePath($path),
242
        ]);
243 1
244
        $this->apiEndpoint = 'files/get_temporary_link';
245 1
246
        $response = $this->doDropboxApiRequest();
247
248
        return $response['link'];
249
    }
250
251
    /**
252
     * Get a thumbnail for an image.
253
     *
254
     * This method currently supports files with the following file extensions:
255
     * jpg, jpeg, png, tiff, tif, gif and bmp.
256
     *
257
     * Photos that are larger than 20MB in size won't be converted to a thumbnail.
258
     *
259
     * @param string $path
260
     * @param string $format
261
     * @param string $size
262 1
     *
263
     * @return string
264 1
     */
265 1
    public function getThumbnail($path, $format = 'jpeg', $size = 'w64h64')
266 1
    {
267 1
        $this->setupRequest([
268 1
            'path'   => $this->normalizePath($path),
269
            'format' => $format,
270 1
            'size'   => $size,
271
        ]);
272 1
273
        $this->apiEndpoint = 'files/get_thumbnail';
274 1
275
        $response = $this->doDropboxApiContentRequest();
276
277
        return (string) $response->getBody();
278
    }
279
280
    /**
281
     * Starts returning the contents of a folder.
282
     *
283
     * If the result's ListFolderResult.has_more field is true, call
284
     * list_folder/continue with the returned ListFolderResult.cursor to retrieve more entries.
285
     *
286
     * Note: auth.RateLimitError may be returned if multiple list_folder or list_folder/continue calls
287
     * with same parameters are made simultaneously by same API app for same user. If your app implements
288
     * retry logic, please hold off the retry until the previous request finishes.
289
     *
290
     * @param string $path
291
     * @param bool   $recursive
292
     *
293
     * @return \Psr\Http\Message\ResponseInterface
294 1
     *
295
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
296 1
     */
297 1 View Code Duplication
    public function listFolder($path = '', $recursive = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298 1
    {
299 1
        $this->setupRequest([
300
            'path'      => $this->normalizePath($path),
301 1
            'recursive' => $recursive,
302
        ]);
303 1
304
        $this->apiEndpoint = 'files/list_folder';
305
306
        return $this->doDropboxApiRequest();
307
    }
308
309
    /**
310
     * Once a cursor has been retrieved from list_folder, use this to paginate through all files and
311
     * retrieve updates to the folder, following the same rules as documented for list_folder.
312
     *
313
     * @param string $cursor
314
     *
315
     * @return \Psr\Http\Message\ResponseInterface
316 1
     *
317
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
318 1
     */
319 1
    public function listFolderContinue($cursor = '')
320 1
    {
321
        $this->setupRequest([
322 1
            'cursor' => $cursor,
323
        ]);
324 1
325
        $this->apiEndpoint = 'files/list_folder/continue';
326
327
        return $this->doDropboxApiRequest();
328
    }
329
330
    /**
331
     * Move a file or folder to a different location in the user's Dropbox.
332
     *
333
     * If the source path is a folder all its contents will be moved.
334
     *
335
     * @param string $fromPath
336
     * @param string $toPath
337
     *
338
     * @return \Psr\Http\Message\ResponseInterface
339 1
     *
340
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-move
341 1
     */
342 1 View Code Duplication
    public function move($fromPath, $toPath)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
343 1
    {
344 1
        $this->setupRequest([
345
            'from_path' => $this->normalizePath($fromPath),
346 1
            'to_path'   => $this->normalizePath($toPath),
347
        ]);
348 1
349
        $this->apiEndpoint = 'files/move_v2';
350
351
        return $this->doDropboxApiRequest();
352
    }
353
354
    /**
355
     * The file should be uploaded in chunks if it size exceeds the 150 MB threshold
356
     * or if the resource size could not be determined (eg. a popen() stream).
357
     *
358
     * @param string|resource $contents
359
     *
360
     * @return bool
361
     */
362
    protected function shouldUploadChunk($contents)
363
    {
364 1
        $size = is_string($contents) ? strlen($contents) : fstat($contents)['size'];
365
366 1
        if ($this->isPipe($contents)) {
367 1
            return true;
368 1
        }
369 1
370
        if ($size === null) {
371 1
            return true;
372
        }
373 1
374
        return $size > static::MAX_CHUNK_SIZE;
375 1
    }
376
377 1
    /**
378 1
     * Check if the contents is a pipe stream (not seekable, no size defined).
379
     *
380 1
     * @param string|resource $contents
381
     *
382
     * @return bool
383
     */
384
    protected function isPipe($contents)
385
    {
386
        return is_resource($contents) ? (fstat($contents)['mode'] & 010000) != 0 : false;
387
    }
388
389
    /**
390
     * Create a new file with the contents provided in the request.
391
     *
392
     * Do not use this to upload a file larger than 150 MB. Instead, create an upload session with upload_session/start.
393
     *
394
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
395
     *
396
     * @param string          $path
397
     * @param string|resource $contents
398
     * @param string|array    $mode
399
     *
400
     * @return array
401
     */
402
    public function upload($path, $contents, $mode = 'add')
403
    {
404
        if ($this->shouldUploadChunk($contents)) {
405
            return $this->uploadChunk($path, $contents, $mode);
0 ignored issues
show
Bug introduced by
It seems like $mode defined by parameter $mode on line 402 can also be of type array; however, Srmklive\Dropbox\Client\...oxClient::uploadChunk() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
406
        }
407
408
        $this->setupRequest([
409
            'path' => $this->normalizePath($path),
410
            'mode' => $mode,
411
        ]);
412
413
        $this->content = $contents;
414
415
        $this->apiEndpoint = 'files/upload';
416 11
417
        $response = $this->doDropboxApiContentRequest();
418 11
419 11
        $metadata = json_decode($response->getBody(), true);
420
        $metadata['.tag'] = 'file';
421
422
        return $metadata;
423
    }
424
425
    /**
426
     * Upload file split in chunks. This allows uploading large files, since
427
     * Dropbox API v2 limits the content size to 150MB.
428 8
     *
429
     * The chunk size will affect directly the memory usage, so be careful.
430
     * Large chunks tends to speed up the upload, while smaller optimizes memory usage.
431 8
     *
432 8
     * @param string          $path
433 8
     * @param string|resource $contents
434 8
     * @param string          $mode
435
     * @param int             $chunkSize
436
     *
437
     * @return array
438 7
     */
439
    public function uploadChunk(string $path, $contents, $mode = 'add', $chunkSize = null)
440
    {
441
        $chunkSize = $chunkSize ?? static::MAX_CHUNK_SIZE;
442
        $stream = $contents;
443
444
        // This method relies on resources, so we need to convert strings to resource
445
        if (is_string($contents)) {
446 3
            $stream = fopen('php://memory', 'r+');
447
            fwrite($stream, $contents);
448
            rewind($stream);
449 3
        }
450 3
451 3
        $data = self::readChunk($stream, $chunkSize);
0 ignored issues
show
Bug introduced by
It seems like $stream defined by $contents on line 442 can also be of type string; however, Srmklive\Dropbox\Client\DropboxClient::readChunk() does only seem to accept resource, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
452 3
        $cursor = null;
453
454 3
        while (! ((strlen($data) < $chunkSize) || feof($stream))) {
455 1
            // Start upload session on first iteration, then just append on subsequent iterations
456 1
            $cursor = isset($cursor) ? $this->appendContentToUploadSession($data, $cursor) : $this->startUploadSession($data);
457
            $data = self::readChunk($stream, $chunkSize);
0 ignored issues
show
Bug introduced by
It seems like $stream defined by $contents on line 442 can also be of type string; however, Srmklive\Dropbox\Client\DropboxClient::readChunk() does only seem to accept resource, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
458 3
        }
459
460
        // If there's no cursor here, our stream is small enough to a single request
461
        if (! isset($cursor)) {
462
            $cursor = $this->startUploadSession($data);
463
            $data = '';
464
        }
465
466
        return $this->finishUploadSession($data, $cursor, $path, $mode);
467
    }
468 3
469
    /**
470
     * Upload sessions allow you to upload a single file in one or more requests,
471 3
     * for example where the size of the file is greater than 150 MB.
472 3
     * This call starts a new upload session with the given data.
473 3
     *
474 3
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
475 3
     *
476
     * @param string $contents
477
     * @param bool   $close
478
     *
479 3
     * @return \Srmklive\Dropbox\DropboxUploadCounter
480
     */
481
    public function startUploadSession($contents, bool $close = false)
482
    {
483
        $this->setupRequest(
484
            compact('close')
485
        );
486
487
        $this->apiEndpoint = 'files/upload_session/start';
488
489 10
        $this->content = $contents;
490
491 10
        $response = json_decode(
492
            $this->doDropboxApiContentRequest()->getBody(),
493 10
            true
494
        );
495
496
        return new DropboxUploadCounter($response['session_id'], strlen($contents));
497
    }
498
499
500
    /**
501
     * Append more data to an upload session.
502
     * When the parameter close is set, this call will close the session.
503
     * A single request should not upload more than 150 MB.
504
     *
505
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
506
     *
507
     * @param string               $contents
508
     * @param DropboxUploadCounter $cursor
509
     * @param bool                 $close
510
     *
511
     * @return \Srmklive\Dropbox\DropboxUploadCounter
512
     */
513
    public function appendContentToUploadSession($contents, DropboxUploadCounter $cursor, bool $close = false)
514
    {
515
        $this->setupRequest(compact('cursor', 'close'));
516
517
        $this->apiEndpoint = 'files/upload_session/append_v2';
518
519
        $this->content = $contents;
520
521
        $this->doDropboxApiContentRequest()->getBody();
522
523
        $cursor->offset += strlen($contents);
524
525
        return $cursor;
526
    }
527
528
    /**
529
     * Finish an upload session and save the uploaded data to the given file path.
530
     * A single request should not upload more than 150 MB.
531
     *
532
     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
533
     *
534
     * @param string                                 $contents
535
     * @param \Srmklive\Dropbox\DropboxUploadCounter $cursor
536
     * @param string                                 $path
537
     * @param string|array                           $mode
538
     * @param bool                                   $autorename
539
     * @param bool                                   $mute
540
     *
541
     * @return array
542
     */
543
    public function finishUploadSession($contents, DropboxUploadCounter $cursor, string $path, $mode = 'add', $autorename = false, $mute = false)
544
    {
545
        $arguments = compact('cursor');
546
        $arguments['commit'] = compact('path', 'mode', 'autorename', 'mute');
547
548
        $this->setupRequest($arguments);
549
550
        $this->apiEndpoint = 'files/upload_session/finish';
551
552
        $this->content = $contents;
553
554
        $response = $this->doDropboxApiContentRequest();
555
556
        $metadata = json_decode($response->getBody(), true);
557
558
        $metadata['.tag'] = 'file';
559
560
        return $metadata;
561
    }
562
563
    /**
564
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
565
     * from network streams).  This function repeatedly calls fread until the requested number of
566
     * bytes have been read or we've reached EOF.
567
     *
568
     * @param resource $stream
569
     * @param int      $chunkSize
570
     *
571
     * @throws \Exception
572
     * @return string
573
     */
574
    protected static function readChunk($stream, int $chunkSize)
575
    {
576
        $chunk = '';
577
        while (! feof($stream) && $chunkSize > 0) {
578
            $part = fread($stream, $chunkSize);
579
580
            if ($part === false) {
581
                throw new \Exception('Error reading from $stream.');
582
            }
583
584
            $chunk .= $part;
585
            $chunkSize -= strlen($part);
586
        }
587
588
        return $chunk;
589
    }
590
591
    /**
592
     * Get Account Info for current authenticated user.
593
     *
594
     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
595
     *
596
     * @return \Psr\Http\Message\ResponseInterface
597
     */
598
    public function getAccountInfo()
599
    {
600
        $this->apiEndpoint = 'users/get_current_account';
601
602
        return $this->doDropboxApiRequest();
603
    }
604
605
    /**
606
     * Revoke current access token.
607
     *
608
     * @link https://www.dropbox.com/developers/documentation/http/documentation#auth-token-revoke
609
     *
610
     * @return \Psr\Http\Message\ResponseInterface
611
     */
612
    public function revokeToken()
613
    {
614
        $this->apiEndpoint = 'auth/token/revoke';
615
616
        return $this->doDropboxApiRequest();
617
    }
618
619
    /**
620
     * Set Dropbox API request data.
621
     *
622
     * @param array $request
623
     */
624
    protected function setupRequest($request)
625
    {
626
        $this->request = new Collection($request);
627
    }
628
629
    /**
630
     * Perform Dropbox API request.
631
     *
632
     * @throws \Exception
633
     *
634
     * @return \Psr\Http\Message\ResponseInterface
635
     */
636
    protected function doDropboxApiRequest()
637
    {
638
        $request = empty($this->request) ? [] : ['json' => $this->request->toArray()];
639
640
        try {
641
            $response = $this->client->post("{$this->apiUrl}{$this->apiEndpoint}", $request);
642
        } catch (HttpClientException $exception) {
643
            throw $this->determineException($exception);
644
        }
645
646
        return json_decode($response->getBody(), true);
647
    }
648
649
    /**
650
     * Setup headers for Dropbox API request.
651
     *
652
     * @return array
653
     */
654
    protected function setupDropboxHeaders()
655
    {
656
        $headers = [
657
            'Dropbox-API-Arg' => json_encode(
658
                $this->request->toArray()
659
            ),
660
        ];
661
662
        if (!empty($this->content)) {
663
            $headers['Content-Type'] = 'application/octet-stream';
664
        }
665
666
        return $headers;
667
    }
668
669
    /**
670
     * Perform Dropbox API request.
671
     *
672
     * @throws \Exception
673
     *
674
     * @return \Psr\Http\Message\ResponseInterface
675
     */
676
    protected function doDropboxApiContentRequest()
677
    {
678
        try {
679
            $response = $this->client->post("{$this->apiContentUrl}{$this->apiEndpoint}", [
680
                'headers' => $this->setupDropboxHeaders(),
681
                'body'    => !empty($this->content) ? $this->content : '',
682
            ]);
683
        } catch (HttpClientException $exception) {
684
            throw $this->determineException($exception);
685
        }
686
687
        return $response;
688
    }
689
690
    /**
691
     * Normalize path.
692
     *
693
     * @param string $path
694
     *
695
     * @return string
696
     */
697
    protected function normalizePath($path)
698
    {
699
        $path = (trim($path, '/') === '') ? '' : '/'.$path;
700
701
        return str_replace('//', '/', $path);
702
    }
703
704
    /**
705
     * Catch Dropbox API request exception.
706
     *
707
     * @param HttpClientException $exception
708
     *
709
     * @return \Exception
710
     */
711
    protected function determineException(HttpClientException $exception)
712
    {
713
        if (!empty($exception->getResponse()) && in_array($exception->getResponse()->getStatusCode(), [400, 409])) {
714
            return new BadRequest($exception->getResponse());
715
        }
716
717
        return $exception;
718
    }
719
}
720