1<?php
2namespace Kunnu\Dropbox;
3
4use Kunnu\Dropbox\Models\File;
5use Kunnu\Dropbox\Models\Account;
6use Kunnu\Dropbox\Models\Thumbnail;
7use Kunnu\Dropbox\Models\AccountList;
8use Kunnu\Dropbox\Models\ModelFactory;
9use Kunnu\Dropbox\Models\FileMetadata;
10use Kunnu\Dropbox\Models\CopyReference;
11use Kunnu\Dropbox\Models\FolderMetadata;
12use Kunnu\Dropbox\Models\ModelCollection;
13use Kunnu\Dropbox\Authentication\OAuth2Client;
14use Kunnu\Dropbox\Store\PersistentDataStoreFactory;
15use Kunnu\Dropbox\Authentication\DropboxAuthHelper;
16use Kunnu\Dropbox\Exceptions\DropboxClientException;
17use Kunnu\Dropbox\Security\RandomStringGeneratorFactory;
18use Kunnu\Dropbox\Http\Clients\DropboxHttpClientFactory;
19
20/**
21 * Dropbox
22 */
23class Dropbox
24{
25    /**
26     * Uploading a file with the 'uploadFile' method, with the file's
27     * size less than this value (~8 MB), the simple `upload` method will be
28     * used, if the file size exceed this value (~8 MB), the `startUploadSession`,
29     * `appendUploadSession` & `finishUploadSession` methods will be used
30     * to upload the file in chunks.
31     *
32     * @const int
33     */
34    const AUTO_CHUNKED_UPLOAD_THRESHOLD = 8000000;
35
36    /**
37     * The Chunk Size the file will be
38     * split into and uploaded (~4 MB)
39     *
40     * @const int
41     */
42    const DEFAULT_CHUNK_SIZE = 4000000;
43
44    /**
45     * Response header containing file metadata
46     *
47     * @const string
48     */
49    const METADATA_HEADER = 'dropbox-api-result';
50
51    /**
52     * The Dropbox App
53     *
54     * @var \Kunnu\Dropbox\DropboxApp
55     */
56    protected $app;
57
58    /**
59     * OAuth2 Access Token
60     *
61     * @var string
62     */
63    protected $accessToken;
64
65    /**
66     * Dropbox Client
67     *
68     * @var \Kunnu\Dropbox\DropboxClient
69     */
70    protected $client;
71
72    /**
73     * OAuth2 Client
74     *
75     * @var \Kunnu\Dropbox\Authentication\OAuth2Client
76     */
77    protected $oAuth2Client;
78
79    /**
80     * Random String Generator
81     *
82     * @var \Kunnu\Dropbox\Security\RandomStringGeneratorInterface
83     */
84    protected $randomStringGenerator;
85
86    /**
87     * Persistent Data Store
88     *
89     * @var \Kunnu\Dropbox\Store\PersistentDataStoreInterface
90     */
91    protected $persistentDataStore;
92
93    /**
94     * Create a new Dropbox instance
95     *
96     * @param \Kunnu\Dropbox\DropboxApp
97     * @param array $config Configuration Array
98     */
99    public function __construct(DropboxApp $app, array $config = [])
100    {
101        //Configuration
102        $config = array_merge([
103            'http_client_handler' => null,
104            'random_string_generator' => null,
105            'persistent_data_store' => null
106        ], $config);
107
108        //Set the app
109        $this->app = $app;
110
111        //Set the access token
112        $this->setAccessToken($app->getAccessToken());
113
114        //Make the HTTP Client
115        $httpClient = DropboxHttpClientFactory::make($config['http_client_handler']);
116
117        //Make and Set the DropboxClient
118        $this->client = new DropboxClient($httpClient);
119
120        //Make and Set the Random String Generator
121        $this->randomStringGenerator = RandomStringGeneratorFactory::makeRandomStringGenerator($config['random_string_generator']);
122
123        //Make and Set the Persistent Data Store
124        $this->persistentDataStore = PersistentDataStoreFactory::makePersistentDataStore($config['persistent_data_store']);
125    }
126
127    /**
128     * Get Dropbox Auth Helper
129     *
130     * @return \Kunnu\Dropbox\Authentication\DropboxAuthHelper
131     */
132    public function getAuthHelper()
133    {
134        return new DropboxAuthHelper(
135            $this->getOAuth2Client(),
136            $this->getRandomStringGenerator(),
137            $this->getPersistentDataStore()
138        );
139    }
140
141    /**
142     * Get OAuth2Client
143     *
144     * @return \Kunnu\Dropbox\Authentication\OAuth2Client
145     */
146    public function getOAuth2Client()
147    {
148        if (!$this->oAuth2Client instanceof OAuth2Client) {
149            return new OAuth2Client(
150                $this->getApp(),
151                $this->getClient(),
152                $this->getRandomStringGenerator()
153            );
154        }
155
156        return $this->oAuth2Client;
157    }
158
159    /**
160     * Get the Dropbox App.
161     *
162     * @return \Kunnu\Dropbox\DropboxApp Dropbox App
163     */
164    public function getApp()
165    {
166        return $this->app;
167    }
168
169    /**
170     * Get the Client
171     *
172     * @return \Kunnu\Dropbox\DropboxClient
173     */
174    public function getClient()
175    {
176        return $this->client;
177    }
178
179    /**
180     * Get the Random String Generator
181     *
182     * @return \Kunnu\Dropbox\Security\RandomStringGeneratorInterface
183     */
184    public function getRandomStringGenerator()
185    {
186        return $this->randomStringGenerator;
187    }
188
189    /**
190     * Get Persistent Data Store
191     *
192     * @return \Kunnu\Dropbox\Store\PersistentDataStoreInterface
193     */
194    public function getPersistentDataStore()
195    {
196        return $this->persistentDataStore;
197    }
198
199    /**
200     * Get the Metadata for a file or folder
201     *
202     * @param  string $path   Path of the file or folder
203     * @param  array  $params Additional Params
204     *
205     * @return \Kunnu\Dropbox\Models\FileMetadata | \Kunnu\Dropbox\Models\FolderMetadata
206     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
207     *
208     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_metadata
209     *
210     */
211    public function getMetadata($path, array $params = [])
212    {
213        //Root folder is unsupported
214        if ($path === '/') {
215            throw new DropboxClientException("Metadata for the root folder is unsupported.");
216        }
217
218        //Set the path
219        $params['path'] = $path;
220
221        //Get File Metadata
222        $response = $this->postToAPI('/files/get_metadata', $params);
223
224        //Make and Return the Model
225        return $this->makeModelFromResponse($response);
226    }
227
228    /**
229     * Make a HTTP POST Request to the API endpoint type
230     *
231     * @param  string $endpoint    API Endpoint to send Request to
232     * @param  array  $params      Request Query Params
233     * @param  string $accessToken Access Token to send with the Request
234     *
235     * @return \Kunnu\Dropbox\DropboxResponse
236     */
237    public function postToAPI($endpoint, array $params = [], $accessToken = null)
238    {
239        return $this->sendRequest("POST", $endpoint, 'api', $params, $accessToken);
240    }
241
242    /**
243     * Make Request to the API
244     *
245     * @param  string      $method       HTTP Request Method
246     * @param  string      $endpoint     API Endpoint to send Request to
247     * @param  string      $endpointType Endpoint type ['api'|'content']
248     * @param  array       $params       Request Query Params
249     * @param  string      $accessToken  Access Token to send with the Request
250     * @param  DropboxFile $responseFile Save response to the file
251     *
252     * @return \Kunnu\Dropbox\DropboxResponse
253     *
254     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
255     */
256    public function sendRequest($method, $endpoint, $endpointType = 'api', array $params = [], $accessToken = null, DropboxFile $responseFile = null)
257    {
258        //Access Token
259        $accessToken = $this->getAccessToken() ? $this->getAccessToken() : $accessToken;
260
261        //Make a DropboxRequest object
262        $request = new DropboxRequest($method, $endpoint, $accessToken, $endpointType, $params);
263
264        //Make a DropboxResponse object if a response should be saved to the file
265        $response = $responseFile ? new DropboxResponseToFile($request, $responseFile) : null;
266
267        //Send Request through the DropboxClient
268        //Fetch and return the Response
269        return $this->getClient()->sendRequest($request, $response);
270    }
271
272    /**
273     * Get the Access Token.
274     *
275     * @return string Access Token
276     */
277    public function getAccessToken()
278    {
279        return $this->accessToken;
280    }
281
282    /**
283     * Set the Access Token.
284     *
285     * @param string $accessToken Access Token
286     *
287     * @return \Kunnu\Dropbox\Dropbox Dropbox Client
288     */
289    public function setAccessToken($accessToken)
290    {
291        $this->accessToken = $accessToken;
292
293        return $this;
294    }
295
296    /**
297     * Make Model from DropboxResponse
298     *
299     * @param  DropboxResponse $response
300     *
301     * @return \Kunnu\Dropbox\Models\ModelInterface
302     *
303     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
304     */
305    public function makeModelFromResponse(DropboxResponse $response)
306    {
307        //Get the Decoded Body
308        $body = $response->getDecodedBody();
309
310        if (is_null($body)) {
311            $body = [];
312        }
313
314        //Make and Return the Model
315        return ModelFactory::make($body);
316    }
317
318    /**
319     * Get the contents of a Folder
320     *
321     * @param  string $path   Path to the folder. Defaults to root.
322     * @param  array  $params Additional Params
323     *
324     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder
325     *
326     * @return \Kunnu\Dropbox\Models\MetadataCollection
327     */
328    public function listFolder($path = null, array $params = [])
329    {
330        //Specify the root folder as an
331        //empty string rather than as "/"
332        if ($path === '/') {
333            $path = "";
334        }
335
336        //Set the path
337        $params['path'] = $path;
338
339        //Get File Metadata
340        $response = $this->postToAPI('/files/list_folder', $params);
341
342        //Make and Return the Model
343        return $this->makeModelFromResponse($response);
344    }
345
346    /**
347     * Paginate through all files and retrieve updates to the folder,
348     * using the cursor retrieved from listFolder or listFolderContinue
349     *
350     * @param  string $cursor The cursor returned by your
351     *                        last call to listFolder or listFolderContinue
352     *
353     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-continue
354     *
355     * @return \Kunnu\Dropbox\Models\MetadataCollection
356     */
357    public function listFolderContinue($cursor)
358    {
359        $response = $this->postToAPI('/files/list_folder/continue', ['cursor' => $cursor]);
360
361        //Make and Return the Model
362        return $this->makeModelFromResponse($response);
363    }
364
365    /**
366     * Get a cursor for the folder's state.
367     *
368     * @param  string $path   Path to the folder. Defaults to root.
369     * @param  array  $params Additional Params
370     *
371     * @return string The Cursor for the folder's state
372     *
373     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
374     *
375     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder-get_latest_cursor
376     *
377     */
378    public function listFolderLatestCursor($path, array $params = [])
379    {
380        //Specify the root folder as an
381        //empty string rather than as "/"
382        if ($path === '/') {
383            $path = "";
384        }
385
386        //Set the path
387        $params['path'] = $path;
388
389        //Fetch the cursor
390        $response = $this->postToAPI('/files/list_folder/get_latest_cursor', $params);
391
392        //Retrieve the cursor
393        $body = $response->getDecodedBody();
394        $cursor = isset($body['cursor']) ? $body['cursor'] : false;
395
396        //No cursor returned
397        if (!$cursor) {
398            throw new DropboxClientException("Could not retrieve cursor. Something went wrong.");
399        }
400
401        //Return the cursor
402        return $cursor;
403    }
404
405    /**
406     * Get Revisions of a File
407     *
408     * @param  string $path   Path to the file
409     * @param  array  $params Additional Params
410     *
411     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-list_revisions
412     *
413     * @return \Kunnu\Dropbox\Models\ModelCollection
414     */
415    public function listRevisions($path, array $params = [])
416    {
417        //Set the Path
418        $params['path'] = $path;
419
420        //Fetch the Revisions
421        $response = $this->postToAPI('/files/list_revisions', $params);
422
423        //The file metadata of the entries, returned by this
424        //endpoint doesn't include a '.tag' attribute, which
425        //is used by the ModelFactory to resolve the correct
426        //model. But since we know that revisions returned
427        //are file metadata objects, we can explicitly cast
428        //them as \Kunnu\Dropbox\Models\FileMetadata manually.
429        $body = $response->getDecodedBody();
430        $entries = isset($body['entries']) ? $body['entries'] : [];
431        $processedEntries = [];
432
433        foreach ($entries as $entry) {
434            $processedEntries[] = new FileMetadata($entry);
435        }
436
437        return new ModelCollection($processedEntries);
438    }
439
440    /**
441     * Search a folder for files/folders
442     *
443     * @param  string $path   Path to search
444     * @param  string $query  Search Query
445     * @param  array  $params Additional Params
446     *
447     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-search
448     *
449     * @return \Kunnu\Dropbox\Models\SearchResults
450     */
451    public function search($path, $query, array $params = [])
452    {
453        //Specify the root folder as an
454        //empty string rather than as "/"
455        if ($path === '/') {
456            $path = "";
457        }
458
459        //Set the path and query
460        $params['path'] = $path;
461        $params['query'] = $query;
462
463        //Fetch Search Results
464        $response = $this->postToAPI('/files/search', $params);
465
466        //Make and Return the Model
467        return $this->makeModelFromResponse($response);
468    }
469
470    /**
471     * Create a folder at the given path
472     *
473     * @param  string  $path       Path to create
474     * @param  boolean $autorename Auto Rename File
475     *
476     * @return \Kunnu\Dropbox\Models\FolderMetadata
477     *
478     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
479     *
480     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder
481     *
482     */
483    public function createFolder($path, $autorename = false)
484    {
485        //Path cannot be null
486        if (is_null($path)) {
487            throw new DropboxClientException("Path cannot be null.");
488        }
489
490        //Create Folder
491        $response = $this->postToAPI('/files/create_folder', ['path' => $path, 'autorename' => $autorename]);
492
493        //Fetch the Metadata
494        $body = $response->getDecodedBody();
495
496        //Make and Return the Model
497        return new FolderMetadata($body);
498    }
499
500    /**
501     * Delete a file or folder at the given path
502     *
503     * @param  string $path Path to file/folder to delete
504     *
505     * @return \Kunnu\Dropbox\Models\DeletedMetadata|\Kunnu\Dropbox\Models\FileMetadata|\Kunnu\Dropbox\Models\FolderMetadata
506     *
507     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
508     *
509     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-delete
510     *
511     */
512    public function delete($path)
513    {
514        //Path cannot be null
515        if (is_null($path)) {
516            throw new DropboxClientException("Path cannot be null.");
517        }
518
519        //Delete
520        $response = $this->postToAPI('/files/delete', ['path' => $path]);
521
522        return $this->makeModelFromResponse($response);
523    }
524
525    /**
526     * Move a file or folder to a different location
527     *
528     * @param  string $fromPath Path to be moved
529     * @param  string $toPath   Path to be moved to
530     *
531     * @return \Kunnu\Dropbox\Models\DeletedMetadata|\Kunnu\Dropbox\Models\FileMetadata
532     *
533     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
534     *
535     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-move
536     *
537     */
538    public function move($fromPath, $toPath)
539    {
540        //From and To paths cannot be null
541        if (is_null($fromPath) || is_null($toPath)) {
542            throw new DropboxClientException("From and To paths cannot be null.");
543        }
544
545        //Response
546        $response = $this->postToAPI('/files/move', ['from_path' => $fromPath, 'to_path' => $toPath]);
547
548        //Make and Return the Model
549        return $this->makeModelFromResponse($response);
550    }
551
552    /**
553     * Copy a file or folder to a different location
554     *
555     * @param  string $fromPath Path to be copied
556     * @param  string $toPath   Path to be copied to
557     *
558     * @return \Kunnu\Dropbox\Models\DeletedMetadata|\Kunnu\Dropbox\Models\FileMetadata
559     *
560     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
561     *
562     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy
563     *
564     */
565    public function copy($fromPath, $toPath)
566    {
567        //From and To paths cannot be null
568        if (is_null($fromPath) || is_null($toPath)) {
569            throw new DropboxClientException("From and To paths cannot be null.");
570        }
571
572        //Response
573        $response = $this->postToAPI('/files/copy', ['from_path' => $fromPath, 'to_path' => $toPath]);
574
575        //Make and Return the Model
576        return $this->makeModelFromResponse($response);
577    }
578
579    /**
580     * Restore a file to the specific version
581     *
582     * @param  string $path Path to the file to restore
583     * @param  string $rev  Revision to store for the file
584     *
585     * @return \Kunnu\Dropbox\Models\DeletedMetadata|\Kunnu\Dropbox\Models\FileMetadata|\Kunnu\Dropbox\Models\FolderMetadata
586     *
587     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
588     *
589     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-restore
590     *
591     */
592    public function restore($path, $rev)
593    {
594        //Path and Revision cannot be null
595        if (is_null($path) || is_null($rev)) {
596            throw new DropboxClientException("Path and Revision cannot be null.");
597        }
598
599        //Response
600        $response = $this->postToAPI('/files/restore', ['path' => $path, 'rev' => $rev]);
601
602        //Fetch the Metadata
603        $body = $response->getDecodedBody();
604
605        //Make and Return the Model
606        return new FileMetadata($body);
607    }
608
609    /**
610     * Get Copy Reference
611     *
612     * @param  string $path Path to the file or folder to get a copy reference to
613     *
614     * @return \Kunnu\Dropbox\Models\CopyReference
615     *
616     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
617     *
618     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy_reference-get
619     *
620     */
621    public function getCopyReference($path)
622    {
623        //Path cannot be null
624        if (is_null($path)) {
625            throw new DropboxClientException("Path cannot be null.");
626        }
627
628        //Get Copy Reference
629        $response = $this->postToAPI('/files/copy_reference/get', ['path' => $path]);
630        $body = $response->getDecodedBody();
631
632        //Make and Return the Model
633        return new CopyReference($body);
634    }
635
636    /**
637     * Save Copy Reference
638     *
639     * @param  string $path          Path to the file or folder to get a copy reference to
640     * @param  string $copyReference Copy reference returned by getCopyReference
641     *
642     * @return \Kunnu\Dropbox\Models\FileMetadata|\Kunnu\Dropbox\Models\FolderMetadata
643     *
644     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
645     *
646     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-copy_reference-save
647     *
648     */
649    public function saveCopyReference($path, $copyReference)
650    {
651        //Path and Copy Reference cannot be null
652        if (is_null($path) || is_null($copyReference)) {
653            throw new DropboxClientException("Path and Copy Reference cannot be null.");
654        }
655
656        //Save Copy Reference
657        $response = $this->postToAPI('/files/copy_reference/save', ['path' => $path, 'copy_reference' => $copyReference]);
658        $body = $response->getDecodedBody();
659
660        //Response doesn't have Metadata
661        if (!isset($body['metadata']) || !is_array($body['metadata'])) {
662            throw new DropboxClientException("Invalid Response.");
663        }
664
665        //Make and return the Model
666        return ModelFactory::make($body['metadata']);
667    }
668
669    /**
670     * Get a temporary link to stream contents of a file
671     *
672     * @param  string $path Path to the file you want a temporary link to
673     *
674     * https://www.dropbox.com/developers/documentation/http/documentation#files-get_temporary_link
675     *
676     * @return \Kunnu\Dropbox\Models\TemporaryLink
677     *
678     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
679     */
680    public function getTemporaryLink($path)
681    {
682        //Path cannot be null
683        if (is_null($path)) {
684            throw new DropboxClientException("Path cannot be null.");
685        }
686
687        //Get Temporary Link
688        $response = $this->postToAPI('/files/get_temporary_link', ['path' => $path]);
689
690        //Make and Return the Model
691        return $this->makeModelFromResponse($response);
692    }
693
694    /**
695     * Save a specified URL into a file in user's Dropbox
696     *
697     * @param  string $path Path where the URL will be saved
698     * @param  string $url  URL to be saved
699     *
700     * @return string Async Job ID
701     *
702     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
703     *
704     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-save_url
705     *
706     */
707    public function saveUrl($path, $url)
708    {
709        //Path and URL cannot be null
710        if (is_null($path) || is_null($url)) {
711            throw new DropboxClientException("Path and URL cannot be null.");
712        }
713
714        //Save URL
715        $response = $this->postToAPI('/files/save_url', ['path' => $path, 'url' => $url]);
716        $body = $response->getDecodedBody();
717
718        if (!isset($body['async_job_id'])) {
719            throw new DropboxClientException("Could not retrieve Async Job ID.");
720        }
721
722        //Return the Async Job ID
723        return $body['async_job_id'];
724    }
725
726    /**
727     * Save a specified URL into a file in user's Dropbox
728     *
729     * @param $asyncJobId
730     *
731     * @return \Kunnu\Dropbox\Models\FileMetadata|string Status (failed|in_progress) or FileMetadata (if complete)
732     *
733     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
734     *
735     * @link     https://www.dropbox.com/developers/documentation/http/documentation#files-save_url-check_job_status
736     *
737     */
738    public function checkJobStatus($asyncJobId)
739    {
740        //Async Job ID cannot be null
741        if (is_null($asyncJobId)) {
742            throw new DropboxClientException("Async Job ID cannot be null.");
743        }
744
745        //Get Job Status
746        $response = $this->postToAPI('/files/save_url/check_job_status', ['async_job_id' => $asyncJobId]);
747        $body = $response->getDecodedBody();
748
749        //Status
750        $status = isset($body['.tag']) ? $body['.tag'] : '';
751
752        //If status is complete
753        if ($status === 'complete') {
754            return new FileMetadata($body);
755        }
756
757        //Return the status
758        return $status;
759    }
760
761    /**
762     * Upload a File to Dropbox
763     *
764     * @param  string|DropboxFile $dropboxFile DropboxFile object or Path to file
765     * @param  string             $path        Path to upload the file to
766     * @param  array              $params      Additional Params
767     *
768     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
769     *
770     * @return \Kunnu\Dropbox\Models\FileMetadata
771     */
772    public function upload($dropboxFile, $path, array $params = [])
773    {
774        //Make Dropbox File
775        $dropboxFile = $this->makeDropboxFile($dropboxFile);
776
777        //If the file is larger than the Chunked Upload Threshold
778        if ($dropboxFile->getSize() > static::AUTO_CHUNKED_UPLOAD_THRESHOLD) {
779            //Upload the file in sessions/chunks
780            return $this->uploadChunked($dropboxFile, $path, null, null, $params);
781        }
782
783        //Simple file upload
784        return $this->simpleUpload($dropboxFile, $path, $params);
785    }
786
787    /**
788     * Make DropboxFile Object
789     *
790     * @param  string|DropboxFile $dropboxFile DropboxFile object or Path to file
791     * @param  int                $maxLength   Max Bytes to read from the file
792     * @param  int                $offset      Seek to specified offset before reading
793     * @param  string             $mode        The type of access
794     *
795     * @return \Kunnu\Dropbox\DropboxFile
796     */
797    public function makeDropboxFile($dropboxFile, $maxLength = null, $offset = null, $mode = DropboxFile::MODE_READ)
798    {
799        //Uploading file by file path
800        if (!$dropboxFile instanceof DropboxFile) {
801            //Create a DropboxFile Object
802            $dropboxFile = new DropboxFile($dropboxFile, $mode);
803        } elseif ($mode !== $dropboxFile->getMode()) {
804            //Reopen the file with expected mode
805            $dropboxFile->close();
806            $dropboxFile = new DropboxFile($dropboxFile->getFilePath(), $mode);
807        }
808
809        if (!is_null($offset)) {
810            $dropboxFile->setOffset($offset);
811        }
812
813        if (!is_null($maxLength)) {
814            $dropboxFile->setMaxLength($maxLength);
815        }
816
817        //Return the DropboxFile Object
818        return $dropboxFile;
819    }
820
821    /**
822     * Upload file in sessions/chunks
823     *
824     * @param  string|DropboxFile $dropboxFile DropboxFile object or Path to file
825     * @param  string             $path        Path to save the file to, on Dropbox
826     * @param  int                $fileSize    The size of the file
827     * @param  int                $chunkSize   The amount of data to upload in each chunk
828     * @param  array              $params      Additional Params
829     *
830     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
831     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
832     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
833     *
834     * @return \Kunnu\Dropbox\Models\FileMetadata
835     */
836    public function uploadChunked($dropboxFile, $path, $fileSize = null, $chunkSize = null, array $params = array())
837    {
838        //Make Dropbox File
839        $dropboxFile = $this->makeDropboxFile($dropboxFile);
840
841        //No file size specified explicitly
842        if (is_null($fileSize)) {
843            $fileSize = $dropboxFile->getSize();
844        }
845
846        //No chunk size specified, use default size
847        if (is_null($chunkSize)) {
848            $chunkSize = static::DEFAULT_CHUNK_SIZE;
849        }
850
851        //If the fileSize is smaller
852        //than the chunk size, we'll
853        //make the chunk size relatively
854        //smaller than the file size
855        if ($fileSize <= $chunkSize) {
856            $chunkSize = intval($fileSize / 2);
857        }
858
859        //Start the Upload Session with the file path
860        //since the DropboxFile object will be created
861        //again using the new chunk size.
862        $sessionId = $this->startUploadSession($dropboxFile->getFilePath(), $chunkSize);
863
864        //Uploaded
865        $uploaded = $chunkSize;
866
867        //Remaining
868        $remaining = $fileSize - $chunkSize;
869
870        //While the remaining bytes are
871        //more than the chunk size, append
872        //the chunk to the upload session.
873        while ($remaining > $chunkSize) {
874            //Append the next chunk to the Upload session
875            $sessionId = $this->appendUploadSession($dropboxFile, $sessionId, $uploaded, $chunkSize);
876
877            //Update remaining and uploaded
878            $uploaded = $uploaded + $chunkSize;
879            $remaining = $remaining - $chunkSize;
880        }
881
882        //Finish the Upload Session and return the Uploaded File Metadata
883        return $this->finishUploadSession($dropboxFile, $sessionId, $uploaded, $remaining, $path, $params);
884    }
885
886    /**
887     * Start an Upload Session
888     *
889     * @param  string|DropboxFile $dropboxFile DropboxFile object or Path to file
890     * @param  int                $chunkSize   Size of file chunk to upload
891     * @param  boolean            $close       Closes the session for "appendUploadSession"
892     *
893     * @return string Unique identifier for the upload session
894     *
895     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
896     *
897     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-start
898     *
899     */
900    public function startUploadSession($dropboxFile, $chunkSize = -1, $close = false)
901    {
902        //Make Dropbox File with the given chunk size
903        $dropboxFile = $this->makeDropboxFile($dropboxFile, $chunkSize);
904
905        //Set the close param
906        $params = [
907            'close' => $close ? true : false,
908            'file' => $dropboxFile
909        ];
910
911        //Upload File
912        $file = $this->postToContent('/files/upload_session/start', $params);
913        $body = $file->getDecodedBody();
914
915        //Cannot retrieve Session ID
916        if (!isset($body['session_id'])) {
917            throw new DropboxClientException("Could not retrieve Session ID.");
918        }
919
920        //Return the Session ID
921        return $body['session_id'];
922    }
923
924    /**
925     * Make a HTTP POST Request to the Content endpoint type
926     *
927     * @param  string      $endpoint     Content Endpoint to send Request to
928     * @param  array       $params       Request Query Params
929     * @param  string      $accessToken  Access Token to send with the Request
930     * @param  DropboxFile $responseFile Save response to the file
931     *
932     * @return \Kunnu\Dropbox\DropboxResponse
933     */
934    public function postToContent($endpoint, array $params = [], $accessToken = null, DropboxFile $responseFile = null)
935    {
936        return $this->sendRequest("POST", $endpoint, 'content', $params, $accessToken, $responseFile);
937    }
938
939    /**
940     * Append more data to an Upload Session
941     *
942     * @param  string|DropboxFile $dropboxFile DropboxFile object or Path to file
943     * @param  string             $sessionId   Session ID returned by `startUploadSession`
944     * @param  int                $offset      The amount of data that has been uploaded so far
945     * @param  int                $chunkSize   The amount of data to upload
946     * @param  boolean            $close       Closes the session for futher "appendUploadSession" calls
947     *
948     * @return string Unique identifier for the upload session
949     *
950     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
951     *
952     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-append_v2
953     *
954     */
955    public function appendUploadSession($dropboxFile, $sessionId, $offset, $chunkSize, $close = false)
956    {
957        //Make Dropbox File
958        $dropboxFile = $this->makeDropboxFile($dropboxFile, $chunkSize, $offset);
959
960        //Session ID, offset, chunkSize and path cannot be null
961        if (is_null($sessionId) || is_null($offset) || is_null($chunkSize)) {
962            throw new DropboxClientException("Session ID, offset and chunk size cannot be null");
963        }
964
965        $params = [];
966
967        //Set the File
968        $params['file'] = $dropboxFile;
969
970        //Set the Cursor: Session ID and Offset
971        $params['cursor'] = ['session_id' => $sessionId, 'offset' => $offset];
972
973        //Set the close param
974        $params['close'] = $close ? true : false;
975
976        //Since this endpoint doesn't have
977        //any return values, we'll disable the
978        //response validation for this request.
979        $params['validateResponse'] = false;
980
981        //Upload File
982        $this->postToContent('/files/upload_session/append_v2', $params);
983
984        //Make and Return the Model
985        return $sessionId;
986    }
987
988    /**
989     * Finish an upload session and save the uploaded data to the given file path
990     *
991     * @param  string|DropboxFile $dropboxFile DropboxFile object or Path to file
992     * @param  string             $sessionId   Session ID returned by `startUploadSession`
993     * @param  int                $offset      The amount of data that has been uploaded so far
994     * @param  int                $remaining   The amount of data that is remaining
995     * @param  string             $path        Path to save the file to, on Dropbox
996     * @param  array              $params      Additional Params
997     *
998     * @return \Kunnu\Dropbox\Models\FileMetadata
999     *
1000     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
1001     *
1002     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload_session-finish
1003     *
1004     */
1005    public function finishUploadSession($dropboxFile, $sessionId, $offset, $remaining, $path, array $params = [])
1006    {
1007        //Make Dropbox File
1008        $dropboxFile = $this->makeDropboxFile($dropboxFile, $remaining, $offset);
1009
1010        //Session ID, offset, remaining and path cannot be null
1011        if (is_null($sessionId) || is_null($path) || is_null($offset) || is_null($remaining)) {
1012            throw new DropboxClientException("Session ID, offset, remaining and path cannot be null");
1013        }
1014
1015        $queryParams = [];
1016
1017        //Set the File
1018        $queryParams['file'] = $dropboxFile;
1019
1020        //Set the Cursor: Session ID and Offset
1021        $queryParams['cursor'] = ['session_id' => $sessionId, 'offset' => $offset];
1022
1023        //Set the path
1024        $params['path'] = $path;
1025        //Set the Commit
1026        $queryParams['commit'] = $params;
1027
1028        //Upload File
1029        $file = $this->postToContent('/files/upload_session/finish', $queryParams);
1030        $body = $file->getDecodedBody();
1031
1032        //Make and Return the Model
1033        return new FileMetadata($body);
1034    }
1035
1036    /**
1037     * Upload a File to Dropbox in a single request
1038     *
1039     * @param  string|DropboxFile $dropboxFile DropboxFile object or Path to file
1040     * @param  string             $path        Path to upload the file to
1041     * @param  array              $params      Additional Params
1042     *
1043     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-upload
1044     *
1045     * @return \Kunnu\Dropbox\Models\FileMetadata
1046     */
1047    public function simpleUpload($dropboxFile, $path, array $params = [])
1048    {
1049        //Make Dropbox File
1050        $dropboxFile = $this->makeDropboxFile($dropboxFile);
1051
1052        //Set the path and file
1053        $params['path'] = $path;
1054        $params['file'] = $dropboxFile;
1055
1056        //Upload File
1057        $file = $this->postToContent('/files/upload', $params);
1058        $body = $file->getDecodedBody();
1059
1060        //Make and Return the Model
1061        return new FileMetadata($body);
1062    }
1063
1064    /**
1065     * Get a thumbnail for an image
1066     *
1067     * @param  string $path   Path to the file you want a thumbnail to
1068     * @param  string $size   Size for the thumbnail image ['thumb','small','medium','large','huge']
1069     * @param  string $format Format for the thumbnail image ['jpeg'|'png']
1070     *
1071     * @return \Kunnu\Dropbox\Models\Thumbnail
1072     *
1073     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
1074     *
1075     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-get_thumbnail
1076     *
1077     */
1078    public function getThumbnail($path, $size = 'small', $format = 'jpeg')
1079    {
1080        //Path cannot be null
1081        if (is_null($path)) {
1082            throw new DropboxClientException("Path cannot be null.");
1083        }
1084
1085        //Invalid Format
1086        if (!in_array($format, ['jpeg', 'png'])) {
1087            throw new DropboxClientException("Invalid format. Must either be 'jpeg' or 'png'.");
1088        }
1089
1090        //Thumbnail size
1091        $size = $this->getThumbnailSize($size);
1092
1093        //Get Thumbnail
1094        $response = $this->postToContent('/files/get_thumbnail', ['path' => $path, 'format' => $format, 'size' => $size]);
1095
1096        //Get file metadata from response headers
1097        $metadata = $this->getMetadataFromResponseHeaders($response);
1098
1099        //File Contents
1100        $contents = $response->getBody();
1101
1102        //Make and return a Thumbnail model
1103        return new Thumbnail($metadata, $contents);
1104    }
1105
1106    /**
1107     * Get thumbnail size
1108     *
1109     * @param  string $size Thumbnail Size
1110     *
1111     * @return string
1112     */
1113    protected function getThumbnailSize($size)
1114    {
1115        $thumbnailSizes = [
1116            'thumb' => 'w32h32',
1117            'small' => 'w64h64',
1118            'medium' => 'w128h128',
1119            'large' => 'w640h480',
1120            'huge' => 'w1024h768'
1121        ];
1122
1123        return isset($thumbnailSizes[$size]) ? $thumbnailSizes[$size] : $thumbnailSizes['small'];
1124    }
1125
1126    /**
1127     * Get metadata from response headers
1128     *
1129     * @param  DropboxResponse $response
1130     *
1131     * @return array
1132     */
1133    protected function getMetadataFromResponseHeaders(DropboxResponse $response)
1134    {
1135        //Response Headers
1136        $headers = $response->getHeaders();
1137
1138        //Empty metadata for when
1139        //metadata isn't returned
1140        $metadata = [];
1141
1142        //If metadata is available
1143        if (isset($headers[static::METADATA_HEADER])) {
1144            //File Metadata
1145            $data = $headers[static::METADATA_HEADER];
1146
1147            //The metadata is present in the first index
1148            //of the metadata response header array
1149            if (is_array($data) && isset($data[0])) {
1150                $data = $data[0];
1151            }
1152
1153            //Since the metadata is returned as a json string
1154            //it needs to be decoded into an associative array
1155            $metadata = json_decode((string)$data, true);
1156        }
1157
1158        //Return the metadata
1159        return $metadata;
1160    }
1161
1162    /**
1163     * Download a File
1164     *
1165     * @param  string                  $path        Path to the file you want to download
1166     * @param  null|string|DropboxFile $dropboxFile DropboxFile object or Path to target file
1167     *
1168     * @return \Kunnu\Dropbox\Models\File
1169     *
1170     * @throws \Kunnu\Dropbox\Exceptions\DropboxClientException
1171     *
1172     * @link https://www.dropbox.com/developers/documentation/http/documentation#files-download
1173     *
1174     */
1175    public function download($path, $dropboxFile = null)
1176    {
1177        //Path cannot be null
1178        if (is_null($path)) {
1179            throw new DropboxClientException("Path cannot be null.");
1180        }
1181
1182        //Make Dropbox File if target is specified
1183        $dropboxFile = $dropboxFile ? $this->makeDropboxFile($dropboxFile, null, null, DropboxFile::MODE_WRITE) : null;
1184
1185        //Download File
1186        $response = $this->postToContent('/files/download', ['path' => $path], null, $dropboxFile);
1187
1188        //Get file metadata from response headers
1189        $metadata = $this->getMetadataFromResponseHeaders($response);
1190
1191        //File Contents
1192        $contents = $dropboxFile ? $this->makeDropboxFile($dropboxFile) : $response->getBody();
1193
1194        //Make and return a File model
1195        return new File($metadata, $contents);
1196    }
1197
1198    /**
1199     * Get Current Account
1200     *
1201     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
1202     *
1203     * @return \Kunnu\Dropbox\Models\Account
1204     */
1205    public function getCurrentAccount()
1206    {
1207        //Get current account
1208        $response = $this->postToAPI('/users/get_current_account', []);
1209        $body = $response->getDecodedBody();
1210
1211        //Make and return the model
1212        return new Account($body);
1213    }
1214
1215    /**
1216     * Get Account
1217     *
1218     * @param string $account_id Account ID of the account to get details for
1219     *
1220     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_account
1221     *
1222     * @return \Kunnu\Dropbox\Models\Account
1223     */
1224    public function getAccount($account_id)
1225    {
1226        //Get account
1227        $response = $this->postToAPI('/users/get_account', ['account_id' => $account_id]);
1228        $body = $response->getDecodedBody();
1229
1230        //Make and return the model
1231        return new Account($body);
1232    }
1233
1234    /**
1235     * Get Multiple Accounts in one call
1236     *
1237     * @param array $account_ids IDs of the accounts to get details for
1238     *
1239     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_account_batch
1240     *
1241     * @return \Kunnu\Dropbox\Models\AccountList
1242     */
1243    public function getAccounts(array $account_ids = [])
1244    {
1245        //Get account
1246        $response = $this->postToAPI('/users/get_account_batch', ['account_ids' => $account_ids]);
1247        $body = $response->getDecodedBody();
1248
1249        //Make and return the model
1250        return new AccountList($body);
1251    }
1252
1253    /**
1254     * Get Space Usage for the current user's account
1255     *
1256     * @link https://www.dropbox.com/developers/documentation/http/documentation#users-get_space_usage
1257     *
1258     * @return array
1259     */
1260    public function getSpaceUsage()
1261    {
1262        //Get space usage
1263        $response = $this->postToAPI('/users/get_space_usage', []);
1264        $body = $response->getDecodedBody();
1265
1266        //Return the decoded body
1267        return $body;
1268    }
1269}
1270