1
|
|
|
<?php |
2
|
|
|
namespace phpbu\App\Backup\Sync; |
3
|
|
|
|
4
|
|
|
use Google_Client as GClient; |
5
|
|
|
use Google_Http_MediaFileUpload as GStream; |
6
|
|
|
use Google_Service_Drive as GDrive; |
7
|
|
|
use Google_Service_Drive as GDriveService; |
8
|
|
|
use Google_Service_Drive_DriveFile as GFile; |
9
|
|
|
use phpbu\App\Backup\Collector; |
10
|
|
|
use phpbu\App\Backup\Path; |
11
|
|
|
use phpbu\App\Configuration; |
12
|
|
|
use phpbu\App\Result; |
13
|
|
|
use phpbu\App\Backup\Target; |
14
|
|
|
use phpbu\App\Util; |
15
|
|
|
use Psr\Http\Message\RequestInterface; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Google Drive |
19
|
|
|
* |
20
|
|
|
* @package phpbu |
21
|
|
|
* @subpackage Backup |
22
|
|
|
* @author Sebastian Feldmann <[email protected]> |
23
|
|
|
* @copyright Sebastian Feldmann <[email protected]> |
24
|
|
|
* @license https://opensource.org/licenses/MIT The MIT License (MIT) |
25
|
|
|
* @link http://phpbu.de/ |
26
|
|
|
* @since Class available since Release 3.1.1 |
27
|
|
|
*/ |
28
|
|
|
class GoogleDrive implements Simulator |
29
|
|
|
{ |
30
|
|
|
use Cleanable; |
|
|
|
|
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Google drive service. |
34
|
|
|
* |
35
|
|
|
* @var \Google_Service_Drive |
36
|
|
|
*/ |
37
|
|
|
private $service; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Google json secret file. |
41
|
|
|
* |
42
|
|
|
* @var string |
43
|
|
|
*/ |
44
|
|
|
private $secret; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Google json credentials file. |
48
|
|
|
* |
49
|
|
|
* @var string |
50
|
|
|
*/ |
51
|
|
|
private $access; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Google drive parent folder id. |
55
|
|
|
* |
56
|
|
|
* @var string |
57
|
|
|
*/ |
58
|
|
|
private $parent; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Upload chunk size. |
62
|
|
|
* |
63
|
|
|
* @var int |
64
|
|
|
*/ |
65
|
|
|
private $chunkSize = 1 * 1024 * 1024; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* (non-PHPDoc) |
69
|
|
|
* |
70
|
|
|
* @see \phpbu\App\Backup\Sync::setup() |
71
|
|
|
* @param array $config |
72
|
|
|
* @throws \phpbu\App\Exception |
73
|
|
|
*/ |
74
|
9 |
|
public function setup(array $config) |
75
|
|
|
{ |
76
|
9 |
|
if (!class_exists('\\Google_Client')) { |
77
|
|
|
throw new Exception('google api client not loaded: use composer to install "google/apiclient"'); |
78
|
|
|
} |
79
|
9 |
|
if (!Util\Arr::isSetAndNotEmptyString($config, 'secret')) { |
80
|
1 |
|
throw new Exception('google secret json file is mandatory'); |
81
|
|
|
} |
82
|
8 |
|
if (!Util\Arr::isSetAndNotEmptyString($config, 'access')) { |
83
|
1 |
|
throw new Exception('google credentials json file is mandatory'); |
84
|
|
|
} |
85
|
7 |
|
$this->setupAuthFiles($config); |
86
|
5 |
|
$this->parent = Util\Arr::getValue($config, 'parentId'); |
87
|
|
|
|
88
|
5 |
|
$this->setUpCleanable($config); |
89
|
5 |
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Make sure both google authentication files exist and determine absolute path to them. |
93
|
|
|
* |
94
|
|
|
* @param array $config |
95
|
|
|
* @throws \phpbu\App\Backup\Sync\Exception |
96
|
|
|
*/ |
97
|
7 |
|
private function setupAuthFiles(array $config) |
98
|
|
|
{ |
99
|
7 |
|
$secret = Util\Path::toAbsolutePath($config['secret'], Configuration::getWorkingDirectory()); |
100
|
7 |
|
if (!file_exists($secret)) { |
101
|
1 |
|
throw new Exception(sprintf('google secret json file not found at %s', $secret)); |
102
|
|
|
} |
103
|
6 |
|
$access = Util\Path::toAbsolutePath($config['access'], Configuration::getWorkingDirectory()); |
104
|
6 |
|
if (!file_exists($access)) { |
105
|
1 |
|
throw new Exception(sprintf('google credentials json file not found at %s', $access)); |
106
|
|
|
} |
107
|
5 |
|
$this->secret = $secret; |
108
|
5 |
|
$this->access = $access; |
109
|
5 |
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Execute the Sync |
113
|
|
|
* |
114
|
|
|
* @see \phpbu\App\Backup\Sync::sync() |
115
|
|
|
* @param \phpbu\App\Backup\Target $target |
116
|
|
|
* @param \phpbu\App\Result $result |
117
|
|
|
* @throws \phpbu\App\Backup\Sync\Exception |
118
|
|
|
*/ |
119
|
3 |
|
public function sync(Target $target, Result $result) |
120
|
|
|
{ |
121
|
|
|
try { |
122
|
3 |
|
$service = $this->createDriveService(); |
123
|
3 |
|
$client = $service->getClient(); |
124
|
2 |
|
$client->setDefer(true); |
125
|
|
|
|
126
|
2 |
|
$status = false; |
127
|
2 |
|
$apiResult = false; |
128
|
2 |
|
$apiFile = $this->createFile($target); |
129
|
2 |
|
$request = $service->files->create($apiFile); |
130
|
2 |
|
$stream = $this->createUploadStream($client, $request, $target); |
|
|
|
|
131
|
2 |
|
$handle = fopen($target->getPathname(), "rb"); |
132
|
2 |
|
while (!$status && !feof($handle)) { |
|
|
|
|
133
|
2 |
|
$chunk = fread($handle, $this->chunkSize); |
|
|
|
|
134
|
2 |
|
$status = $stream->nextChunk($chunk); |
135
|
|
|
} |
136
|
2 |
|
fclose($handle); |
|
|
|
|
137
|
2 |
|
$client->setDefer(false); |
138
|
|
|
|
139
|
|
|
/** @var \Google_Service_Drive_DriveFile $apiResult */ |
140
|
2 |
|
if ($status != false) { |
141
|
2 |
|
$apiResult = $status; |
142
|
|
|
} |
143
|
2 |
|
$result->debug(sprintf('upload: done: %s', $apiResult->getId())); |
|
|
|
|
144
|
2 |
|
$this->cleanup($target, $result); |
145
|
1 |
|
} catch (\Exception $e) { |
146
|
1 |
|
throw new Exception($e->getMessage(), null, $e); |
147
|
|
|
} |
148
|
2 |
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Simulate the sync execution. |
152
|
|
|
* |
153
|
|
|
* @param \phpbu\App\Backup\Target $target |
154
|
|
|
* @param \phpbu\App\Result $result |
155
|
|
|
*/ |
156
|
1 |
|
public function simulate(Target $target, Result $result) |
157
|
|
|
{ |
158
|
1 |
|
$result->debug('sync backup to google drive' . PHP_EOL); |
159
|
|
|
|
160
|
1 |
|
$this->isSimulation = true; |
161
|
1 |
|
$this->simulateRemoteCleanup($target, $result); |
162
|
1 |
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Setup google api client and google drive service. |
166
|
|
|
* |
167
|
|
|
* @throws \Google_Exception |
168
|
|
|
*/ |
169
|
|
|
protected function createDriveService() : GDriveService |
170
|
|
|
{ |
171
|
|
|
if (!$this->service) { |
172
|
|
|
$client = new GClient(); |
173
|
|
|
$client->setApplicationName('phpbu'); |
174
|
|
|
$client->setScopes(GDrive::DRIVE); |
175
|
|
|
$client->setAuthConfig($this->secret); |
176
|
|
|
$client->setAccessType('offline'); |
177
|
|
|
$client->setAccessToken($this->getAccessToken()); |
178
|
|
|
|
179
|
|
|
if ($client->isAccessTokenExpired()) { |
180
|
|
|
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken()); |
181
|
|
|
$this->updateAccessToken($client->getAccessToken()); |
182
|
|
|
} |
183
|
|
|
$this->service = new GDriveService($client); |
184
|
|
|
} |
185
|
|
|
return $this->service; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Create google api file. |
190
|
|
|
* |
191
|
|
|
* @param \phpbu\App\Backup\Target $target |
192
|
|
|
* @return \Google_Service_Drive_DriveFile |
193
|
|
|
*/ |
194
|
2 |
|
protected function createFile(Target $target) : GFile |
195
|
|
|
{ |
196
|
2 |
|
$file = new GFile(); |
197
|
2 |
|
$file->setName($target->getFilename()); |
198
|
2 |
|
$file->setParents([$this->parent]); |
199
|
|
|
|
200
|
2 |
|
return $file; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Create google api file deferred upload. |
205
|
|
|
* |
206
|
|
|
* @param \Google_Client $client |
207
|
|
|
* @param \Psr\Http\Message\RequestInterface $request |
208
|
|
|
* @param \phpbu\App\Backup\Target $target |
209
|
|
|
* @return \Google_Http_MediaFileUpload |
210
|
|
|
* @throws \phpbu\App\Exception |
211
|
|
|
*/ |
212
|
|
|
protected function createUploadStream(GClient $client, RequestInterface $request, Target $target) : GStream |
213
|
|
|
{ |
214
|
|
|
$media = new GStream( |
215
|
|
|
$client, |
216
|
|
|
$request, |
217
|
|
|
'application/octet-stream', |
218
|
|
|
null, |
219
|
|
|
true, |
220
|
|
|
$this->chunkSize |
|
|
|
|
221
|
|
|
); |
222
|
|
|
$media->setFileSize($target->getSize()); |
223
|
|
|
|
224
|
|
|
return $media; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Return google credentials. |
229
|
|
|
* |
230
|
|
|
* @return array |
231
|
|
|
*/ |
232
|
|
|
private function getAccessToken() : array |
233
|
|
|
{ |
234
|
|
|
return json_decode(file_get_contents($this->access), true); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Update the access token in the google credentials file. |
239
|
|
|
* |
240
|
|
|
* @param array $accessToken |
241
|
|
|
* @return void |
242
|
|
|
*/ |
243
|
|
|
private function updateAccessToken(array $accessToken) |
244
|
|
|
{ |
245
|
|
|
file_put_contents($this->access, json_encode($accessToken)); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Creates collector for remote cleanup. |
250
|
|
|
* |
251
|
|
|
* @param \phpbu\App\Backup\Target $target |
252
|
|
|
* @return \phpbu\App\Backup\Collector |
253
|
|
|
* @throws \Google_Exception |
254
|
|
|
*/ |
255
|
1 |
|
protected function createCollector(Target $target): Collector |
256
|
|
|
{ |
257
|
1 |
|
return new Collector\GoogleDrive($target, new Path($this->parent), $this->createDriveService()); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
|