├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── bin
└── clouddrive
├── box.json
├── composer.json
├── composer.lock
└── src
└── CloudDrive
├── Account.php
├── Cache.php
├── Cache
├── MySQL.php
├── SQL.php
└── SQLite.php
├── CloudDrive.php
├── Commands
├── CatCommand.php
├── ClearCacheCommand.php
├── Command.php
├── ConfigCommand.php
├── DiskUsageCommand.php
├── DownloadCommand.php
├── FindCommand.php
├── InitCommand.php
├── ListCommand.php
├── ListPendingCommand.php
├── ListTrashCommand.php
├── MetadataCommand.php
├── MkdirCommand.php
├── MoveCommand.php
├── QuotaCommand.php
├── RenameCommand.php
├── RenewCommand.php
├── ResolveCommand.php
├── RestoreCommand.php
├── SyncCommand.php
├── TempLinkCommand.php
├── TrashCommand.php
├── TreeCommand.php
├── UploadCommand.php
└── UsageCommand.php
└── Node.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /vendor/
3 | /.cache/*
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All Notable changes to `clouddrive-php` will be documented in this file
4 |
5 | ## 0.1.1
6 |
7 | ### Added
8 | - Indices to database tables for improved performance
9 | - `composer.json` now has PHP version requirement
10 | - `box.json` file added for packaging as `.phar`
11 | - JSON output in CLI is now considered `verbose` and is not outputted unless desired
12 | - 'Success' and 'failure' messages are colored accordingly
13 | - A `callable` is now accepted to be passed into the `upload` command instead of a resource stream for writing
14 | - Added config value to allow duplicate file uploads in different locations (suppress dedup check in API)
15 | - Added support of the `ls` command with a direct file node to just display its information (pass `-a` flag to show its child assets)
16 | - Added `link` command to generate pre-authenticated temp links to share files
17 | - Added config value to suppress trashed nodes in `ls` output
18 | - Method in the `Node` class `inTrash` returns if the node's status is in the trash or not
19 | - Ability to now download FOLDER nodes
20 |
21 | ### Deprecated
22 | - Passing in a resource stream into the `upload` method has been replaced by a callable (see 'added')
23 |
24 | ### Fixed
25 | - `config` command was not properly outputting `bool` values when reading an individual item
26 | - `PHP` shebang path is now more universal in the `bin` file
27 | - Error messages now to through `STDERR`
28 | - MD5 queries return an array since multiple files can have the same MD5 with duplicate uploads enabled
29 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/:package_name).
6 |
7 |
8 | ## Pull Requests
9 |
10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
11 |
12 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
13 |
14 | - **Create feature branches** - Don't ask us to pull from your master branch.
15 |
16 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
17 |
18 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
19 |
20 | **Happy coding**!
21 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright (c) 2015 :author_name <:author_email>
4 |
5 | > Permission is hereby granted, free of charge, to any person obtaining a copy
6 | > of this software and associated documentation files (the "Software"), to deal
7 | > in the Software without restriction, including without limitation the rights
8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | > copies of the Software, and to permit persons to whom the Software is
10 | > furnished to do so, subject to the following conditions:
11 | >
12 | > The above copyright notice and this permission notice shall be included in
13 | > all copies or substantial portions of the Software.
14 | >
15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | > THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CloudDrive PHP
2 |
3 | NOTE: This project is in active development.
4 |
5 | CloudDrive-PHP is an SDK and CLI application for interacting with Amazon's [Cloud Drive](https://www.amazon.com/clouddrive/home).
6 |
7 | The project originally started out as an application to manage storage in Cloud Drive from the command line but figured other's may want to take advantage of the API calls and develop their own software using it, so I made sure to build the library so it can be built upon and make the CLI application an included tool that could also be used as an example for implementation.
8 |
9 | ## Install
10 |
11 | Via Composer (for use in a project)
12 |
13 | ```
14 | $ composer require alex-phillips/clouddrive
15 | ```
16 |
17 | Install globally to run the CLI from any location (as long as the global composer `bin` directory is in your `$PATH`).
18 |
19 | ```
20 | $ composer global require alex-phillips/clouddrive
21 | ```
22 |
23 | ## CLI
24 |
25 | ### Setup
26 |
27 | The first run of the CLI needs to authenticate your Amazon Cloud Drive account with the application using your Amazon Cloud Drive credentials. Use the `config` command to set these credentials as well as the email associated with your Amazon account.
28 |
29 | ```
30 | $ clouddrive config email me@example.com
31 | $ clouddrive config client-id CLIENT_ID
32 | $ clouddrive config client-secret CLIENT_SECRET
33 | ```
34 |
35 | Once the credentials are set, simply run the `init` command. This will provide you with an authentication URL to visit. Paste the URL you are redirected to into the terminal and press enter.
36 |
37 | ```
38 | $ clouddrive init
39 | Initial authorization required.
40 | Navigate to the following URL and paste in the redirect URL here.
41 | https://www.amazon.com/ap/oa?client_id=CLIENT_ID&scope=clouddrive%3Aread_all%20clouddrive%3Awrite&response_type=code&redirect_uri=http://localhost
42 | ...
43 | Successfully authenticated with Amazon CloudDrive.
44 | ```
45 |
46 | After you have been authenticated, run the `sync` command to sync your local cache with the current state of your Cloud Drive.
47 |
48 | ### Usage
49 |
50 | The CLI application relies on your local cache being in sync with your Cloud Drive, so run the `sync` command periodically to retrieve any changes since the last sync. You can view all available commands and usage of each command using the `-h` flag at any point.
51 |
52 | ```
53 | Cloud Drive version 0.1.0
54 |
55 | Usage:
56 | command [options] [arguments]
57 |
58 | Options:
59 | -h, --help Display this help message
60 | -q, --quiet Do not output any message
61 | -V, --version Display this application version
62 | --ansi Force ANSI output
63 | --no-ansi Disable ANSI output
64 | -n, --no-interaction Do not ask any interactive question
65 | -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
66 |
67 | Available commands:
68 | cat Output a file to the standard output stream
69 | clear-cache Clear the local cache
70 | clearcache Clear the local cache
71 | config Read, write, and remove config options
72 | download Download remote file or folder to specified local path
73 | du Display disk usage for the given node
74 | find Find nodes by name or MD5 checksum
75 | help Displays help for a command
76 | init Initialize the command line application for use with an Amazon account
77 | link Generate a temporary, pre-authenticated download link
78 | list Lists commands
79 | ls List all remote nodes inside of a specified directory
80 | metadata Retrieve the metadata (JSON) of a node by its path
81 | mkdir Create a new remote directory given a path
82 | mv Move a node to a new remote folder
83 | pending List the nodes that have a status of 'PENDING'
84 | quota Show Cloud Drive quota
85 | rename Rename remote node
86 | renew Renew authorization
87 | resolve Return a node's remote path by its ID
88 | restore Restore a remote node from the trash
89 | rm Move a remote Node to the trash
90 | sync Sync the local cache with Amazon CloudDrive
91 | trash List the nodes that are in trash
92 | tree Print directory tree of the given node
93 | upload Upload local file or folder to remote directory
94 | usage Show Cloud Drive usage
95 | ```
96 |
97 | ## SDK
98 |
99 | ### SDK Responses
100 |
101 | All of the method calls return a reponse in a REST API-like structure with the exception of those methods that return `Node` objects.
102 |
103 | Example response:
104 | ```php
105 | [
106 | 'result' => true,
107 | 'data' => [
108 | 'message' => 'The response was successful'
109 | ]
110 | ]
111 | ```
112 |
113 | Every API-like reponse will have at least 2 keys: `success` and `data`. `Success` is a boolean on whether or not the request was completed successfully and the data contains various information related to the request.
114 |
115 | ** NOTE: ** The response for `nodeExists` will return `success = true` if the node exists, `false` if the node doesn't exist.
116 |
117 | Various method calls that return `Node` objects such as `findNodeByPath` and `findNodeById` will return either a `Node` object or `null` if the node was not found.
118 |
119 | ### Getting started
120 |
121 | The first thing to do is create a new `CloudDrive` object using the email of the account you wish you talk with and the necessary API credentials for Cloud Drive and a `Cache` object for storing local data. (Currently there is only a SQLite cache store).
122 |
123 | ```php
124 | $clouddrive = new CloudDrive\CloudDrive($email, $clientId, $clientSecret, new \CloudDrive\Cache\SQLite($email));
125 | $response = $clouddrive->getAccount()->authorize();
126 | ```
127 |
128 | The first time you go to authorize the account, the `response` will "fail" and the `data` key in the response will contain an `auth_url`. This is required during the initial authorization to grant access to your application with Cloud Drive. Simply navigate to the URL, you will be redirected to a "localhost" URL which will contain the necessary code for access.
129 |
130 | Next, call authorization once more with the redirected URL passed in:
131 |
132 | ```php
133 | $clouddrive = new CloudDrive\CloudDrive($email, $clientId, $clientSecret, new \CloudDrive\Cache\SQLite($email));
134 | $response = $clouddrive->getAccount()->authorize($redirectUrl);
135 | ```
136 |
137 | The response will now be successful and the access token will be stored in the cache. From now on, when the account needs to renew its authorization, it will do so automatically with its 'refresh token' inside of the `authorize` method.
138 |
139 | ### Local Cache
140 |
141 | There is currently support for MySQL (and MariaDB) and SQLite3 for the local cache store. Simply instantiate these with the necessary parameters. If you are using MySQL, make sure the database is created. The initialization of the cache store will automatically create the necessary tables.
142 |
143 | ```
144 | $cacheStore = new \CloudDrive\Cache\SQLite('my-cache', './.cache');
145 | ```
146 |
147 | ### Node
148 |
149 | Once you have authenticated the `Account` object and created a local cache, initialize the `Node` object to utilize these.
150 |
151 |
152 | ```
153 | Node::init($account, $cache);
154 | ```
155 |
156 | Now all static `Node` methods will be available to retrieve, find, and manipulate `Node` objects.
157 |
158 | ```
159 | $results = Node::loadByName('myfile.txt');
160 | ```
161 |
162 | Various `Node` methods will either return an array if multiple nodes are able to be returned and a `Node` object if only 1 is meant to be returned (i.e., lookup by ID). If no nodes are found, then the methods will return an empty array or `null` value respectively.
163 |
164 | ## Contributing
165 |
166 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
167 |
168 | ## License
169 |
170 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
171 |
--------------------------------------------------------------------------------
/bin/clouddrive:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | command(new \CloudDrive\Commands\MetadataCommand());
27 | $app->command(new \CloudDrive\Commands\InitCommand());
28 | $app->command(new \CloudDrive\Commands\SyncCommand());
29 | $app->command(new \CloudDrive\Commands\ClearCacheCommand());
30 | $app->command(new \CloudDrive\Commands\UploadCommand());
31 | $app->command(new \CloudDrive\Commands\ListCommand());
32 | $app->command(new \CloudDrive\Commands\DownloadCommand());
33 | $app->command(new \CloudDrive\Commands\MkdirCommand());
34 | $app->command(new \CloudDrive\Commands\TrashCommand());
35 | $app->command(new \CloudDrive\Commands\RestoreCommand());
36 | $app->command(new \CloudDrive\Commands\RenameCommand());
37 | $app->command(new \CloudDrive\Commands\ListTrashCommand());
38 | $app->command(new \CloudDrive\Commands\ResolveCommand());
39 | $app->command(new \CloudDrive\Commands\MoveCommand());
40 | $app->command(new \CloudDrive\Commands\FindCommand());
41 | $app->command(new \CloudDrive\Commands\QuotaCommand());
42 | $app->command(new \CloudDrive\Commands\UsageCommand());
43 | $app->command(new \CloudDrive\Commands\ConfigCommand());
44 | $app->command(new \CloudDrive\Commands\CatCommand());
45 | $app->command(new \CloudDrive\Commands\TreeCommand());
46 | $app->command(new \CloudDrive\Commands\DiskUsageCommand());
47 | $app->command(new \CloudDrive\Commands\RenewCommand());
48 | $app->command(new \CloudDrive\Commands\TempLinkCommand());
49 | $app->command(new \CloudDrive\Commands\ListPendingCommand());
50 |
51 | $app->run();
52 |
--------------------------------------------------------------------------------
/box.json:
--------------------------------------------------------------------------------
1 | {
2 | "chmod": "0755",
3 | "directories": [
4 | "src"
5 | ],
6 | "files": [
7 | "LICENSE.md"
8 | ],
9 | "finder": [
10 | {
11 | "name": "*.php",
12 | "exclude": [
13 | "Tests"
14 | ],
15 | "in": "vendor"
16 | }
17 | ],
18 | "git-version": "0.1.0",
19 | "main": "bin/clouddrive",
20 | "output": "clouddrive.phar",
21 | "stub": true
22 | }
23 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "alex-phillips/clouddrive",
3 | "description": "PHP SDK and CLI for Amazon's CloudDrive",
4 | "require": {
5 | "php": ">=5.5",
6 | "guzzlehttp/guzzle": "^6.0",
7 | "alex-phillips/utilities": "~0.1",
8 | "cilex/cilex": "^1.1",
9 | "j4mie/idiorm": "^1.5"
10 | },
11 | "authors": [
12 | {
13 | "name": "Alex Phillips",
14 | "email": "ahp118@gmail.com"
15 | }
16 | ],
17 | "autoload": {
18 | "psr-4": {
19 | "": "src/"
20 | }
21 | },
22 | "bin": [
23 | "bin/clouddrive"
24 | ],
25 | "require-dev": {
26 | "squizlabs/php_codesniffer": "^2.3"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "hash": "443cca422aa5c7d79f8d5db346141c51",
8 | "content-hash": "749b384d152182f59f0c3dc62b05d760",
9 | "packages": [
10 | {
11 | "name": "alex-phillips/utilities",
12 | "version": "0.1.2",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/alex-phillips/Utilities.git",
16 | "reference": "78f0f4c57f546ac120a9f39b13bd0c0629d2161e"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/alex-phillips/Utilities/zipball/78f0f4c57f546ac120a9f39b13bd0c0629d2161e",
21 | "reference": "78f0f4c57f546ac120a9f39b13bd0c0629d2161e",
22 | "shasum": ""
23 | },
24 | "require-dev": {
25 | "phpunit/phpunit": "^4.6",
26 | "squizlabs/php_codesniffer": "^2.3"
27 | },
28 | "type": "library",
29 | "autoload": {
30 | "psr-4": {
31 | "": "src/"
32 | }
33 | },
34 | "notification-url": "https://packagist.org/downloads/",
35 | "license": [
36 | "MIT"
37 | ],
38 | "authors": [
39 | {
40 | "name": "Alex Phillips",
41 | "email": "ahp118@gmail.com"
42 | }
43 | ],
44 | "time": "2015-08-15 15:12:09"
45 | },
46 | {
47 | "name": "cilex/cilex",
48 | "version": "1.1.0",
49 | "source": {
50 | "type": "git",
51 | "url": "https://github.com/Cilex/Cilex.git",
52 | "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5"
53 | },
54 | "dist": {
55 | "type": "zip",
56 | "url": "https://api.github.com/repos/Cilex/Cilex/zipball/7acd965a609a56d0345e8b6071c261fbdb926cb5",
57 | "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5",
58 | "shasum": ""
59 | },
60 | "require": {
61 | "cilex/console-service-provider": "1.*",
62 | "php": ">=5.3.3",
63 | "pimple/pimple": "~1.0",
64 | "symfony/finder": "~2.1",
65 | "symfony/process": "~2.1"
66 | },
67 | "require-dev": {
68 | "phpunit/phpunit": "3.7.*",
69 | "symfony/validator": "~2.1"
70 | },
71 | "suggest": {
72 | "monolog/monolog": ">=1.0.0",
73 | "symfony/validator": ">=1.0.0",
74 | "symfony/yaml": ">=1.0.0"
75 | },
76 | "type": "library",
77 | "extra": {
78 | "branch-alias": {
79 | "dev-master": "1.0-dev"
80 | }
81 | },
82 | "autoload": {
83 | "psr-0": {
84 | "Cilex": "src/"
85 | }
86 | },
87 | "notification-url": "https://packagist.org/downloads/",
88 | "license": [
89 | "MIT"
90 | ],
91 | "authors": [
92 | {
93 | "name": "Mike van Riel",
94 | "email": "mike.vanriel@naenius.com"
95 | }
96 | ],
97 | "description": "The PHP micro-framework for Command line tools based on the Symfony2 Components",
98 | "homepage": "http://cilex.github.com",
99 | "keywords": [
100 | "cli",
101 | "microframework"
102 | ],
103 | "time": "2014-03-29 14:03:13"
104 | },
105 | {
106 | "name": "cilex/console-service-provider",
107 | "version": "1.0.0",
108 | "source": {
109 | "type": "git",
110 | "url": "https://github.com/Cilex/console-service-provider.git",
111 | "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e"
112 | },
113 | "dist": {
114 | "type": "zip",
115 | "url": "https://api.github.com/repos/Cilex/console-service-provider/zipball/25ee3d1875243d38e1a3448ff94bdf944f70d24e",
116 | "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e",
117 | "shasum": ""
118 | },
119 | "require": {
120 | "php": ">=5.3.3",
121 | "pimple/pimple": "1.*@dev",
122 | "symfony/console": "~2.1"
123 | },
124 | "require-dev": {
125 | "cilex/cilex": "1.*@dev",
126 | "silex/silex": "1.*@dev"
127 | },
128 | "type": "library",
129 | "extra": {
130 | "branch-alias": {
131 | "dev-master": "1.0-dev"
132 | }
133 | },
134 | "autoload": {
135 | "psr-0": {
136 | "Cilex\\Provider\\Console": "src"
137 | }
138 | },
139 | "notification-url": "https://packagist.org/downloads/",
140 | "license": [
141 | "MIT"
142 | ],
143 | "authors": [
144 | {
145 | "name": "Beau Simensen",
146 | "email": "beau@dflydev.com",
147 | "homepage": "http://beausimensen.com"
148 | },
149 | {
150 | "name": "Mike van Riel",
151 | "email": "mike.vanriel@naenius.com"
152 | }
153 | ],
154 | "description": "Console Service Provider",
155 | "keywords": [
156 | "cilex",
157 | "console",
158 | "pimple",
159 | "service-provider",
160 | "silex"
161 | ],
162 | "time": "2012-12-19 10:50:58"
163 | },
164 | {
165 | "name": "guzzlehttp/guzzle",
166 | "version": "6.1.1",
167 | "source": {
168 | "type": "git",
169 | "url": "https://github.com/guzzle/guzzle.git",
170 | "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c"
171 | },
172 | "dist": {
173 | "type": "zip",
174 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c",
175 | "reference": "c6851d6e48f63b69357cbfa55bca116448140e0c",
176 | "shasum": ""
177 | },
178 | "require": {
179 | "guzzlehttp/promises": "~1.0",
180 | "guzzlehttp/psr7": "~1.1",
181 | "php": ">=5.5.0"
182 | },
183 | "require-dev": {
184 | "ext-curl": "*",
185 | "phpunit/phpunit": "~4.0",
186 | "psr/log": "~1.0"
187 | },
188 | "type": "library",
189 | "extra": {
190 | "branch-alias": {
191 | "dev-master": "6.1-dev"
192 | }
193 | },
194 | "autoload": {
195 | "files": [
196 | "src/functions_include.php"
197 | ],
198 | "psr-4": {
199 | "GuzzleHttp\\": "src/"
200 | }
201 | },
202 | "notification-url": "https://packagist.org/downloads/",
203 | "license": [
204 | "MIT"
205 | ],
206 | "authors": [
207 | {
208 | "name": "Michael Dowling",
209 | "email": "mtdowling@gmail.com",
210 | "homepage": "https://github.com/mtdowling"
211 | }
212 | ],
213 | "description": "Guzzle is a PHP HTTP client library",
214 | "homepage": "http://guzzlephp.org/",
215 | "keywords": [
216 | "client",
217 | "curl",
218 | "framework",
219 | "http",
220 | "http client",
221 | "rest",
222 | "web service"
223 | ],
224 | "time": "2015-11-23 00:47:50"
225 | },
226 | {
227 | "name": "guzzlehttp/promises",
228 | "version": "1.0.3",
229 | "source": {
230 | "type": "git",
231 | "url": "https://github.com/guzzle/promises.git",
232 | "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea"
233 | },
234 | "dist": {
235 | "type": "zip",
236 | "url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea",
237 | "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea",
238 | "shasum": ""
239 | },
240 | "require": {
241 | "php": ">=5.5.0"
242 | },
243 | "require-dev": {
244 | "phpunit/phpunit": "~4.0"
245 | },
246 | "type": "library",
247 | "extra": {
248 | "branch-alias": {
249 | "dev-master": "1.0-dev"
250 | }
251 | },
252 | "autoload": {
253 | "psr-4": {
254 | "GuzzleHttp\\Promise\\": "src/"
255 | },
256 | "files": [
257 | "src/functions_include.php"
258 | ]
259 | },
260 | "notification-url": "https://packagist.org/downloads/",
261 | "license": [
262 | "MIT"
263 | ],
264 | "authors": [
265 | {
266 | "name": "Michael Dowling",
267 | "email": "mtdowling@gmail.com",
268 | "homepage": "https://github.com/mtdowling"
269 | }
270 | ],
271 | "description": "Guzzle promises library",
272 | "keywords": [
273 | "promise"
274 | ],
275 | "time": "2015-10-15 22:28:00"
276 | },
277 | {
278 | "name": "guzzlehttp/psr7",
279 | "version": "1.2.1",
280 | "source": {
281 | "type": "git",
282 | "url": "https://github.com/guzzle/psr7.git",
283 | "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982"
284 | },
285 | "dist": {
286 | "type": "zip",
287 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/4d0bdbe1206df7440219ce14c972aa57cc5e4982",
288 | "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982",
289 | "shasum": ""
290 | },
291 | "require": {
292 | "php": ">=5.4.0",
293 | "psr/http-message": "~1.0"
294 | },
295 | "provide": {
296 | "psr/http-message-implementation": "1.0"
297 | },
298 | "require-dev": {
299 | "phpunit/phpunit": "~4.0"
300 | },
301 | "type": "library",
302 | "extra": {
303 | "branch-alias": {
304 | "dev-master": "1.0-dev"
305 | }
306 | },
307 | "autoload": {
308 | "psr-4": {
309 | "GuzzleHttp\\Psr7\\": "src/"
310 | },
311 | "files": [
312 | "src/functions_include.php"
313 | ]
314 | },
315 | "notification-url": "https://packagist.org/downloads/",
316 | "license": [
317 | "MIT"
318 | ],
319 | "authors": [
320 | {
321 | "name": "Michael Dowling",
322 | "email": "mtdowling@gmail.com",
323 | "homepage": "https://github.com/mtdowling"
324 | }
325 | ],
326 | "description": "PSR-7 message implementation",
327 | "keywords": [
328 | "http",
329 | "message",
330 | "stream",
331 | "uri"
332 | ],
333 | "time": "2015-11-03 01:34:55"
334 | },
335 | {
336 | "name": "j4mie/idiorm",
337 | "version": "v1.5.1",
338 | "source": {
339 | "type": "git",
340 | "url": "https://github.com/j4mie/idiorm.git",
341 | "reference": "b0922d8719a94e3a0e0e4a0ca3876f4f91475dcf"
342 | },
343 | "dist": {
344 | "type": "zip",
345 | "url": "https://api.github.com/repos/j4mie/idiorm/zipball/b0922d8719a94e3a0e0e4a0ca3876f4f91475dcf",
346 | "reference": "b0922d8719a94e3a0e0e4a0ca3876f4f91475dcf",
347 | "shasum": ""
348 | },
349 | "require": {
350 | "php": ">=5.2.0"
351 | },
352 | "type": "library",
353 | "autoload": {
354 | "classmap": [
355 | "idiorm.php"
356 | ]
357 | },
358 | "notification-url": "https://packagist.org/downloads/",
359 | "license": [
360 | "BSD-2-Clause",
361 | "BSD-3-Clause",
362 | "BSD-4-Clause"
363 | ],
364 | "authors": [
365 | {
366 | "name": "Simon Holywell",
367 | "email": "treffynnon@php.net",
368 | "homepage": "http://simonholywell.com",
369 | "role": "Maintainer"
370 | },
371 | {
372 | "name": "Jamie Matthews",
373 | "email": "jamie.matthews@gmail.com",
374 | "homepage": "http://j4mie.org",
375 | "role": "Developer"
376 | },
377 | {
378 | "name": "Durham Hale",
379 | "email": "me@durhamhale.com",
380 | "homepage": "http://durhamhale.com",
381 | "role": "Maintainer"
382 | }
383 | ],
384 | "description": "A lightweight nearly-zero-configuration object-relational mapper and fluent query builder for PHP5",
385 | "homepage": "http://j4mie.github.com/idiormandparis",
386 | "keywords": [
387 | "idiorm",
388 | "orm",
389 | "query builder"
390 | ],
391 | "time": "2014-06-23 13:08:57"
392 | },
393 | {
394 | "name": "pimple/pimple",
395 | "version": "v1.1.1",
396 | "source": {
397 | "type": "git",
398 | "url": "https://github.com/silexphp/Pimple.git",
399 | "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d"
400 | },
401 | "dist": {
402 | "type": "zip",
403 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d",
404 | "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d",
405 | "shasum": ""
406 | },
407 | "require": {
408 | "php": ">=5.3.0"
409 | },
410 | "type": "library",
411 | "extra": {
412 | "branch-alias": {
413 | "dev-master": "1.1.x-dev"
414 | }
415 | },
416 | "autoload": {
417 | "psr-0": {
418 | "Pimple": "lib/"
419 | }
420 | },
421 | "notification-url": "https://packagist.org/downloads/",
422 | "license": [
423 | "MIT"
424 | ],
425 | "authors": [
426 | {
427 | "name": "Fabien Potencier",
428 | "email": "fabien@symfony.com"
429 | }
430 | ],
431 | "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
432 | "homepage": "http://pimple.sensiolabs.org",
433 | "keywords": [
434 | "container",
435 | "dependency injection"
436 | ],
437 | "time": "2013-11-22 08:30:29"
438 | },
439 | {
440 | "name": "psr/http-message",
441 | "version": "1.0",
442 | "source": {
443 | "type": "git",
444 | "url": "https://github.com/php-fig/http-message.git",
445 | "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298"
446 | },
447 | "dist": {
448 | "type": "zip",
449 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
450 | "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
451 | "shasum": ""
452 | },
453 | "require": {
454 | "php": ">=5.3.0"
455 | },
456 | "type": "library",
457 | "extra": {
458 | "branch-alias": {
459 | "dev-master": "1.0.x-dev"
460 | }
461 | },
462 | "autoload": {
463 | "psr-4": {
464 | "Psr\\Http\\Message\\": "src/"
465 | }
466 | },
467 | "notification-url": "https://packagist.org/downloads/",
468 | "license": [
469 | "MIT"
470 | ],
471 | "authors": [
472 | {
473 | "name": "PHP-FIG",
474 | "homepage": "http://www.php-fig.org/"
475 | }
476 | ],
477 | "description": "Common interface for HTTP messages",
478 | "keywords": [
479 | "http",
480 | "http-message",
481 | "psr",
482 | "psr-7",
483 | "request",
484 | "response"
485 | ],
486 | "time": "2015-05-04 20:22:00"
487 | },
488 | {
489 | "name": "symfony/console",
490 | "version": "v2.8.0",
491 | "source": {
492 | "type": "git",
493 | "url": "https://github.com/symfony/console.git",
494 | "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41"
495 | },
496 | "dist": {
497 | "type": "zip",
498 | "url": "https://api.github.com/repos/symfony/console/zipball/d232bfc100dfd32b18ccbcab4bcc8f28697b7e41",
499 | "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41",
500 | "shasum": ""
501 | },
502 | "require": {
503 | "php": ">=5.3.9",
504 | "symfony/polyfill-mbstring": "~1.0"
505 | },
506 | "require-dev": {
507 | "psr/log": "~1.0",
508 | "symfony/event-dispatcher": "~2.1|~3.0.0",
509 | "symfony/process": "~2.1|~3.0.0"
510 | },
511 | "suggest": {
512 | "psr/log": "For using the console logger",
513 | "symfony/event-dispatcher": "",
514 | "symfony/process": ""
515 | },
516 | "type": "library",
517 | "extra": {
518 | "branch-alias": {
519 | "dev-master": "2.8-dev"
520 | }
521 | },
522 | "autoload": {
523 | "psr-4": {
524 | "Symfony\\Component\\Console\\": ""
525 | },
526 | "exclude-from-classmap": [
527 | "/Tests/"
528 | ]
529 | },
530 | "notification-url": "https://packagist.org/downloads/",
531 | "license": [
532 | "MIT"
533 | ],
534 | "authors": [
535 | {
536 | "name": "Fabien Potencier",
537 | "email": "fabien@symfony.com"
538 | },
539 | {
540 | "name": "Symfony Community",
541 | "homepage": "https://symfony.com/contributors"
542 | }
543 | ],
544 | "description": "Symfony Console Component",
545 | "homepage": "https://symfony.com",
546 | "time": "2015-11-30 12:35:10"
547 | },
548 | {
549 | "name": "symfony/finder",
550 | "version": "v2.8.0",
551 | "source": {
552 | "type": "git",
553 | "url": "https://github.com/symfony/finder.git",
554 | "reference": "ead9b07af4ba77b6507bee697396a5c79e633f08"
555 | },
556 | "dist": {
557 | "type": "zip",
558 | "url": "https://api.github.com/repos/symfony/finder/zipball/ead9b07af4ba77b6507bee697396a5c79e633f08",
559 | "reference": "ead9b07af4ba77b6507bee697396a5c79e633f08",
560 | "shasum": ""
561 | },
562 | "require": {
563 | "php": ">=5.3.9"
564 | },
565 | "type": "library",
566 | "extra": {
567 | "branch-alias": {
568 | "dev-master": "2.8-dev"
569 | }
570 | },
571 | "autoload": {
572 | "psr-4": {
573 | "Symfony\\Component\\Finder\\": ""
574 | },
575 | "exclude-from-classmap": [
576 | "/Tests/"
577 | ]
578 | },
579 | "notification-url": "https://packagist.org/downloads/",
580 | "license": [
581 | "MIT"
582 | ],
583 | "authors": [
584 | {
585 | "name": "Fabien Potencier",
586 | "email": "fabien@symfony.com"
587 | },
588 | {
589 | "name": "Symfony Community",
590 | "homepage": "https://symfony.com/contributors"
591 | }
592 | ],
593 | "description": "Symfony Finder Component",
594 | "homepage": "https://symfony.com",
595 | "time": "2015-10-30 20:15:42"
596 | },
597 | {
598 | "name": "symfony/polyfill-mbstring",
599 | "version": "v1.0.0",
600 | "source": {
601 | "type": "git",
602 | "url": "https://github.com/symfony/polyfill-mbstring.git",
603 | "reference": "0b6a8940385311a24e060ec1fe35680e17c74497"
604 | },
605 | "dist": {
606 | "type": "zip",
607 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0b6a8940385311a24e060ec1fe35680e17c74497",
608 | "reference": "0b6a8940385311a24e060ec1fe35680e17c74497",
609 | "shasum": ""
610 | },
611 | "require": {
612 | "php": ">=5.3.3"
613 | },
614 | "type": "library",
615 | "extra": {
616 | "branch-alias": {
617 | "dev-master": "1.0-dev"
618 | }
619 | },
620 | "autoload": {
621 | "psr-4": {
622 | "Symfony\\Polyfill\\Mbstring\\": ""
623 | },
624 | "files": [
625 | "bootstrap.php"
626 | ]
627 | },
628 | "notification-url": "https://packagist.org/downloads/",
629 | "license": [
630 | "MIT"
631 | ],
632 | "authors": [
633 | {
634 | "name": "Nicolas Grekas",
635 | "email": "p@tchwork.com"
636 | },
637 | {
638 | "name": "Symfony Community",
639 | "homepage": "https://symfony.com/contributors"
640 | }
641 | ],
642 | "description": "Symfony polyfill for the Mbstring extension",
643 | "homepage": "https://symfony.com",
644 | "keywords": [
645 | "compatibility",
646 | "mbstring",
647 | "polyfill",
648 | "portable",
649 | "shim"
650 | ],
651 | "time": "2015-11-04 20:28:58"
652 | },
653 | {
654 | "name": "symfony/process",
655 | "version": "v2.8.0",
656 | "source": {
657 | "type": "git",
658 | "url": "https://github.com/symfony/process.git",
659 | "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312"
660 | },
661 | "dist": {
662 | "type": "zip",
663 | "url": "https://api.github.com/repos/symfony/process/zipball/1b988a88e3551102f3c2d9e1d47a18c3a78d6312",
664 | "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312",
665 | "shasum": ""
666 | },
667 | "require": {
668 | "php": ">=5.3.9"
669 | },
670 | "type": "library",
671 | "extra": {
672 | "branch-alias": {
673 | "dev-master": "2.8-dev"
674 | }
675 | },
676 | "autoload": {
677 | "psr-4": {
678 | "Symfony\\Component\\Process\\": ""
679 | },
680 | "exclude-from-classmap": [
681 | "/Tests/"
682 | ]
683 | },
684 | "notification-url": "https://packagist.org/downloads/",
685 | "license": [
686 | "MIT"
687 | ],
688 | "authors": [
689 | {
690 | "name": "Fabien Potencier",
691 | "email": "fabien@symfony.com"
692 | },
693 | {
694 | "name": "Symfony Community",
695 | "homepage": "https://symfony.com/contributors"
696 | }
697 | ],
698 | "description": "Symfony Process Component",
699 | "homepage": "https://symfony.com",
700 | "time": "2015-11-30 12:35:10"
701 | }
702 | ],
703 | "packages-dev": [
704 | {
705 | "name": "squizlabs/php_codesniffer",
706 | "version": "2.5.0",
707 | "source": {
708 | "type": "git",
709 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
710 | "reference": "e4fb41d5d0387d556e2c25534d630b3cce90ea67"
711 | },
712 | "dist": {
713 | "type": "zip",
714 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/e4fb41d5d0387d556e2c25534d630b3cce90ea67",
715 | "reference": "e4fb41d5d0387d556e2c25534d630b3cce90ea67",
716 | "shasum": ""
717 | },
718 | "require": {
719 | "ext-tokenizer": "*",
720 | "ext-xmlwriter": "*",
721 | "php": ">=5.1.2"
722 | },
723 | "require-dev": {
724 | "phpunit/phpunit": "~4.0"
725 | },
726 | "bin": [
727 | "scripts/phpcs",
728 | "scripts/phpcbf"
729 | ],
730 | "type": "library",
731 | "extra": {
732 | "branch-alias": {
733 | "dev-master": "2.0.x-dev"
734 | }
735 | },
736 | "autoload": {
737 | "classmap": [
738 | "CodeSniffer.php",
739 | "CodeSniffer/CLI.php",
740 | "CodeSniffer/Exception.php",
741 | "CodeSniffer/File.php",
742 | "CodeSniffer/Fixer.php",
743 | "CodeSniffer/Report.php",
744 | "CodeSniffer/Reporting.php",
745 | "CodeSniffer/Sniff.php",
746 | "CodeSniffer/Tokens.php",
747 | "CodeSniffer/Reports/",
748 | "CodeSniffer/Tokenizers/",
749 | "CodeSniffer/DocGenerators/",
750 | "CodeSniffer/Standards/AbstractPatternSniff.php",
751 | "CodeSniffer/Standards/AbstractScopeSniff.php",
752 | "CodeSniffer/Standards/AbstractVariableSniff.php",
753 | "CodeSniffer/Standards/IncorrectPatternException.php",
754 | "CodeSniffer/Standards/Generic/Sniffs/",
755 | "CodeSniffer/Standards/MySource/Sniffs/",
756 | "CodeSniffer/Standards/PEAR/Sniffs/",
757 | "CodeSniffer/Standards/PSR1/Sniffs/",
758 | "CodeSniffer/Standards/PSR2/Sniffs/",
759 | "CodeSniffer/Standards/Squiz/Sniffs/",
760 | "CodeSniffer/Standards/Zend/Sniffs/"
761 | ]
762 | },
763 | "notification-url": "https://packagist.org/downloads/",
764 | "license": [
765 | "BSD-3-Clause"
766 | ],
767 | "authors": [
768 | {
769 | "name": "Greg Sherwood",
770 | "role": "lead"
771 | }
772 | ],
773 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
774 | "homepage": "http://www.squizlabs.com/php-codesniffer",
775 | "keywords": [
776 | "phpcs",
777 | "standards"
778 | ],
779 | "time": "2015-12-11 00:12:46"
780 | }
781 | ],
782 | "aliases": [],
783 | "minimum-stability": "stable",
784 | "stability-flags": [],
785 | "prefer-stable": false,
786 | "prefer-lowest": false,
787 | "platform": {
788 | "php": ">=5.5"
789 | },
790 | "platform-dev": []
791 | }
792 |
--------------------------------------------------------------------------------
/src/CloudDrive/Account.php:
--------------------------------------------------------------------------------
1 | email = $email;
108 | $this->clientId = $clientId;
109 | $this->clientSecret = $clientSecret;
110 | $this->cache = $cache;
111 | $this->httpClient = new Client();
112 | }
113 |
114 | /**
115 | * Authorize the user object. If the initial authorization has not been
116 | * completed, the `auth_url` is returned. If authorization has already
117 | * happened and the `access_token` hasn't passed its expiration time, we
118 | * are already authorized. Otherwise, if the `access_token` has expired,
119 | * request a new token. This method also retrieves the user's API endpoints.
120 | *
121 | * @param null|string $redirectUrl The URL the user is redirected to after
122 | * navigating to the authorization URL.
123 | *
124 | * @return array
125 | */
126 | public function authorize($redirectUrl = null)
127 | {
128 | $retval = [
129 | 'success' => true,
130 | 'data' => [],
131 | ];
132 |
133 | $config = $this->cache->loadAccountConfig($this->email) ?: [];
134 |
135 | $this->token = new ParameterBag($config);
136 |
137 | $scope = rawurlencode(implode(' ', $this->scope));
138 |
139 | if (!$this->token["access_token"]) {
140 | if (!$redirectUrl) {
141 | $retval['success'] = false;
142 | if (!$this->clientId || !$this->clientSecret) {
143 | $retval['data'] = [
144 | 'message' => 'Initial authorization is required',
145 | 'auth_url' => 'https://data-mind-687.appspot.com/clouddrive',
146 | ];
147 | } else {
148 | $retval['data'] = [
149 | 'message' => 'Initial authorization required.',
150 | 'auth_url' => "https://www.amazon.com/ap/oa?client_id={$this->clientId}&scope={$scope}&response_type=code&redirect_uri=http://localhost",
151 | ];
152 | }
153 |
154 | return $retval;
155 | }
156 |
157 | $response = $this->requestAuthorization($redirectUrl);
158 |
159 | if (!$response["success"]) {
160 | return $response;
161 | }
162 | } else {
163 | if (time() - $this->token["last_authorized"] > $this->token["expires_in"]) {
164 | $response = $this->renewAuthorization();
165 | if (!$response["success"]) {
166 | return $response;
167 | }
168 | }
169 | }
170 |
171 | if (isset($response)) {
172 | $this->token->merge($response['data']);
173 | }
174 |
175 | if (!$this->token["metadata_url"] || !$this->token["content_url"]) {
176 | $response = $this->fetchEndpoint();
177 | if (!$response['success']) {
178 | return $response;
179 | }
180 |
181 | $this->token['metadata_url'] = $response['data']['metadataUrl'];
182 | $this->token['content_url'] = $response['data']['contentUrl'];
183 | }
184 |
185 | $this->checkpoint = $this->token["checkpoint"];
186 | $this->metadataUrl = $this->token["metadata_url"];
187 | $this->contentUrl = $this->token["content_url"];
188 |
189 | $this->save();
190 |
191 | return $retval;
192 | }
193 |
194 | /**
195 | * Reset the account's sync checkpoint.
196 | */
197 | public function clearCache()
198 | {
199 | $this->checkpoint = null;
200 | $this->save();
201 | $this->cache->deleteAllNodes();
202 | }
203 |
204 | /**
205 | * Fetch the user's API endpoints from the REST API.
206 | *
207 | * @return array
208 | */
209 | private function fetchEndpoint()
210 | {
211 | $retval = [
212 | 'success' => false,
213 | 'data' => [],
214 | ];
215 |
216 | $response = $this->httpClient->get('https://cdws.us-east-1.amazonaws.com/drive/v1/account/endpoint', [
217 | 'headers' => [
218 | 'Authorization' => "Bearer {$this->token["access_token"]}",
219 | ],
220 | 'exceptions' => false,
221 | ]);
222 |
223 | $retval['data'] = json_decode((string)$response->getBody(), true);
224 |
225 | if ($response->getStatusCode() === 200) {
226 | $retval['success'] = true;
227 | }
228 |
229 | return $retval;
230 | }
231 |
232 | /**
233 | * Retrieve the last sync checkpoint.
234 | *
235 | * @return null|string
236 | */
237 | public function getCheckpoint()
238 | {
239 | return $this->checkpoint;
240 | }
241 |
242 | /**
243 | * Retrieve the user's API content URL.
244 | *
245 | * @return string
246 | */
247 | public function getContentUrl()
248 | {
249 | return $this->contentUrl;
250 | }
251 |
252 | /**
253 | * Retrieve the account's email.
254 | *
255 | * @return string
256 | */
257 | public function getEmail()
258 | {
259 | return $this->email;
260 | }
261 |
262 | /**
263 | * Retrieve the user's API metadata URL.
264 | *
265 | * @return string
266 | */
267 | public function getMetadataUrl()
268 | {
269 | return $this->metadataUrl;
270 | }
271 |
272 | /**
273 | * Retrieve the account's quota.
274 | *
275 | * @return array
276 | */
277 | public function getQuota()
278 | {
279 | $retval = [
280 | 'success' => false,
281 | 'data' => [],
282 | ];
283 |
284 | $response = $this->httpClient->get("{$this->getMetadataUrl()}account/quota", [
285 | 'headers' => [
286 | 'Authorization' => "Bearer {$this->token["access_token"]}",
287 | ],
288 | 'exceptions' => false,
289 | ]);
290 |
291 | $retval['data'] = json_decode((string)$response->getBody(), true);
292 |
293 | if ($response->getStatusCode() === 200) {
294 | $retval['success'] = true;
295 | }
296 |
297 | return $retval;
298 | }
299 |
300 | /**
301 | * Retrieve access token data.
302 | *
303 | * @return ParameterBag
304 | */
305 | public function getToken()
306 | {
307 | return $this->token;
308 | }
309 |
310 | /**
311 | * Retrieve the account's current usage.
312 | *
313 | * @return array
314 | */
315 | public function getUsage()
316 | {
317 | $retval = [
318 | 'success' => false,
319 | 'data' => [],
320 | ];
321 |
322 | $response = $this->httpClient->get(
323 | "{$this->getMetadataUrl()}account/usage",
324 | [
325 | 'headers' => [
326 | 'Authorization' => "Bearer {$this->token["access_token"]}",
327 | ],
328 | 'exceptions' => false,
329 | ]
330 | );
331 |
332 | $retval['data'] = json_decode((string)$response->getBody(), true);
333 |
334 | if ($response->getStatusCode() === 200) {
335 | $retval['success'] = true;
336 | }
337 |
338 | return $retval;
339 | }
340 |
341 | /**
342 | * Renew the OAuth2 access token after the current access token has expired.
343 | *
344 | * @return array
345 | */
346 | public function renewAuthorization()
347 | {
348 | $retval = [
349 | "success" => false,
350 | "data" => [],
351 | ];
352 |
353 | if ($this->clientId && $this->clientSecret) {
354 | $response = $this->httpClient->post(
355 | 'https://api.amazon.com/auth/o2/token',
356 | [
357 | 'form_params' => [
358 | 'grant_type' => "refresh_token",
359 | 'refresh_token' => $this->token["refresh_token"],
360 | 'client_id' => $this->clientId,
361 | 'client_secret' => $this->clientSecret,
362 | 'redirect_uri' => "http://localhost",
363 | ],
364 | 'exceptions' => false,
365 | ]
366 | );
367 | } else {
368 | $response = $this->httpClient->get(
369 | 'https://data-mind-687.appspot.com/clouddrive?refresh_token=${this.token.refresh_token}'
370 | );
371 | }
372 |
373 | $retval["data"] = json_decode((string)$response->getBody(), true);
374 |
375 | if ($response->getStatusCode() === 200) {
376 | $retval["success"] = true;
377 | $retval["data"]["last_authorized"] = time();
378 | }
379 |
380 | return $retval;
381 | }
382 |
383 | /**
384 | * Use the `code` from the passed in `authUrl` to retrieve the OAuth2
385 | * tokens for API access.
386 | *
387 | * @param string $authUrl The redirect URL from the authorization request
388 | *
389 | * @return array
390 | */
391 | private function requestAuthorization($authUrl)
392 | {
393 | $retval = [
394 | 'success' => false,
395 | 'data' => [],
396 | ];
397 |
398 | if (!$token = json_decode($authUrl, true)) {
399 | $url = parse_url($authUrl);
400 | parse_str($url['query'], $params);
401 |
402 | if (!isset($params['code'])) {
403 | $retval['data']['message'] = "No authorization code found in callback URL: $authUrl";
404 |
405 | return $retval;
406 | }
407 |
408 | $response = $this->httpClient->post(
409 | 'https://api.amazon.com/auth/o2/token',
410 | [
411 | 'form_params' => [
412 | 'grant_type' => 'authorization_code',
413 | 'code' => $params['code'],
414 | 'client_id' => $this->clientId,
415 | 'client_secret' => $this->clientSecret,
416 | 'redirect_uri' => 'http://localhost',
417 | ],
418 | 'exceptions' => false,
419 | ]
420 | );
421 |
422 | $retval["data"] = json_decode((string)$response->getBody(), true);
423 |
424 | if ($response->getStatusCode() === 200) {
425 | $retval["success"] = true;
426 | $retval["data"]["last_authorized"] = time();
427 | }
428 | } else {
429 | $retval["success"] = true;
430 | $retval["data"] = $token;
431 | $retval["data"]["last_authorized"] = time();
432 | }
433 |
434 | return $retval;
435 | }
436 |
437 | /**
438 | * Save the account config into the cache database. This includes authorization
439 | * tokens, endpoint URLs, and the last sync checkpoint.
440 | *
441 | * @return bool
442 | */
443 | public function save()
444 | {
445 | return $this->cache->saveAccountConfig($this);
446 | }
447 |
448 | /**
449 | * Set the permission scope of the API before requesting authentication.
450 | *
451 | * @param array $scopes The permissions requested
452 | *
453 | * @return $this
454 | */
455 | public function setScope(array $scopes)
456 | {
457 | $this->scope = $scopes;
458 |
459 | return $this;
460 | }
461 |
462 | /**
463 | * Sync the local cache with the remote changes. If checkpoint is null, this
464 | * will sync all remote node data.
465 | *
466 | * @throws \Exception
467 | */
468 | public function sync()
469 | {
470 | $params = [
471 | 'maxNodes' => 5000,
472 | ];
473 |
474 | if ($this->checkpoint) {
475 | $params['includePurged'] = "true";
476 | }
477 |
478 | while (true) {
479 | if ($this->checkpoint) {
480 | $params['checkpoint'] = $this->checkpoint;
481 | }
482 |
483 | $loop = true;
484 |
485 | $response = $this->httpClient->post(
486 | "{$this->getMetadataUrl()}changes",
487 | [
488 | 'headers' => [
489 | 'Authorization' => "Bearer {$this->token['access_token']}",
490 | ],
491 | 'body' => json_encode($params),
492 | 'exceptions' => false,
493 | ]
494 | );
495 |
496 | if ($response->getStatusCode() !== 200) {
497 | throw new \Exception((string)$response->getBody());
498 | }
499 |
500 | $data = explode("\n", (string)$response->getBody());
501 | foreach ($data as $part) {
502 | $part = json_decode($part, true);
503 |
504 | if (isset($part['end']) && $part['end'] === true) {
505 | break;
506 | }
507 |
508 | if (isset($part['reset']) && $part['reset'] === true) {
509 | $this->cache->deleteAllNodes();
510 | }
511 |
512 | if (isset($part['nodes'])) {
513 | if (empty($part['nodes'])) {
514 | $loop = false;
515 | } else {
516 | foreach ($part['nodes'] as $node) {
517 | $node = new Node($node);
518 | if ($node['status'] === 'PURGED') {
519 | $node->delete();
520 | } else {
521 | $node->save();
522 | }
523 | }
524 | }
525 | }
526 |
527 | if (isset($part['checkpoint'])) {
528 | $this->checkpoint = $part['checkpoint'];
529 | }
530 |
531 | $this->save();
532 | }
533 |
534 | if (!$loop) {
535 | break;
536 | }
537 | }
538 | }
539 | }
540 |
--------------------------------------------------------------------------------
/src/CloudDrive/Cache.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/11/15
5 | * Time: 11:06 AM
6 | */
7 |
8 | namespace CloudDrive;
9 |
10 | /**
11 | * Class that handles local storage of remote node information.
12 | *
13 | * @package CloudDrive
14 | */
15 | interface Cache
16 | {
17 | /**
18 | * Delete all nodes from the cache.
19 | *
20 | * @return bool
21 | */
22 | public function deleteAllNodes();
23 |
24 | /**
25 | * Delete node with the given ID from the cache.
26 | *
27 | * @param string $id
28 | *
29 | * @return bool
30 | */
31 | public function deleteNodeById($id);
32 |
33 | /**
34 | * Find nodes in the local cache based on query filters.
35 | *
36 | * @param array $filters
37 | *
38 | * @return array
39 | */
40 | public function filterNodes(array $filters);
41 |
42 | /**
43 | * Find the node by the given ID in the cache.
44 | *
45 | * @param string $id
46 | *
47 | * @return \CloudDrive\Node|null
48 | */
49 | public function findNodeById($id);
50 |
51 | /**
52 | * Find all nodes by the given MD5 in the cache.
53 | *
54 | * @param string $md5
55 | *
56 | * @return array
57 | */
58 | public function findNodesByMd5($md5);
59 |
60 | /**
61 | * Retrieve all node matching the given name in the cache.
62 | *
63 | * @param string $name
64 | *
65 | * @return array
66 | */
67 | public function findNodesByName($name);
68 |
69 | /**
70 | * Retrieve all nodes who have the given node as their parent.
71 | *
72 | * @param Node $node
73 | *
74 | * @return array
75 | */
76 | public function getNodeChildren(Node $node);
77 |
78 | /**
79 | * Retrieve the config for the account matched with the given email.
80 | *
81 | * @param string $email
82 | *
83 | * @return array
84 | */
85 | public function loadAccountConfig($email);
86 |
87 | /**
88 | * Save the config for the provided account.
89 | *
90 | * @param Account $account
91 | *
92 | * @return bool
93 | */
94 | public function saveAccountConfig(Account $account);
95 |
96 | /**
97 | * Save the given node into the cache.
98 | *
99 | * @param Node $node
100 | *
101 | * @return bool
102 | */
103 | public function saveNode(Node $node);
104 |
105 | /**
106 | * Search for nodes in the local cache that contain a string in their name.
107 | *
108 | * @param string $name
109 | *
110 | * @return array
111 | */
112 | public function searchNodesByName($name);
113 | }
114 |
--------------------------------------------------------------------------------
/src/CloudDrive/Cache/MySQL.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/27/15
5 | * Time: 3:56 PM
6 | */
7 |
8 | namespace CloudDrive\Cache;
9 |
10 | use ORM;
11 |
12 | class MySQL extends SQL
13 | {
14 | public function __construct($host, $database, $username, $password)
15 | {
16 | ORM::configure("mysql:host=$host;dbname=$database");
17 | ORM::configure('username', $username);
18 | ORM::configure('password', $password);
19 | ORM::get_db()->exec('
20 | CREATE TABLE IF NOT EXISTS configs (
21 | id INT(11) NOT NULL auto_increment,
22 | email VARCHAR(32),
23 | token_type VARCHAR(16),
24 | expires_in INT(12),
25 | refresh_token TEXT,
26 | access_token TEXT,
27 | last_authorized INT(12),
28 | content_url MEDIUMTEXT,
29 | metadata_url MEDIUMTEXT,
30 | checkpoint TEXT,
31 | PRIMARY KEY (id),
32 | INDEX (email)
33 | );
34 | ');
35 | ORM::get_db()->exec('
36 | CREATE TABLE IF NOT EXISTS nodes (
37 | id VARCHAR(255) NOT NULL,
38 | name VARCHAR(128),
39 | kind VARCHAR(16),
40 | md5 VARCHAR(128),
41 | status VARCHAR(16),
42 | created DATETIME,
43 | modified DATETIME,
44 | raw_data LONGTEXT,
45 | PRIMARY KEY (id),
46 | INDEX (id, name, md5)
47 | );
48 | ');
49 | ORM::get_db()->exec('
50 | CREATE TABLE IF NOT EXISTS nodes_nodes (
51 | id INT(11) NOT NULL auto_increment,
52 | id_node VARCHAR(255) NOT NULL,
53 | id_parent VARCHAR(255) NOT NULL,
54 | PRIMARY KEY (id),
55 | UNIQUE KEY (id_node, id_parent),
56 | INDEX(id_node, id_parent)
57 | );
58 | ');
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/CloudDrive/Cache/SQL.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/23/15
5 | * Time: 5:19 PM
6 | */
7 |
8 | namespace CloudDrive\Cache;
9 |
10 | use CloudDrive\Account;
11 | use CloudDrive\Cache;
12 | use CloudDrive\Node;
13 | use ORM;
14 |
15 | /**
16 | * The SQL abstract class is what all SQL database cache classes will inherit
17 | * from.
18 | *
19 | * @package CloudDrive\Cache
20 | */
21 | abstract class SQL implements Cache
22 | {
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function deleteAllNodes()
27 | {
28 | try {
29 | ORM::get_db()->beginTransaction();
30 | ORM::for_table('nodes')->delete_many();
31 | ORM::get_db()->exec('TRUNCATE nodes_nodes');
32 | ORM::get_db()->commit();
33 | } catch (\Exception $e) {
34 | return false;
35 | }
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function deleteNodeById($id)
42 | {
43 | $node = ORM::for_table('nodes')->find_one($id);
44 | if ($node) {
45 | try {
46 | ORM::get_db()->beginTransaction();
47 |
48 | $node->delete();
49 | ORM::for_table('nodes_nodes')
50 | ->where('id_node', $id)
51 | ->delete_many();
52 |
53 | return ORM::get_db()->commit();
54 | } catch (\Exception $e) {
55 | ORM::get_db()->rollBack();
56 | }
57 |
58 | return false;
59 | }
60 |
61 | return true;
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | public function filterNodes(array $filters)
68 | {
69 | $nodes = ORM::for_table('nodes')
70 | ->select('raw_data')
71 | ->where_any_is($filters)
72 | ->find_many();
73 |
74 | foreach ($nodes as &$node) {
75 | $node = new Node(
76 | json_decode($node->as_array()['raw_data'], true)
77 | );
78 | }
79 |
80 | return $nodes;
81 | }
82 |
83 | /**
84 | * {@inheritdoc}
85 | */
86 | public function findNodeById($id)
87 | {
88 | $result = ORM::for_table('nodes')->select('raw_data')->where('id', $id)->find_one();
89 | if ($result) {
90 | return new Node(
91 | json_decode($result['raw_data'], true)
92 | );
93 | }
94 |
95 | return null;
96 | }
97 |
98 | /**
99 | * {@inheritdoc}
100 | */
101 | public function findNodesByMd5($md5)
102 | {
103 | $nodes = ORM::for_table('nodes')
104 | ->select('raw_data')
105 | ->where('md5', $md5)
106 | ->find_many();
107 |
108 | foreach ($nodes as &$node) {
109 | $node = new Node(
110 | json_decode($node->as_array()['raw_data'], true)
111 | );
112 | }
113 |
114 | return $nodes;
115 | }
116 |
117 | /**
118 | * {@inheritdoc}
119 | */
120 | public function findNodesByName($name)
121 | {
122 | $nodes = ORM::for_table('nodes')
123 | ->select('raw_data')
124 | ->where('name', $name)
125 | ->find_many();
126 |
127 | foreach ($nodes as &$node) {
128 | $node = new Node(
129 | json_decode($node->as_array()['raw_data'], true)
130 | );
131 | }
132 |
133 | return $nodes;
134 | }
135 |
136 | /**
137 | * {@inheritdoc}
138 | */
139 | public function getNodeChildren(Node $node)
140 | {
141 | $results = ORM::for_table('nodes')
142 | ->select('raw_data')
143 | ->join('nodes_nodes', ['nodes.id', '=', 'nodes_nodes.id_node'])
144 | ->where('nodes_nodes.id_parent', $node['id'])
145 | ->find_many();
146 |
147 | foreach ($results as &$result) {
148 | $result = new Node(
149 | json_decode($result['raw_data'], true)
150 | );
151 | }
152 |
153 | return $results;
154 | }
155 |
156 | /**
157 | * {@inheritdoc}
158 | */
159 | public function loadAccountConfig($email)
160 | {
161 | $config = ORM::for_table('configs')
162 | ->where('email', $email)
163 | ->find_one();
164 |
165 | if (!$config) {
166 | return null;
167 | }
168 |
169 | return $config->as_array();
170 | }
171 |
172 | /**
173 | * {@inheritdoc}
174 | */
175 | public function saveAccountConfig(Account $account)
176 | {
177 | $config = ORM::for_table('configs')->where('email', $account->getEmail())->find_one();
178 | if (!$config) {
179 | $config = ORM::for_table('configs')->create();
180 | }
181 |
182 | $config->set([
183 | 'email' => $account->getEmail(),
184 | 'token_type' => $account->getToken()['token_type'],
185 | 'expires_in' => $account->getToken()['expires_in'],
186 | 'refresh_token' => $account->getToken()['refresh_token'],
187 | 'access_token' => $account->getToken()['access_token'],
188 | 'last_authorized' => $account->getToken()['last_authorized'],
189 | 'content_url' => $account->getContentUrl(),
190 | 'metadata_url' => $account->getMetadataUrl(),
191 | 'checkpoint' => $account->getCheckpoint(),
192 | ]);
193 |
194 | return $config->save();
195 | }
196 |
197 | /**
198 | * {@inheritdoc}
199 | */
200 | public function saveNode(Node $node)
201 | {
202 | if (!$node['name'] && $node['isRoot'] === true) {
203 | $node['name'] = 'Cloud Drive';
204 | }
205 |
206 | $n = ORM::for_table('nodes')->find_one($node['id']);
207 | if (!$n) {
208 | $n = ORM::for_table('nodes')->create();
209 | }
210 |
211 | $n->set([
212 | 'id' => $node['id'],
213 | 'name' => $node['name'],
214 | 'kind' => $node['kind'],
215 | 'md5' => $node['contentProperties']['md5'],
216 | 'status' => $node['status'],
217 | 'created' => $node['createdDate'],
218 | 'modified' => $node['modifiedDate'],
219 | 'raw_data' => json_encode($node),
220 | ]);
221 |
222 | try {
223 | ORM::get_db()->beginTransaction();
224 |
225 | $n->save();
226 |
227 | $parentIds = $node['parents'];
228 | $previousParents = ORM::for_table('nodes_nodes')
229 | ->where('id_node', $node['id'])
230 | ->find_array();
231 |
232 | foreach ($previousParents as $parent) {
233 | if ($index = array_search($parent['id_parent'], $parentIds)) {
234 | unset($parentIds[$index]);
235 | continue;
236 | } else {
237 | ORM::for_table('nodes_nodes')
238 | ->find_one($parent['id'])
239 | ->delete();
240 | }
241 | }
242 |
243 | foreach ($parentIds as $parentId) {
244 | $p = ORM::for_table('nodes_nodes')->create();
245 | $p->set([
246 | 'id_node' => $node['id'],
247 | 'id_parent' => $parentId,
248 | ]);
249 | $p->save();
250 | }
251 |
252 | return ORM::get_db()->commit();
253 | } catch (\Exception $e) {
254 | ORM::get_db()->rollBack();
255 |
256 | return false;
257 | }
258 | }
259 |
260 | /**
261 | * {@inheritdoc}
262 | */
263 | public function searchNodesByName($name)
264 | {
265 | $results = ORM::for_table('nodes')
266 | ->where_like('name', "%$name%")
267 | ->find_many();
268 |
269 | foreach ($results as &$result) {
270 | $result = new Node(
271 | json_decode($result['raw_data'], true)
272 | );
273 | }
274 |
275 | return $results;
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/CloudDrive/Cache/SQLite.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/11/15
5 | * Time: 11:08 AM
6 | */
7 |
8 | namespace CloudDrive\Cache;
9 |
10 | use CloudDrive\Cache;
11 | use ORM;
12 | use SQLite3;
13 |
14 | class SQLite extends SQL
15 | {
16 | /**
17 | * Construct a new SQLite cache object. The database will be saved in the
18 | * provided `cacheDir` under the name `$email.db`.
19 | *
20 | * @param string $email The email for the account
21 | * @param string $cacheDir The directory to save the database in
22 | */
23 | public function __construct($email, $cacheDir)
24 | {
25 | if (!file_exists($cacheDir)) {
26 | mkdir($cacheDir, 0777, true);
27 | }
28 |
29 | $cacheDir = rtrim($cacheDir, '/');
30 |
31 | if (!file_exists("$cacheDir/$email.db")) {
32 | $db = new SQLite3("$cacheDir/$email.db");
33 | $db->exec(
34 | 'CREATE TABLE nodes(
35 | id VARCHAR PRIMARY KEY NOT NULL,
36 | name VARCHAR NOT NULL,
37 | kind VARCHAR NOT NULL,
38 | md5 VARCHAR,
39 | status VARCHAR,
40 | created DATETIME NOT NULL,
41 | modified DATETIME NOT NULL,
42 | raw_data TEXT NOT NULL
43 | );
44 | CREATE INDEX node_id on nodes(id);
45 | CREATE INDEX node_name on nodes(name);
46 | CREATE INDEX node_md5 on nodes(md5);'
47 | );
48 | $db->exec(
49 | 'CREATE TABLE configs
50 | (
51 | id INTEGER PRIMARY KEY,
52 | email VARCHAR NOT NULL,
53 | token_type VARCHAR,
54 | expires_in INT,
55 | refresh_token TEXT,
56 | access_token TEXT,
57 | last_authorized INT,
58 | content_url VARCHAR,
59 | metadata_url VARCHAR,
60 | checkpoint VARCHAR
61 | );
62 | CREATE INDEX config_email on configs(email);'
63 | );
64 | $db->exec(
65 | 'CREATE TABLE nodes_nodes
66 | (
67 | id INTEGER PRIMARY KEY,
68 | id_node VARCHAR NOT NULL,
69 | id_parent VARCHAR NOT NULL,
70 | UNIQUE (id_node, id_parent)
71 | );
72 | CREATE INDEX nodes_id_node on nodes_nodes(id_node);
73 | CREATE INDEX nodes_id_parent on nodes_nodes(id_parent);'
74 | );
75 | }
76 |
77 | ORM::configure("sqlite:$cacheDir/$email.db");
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/CloudDrive/CloudDrive.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/10/15
5 | * Time: 3:38 PM
6 | */
7 |
8 | namespace CloudDrive;
9 |
10 | use GuzzleHttp\Client;
11 |
12 | /**
13 | * Class that handles all communication for accessing and altering nodes,
14 | * retrieving account information, and managing the local cache store.
15 | *
16 | * @package CloudDrive
17 | */
18 | class CloudDrive
19 | {
20 | /**
21 | * @var \CloudDrive\Account
22 | */
23 | private $account;
24 |
25 | /**
26 | * @var \GuzzleHttp\Client
27 | */
28 | private $httpClient;
29 |
30 | /**
31 | * @var string
32 | */
33 | private $clientId;
34 |
35 | /**
36 | * @var string
37 | */
38 | private $clientSecret;
39 |
40 | /**
41 | * @var string
42 | */
43 | private $email;
44 |
45 | /**
46 | * Construct a new instance of `CloudDrive`. This handles all communication
47 | * for accessing and altering nodes, retrieving account information, and
48 | * managing the local cache store.
49 | *
50 | * @param string $email The email for the account to connect to
51 | * @param string $clientId Amazon CloudDrive API client ID credential
52 | * @param string $clientSecret Amazon CloudDrive API client secret credential
53 | * @param Cache $cacheStore Local cache storage object
54 | * @param Account|null $account `Account` object. If not passed in, this will
55 | * be created using the email and credentials used
56 | * here
57 | */
58 | public function __construct($email, $clientId, $clientSecret, Cache $cacheStore, Account $account = null)
59 | {
60 | $this->email = $email;
61 | $this->clientId = $clientId;
62 | $this->clientSecret = $clientSecret;
63 | $this->httpClient = new Client();
64 |
65 | if (is_null($account)) {
66 | $account = new Account($this->email, $this->clientId, $this->clientSecret, $cacheStore);
67 | }
68 |
69 | $this->account = $account;
70 | }
71 |
72 | /**
73 | * Recursively create a remote directory path. If parts of the path already
74 | * exist, it will continue until the entire path exists.
75 | *
76 | * @param string $path The directory path to create
77 | *
78 | * @return array
79 | * @throws \Exception
80 | */
81 | public function createDirectoryPath($path)
82 | {
83 | $retval = [
84 | 'success' => true,
85 | 'data' => [],
86 | 'resonse_code' => null,
87 | ];
88 |
89 | $path = $this->getPathArray($path);
90 | $previousNode = Node::loadRoot();
91 |
92 | $match = null;
93 | foreach ($path as $index => $folder) {
94 | $xary = array_slice($path, 0, $index + 1);
95 | if (!($match = Node::loadByPath(implode('/', $xary)))) {
96 | $response = $this->createFolder($folder, $previousNode['id']);
97 | if (!$response['success']) {
98 | return $response;
99 | }
100 |
101 | $match = $response['data'];
102 | }
103 |
104 | $previousNode = $match;
105 | }
106 |
107 | if (is_null($match)) {
108 | $retval['data'] = $previousNode;
109 | } else {
110 | $retval['data'] = $match;
111 | }
112 |
113 | return $retval;
114 | }
115 |
116 | /**
117 | * Create a new remote node nested under the provided parents (created under
118 | * root node if none given).
119 | *
120 | * @param string $name Name of the new remote folder
121 | * @param null $parents Parent IDs to give the folder
122 | *
123 | * @return array
124 | * @throws \Exception
125 | */
126 | public function createFolder($name, $parents = null)
127 | {
128 | $retval = [
129 | 'success' => false,
130 | 'data' => [],
131 | 'response_code' => null,
132 | ];
133 |
134 | if (is_null($parents)) {
135 | $parents = Node::loadRoot()['id'];
136 | }
137 |
138 | if (!is_array($parents)) {
139 | $parents = [$parents];
140 | }
141 |
142 | $response = $this->httpClient->post(
143 | "{$this->account->getMetadataUrl()}nodes",
144 | [
145 | 'headers' => [
146 | 'Authorization' => "Bearer {$this->account->getToken()["access_token"]}",
147 | ],
148 | 'json' => [
149 | 'name' => $name,
150 | 'parents' => $parents,
151 | 'kind' => 'FOLDER',
152 | ],
153 | 'exceptions' => false,
154 | ]
155 | );
156 |
157 | $retval['data'] = json_decode((string)$response->getBody(), true);
158 |
159 | if (($retval['response_code'] = $response->getStatusCode()) === 201) {
160 | $retval['success'] = true;
161 | (new Node($retval['data']))->save();
162 | }
163 |
164 | return $retval;
165 | }
166 |
167 | /**
168 | * Retrieve the associated `Account` object.
169 | *
170 | * @return \CloudDrive\Account
171 | */
172 | public function getAccount()
173 | {
174 | return $this->account;
175 | }
176 |
177 | /**
178 | * Convert a given path string into an array of directory names.
179 | *
180 | * @param string|array $path
181 | *
182 | * @return array
183 | */
184 | public function getPathArray($path)
185 | {
186 | if (is_array($path)) {
187 | return $path;
188 | }
189 |
190 | return array_filter(explode('/', $path));
191 | }
192 |
193 | /**
194 | * Properly format a string or array of folders into a path string.
195 | *
196 | * @param string|array $path The remote path to format
197 | *
198 | * @return string
199 | */
200 | public function getPathString($path)
201 | {
202 | if (is_string($path)) {
203 | return trim($path, '/');
204 | }
205 |
206 | return trim(implode('/', $path));
207 | }
208 |
209 | /**
210 | * Determine if a node matching the given path exists remotely. If a local
211 | * path is given, the MD5 will be compared as well.
212 | *
213 | * @param string $remotePath The remote path to check
214 | * @param null|string $localPath Local path of file to compare MD5
215 | *
216 | * @return array
217 | * @throws \Exception'
218 | */
219 | public function nodeExists($remotePath, $localPath = null)
220 | {
221 | if (is_null($file = Node::loadByPath($remotePath))) {
222 | if (!is_null($localPath)) {
223 | if (!empty($nodes = Node::loadByMd5(md5_file($localPath)))) {
224 | $ids = [];
225 | foreach ($nodes as $node) {
226 | $ids[] = $node['id'];
227 | }
228 |
229 | return [
230 | 'success' => true,
231 | 'data' => [
232 | 'message' => "File(s) with same MD5: " . implode(', ', $ids),
233 | 'path_match' => false,
234 | 'md5_match' => true,
235 | 'nodes' => $nodes,
236 | ],
237 | ];
238 | }
239 | }
240 |
241 | return [
242 | 'success' => false,
243 | 'data' => [
244 | 'message' => "File $remotePath does not exist.",
245 | 'path_match' => false,
246 | 'md5_match' => false,
247 | ]
248 | ];
249 | }
250 |
251 | $retval = [
252 | 'success' => true,
253 | 'data' => [
254 | 'message' => "File $remotePath exists.",
255 | 'path_match' => true,
256 | 'md5_match' => false,
257 | 'node' => $file,
258 | ],
259 | ];
260 |
261 | if (!is_null($localPath)) {
262 | if (!is_null($file['contentProperties']['md5'])) {
263 | if (md5_file($localPath) !== $file['contentProperties']['md5']) {
264 | $retval['data']['message'] = "File $remotePath exists but does not match local checksum.";
265 | } else {
266 | $retval['data']['message'] = "File $remotePath exists and is identical to local copy.";
267 | $retval['data']['md5_match'] = true;
268 | }
269 | } else {
270 | $retval['data']['message'] = "File $remotePath exists but no checksum is available.";
271 | }
272 | }
273 |
274 | return $retval;
275 | }
276 |
277 | /**
278 | * Upload a local directory to Amazon Cloud Drive.
279 | *
280 | * @param string $localPath Local path of directory to upload
281 | * @param string $remoteFolder Remote folder to place the directory in
282 | * @param bool $overwrite Flag to overwrite files if they exist remotely
283 | * @param callable|null $callback Callable to perform after each file upload
284 | *
285 | * @return array
286 | * @throws \Exception
287 | */
288 | public function uploadDirectory($localPath, $remoteFolder, $overwrite = false, $callback = null)
289 | {
290 | $localPath = realpath($localPath);
291 |
292 | $remoteFolder = $this->getPathArray($remoteFolder);
293 | $tmp = $this->getPathArray($localPath);
294 | $remoteFolder[] = array_pop($tmp);
295 | $remoteFolder = $this->getPathString($remoteFolder);
296 |
297 | $retval = [];
298 |
299 | $files = new \RecursiveIteratorIterator(
300 | new \RecursiveDirectoryIterator($localPath),
301 | \RecursiveIteratorIterator::SELF_FIRST
302 | );
303 |
304 | foreach ($files as $name => $file) {
305 | if (is_dir($file)) {
306 | continue;
307 | }
308 |
309 | $info = pathinfo($file);
310 | $remotePath = str_replace($localPath, $remoteFolder, $info['dirname']);
311 |
312 | $attempts = 0;
313 | while (true) {
314 | if ($attempts > 1) {
315 | throw new \Exception(
316 | "Failed to upload file '{$file->getPathName()}' after reauthentication. " .
317 | "Upload may take longer than the access token is valid for."
318 | );
319 | }
320 |
321 | $response = $this->uploadFile($file->getPathname(), $remotePath, $overwrite);
322 |
323 | if ($response['success'] === false && $response['response_code'] === 401) {
324 | $auth = $this->account->authorize();
325 | if ($auth['success'] === false) {
326 | throw new \Exception("Failed to renew account authorization.");
327 | }
328 |
329 | $attempts++;
330 | continue;
331 | }
332 |
333 | break;
334 | }
335 |
336 | if (is_callable($callback)) {
337 | call_user_func($callback, $response, [
338 | 'file' => $file,
339 | 'local_path' => $localPath,
340 | 'name' => $name,
341 | 'remote_folder' => $remoteFolder,
342 | 'remote_path' => $remotePath,
343 | ]);
344 | }
345 |
346 | $retval[] = $response;
347 | }
348 |
349 | return $retval;
350 | }
351 |
352 | /**
353 | * Upload a single file to Amazon Cloud Drive.
354 | *
355 | * @param string $localPath The local path to the file to upload
356 | * @param string $remotePath The remote folder to upload the file to
357 | * @param bool|false $overwrite Whether to overwrite the file if it already
358 | * exists remotely
359 | * @param bool $suppressDedup Disables checking for duplicates when uploading
360 | *
361 | * @return array
362 | */
363 | public function uploadFile($localPath, $remotePath, $overwrite = false, $suppressDedup = false)
364 | {
365 | $retval = [
366 | 'success' => false,
367 | 'data' => [],
368 | 'response_code' => null,
369 | ];
370 |
371 | $info = pathinfo($localPath);
372 | $remotePath = $this->getPathString($this->getPathArray($remotePath));
373 |
374 | if (!($remoteFolder = Node::loadByPath($remotePath))) {
375 | $response = $this->createDirectoryPath($remotePath);
376 | if ($response['success'] === false) {
377 | return $response;
378 | }
379 |
380 | $remoteFolder = $response['data'];
381 | }
382 |
383 | $response = $this->nodeExists("$remotePath/{$info['basename']}", $localPath);
384 | if ($response['success'] === true) {
385 | $pathMatch = $response['data']['path_match'];
386 | $md5Match = $response['data']['md5_match'];
387 |
388 | if ($pathMatch === true && $md5Match === true) {
389 | // Skip if path and MD5 match
390 | $retval['data'] = $response['data'];
391 |
392 | return $retval;
393 | } else if ($pathMatch === true && $md5Match === false) {
394 | // If path is the same and checksum differs, only overwrite
395 | if ($overwrite === true) {
396 | return $response['data']['node']->overwrite($localPath);
397 | }
398 |
399 | $retval['data'] = $response['data'];
400 |
401 | return $retval;
402 | } else if ($pathMatch === false && $md5Match === true) {
403 | // If path differs and checksum is the same, check for dedup
404 | if ($suppressDedup === false) {
405 | $retval['data'] = $response['data'];
406 |
407 | return $retval;
408 | }
409 | }
410 | }
411 |
412 | $suppressDedup = $suppressDedup ? '?suppress=deduplication' : '';
413 |
414 | $response = $this->httpClient->post(
415 | "{$this->account->getContentUrl()}nodes{$suppressDedup}",
416 | [
417 | 'headers' => [
418 | 'Authorization' => "Bearer {$this->account->getToken()['access_token']}",
419 | ],
420 | 'multipart' => [
421 | [
422 | 'name' => 'metadata',
423 | 'contents' => json_encode(
424 | [
425 | 'kind' => 'FILE',
426 | 'name' => $info['basename'],
427 | 'parents' => [
428 | $remoteFolder['id'],
429 | ]
430 | ]
431 | ),
432 | ],
433 | [
434 | 'name' => 'contents',
435 | 'contents' => fopen($localPath, 'r'),
436 | ],
437 | ],
438 | 'exceptions' => false,
439 | ]
440 | );
441 |
442 | $retval['data'] = json_decode((string)$response->getBody(), true);
443 | $retval['response_code'] = $response->getStatusCode();
444 |
445 | if (($retval['response_code'] = $response->getStatusCode()) === 201) {
446 | $retval['success'] = true;
447 | (new Node($retval['data']))->save();
448 | }
449 |
450 | return $retval;
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/CatCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/13/15
5 | * Time: 12:55 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class CatCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('cat')
18 | ->setDescription('Output a file to the standard output stream')
19 | ->addArgument('remote_path', InputArgument::REQUIRED, 'The remote file path to download')
20 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->initOnlineCommand();
26 |
27 | $remotePath = $this->input->getArgument('remote_path');
28 |
29 | if ($this->input->getOption('id')) {
30 | if (!($node = Node::loadById($remotePath))) {
31 | throw new \Exception("No node exists with ID '$remotePath'.");
32 | }
33 | } else {
34 | if (!($node = Node::loadByPath($remotePath))) {
35 | throw new \Exception("No node exists at remote path '$remotePath'.");
36 | }
37 | }
38 |
39 | if ($node->isFolder()) {
40 | throw new \Exception("Folder downloads are not currently supported.");
41 | }
42 |
43 | $node->download($this->output->getStream());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/ClearCacheCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/11/15
5 | * Time: 2:18 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | class ClearCacheCommand extends Command
11 | {
12 | protected function configure()
13 | {
14 | $this->setName('clearcache')
15 | ->setAliases([
16 | 'clear-cache',
17 | ])
18 | ->setDescription('Clear the local cache');
19 | }
20 |
21 | protected function main()
22 | {
23 | $this->init();
24 | $this->clouddrive->getAccount()->clearCache();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/Command.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/10/15
5 | * Time: 5:36 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use Cilex\Command\Command as CilexCommand;
11 | use CloudDrive\Cache\MySQL;
12 | use CloudDrive\Cache\SQLite;
13 | use CloudDrive\CloudDrive;
14 | use CloudDrive\Node;
15 | use Symfony\Component\Console\Formatter\OutputFormatterStyle;
16 | use Symfony\Component\Console\Input\InputInterface;
17 | use Symfony\Component\Console\Output\OutputInterface;
18 | use Utility\ParameterBag;
19 |
20 | abstract class Command extends CilexCommand
21 | {
22 | /**
23 | * @var \CloudDrive\Cache
24 | */
25 | protected $cacheStore;
26 |
27 | /**
28 | * @var \CloudDrive\CloudDrive
29 | */
30 | protected $clouddrive;
31 |
32 | /**
33 | * @var \Utility\ParameterBag
34 | */
35 | protected $config;
36 |
37 | /**
38 | * @var string
39 | */
40 | protected $configFile;
41 |
42 | /**
43 | * @var string
44 | */
45 | protected $configPath;
46 |
47 | /**
48 | * Default and accepted values for the CLI config
49 | *
50 | * @var array
51 | */
52 | protected $configValues = [
53 | 'email' => [
54 | 'type' => 'string',
55 | 'default' => '',
56 | ],
57 | 'client-id' => [
58 | 'type' => 'string',
59 | 'default' => '',
60 | ],
61 | 'client-secret' => [
62 | 'type' => 'string',
63 | 'default' => '',
64 | ],
65 | 'json.pretty' => [
66 | 'type' => 'bool',
67 | 'default' => false,
68 | ],
69 | 'upload.duplicates' => [
70 | 'type' => 'bool',
71 | 'default' => false,
72 | ],
73 | 'database.driver' => [
74 | 'type' => 'string',
75 | 'default' => 'sqlite',
76 | ],
77 | 'database.database' => [
78 | 'type' => 'string',
79 | 'default' => 'clouddrive_php',
80 | ],
81 | 'database.host' => [
82 | 'type' => 'string',
83 | 'default' => '127.0.0.1',
84 | ],
85 | 'database.username' => [
86 | 'type' => 'string',
87 | 'default' => 'root',
88 | ],
89 | 'database.password' => [
90 | 'type' => 'string',
91 | 'default' => '',
92 | ],
93 | 'display.trash' => [
94 | 'type' => 'bool',
95 | 'default' => false,
96 | ],
97 | ];
98 |
99 | /**
100 | * @var \Symfony\Component\Console\Input\InputInterface
101 | */
102 | protected $input;
103 |
104 | /**
105 | * @var bool
106 | */
107 | protected $onlineCommand = true;
108 |
109 | /**
110 | * @var \Symfony\Component\Console\Output\ConsoleOutput
111 | */
112 | protected $output;
113 |
114 | const SORT_BY_NAME = 0;
115 | const SORT_BY_TIME = 1;
116 |
117 | protected function convertFilesize($bytes, $decimals = 2)
118 | {
119 | $bytes = $bytes ?: 0;
120 | $size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
121 | $factor = floor((strlen($bytes) - 1) / 3);
122 |
123 | return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$size[$factor];
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | protected function execute(InputInterface $input, OutputInterface $output)
130 | {
131 | $home = getenv('HOME');
132 | if (!$home) {
133 | throw new \RuntimeException("'HOME' environment variable must be set for Cloud Drive to properly run.");
134 | }
135 |
136 | $this->configPath = rtrim($home, '/') . '/.cache/clouddrive-php/';
137 | if (!file_exists($this->configPath)) {
138 | mkdir($this->configPath, 0777, true);
139 | }
140 |
141 | $this->configFile = "{$this->configPath}config.json";
142 |
143 | $this->input = $input;
144 | $this->output = $output;
145 |
146 | // Set up basic styling
147 | $this->output->getFormatter()->setStyle('blue', new OutputFormatterStyle('blue'));
148 |
149 | $this->readConfig();
150 | $this->main();
151 | }
152 |
153 | protected function generateCacheStore()
154 | {
155 | switch ($this->config->get('database.driver')) {
156 | case 'sqlite':
157 | return new SQLite($this->config['email'], $this->configPath);
158 | break;
159 | case 'mysql':
160 | return new MySQL(
161 | $this->config['database.host'],
162 | $this->config['database.database'],
163 | $this->config['database.username'],
164 | $this->config['database.password']
165 | );
166 | break;
167 | }
168 | }
169 |
170 | protected function init()
171 | {
172 | if ($this->onlineCommand === true) {
173 | $this->initOnlineCommand();
174 | } else {
175 | $this->initOfflineCommand();
176 | }
177 | }
178 |
179 | protected function initOfflineCommand()
180 | {
181 | if (count($this->config) === 0) {
182 | throw new \Exception('Account has not been authorized. Please do so using the `init` command.');
183 | }
184 |
185 | $this->cacheStore = $this->generateCacheStore();
186 |
187 | if ($this->config['email']) {
188 | $clouddrive = new CloudDrive(
189 | $this->config['email'],
190 | $this->config['client-id'],
191 | $this->config['client-secret'],
192 | $this->cacheStore
193 | );
194 |
195 | $this->clouddrive = $clouddrive;
196 | Node::init($this->clouddrive->getAccount(), $this->cacheStore);
197 | }
198 | }
199 |
200 | /**
201 | * @throws \Exception
202 | */
203 | protected function initOnlineCommand()
204 | {
205 | if (count($this->config) === 0) {
206 | throw new \Exception('Account has not been authorized. Please do so using the `init` command.');
207 | }
208 |
209 | $this->cacheStore = $this->generateCacheStore();
210 |
211 | if ($this->config['email']) {
212 | $clouddrive = new CloudDrive(
213 | $this->config['email'],
214 | $this->config['client-id'],
215 | $this->config['client-secret'],
216 | $this->cacheStore
217 | );
218 |
219 | if ($this->output->getVerbosity() === 2) {
220 | $this->output->writeln("Authorizing...", OutputInterface::VERBOSITY_VERBOSE);
221 | }
222 | if ($clouddrive->getAccount()->authorize()['success']) {
223 | if ($this->output->getVerbosity() === 2) {
224 | $this->output->writeln("Done.");
225 | }
226 | $this->clouddrive = $clouddrive;
227 | Node::init($this->clouddrive->getAccount(), $this->cacheStore);
228 | } else {
229 | throw new \Exception('Account has not been authorized. Please do so using the `init` command.');
230 | }
231 | }
232 | }
233 |
234 | protected function listNodes(array $nodes, $sortBy = self::SORT_BY_NAME)
235 | {
236 | switch ($sortBy) {
237 | case self::SORT_BY_NAME:
238 | usort($nodes, function ($a, $b) {
239 | return strcasecmp($a['name'], $b['name']);
240 | });
241 | break;
242 | case self::SORT_BY_TIME:
243 | usort($nodes, function ($a, $b) {
244 | return strtotime($a['modifiedDate']) < strtotime($b['modifiedDate']);
245 | });
246 | break;
247 | }
248 |
249 | foreach ($nodes as $node) {
250 | if ($node->inTrash() && !$this->config['display.trash']) {
251 | continue;
252 | }
253 |
254 | $modified = new \DateTime($node['modifiedDate']);
255 | if ($modified->format('Y') === date('Y')) {
256 | $date = $modified->format('M d H:m');
257 | } else {
258 | $date = $modified->format('M d Y');
259 | }
260 |
261 | $name = $node['kind'] === 'FOLDER' ? "{$node['name']}" : $node['name'];
262 | $this->output->writeln(
263 | sprintf(
264 | "%s %s %s %s %s %s",
265 | $node['id'],
266 | $date,
267 | str_pad($node['status'], 10),
268 | str_pad($node['kind'], 7),
269 | str_pad($this->convertFilesize($node['contentProperties']['size'], 0), 6),
270 | $name
271 | )
272 | );
273 | }
274 | }
275 |
276 | abstract protected function main();
277 |
278 | protected function readConfig()
279 | {
280 | $this->config = new ParameterBag();
281 | if (!file_exists($this->configFile) || !($data = json_decode(file_get_contents($this->configFile), true))) {
282 | $data = [];
283 | }
284 |
285 | $this->setConfig($data);
286 | }
287 |
288 | protected function removeConfigValue($key)
289 | {
290 | $this->config[$key] = $this->configValues[$key]['default'];
291 | }
292 |
293 | protected function setConfig(array $data)
294 | {
295 | $data = (new ParameterBag($data))->flatten();
296 | foreach ($this->configValues as $option => $config) {
297 | if (isset($data[$option])) {
298 | $this->setConfigValue($option, $data[$option]);
299 | } else {
300 | $this->setConfigValue($option, $config['default']);
301 | }
302 | }
303 | }
304 |
305 | protected function setConfigValue($key, $value = null)
306 | {
307 | if (array_key_exists($key, $this->configValues)) {
308 | switch ($this->configValues[$key]['type']) {
309 | case 'bool':
310 | $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
311 | break;
312 | }
313 |
314 | settype($value, $this->configValues[$key]['type']);
315 |
316 | $this->config[$key] = $value;
317 | }
318 | }
319 |
320 | protected function saveConfig()
321 | {
322 | file_put_contents("{$this->configPath}config.json", json_encode($this->config));
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/ConfigCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/12/15
5 | * Time: 2:34 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use Symfony\Component\Console\Input\InputArgument;
11 |
12 | class ConfigCommand extends Command
13 | {
14 | protected function configure()
15 | {
16 | $this->setName('config')
17 | ->setDescription('Read, write, and remove config options')
18 | ->addArgument('option', InputArgument::OPTIONAL, 'Config option to read, write, or remove')
19 | ->addArgument('value', InputArgument::OPTIONAL, 'Value to set to config option')
20 | ->addOption('remove', 'r', null, 'Remove config value');
21 | }
22 |
23 | protected function main()
24 | {
25 | if (!($option = $this->input->getArgument('option'))) {
26 | $maxLength = max(
27 | array_map('strlen', array_keys($this->config->flatten()))
28 | );
29 | foreach ($this->config->flatten() as $key => $value) {
30 | if ($this->configValues[$key]['type'] === 'bool') {
31 | $value = $value ? 'true' : 'false';
32 | }
33 |
34 | $key = str_pad($key, $maxLength);
35 |
36 | $this->output->writeln("$key = $value");
37 | }
38 | } else {
39 | if (!array_key_exists($option, $this->configValues)) {
40 | throw new \Exception("Option '$option' not found.");
41 | }
42 |
43 | if ($value = $this->input->getArgument('value')) {
44 | $this->setConfigValue($option, $value);
45 | $this->output->writeln("$option saved");
46 | } else {
47 | if ($this->input->getOption('remove')) {
48 | $this->removeConfigValue($option);
49 | } else {
50 | $value = $this->config[$option];
51 | if ($this->configValues[$option]['type'] === 'bool') {
52 | $value = $value ? 'true' : 'false';
53 | }
54 |
55 | $this->output->writeln($value);
56 | }
57 | }
58 | }
59 |
60 | $this->saveConfig();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/DiskUsageCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/15/15
5 | * Time: 10:14 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class DiskUsageCommand extends Command
14 | {
15 | protected $onlineCommand = false;
16 |
17 | protected function configure()
18 | {
19 | $this->setName('du')
20 | ->setDescription('Display disk usage (recursively) for the given node')
21 | ->addArgument('path', InputArgument::OPTIONAL, 'The remote path of the node')
22 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path')
23 | ->addOption('assets', 'a', null, 'Include assets in output');
24 | }
25 |
26 | protected function main()
27 | {
28 | $this->init();
29 |
30 | $path = $this->input->getArgument('path') ?: '';
31 |
32 | if ($this->input->getOption('id')) {
33 | if (!($node = Node::loadById($path))) {
34 | throw new \Exception("No node exists with ID '$path'.");
35 | }
36 | } else {
37 | if (!($node = Node::loadByPath($path))) {
38 | throw new \Exception("No node exists at remote path '$path'.");
39 | }
40 | }
41 |
42 | $this->output->writeln($this->convertFilesize($this->calculateTotalSize($node)));
43 | }
44 |
45 | protected function calculateTotalSize(Node $node)
46 | {
47 | $size = $node['contentProperties']['size'] ?: 0;
48 |
49 | if ($node->isFolder() || $this->input->getOption('assets')) {
50 | foreach ($node->getChildren() as $child) {
51 | $size += $this->calculateTotalSize($child);
52 | }
53 | }
54 |
55 | return $size;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/DownloadCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/17/15
5 | * Time: 9:30 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class DownloadCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('download')
18 | ->setDescription('Download remote file or folder to specified local path')
19 | ->addArgument('remote_path', InputArgument::REQUIRED, 'The remote file path to download')
20 | ->addArgument('local_path', InputArgument::OPTIONAL, 'The path to save the file / folder to')
21 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path');
22 | }
23 |
24 | protected function main()
25 | {
26 | $this->init();
27 |
28 | $remotePath = $this->input->getArgument('remote_path');
29 | $savePath = $this->input->getArgument('local_path') ?: getcwd();
30 |
31 | if ($this->input->getOption('id')) {
32 | if (!($node = Node::loadById($remotePath))) {
33 | throw new \Exception("No node exists with ID '$remotePath'.");
34 | }
35 | } else {
36 | if (!($node = Node::loadByPath($remotePath))) {
37 | throw new \Exception("No node exists at remote path '$remotePath'.");
38 | }
39 | }
40 |
41 | $node->download($savePath, function ($result, $dest) {
42 | if ($result['success']) {
43 | $this->output->writeln("Successfully downloaded file to '$dest'");
44 | } else {
45 | $this->output->getErrorOutput()
46 | ->writeln("Failed to download node to '$dest'");
47 | if ($this->output->isVerbose()) {
48 | $this->output->getErrorOutput()->writeln(json_encode($result['data']));
49 | }
50 | }
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/FindCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/6/15
5 | * Time: 1:30 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class FindCommand extends Command
14 | {
15 | protected $onlineCommand = false;
16 |
17 | protected function configure()
18 | {
19 | $this->setName('find')
20 | ->setDescription('Find nodes by name or MD5 checksum')
21 | ->addArgument('query', InputArgument::REQUIRED, 'Query string to search for')
22 | ->addOption('md5', 'm', null, 'Search for nodes by MD5 rather than name')
23 | ->addOption('time', 't', null, 'Order output by date modified');
24 | }
25 |
26 | protected function main()
27 | {
28 | $this->init();
29 |
30 | $query = $this->input->getArgument('query');
31 | if ($this->input->getOption('md5')) {
32 | $nodes = Node::loadByMd5($query);
33 | } else {
34 | $nodes = Node::searchNodesByName($query);
35 | }
36 |
37 | $sort = Command::SORT_BY_NAME;
38 | if ($this->input->getOption('time')) {
39 | $sort = Command::SORT_BY_TIME;
40 | }
41 |
42 | $this->listNodes($nodes, $sort);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/InitCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/11/15
5 | * Time: 10:04 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\CloudDrive;
11 |
12 | class InitCommand extends Command
13 | {
14 | protected function configure()
15 | {
16 | $this->setName('init')
17 | ->setDescription('Initialize the command line application for use with an Amazon account');
18 | }
19 |
20 | protected function main()
21 | {
22 | if (!file_exists($this->configPath)) {
23 | mkdir($this->configPath, 0777, true);
24 | }
25 |
26 | $this->readConfig();
27 |
28 | if (!$this->config['email']) {
29 | throw new \Exception('Email is required for initialization.');
30 | }
31 |
32 | $this->saveConfig();
33 |
34 | $this->cacheStore = $this->generateCacheStore();
35 | $this->clouddrive = new CloudDrive(
36 | $this->config['email'],
37 | $this->config['client-id'],
38 | $this->config['client-secret'],
39 | $this->cacheStore
40 | );
41 |
42 | $response = $this->clouddrive->getAccount()->authorize();
43 | if (!$response['success']) {
44 | $this->output->writeln($response['data']['message']);
45 | if (isset($response['data']['auth_url'])) {
46 | $this->output->writeln('Navigate to the following URL and paste in the redirect URL here.');
47 | $this->output->writeln($response['data']['auth_url']);
48 |
49 | $redirectUrl = readline();
50 |
51 | $response = $this->clouddrive->getAccount()->authorize($redirectUrl);
52 | if ($response['success']) {
53 | $this->output->writeln('Successfully authenticated with Amazon Cloud Drive.');
54 | return;
55 | }
56 |
57 | $this->output->getErrorOutput()->writeln(
58 | 'Failed to authenticate with Amazon Cloud Drive: ' . json_encode($response['data']) . ''
59 | );
60 | }
61 | } else {
62 | $this->output->writeln('That user is already authenticated with Amazon Cloud Drive.');
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/ListCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/11/15
5 | * Time: 3:22 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class ListCommand extends Command
14 | {
15 | protected $onlineCommand = false;
16 |
17 | protected function configure()
18 | {
19 | $this->setName('ls')
20 | ->setDescription('List all remote nodes inside of a specified directory')
21 | ->addArgument('remote_path', InputArgument::OPTIONAL, 'The remote directory to list')
22 | ->addOption('time', 't', null, 'Order output by date modified')
23 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path')
24 | ->addOption('assets', 'a', null, "List node's assets if requesting a FILE node");
25 | }
26 |
27 | protected function main()
28 | {
29 | $this->init();
30 | $this->clouddrive->getAccount()->authorize();
31 |
32 | $remotePath = $this->input->getArgument('remote_path') ?: '';
33 |
34 | $sort = Command::SORT_BY_NAME;
35 | if ($this->input->getOption('time')) {
36 | $sort = Command::SORT_BY_TIME;
37 | }
38 |
39 | if ($this->input->getOption('id')) {
40 | if (!($node = Node::loadById($remotePath))) {
41 | throw new \Exception("No node exists with ID '$remotePath'.");
42 | }
43 | } else {
44 | if (!($node = Node::loadByPath($remotePath))) {
45 | throw new \Exception("No node exists at remote path '$remotePath'.");
46 | }
47 | }
48 |
49 | if ($node->isFolder() || $this->input->getOption('assets')) {
50 | $this->listNodes($node->getChildren(), $sort);
51 | } else {
52 | $this->listNodes([$node], $sort);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/ListPendingCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 9/3/15
5 | * Time: 5:28 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 |
12 | class ListPendingCommand extends Command
13 | {
14 | protected $onlineCommand = false;
15 |
16 | protected function configure()
17 | {
18 | $this->setName('pending')
19 | ->setDescription("List the nodes that have a status of 'PENDING'")
20 | ->addOption('time', 't', null, 'Order output by date modified');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->init();
26 |
27 | $sort = Command::SORT_BY_NAME;
28 | if ($this->input->getOption('time')) {
29 | $sort = Command::SORT_BY_TIME;
30 | }
31 |
32 | $this->listNodes(
33 | Node::filter([
34 | ['status' => 'PENDING'],
35 | ]),
36 | $sort
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/ListTrashCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/5/15
5 | * Time: 3:42 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 |
12 | class ListTrashCommand extends Command
13 | {
14 | protected $onlineCommand = false;
15 |
16 | protected function configure()
17 | {
18 | $this->setName('trash')
19 | ->setDescription('List the nodes that are in trash')
20 | ->addOption('time', 't', null, 'Order output by date modified');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->init();
26 |
27 | $sort = Command::SORT_BY_NAME;
28 | if ($this->input->getOption('time')) {
29 | $sort = Command::SORT_BY_TIME;
30 | }
31 |
32 | $this->listNodes(
33 | Node::filter([
34 | ['status' => 'TRASH'],
35 | ]),
36 | $sort
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/MetadataCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/10/15
5 | * Time: 5:35 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class MetadataCommand extends Command
14 | {
15 | protected $onlineCommand = false;
16 |
17 | protected function configure()
18 | {
19 | $this->setName('metadata')
20 | ->setDescription('Retrieve the metadata (JSON) of a node')
21 | ->addArgument('path', InputArgument::OPTIONAL, 'The remote path of the node')
22 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path');
23 | }
24 |
25 | protected function main()
26 | {
27 | $this->init();
28 |
29 | $path = $this->input->getArgument('path') ?: '';
30 |
31 | if ($this->input->getOption('id')) {
32 | if (!($node = Node::loadById($path))) {
33 | throw new \Exception("No node exists with ID '$path'.");
34 | }
35 | } else {
36 | if (!($node = Node::loadByPath($path))) {
37 | throw new \Exception("No node exists at remote path '$path'.");
38 | }
39 | }
40 |
41 | if ($this->config['json.pretty']) {
42 | $this->output->writeln(json_encode($node, JSON_PRETTY_PRINT));
43 | } else {
44 | $this->output->writeln(json_encode($node));
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/MkdirCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/3/15
5 | * Time: 10:14 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class MkdirCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('mkdir')
18 | ->setDescription('Create a new remote directory given a path')
19 | ->addArgument('remote_path', InputArgument::REQUIRED, 'The remote path to create');
20 | }
21 |
22 | protected function main()
23 | {
24 | $this->init();
25 |
26 | $remotePath = $this->input->getArgument('remote_path');
27 |
28 | if ($node = Node::loadByPath($remotePath)) {
29 | throw new \Exception("Node already exists at remote path '$remotePath'. Make sure it's not in the trash.");
30 | }
31 |
32 | $result = $this->clouddrive->createDirectoryPath($remotePath);
33 |
34 | if (!$result['success']) {
35 | $this->output->writeln("Failed to create remote path '$remotePath': " . json_encode($result['data']));
36 | } else {
37 | $this->output->writeln("Successfully created remote path '$remotePath': " . json_encode($result['data']));
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/MoveCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/6/15
5 | * Time: 12:35 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class MoveCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('mv')
18 | ->setDescription('Move a node to a new remote folder')
19 | ->addArgument('node', InputArgument::REQUIRED, 'Remote node path to move')
20 | ->addArgument('new_path', InputArgument::REQUIRED, 'Remote folder to move node into');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->init();
26 |
27 | $nodePath = $this->input->getArgument('node');
28 | $newPath = $this->input->getArgument('new_path');
29 |
30 | if (!($node = Node::loadByPath($nodePath))) {
31 | throw new \Exception("No node exists at remote path '$nodePath'.");
32 | }
33 |
34 | if (!($newParent = Node::loadByPath($newPath))) {
35 | throw new \Exception("No node exists at remote path '$newPath'.");
36 | }
37 |
38 | $result = $node->move($newParent);
39 | if ($result['success']) {
40 | $this->output->writeln(
41 | "Successfully moved node '{$node['name']}' to '$newPath'"
42 | );
43 | if ($this->output->isVerbose()) {
44 | $this->output->writeln(json_encode($result['data']));
45 | }
46 | } else {
47 | $this->output->getErrorOutput()->writeln(
48 | "Failed to move node '{$node['name']}' to '$newPath'"
49 | );
50 | if ($this->output->isVerbose()) {
51 | $this->output->getErrorOutput()->writeln(json_encode($result['data']));
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/QuotaCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/9/15
5 | * Time: 7:59 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | class QuotaCommand extends Command
11 | {
12 | protected function configure()
13 | {
14 | $this->setName('quota')
15 | ->setDescription('Show Cloud Drive quota');
16 | }
17 |
18 | protected function main()
19 | {
20 | $this->init();
21 |
22 | $result = $this->clouddrive->getAccount()->getQuota();
23 | if ($result['success']) {
24 | if ($this->config['json.pretty']) {
25 | $this->output->writeln(json_encode($result['data'], JSON_PRETTY_PRINT));
26 | } else {
27 | $this->output->writeln(json_encode($result['data']));
28 | }
29 | } else {
30 | $this->output->writeln("Failed to retrieve accoutn quota: " . json_encode($result['data']));
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/RenameCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/5/15
5 | * Time: 2:47 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class RenameCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('rename')
18 | ->setDescription('Rename remote node')
19 | ->addArgument('remote_path', InputArgument::REQUIRED, 'Path to remote node')
20 | ->addArgument('name', InputArgument::REQUIRED, 'New name for the node')
21 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path');
22 | }
23 |
24 | protected function main()
25 | {
26 | $this->init();
27 |
28 | $remotePath = $this->input->getArgument('remote_path');
29 | $name = $this->input->getArgument('name');
30 |
31 | if ($this->input->getOption('id')) {
32 | if (!($node = Node::loadById($remotePath))) {
33 | throw new \Exception("No node exists with ID '$remotePath'.");
34 | }
35 | } else {
36 | if (!($node = Node::loadByPath($remotePath))) {
37 | throw new \Exception("No node exists at remote path '$remotePath'.");
38 | }
39 | }
40 |
41 | $result = $node->rename($name);
42 | if ($result['success']) {
43 | $this->output->writeln("Successfully renamed '$remotePath' to '$name': " . json_encode($result['data']));
44 | } else {
45 | $this->output->writeln("Failed to rename '$remotePath' to '$name': " . json_encode($result['data']));
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/RenewCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/17/15
5 | * Time: 11:39 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | class RenewCommand extends Command
11 | {
12 | protected function configure()
13 | {
14 | $this->setName('renew')
15 | ->setDescription('Renew authorization');
16 | }
17 |
18 | protected function main()
19 | {
20 | $this->init();
21 |
22 | $result = $this->clouddrive->getAccount()->renewAuthorization();
23 | if ($result['success']) {
24 | $this->output->writeln("Successfully renewed authorization.");
25 | } else {
26 | $this->output->getErrorOutput()
27 | ->writeln("Failed to renew authorization.");
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/ResolveCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/6/15
5 | * Time: 9:06 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class ResolveCommand extends Command
14 | {
15 | protected $onlineCommand = false;
16 |
17 | protected function configure()
18 | {
19 | $this->setName('resolve')
20 | ->setDescription("Return a node's remote path by its ID")
21 | ->addArgument('id', InputArgument::REQUIRED, 'The ID of the node to resolve');
22 | }
23 |
24 | protected function main()
25 | {
26 | $this->init();
27 |
28 | $id = $this->input->getArgument('id');
29 | if (!($node = Node::loadById($id))) {
30 | throw new \Exception("No node exists with ID '$id'.");
31 | }
32 |
33 | $this->output->writeln($node->getPath());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/RestoreCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/4/15
5 | * Time: 11:42 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class RestoreCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('restore')
18 | ->setDescription('Restore a remote node from the trash')
19 | ->addArgument('remote_path', InputArgument::REQUIRED, 'The remote path of the node')
20 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->init();
26 |
27 | $remotePath = $this->input->getArgument('remote_path');
28 |
29 | if ($this->input->getOption('id')) {
30 | if (!($node = Node::loadById($remotePath))) {
31 | throw new \Exception("No node exists with ID '$remotePath'.");
32 | }
33 | } else {
34 | if (!($node = Node::loadByPath($remotePath))) {
35 | throw new \Exception("No node exists at remote path '$remotePath'.");
36 | }
37 | }
38 |
39 | $result = $node->restore();
40 | if ($result['success']) {
41 | $this->output->writeln("Successfully restored node at '$remotePath': " . json_encode($result['data']));
42 | } else {
43 | $this->output->writeln("Failed to restore node at '$remotePath': " . json_encode($result['data']));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/SyncCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/11/15
5 | * Time: 10:25 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | class SyncCommand extends Command
11 | {
12 | protected function configure()
13 | {
14 | $this->setName('sync')
15 | ->setDescription('Sync the local cache with Amazon Cloud Drive');
16 | }
17 |
18 | protected function main()
19 | {
20 | $this->init();
21 |
22 | $this->output->writeln("Syncing...");
23 | $this->clouddrive->getAccount()->sync();
24 | $this->output->writeln("Done.");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/TempLinkCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/21/15
5 | * Time: 3:49 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class TempLinkCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('link')
18 | ->setDescription('Generate a temporary, pre-authenticated download link')
19 | ->addArgument('remote_path', InputArgument::OPTIONAL, 'The remote directory to list')
20 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->init();
26 |
27 | $remotePath = $this->input->getArgument('remote_path') ?: '';
28 |
29 | if ($this->input->getOption('id')) {
30 | if (!($node = Node::loadById($remotePath))) {
31 | throw new \Exception("No node exists with ID '$remotePath'.");
32 | }
33 | } else {
34 | if (!($node = Node::loadByPath($remotePath))) {
35 | throw new \Exception("No node exists at remote path '$remotePath'.");
36 | }
37 | }
38 |
39 | if ($node->isFolder()) {
40 | throw new \Exception("Links can only be created for files.");
41 | }
42 |
43 | $response = $node->getMetadata(true);
44 | if ($response['success']) {
45 | if (isset($response['data']['tempLink'])) {
46 | $this->output->writeln($response['data']['tempLink']);
47 | } else {
48 | $this->output->getErrorOutput()
49 | ->writeln("Failed retrieving temporary link. Make sure you have permission.");
50 | }
51 | } else {
52 | $this->output->getErrorOutput()
53 | ->writeln("Failed retrieving metadata for node '$remotePath'");
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/TrashCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/4/15
5 | * Time: 11:42 AM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class TrashCommand extends Command
14 | {
15 | protected function configure()
16 | {
17 | $this->setName('rm')
18 | ->setDescription('Move a remote Node to the trash')
19 | ->addArgument('remote_path', InputArgument::REQUIRED, 'The remote path of the node')
20 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->init();
26 |
27 | $remotePath = $this->input->getArgument('remote_path');
28 |
29 | if ($this->input->getOption('id')) {
30 | if (!($node = Node::loadById($remotePath))) {
31 | throw new \Exception("No node exists with ID '$remotePath'.");
32 | }
33 | } else {
34 | if (!($node = Node::loadByPath($remotePath))) {
35 | throw new \Exception("No node exists at remote path '$remotePath'.");
36 | }
37 | }
38 |
39 | $result = $node->trash();
40 | if ($result['success']) {
41 | $this->output->writeln("Successfully trashed node at '$remotePath'");
42 | if ($this->output->isVerbose()) {
43 | $this->output->writeln(json_encode($result['data']));
44 | }
45 | } else {
46 | $this->output->getErrorOutput()->writeln("Failed to trash node at '$remotePath'");
47 | if ($this->output->isVerbose()) {
48 | $this->output->getErrorOutput()
49 | ->writeln(json_encode($result['data']));
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/TreeCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/14/15
5 | * Time: 2:35 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use CloudDrive\Node;
11 | use Symfony\Component\Console\Input\InputArgument;
12 |
13 | class TreeCommand extends Command
14 | {
15 | protected $onlineCommand = false;
16 |
17 | protected function configure()
18 | {
19 | $this->setName('tree')
20 | ->setDescription('Print directory tree of the given node')
21 | ->addArgument('path', InputArgument::OPTIONAL, 'The remote path of the node')
22 | ->addOption('assets', 'a', null, 'Include assets in output')
23 | ->addOption('id', 'i', null, 'Designate the remote node by its ID instead of its remote path')
24 | ->addOption('markdown', 'm', null, 'Output the tree in Markdown');
25 | }
26 |
27 | protected function main()
28 | {
29 | $this->init();
30 |
31 | $path = $this->input->getArgument('path') ?: '';
32 |
33 | $includeAssets = $this->input->getOption('assets') ? true : false;
34 |
35 | if ($this->input->getOption('id')) {
36 | if (!($node = Node::loadById($path))) {
37 | throw new \Exception("No node exists with ID '$path'.");
38 | }
39 | } else {
40 | if (!($node = Node::loadByPath($path))) {
41 | throw new \Exception("No node exists at remote path '$path'.");
42 | }
43 | }
44 |
45 | if ($this->input->getOption('markdown')) {
46 | $this->buildMarkdownTree($node, $includeAssets);
47 | } else {
48 | $this->buildAsciiTree($node, $includeAssets);
49 | }
50 | }
51 |
52 | protected function buildAsciiTree(Node $node, $includeAssets = false, $prefix = '')
53 | {
54 | static $first;
55 | if (is_null($first)) {
56 | $first = false;
57 |
58 | if ($node->isFolder()) {
59 | $this->output->writeln("{$node['name']}");
60 | } else {
61 | $this->output->writeln($node['name']);
62 | }
63 | }
64 |
65 | $children = $node->getChildren();
66 | for ($i = 0, $count = count($children); $i < $count; ++$i) {
67 | $itemPrefix = $prefix;
68 | $next = $children[$i];
69 |
70 | if ($i === $count - 1) {
71 | if ($next->isFolder()) {
72 | $itemPrefix .= '└─┬ ';
73 | } else {
74 | if ($next->isFile() || $includeAssets === true) {
75 | $itemPrefix .= '└── ';
76 | }
77 | }
78 | } else {
79 | if ($next->isFolder()) {
80 | $itemPrefix .= '├─┬ ';
81 | } else {
82 | if ($next->isFile() || $includeAssets === true) {
83 | $itemPrefix .= '├── ';
84 | }
85 | }
86 | }
87 |
88 | if ($next->isFolder()) {
89 | $this->output->writeln(
90 | $itemPrefix . '' . (string)$next['name'] . ''
91 | );
92 | } else {
93 | $this->output->writeln(
94 | $itemPrefix . (string)$next['name']
95 | );
96 | }
97 |
98 | if ($next->isFolder() || $includeAssets === true) {
99 | $this->buildAsciiTree(
100 | $next,
101 | $includeAssets,
102 | $prefix . ($i == $count - 1 ? ' ' : '| ')
103 | );
104 | }
105 | }
106 | }
107 |
108 | protected function buildMarkdownTree(Node $node, $includeAssets = false, $prefix = '')
109 | {
110 | static $first;
111 | if (is_null($first)) {
112 | $first = false;
113 |
114 | if ($node->isFolder()) {
115 | $this->output->writeln("{$node['name']}");
116 | } else {
117 | $this->output->writeln($node['name']);
118 | }
119 | }
120 |
121 | foreach ($node->getChildren() as $node) {
122 | $this->output->writeln("$prefix- {$node['name']}");
123 | if ($node->isFolder() || $includeAssets === true) {
124 | $this->buildMarkdownTree($node, $includeAssets, "$prefix ");
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/UploadCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/11/15
5 | * Time: 1:02 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | use Symfony\Component\Console\Input\InputArgument;
11 |
12 | class UploadCommand extends Command
13 | {
14 | protected function configure()
15 | {
16 | $this->setName('upload')
17 | ->setDescription('Upload local file or folder to remote directory')
18 | ->addArgument('local_path', InputArgument::REQUIRED, 'The location of the local file')
19 | ->addArgument('remote_path', InputArgument::OPTIONAL, 'The remote folder to upload to')
20 | ->addOption('overwrite', 'o', null, 'Overwrite remote file if file exists and does not match local copy');
21 | }
22 |
23 | protected function main()
24 | {
25 | $this->init();
26 |
27 | $overwrite = $this->input->getOption('overwrite') ?: false;
28 |
29 | $source = realpath($this->input->getArgument('local_path'));
30 | $remote = $this->input->getArgument('remote_path') ?: '';
31 |
32 | if (is_dir($source)) {
33 | $this->clouddrive->uploadDirectory($source, $remote, $overwrite, [$this, 'outputResult']);
34 | } else {
35 | $response = $this->clouddrive->uploadFile($source, $remote, $overwrite, $this->config['upload.duplicates']);
36 | $this->outputResult($response, [
37 | 'name' => $source,
38 | ]);
39 | }
40 | }
41 |
42 | public function outputResult($response, $info = null)
43 | {
44 | if ($response['success']) {
45 | $this->output->writeln("Successfully uploaded file '{$info['name']}'");
46 | if ($this->output->isVerbose()) {
47 | $this->output->writeln(json_encode($response));
48 | }
49 | } else {
50 | $this->output->getErrorOutput()
51 | ->writeln("Failed to upload file '{$info['name']}': {$response['data']['message']}");
52 | if ($this->output->isVerbose()) {
53 | $this->output->getErrorOutput()->writeln(json_encode($response));
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/CloudDrive/Commands/UsageCommand.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 8/9/15
5 | * Time: 8:04 PM
6 | */
7 |
8 | namespace CloudDrive\Commands;
9 |
10 | class UsageCommand extends Command
11 | {
12 | protected function configure()
13 | {
14 | $this->setName('usage')
15 | ->setDescription('Show Cloud Drive usage');
16 | }
17 |
18 | protected function main()
19 | {
20 | $this->init();
21 |
22 | $result = $this->clouddrive->getAccount()->getUsage();
23 | if ($result['success']) {
24 | if ($this->config['json.pretty']) {
25 | $this->output->writeln(json_encode($result['data'], JSON_PRETTY_PRINT));
26 | } else {
27 | $this->output->writeln(json_encode($result['data']));
28 | }
29 | } else {
30 | $this->output->getErrorOutput()
31 | ->writeln("Failed to retrieve account quota");
32 | if ($this->output->isVerbose()) {
33 | $this->output->getErrorOutput()
34 | ->writeln(json_encode($result['data']));
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/CloudDrive/Node.php:
--------------------------------------------------------------------------------
1 |
4 | * Date: 7/10/15
5 | * Time: 3:36 PM
6 | */
7 |
8 | namespace CloudDrive;
9 |
10 | use ArrayAccess;
11 | use Countable;
12 | use GuzzleHttp\Client;
13 | use IteratorAggregate;
14 | use JsonSerializable;
15 | use Utility\Traits\Bag;
16 |
17 | /**
18 | * Class representing a remote `Node` object in Amazon's CloudDrive.
19 | *
20 | * @package CloudDrive
21 | */
22 | class Node implements ArrayAccess, IteratorAggregate, JsonSerializable, Countable
23 | {
24 | use Bag {
25 | __construct as constructor;
26 | }
27 |
28 | /**
29 | * Cloud Drive `Account` object
30 | *
31 | * @var \CloudDrive\Account
32 | */
33 | protected static $account;
34 |
35 | /**
36 | * Local `Cache` storage object
37 | *
38 | * @var \CloudDrive\Cache
39 | */
40 | protected static $cacheStore;
41 |
42 | /**
43 | * HTTP client
44 | *
45 | * @var \GuzzleHttp\Client
46 | */
47 | protected static $httpClient;
48 |
49 | /**
50 | * Flag set if the `Node` class has already been initialized
51 | *
52 | * @var bool
53 | */
54 | protected static $initialized = false;
55 |
56 | /**
57 | * Construct a new instance of a remote `Node` object given the metadata
58 | * provided.
59 | *
60 | * @param array $data
61 | *
62 | * @throws \Exception
63 | */
64 | public function __construct($data = [])
65 | {
66 | if (self::$initialized === false) {
67 | throw new \Exception("`Node` class must first be initialized.");
68 | }
69 |
70 | $this->constructor($data);
71 | }
72 |
73 | /**
74 | * Delete a `Node` and its parent associations.
75 | *
76 | * @return bool
77 | */
78 | public function delete()
79 | {
80 | return self::$cacheStore->deleteNodeById($this['id']);
81 | }
82 |
83 | /**
84 | * Download contents of `Node` to local save path. If only the local
85 | * directory is given, the file will be saved as its remote name.
86 | *
87 | * @param resource|string $dest
88 | * @param callable $callback
89 | *
90 | * @return array
91 | * @throws \Exception
92 | */
93 | public function download($dest, $callback = null)
94 | {
95 | if ($this->isFolder()) {
96 | return $this->downloadFolder($dest, $callback);
97 | }
98 |
99 | return $this->downloadFile($dest, $callback);
100 | }
101 |
102 | /**
103 | * Save a FILE node to the specified local destination.
104 | *
105 | * @param resource|string $dest
106 | * @param callable $callback
107 | *
108 | * @return array
109 | * @throws \Exception
110 | */
111 | protected function downloadFile($dest, $callback = null)
112 | {
113 | $retval = [
114 | 'success' => false,
115 | 'data' => [],
116 | ];
117 |
118 | if (is_resource($dest)) {
119 | $handle = $dest;
120 | $metadata = stream_get_meta_data($handle);
121 | $dest = $metadata["uri"];
122 | } else {
123 | $dest = rtrim($dest, '/');
124 |
125 | if (file_exists($dest)) {
126 | if (is_dir($dest)) {
127 | $dest = rtrim($dest, '/') . "/{$this['name']}";
128 | } else {
129 | $retval['data']['message'] = "File already exists at '$dest'.";
130 | if (is_callable($callback)) {
131 | call_user_func($callback, $retval, $dest);
132 | }
133 |
134 | return $retval;
135 | }
136 | }
137 |
138 | $handle = @fopen($dest, 'a');
139 |
140 | if (!$handle) {
141 | throw new \Exception("Unable to open file at '$dest'. Make sure the directory path exists.");
142 | }
143 | }
144 |
145 | $response = self::$httpClient->get(
146 | self::$account->getContentUrl() . "nodes/{$this['id']}/content",
147 | [
148 | 'headers' => [
149 | 'Authorization' => 'Bearer ' . self::$account->getToken()['access_token'],
150 | ],
151 | 'stream' => true,
152 | 'exceptions' => false,
153 | ]
154 | );
155 |
156 | $retval['data'] = json_decode((string)$response->getBody(), true);
157 |
158 | if ($response->getStatusCode() !== 200) {
159 | if (is_callable($callback)) {
160 | call_user_func($callback, $retval, $dest);
161 | }
162 |
163 | return $retval;
164 | }
165 |
166 | $retval['success'] = true;
167 |
168 | $body = $response->getBody();
169 | while (!$body->eof()) {
170 | fwrite($handle, $body->read(1024));
171 | }
172 |
173 | fclose($handle);
174 |
175 | if (is_callable($callback)) {
176 | call_user_func($callback, $retval, $dest);
177 | }
178 |
179 | return $retval;
180 | }
181 |
182 | /**
183 | * Recursively download all children of a FOLDER node to the specified
184 | * local destination.
185 | *
186 | * @param string $dest
187 | * @param callable $callback
188 | *
189 | * @return array
190 | * @throws \Exception
191 | */
192 | protected function downloadFolder($dest, $callback = null)
193 | {
194 | $retval = [
195 | 'success' => false,
196 | 'data' => [],
197 | ];
198 |
199 | if (!is_string($dest)) {
200 | throw new \Exception("Must pass in local path to download directory to.");
201 | }
202 |
203 | $nodes = $this->getChildren();
204 |
205 | $dest = rtrim($dest) . "/{$this['name']}";
206 | if (!file_exists($dest)) {
207 | mkdir($dest);
208 | }
209 |
210 | foreach ($nodes as $node) {
211 | if ($node->isFile()) {
212 | $node->download("{$dest}/{$node['name']}", $callback);
213 | } else if ($node->isFolder()) {
214 | $node->download($dest, $callback);
215 | }
216 | }
217 |
218 | $retval['success'] = true;
219 |
220 | return $retval;
221 | }
222 |
223 | /**
224 | * Search for nodes in the local cache by filters.
225 | *
226 | * @param array $filters
227 | *
228 | * @return array
229 | */
230 | public static function filter(array $filters)
231 | {
232 | return self::$cacheStore->filterNodes($filters);
233 | }
234 |
235 | /**
236 | * Get all children of the given `Node`.
237 | *
238 | * @return array
239 | */
240 | public function getChildren()
241 | {
242 | return self::$cacheStore->getNodeChildren($this);
243 | }
244 |
245 | /**
246 | * Get the MD5 checksum of the `Node`.
247 | *
248 | * @return mixed
249 | */
250 | public function getMd5()
251 | {
252 | return $this['contentProperties']['md5'];
253 | }
254 |
255 | /**
256 | * Retrieve the node's metadata directly from the API.
257 | *
258 | * @param bool|false $tempLink
259 | *
260 | * @return array
261 | */
262 | public function getMetadata($tempLink = false)
263 | {
264 | $retval = [
265 | 'success' => false,
266 | 'data' => [],
267 | ];
268 |
269 | $query = [];
270 |
271 | if ($tempLink) {
272 | $query['tempLink'] = true;
273 | }
274 |
275 | $response = self::$httpClient->get(
276 | self::$account->getMetadataUrl() . "nodes/{$this['id']}",
277 | [
278 | 'headers' => [
279 | 'Authorization' => 'Bearer ' . self::$account->getToken()['access_token'],
280 | ],
281 | 'query' => [
282 | 'tempLink' => $tempLink ? 'true' : 'false',
283 | ],
284 | 'exceptions' => false,
285 | ]
286 | );
287 |
288 | $retval['data'] = json_decode((string)$response->getBody(), true);
289 |
290 | if ($response->getStatusCode() === 200) {
291 | $retval['success'] = true;
292 | }
293 |
294 | return $retval;
295 | }
296 |
297 | /**
298 | * Build and return the remote directory path of the given `Node`.
299 | *
300 | * @return string
301 | * @throws \Exception
302 | */
303 | public function getPath()
304 | {
305 | $node = $this;
306 | $path = [];
307 |
308 | while (true) {
309 | $path[] = $node["name"];
310 | if ($node->isRoot()) {
311 | break;
312 | }
313 |
314 | $node = self::loadById($node["parents"][0]);
315 | if (is_null($node)) {
316 | throw new \Exception("No parent node found with ID {$node['parents'][0]}.");
317 | }
318 |
319 | if ($node->isRoot()) {
320 | break;
321 | }
322 | }
323 |
324 | $path = array_reverse($path);
325 |
326 | return implode('/', $path);
327 | }
328 |
329 | /**
330 | * Set the local storage cache.
331 | *
332 | * @param \CloudDrive\Account $account
333 | * @param \CloudDrive\Cache $cacheStore
334 | *
335 | * @throws \Exception
336 | */
337 | public static function init(Account $account, Cache $cacheStore)
338 | {
339 | if (self::$initialized === true) {
340 | throw new \Exception("`Node` class has already been initialized.");
341 | }
342 |
343 | self::$account = $account;
344 | self::$cacheStore = $cacheStore;
345 | self::$httpClient = new Client();
346 |
347 | self::$initialized = true;
348 | }
349 |
350 | /**
351 | * Return `true` if the node is in the trash.
352 | *
353 | * @return bool
354 | */
355 | public function inTrash()
356 | {
357 | return $this['status'] === 'TRASH';
358 | }
359 |
360 | /**
361 | * Returns whether the `Node` is an asset or not.
362 | *
363 | * @return bool
364 | */
365 | public function isAsset()
366 | {
367 | return $this['kind'] === 'ASSET';
368 | }
369 |
370 | /**
371 | * Returns whether the `Node` is a file or not.
372 | *
373 | * @return bool
374 | */
375 | public function isFile()
376 | {
377 | return $this['kind'] === 'FILE';
378 | }
379 |
380 | /**
381 | * Returns whether the `Node` is a folder or not.
382 | *
383 | * @return bool
384 | */
385 | public function isFolder()
386 | {
387 | return $this['kind'] === 'FOLDER';
388 | }
389 |
390 | /**
391 | * Returns whether the `Node` is the `root` node.
392 | *
393 | * @return bool
394 | */
395 | public function isRoot()
396 | {
397 | return $this['isRoot'] ? true : false;
398 | }
399 |
400 | /**
401 | * Load a `Node` given an ID or remote path.
402 | *
403 | * @param string $param Parameter to find the `Node` by: ID or path
404 | *
405 | * @return \CloudDrive\Node|null
406 | */
407 | public static function load($param)
408 | {
409 | if (!($node = self::loadById($param))) {
410 | $node = self::loadByPath($param);
411 | }
412 |
413 | return $node;
414 | }
415 |
416 | /**
417 | * Find and return the `Node` matching the given ID.
418 | *
419 | * @param int|string $id ID of the node
420 | *
421 | * @return \CloudDrive\Node|null
422 | */
423 | public static function loadById($id)
424 | {
425 | return self::$cacheStore->findNodeById($id);
426 | }
427 |
428 | /**
429 | * Find and return `Nodes` that have the given MD5 checksum.
430 | *
431 | * @param string $md5
432 | *
433 | * @return array
434 | */
435 | public static function loadByMd5($md5)
436 | {
437 | return self::$cacheStore->findNodesByMd5($md5);
438 | }
439 |
440 | /**
441 | * Find all nodes whose name matches the given string.
442 | *
443 | * @param string $name
444 | *
445 | * @return array
446 | */
447 | public static function loadByName($name)
448 | {
449 | return self::$cacheStore->findNodesByName($name);
450 | }
451 |
452 | /**
453 | * Find and return `Node` that matches the given remote path.
454 | *
455 | * @param string $path Remote path of the `Node`
456 | *
457 | * @return \CloudDrive\Node|null
458 | * @throws \Exception
459 | */
460 | public static function loadByPath($path)
461 | {
462 | $path = trim($path, '/');
463 | if (!$path) {
464 | return self::loadRoot();
465 | }
466 |
467 | $info = pathinfo($path);
468 | $nodes = self::loadByName($info['basename']);
469 | if (empty($nodes)) {
470 | return null;
471 | }
472 |
473 | foreach ($nodes as $node) {
474 | if ($node->getPath() === $path) {
475 | return $node;
476 | }
477 | }
478 |
479 | return null;
480 | }
481 |
482 | /**
483 | * Return the root `Node`.
484 | *
485 | * @return \CloudDrive\Node
486 | * @throws \Exception
487 | */
488 | public static function loadRoot()
489 | {
490 | $results = self::loadByName('Cloud Drive');
491 | if (empty($results)) {
492 | throw new \Exception("No node by name 'Cloud Drive' found in the database.");
493 | }
494 |
495 | foreach ($results as $result) {
496 | if ($result->isRoot()) {
497 | return $result;
498 | }
499 | }
500 |
501 | throw new \Exception("Unable to find root node.");
502 | }
503 |
504 | /**
505 | * Move a FILE or FOLDER `Node` to a new remote location.
506 | *
507 | * @param \CloudDrive\Node $newFolder
508 | *
509 | * @return array
510 | * @throws \Exception
511 | */
512 | public function move(Node $newFolder)
513 | {
514 | if (!$newFolder->isFolder()) {
515 | throw new \Exception("New destination node is not a folder.");
516 | }
517 |
518 | if (!$this->isFile() && !$this->isFolder()) {
519 | throw new \Exception("Moving a node can only be performed on FILE and FOLDER kinds.");
520 | }
521 |
522 | $retval = [
523 | 'success' => false,
524 | 'data' => [],
525 | ];
526 |
527 | $response = self::$httpClient->post(
528 | self::$account->getMetadataUrl() . "nodes/{$newFolder['id']}/children",
529 | [
530 | 'headers' => [
531 | 'Authorization' => 'Bearer ' . self::$account->getToken()['access_token'],
532 | ],
533 | 'json' => [
534 | 'fromParent' => $this['parents'][0],
535 | 'childId' => $this['id'],
536 | ],
537 | 'exceptions' => false,
538 | ]
539 | );
540 |
541 | $retval['data'] = json_decode((string)$response->getBody(), true);
542 |
543 | if ($response->getStatusCode() === 200) {
544 | $retval['success'] = true;
545 | $this->replace($retval['data']);
546 | $this->save();
547 | }
548 |
549 | return $retval;
550 | }
551 |
552 | /**
553 | * Replace file contents of the `Node` with the file located at the given
554 | * local path.
555 | *
556 | * @param string $localPath
557 | *
558 | * @return array
559 | */
560 | public function overwrite($localPath)
561 | {
562 | $retval = [
563 | 'success' => false,
564 | 'data' => [],
565 | ];
566 |
567 | $response = self::$httpClient->put(
568 | self::$account->getContentUrl() . "nodes/{$this['id']}/content",
569 | [
570 | 'headers' => [
571 | 'Authorization' => 'Bearer ' . self::$account->getToken()['access_token'],
572 | ],
573 | 'multipart' => [
574 | [
575 | 'name' => 'content',
576 | 'contents' => fopen($localPath, 'r'),
577 | ],
578 | ],
579 | 'exceptions' => false,
580 | ]
581 | );
582 |
583 | $retval['data'] = json_decode((string)$response->getBody(), true);
584 |
585 | if ($response->getStatusCode() === 200) {
586 | $retval['success'] = true;
587 | }
588 |
589 | return $retval;
590 | }
591 |
592 | /**
593 | * Modify the name of a remote `Node`.
594 | *
595 | * @param string $name
596 | *
597 | * @return array
598 | */
599 | public function rename($name)
600 | {
601 | $retval = [
602 | 'success' => false,
603 | 'data' => [],
604 | ];
605 |
606 | $response = self::$httpClient->patch(
607 | self::$account->getMetadataUrl() . "nodes/{$this['id']}",
608 | [
609 | 'headers' => [
610 | 'Authorization' => 'Bearer ' . self::$account->getToken()['access_token'],
611 | ],
612 | 'json' => [
613 | 'name' => $name,
614 | ],
615 | 'exceptions' => false,
616 | ]
617 | );
618 |
619 | $retval['data'] = json_decode((string)$response->getBody(), true);
620 |
621 | if ($response->getStatusCode() === 200) {
622 | $retval['success'] = true;
623 | $this->replace($retval['data']);
624 | $this->save();
625 | }
626 |
627 | return $retval;
628 | }
629 |
630 | /**
631 | * Restore the `Node` from the trash.
632 | *
633 | * @return array
634 | */
635 | public function restore()
636 | {
637 | $retval = [
638 | 'success' => false,
639 | 'data' => [],
640 | ];
641 |
642 | if ($this['status'] === 'AVAILABLE') {
643 | $retval['data']['message'] = 'Node is already available.';
644 |
645 | return $retval;
646 | }
647 |
648 | $response = self::$httpClient->post(
649 | self::$account->getMetadataUrl() . "trash/{$this['id']}/restore",
650 | [
651 | 'headers' => [
652 | 'Authorization' => 'Bearer ' . self::$account->getToken()['access_token'],
653 | ],
654 | 'exceptions' => false,
655 | ]
656 | );
657 |
658 | $retval['data'] = json_decode((string)$response->getBody(), true);
659 |
660 | if ($response->getStatusCode() === 200) {
661 | $retval['success'] = true;
662 | $this->replace($retval['data']);
663 | $this->save();
664 | }
665 |
666 | return $retval;
667 | }
668 |
669 | /**
670 | * Save the `Node` to the local cache.
671 | *
672 | * @return bool
673 | */
674 | public function save()
675 | {
676 | return self::$cacheStore->saveNode($this);
677 | }
678 |
679 | /**
680 | * Find all nodes that contain a string in the name.
681 | *
682 | * @param string $name
683 | *
684 | * @return array
685 | */
686 | public static function searchNodesByName($name)
687 | {
688 | return self::$cacheStore->searchNodesByName($name);
689 | }
690 |
691 | /**
692 | * Add the `Node` to trash.
693 | *
694 | * @return array
695 | */
696 | public function trash()
697 | {
698 | $retval = [
699 | 'success' => false,
700 | 'data' => [],
701 | ];
702 |
703 | if ($this['status'] === 'TRASH') {
704 | $retval['data']['message'] = 'Node is already in trash.';
705 |
706 | return $retval;
707 | }
708 |
709 | $response = self::$httpClient->put(
710 | self::$account->getMetadataUrl() . "trash/{$this['id']}",
711 | [
712 | 'headers' => [
713 | 'Authorization' => 'Bearer ' . self::$account->getToken()['access_token'],
714 | ],
715 | 'exceptions' => false,
716 | ]
717 | );
718 |
719 | $retval['data'] = json_decode((string)$response->getBody(), true);
720 |
721 | if ($response->getStatusCode() === 200) {
722 | $retval['success'] = true;
723 | $this->replace($retval['data']);
724 | $this->save();
725 | }
726 |
727 | return $retval;
728 | }
729 | }
730 |
--------------------------------------------------------------------------------