1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Archivr; |
4
|
|
|
|
5
|
|
|
use Archivr\ConflictHandler\ConflictHandlerFactory; |
6
|
|
|
use Archivr\ConflictHandler\ConflictHandlerInterface; |
7
|
|
|
use Archivr\IndexMerger\IndexMergerFactory; |
8
|
|
|
use Archivr\LockAdapter\LockAdapterFactory; |
9
|
|
|
use Archivr\OperationListBuilder\OperationListBuilderFactory; |
10
|
|
|
use Archivr\StorageAdapter\StorageAdapterFactory; |
11
|
|
|
use Archivr\StorageAdapter\StorageAdapterInterface; |
12
|
|
|
use Archivr\Exception\Exception; |
13
|
|
|
use Archivr\IndexMerger\IndexMergerInterface; |
14
|
|
|
use Archivr\LockAdapter\LockAdapterInterface; |
15
|
|
|
use Archivr\OperationListBuilder\OperationListBuilderInterface; |
16
|
|
|
use Archivr\SynchronizationProgressListener\DummySynchronizationProgressListener; |
17
|
|
|
use Archivr\SynchronizationProgressListener\SynchronizationProgressListenerInterface; |
18
|
|
|
use Ramsey\Uuid\Uuid; |
19
|
|
|
use Symfony\Component\Finder\Finder; |
20
|
|
|
use Symfony\Component\Finder\SplFileInfo; |
21
|
|
|
use Archivr\Operation\OperationInterface; |
22
|
|
|
|
23
|
|
|
class Vault |
24
|
|
|
{ |
25
|
|
|
const METADATA_DIRECTORY_NAME = '.archivr'; |
26
|
|
|
const SYNCHRONIZATION_LIST_FILE_NAME = 'index'; |
27
|
|
|
const LOCK_SYNC = 'sync'; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var ArchivR |
31
|
|
|
*/ |
32
|
|
|
protected $archivr; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var VaultConfiguration |
36
|
|
|
*/ |
37
|
|
|
protected $vaultConfiguration; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var StorageAdapterInterface |
41
|
|
|
*/ |
42
|
|
|
protected $storageAdapter; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var LockAdapterInterface |
46
|
|
|
*/ |
47
|
|
|
protected $lockAdapter; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var IndexMergerInterface |
51
|
|
|
*/ |
52
|
|
|
protected $indexMerger; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var ConflictHandlerInterface |
56
|
|
|
*/ |
57
|
|
|
protected $conflictHandler; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var OperationListBuilderInterface |
61
|
|
|
*/ |
62
|
|
|
protected $operationListBuilder; |
63
|
|
|
|
64
|
|
|
public function __construct(ArchivR $archivR, VaultConfiguration $vaultConfiguration) |
65
|
|
|
{ |
66
|
|
|
$this->archivr = $archivR; |
67
|
|
|
$this->vaultConfiguration = $vaultConfiguration; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
public function getVaultConfiguration(): VaultConfiguration |
71
|
|
|
{ |
72
|
|
|
return $this->vaultConfiguration; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
public function getStorageAdapter(): StorageAdapterInterface |
76
|
|
|
{ |
77
|
|
|
if ($this->storageAdapter === null) |
78
|
|
|
{ |
79
|
|
|
$this->storageAdapter = StorageAdapterFactory::create( |
80
|
|
|
$this->vaultConfiguration->getAdapter(), |
81
|
|
|
$this->vaultConfiguration |
82
|
|
|
); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
return $this->storageAdapter; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
public function getLockAdapter(): LockAdapterInterface |
89
|
|
|
{ |
90
|
|
|
if ($this->lockAdapter === null) |
91
|
|
|
{ |
92
|
|
|
$this->lockAdapter = LockAdapterFactory::create( |
93
|
|
|
$this->vaultConfiguration->getLockAdapter(), |
94
|
|
|
$this->vaultConfiguration, |
95
|
|
|
$this->getStorageAdapter() |
96
|
|
|
); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $this->lockAdapter; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
public function getIndexMerger(): IndexMergerInterface |
103
|
|
|
{ |
104
|
|
|
if ($this->indexMerger === null) |
105
|
|
|
{ |
106
|
|
|
$this->indexMerger = IndexMergerFactory::create( |
107
|
|
|
$this->vaultConfiguration->getIndexMerger() |
108
|
|
|
); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
return $this->indexMerger; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
public function getConflictHandler(): ConflictHandlerInterface |
115
|
|
|
{ |
116
|
|
|
if ($this->conflictHandler === null) |
117
|
|
|
{ |
118
|
|
|
$this->conflictHandler = ConflictHandlerFactory::create( |
119
|
|
|
$this->vaultConfiguration->getConflictHandler() |
120
|
|
|
); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
return $this->conflictHandler; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
public function getOperationListBuilder(): OperationListBuilderInterface |
127
|
|
|
{ |
128
|
|
|
if ($this->operationListBuilder === null) |
129
|
|
|
{ |
130
|
|
|
$this->operationListBuilder = OperationListBuilderFactory::create( |
131
|
|
|
$this->vaultConfiguration->getOperationListBuilder() |
132
|
|
|
); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
return $this->operationListBuilder; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Builds and returns an index representing the current local state. |
140
|
|
|
* |
141
|
|
|
* @return Index |
142
|
|
|
*/ |
143
|
|
|
public function buildLocalIndex(): Index |
144
|
|
|
{ |
145
|
|
|
return $this->doBuildLocalIndex(); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Reads and returns the index representing the local state on the last synchronization. |
150
|
|
|
* |
151
|
|
|
* @return Index |
152
|
|
|
* @throws Exception |
153
|
|
|
*/ |
154
|
|
|
public function loadLastLocalIndex() |
155
|
|
|
{ |
156
|
|
|
$index = null; |
157
|
|
|
$path = $this->getLastLocalIndexFilePath(); |
158
|
|
|
|
159
|
|
|
if (is_file($path)) |
160
|
|
|
{ |
161
|
|
|
$stream = fopen($path, 'rb'); |
162
|
|
|
|
163
|
|
|
$index = $this->readIndexFromStream($stream); |
164
|
|
|
|
165
|
|
|
fclose($stream); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
return $index; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Reads and returns the current remote index. |
173
|
|
|
* |
174
|
|
|
* @param int $revision Revision to load. Defaults to the last revision. |
175
|
|
|
* |
176
|
|
|
* @return Index |
177
|
|
|
*/ |
178
|
|
|
public function loadRemoteIndex(int $revision = null) |
179
|
|
|
{ |
180
|
|
|
$list = null; |
181
|
|
|
|
182
|
|
View Code Duplication |
if ($revision === null) |
|
|
|
|
183
|
|
|
{ |
184
|
|
|
$list = $this->loadSynchronizationList(); |
185
|
|
|
|
186
|
|
|
if (!$list->getLastSynchronization()) |
187
|
|
|
{ |
188
|
|
|
return null; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
$revision = $list->getLastSynchronization()->getRevision(); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return $this->doLoadRemoteIndex($revision, $list); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Computes and returns the index representing the vault state after the local index has been merged with the remote index. |
199
|
|
|
* |
200
|
|
|
* @return Index |
201
|
|
|
*/ |
202
|
|
|
public function buildMergedIndex(): Index |
203
|
|
|
{ |
204
|
|
|
return $this->doBuildMergedIndex(); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Returns ordered list of operations required to synchronize the vault with the local path. |
209
|
|
|
* In addition to the object specific operations contained in the returned OperationList additional operations |
210
|
|
|
* might be necessary like index updates that do not belong to specific index objects. |
211
|
|
|
* |
212
|
|
|
* @return OperationList |
213
|
|
|
*/ |
214
|
|
|
public function getOperationList(): OperationList |
215
|
|
|
{ |
216
|
|
|
$localIndex = $this->buildLocalIndex(); |
217
|
|
|
$lastLocalIndex = $this->loadLastLocalIndex(); |
218
|
|
|
$remoteIndex = $this->loadRemoteIndex(); |
219
|
|
|
|
220
|
|
|
$mergedIndex = $this->doBuildMergedIndex($localIndex, $lastLocalIndex, $remoteIndex); |
221
|
|
|
|
222
|
|
|
return $this->getOperationListBuilder()->buildOperationList($mergedIndex, $localIndex, $remoteIndex); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Synchronizes the local with the remote state by executing all operations returned by getOperationList() |
227
|
|
|
* |
228
|
|
|
* @param int $newRevision |
229
|
|
|
* @param SynchronizationProgressListenerInterface $progressionListener |
230
|
|
|
* |
231
|
|
|
* @return OperationResultList |
232
|
|
|
* @throws Exception |
233
|
|
|
*/ |
234
|
|
|
public function synchronize(int $newRevision = null, SynchronizationProgressListenerInterface $progressionListener = null): OperationResultList |
235
|
|
|
{ |
236
|
|
|
if ($progressionListener === null) |
237
|
|
|
{ |
238
|
|
|
$progressionListener = new DummySynchronizationProgressListener(); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
$localIndex = $this->buildLocalIndex(); |
242
|
|
|
$lastLocalIndex = $this->loadLastLocalIndex(); |
243
|
|
|
|
244
|
|
|
|
245
|
|
|
if (!$this->getLockAdapter()->acquireLock(static::LOCK_SYNC)) |
246
|
|
|
{ |
247
|
|
|
throw new Exception('Failed to acquire lock.'); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
|
251
|
|
|
$synchronizationList = $this->loadSynchronizationList(); |
252
|
|
|
$lastSynchronization = $synchronizationList->getLastSynchronization(); |
253
|
|
|
|
254
|
|
|
if ($lastSynchronization) |
255
|
|
|
{ |
256
|
|
|
$newRevision = $newRevision ?: ($lastSynchronization->getRevision() + 1); |
257
|
|
|
$remoteIndex = $this->doLoadRemoteIndex($lastSynchronization->getRevision(), $synchronizationList); |
258
|
|
|
} |
259
|
|
|
else |
260
|
|
|
{ |
261
|
|
|
$newRevision = $newRevision ?: 1; |
262
|
|
|
$remoteIndex = null; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
$synchronization = new Synchronization($newRevision, $this->generateNewBlobId(), new \DateTime(), $this->archivr->getConfiguration()->getIdentity()); |
266
|
|
|
$synchronizationList->addSynchronization($synchronization); |
267
|
|
|
|
268
|
|
|
// compute merged index |
269
|
|
|
$mergedIndex = $this->doBuildMergedIndex($localIndex, $lastLocalIndex, $remoteIndex); |
270
|
|
|
|
271
|
|
|
$operationList = $this->getOperationListBuilder()->buildOperationList($mergedIndex, $localIndex, $remoteIndex); |
272
|
|
|
|
273
|
|
|
$operationResultList = new OperationResultList(); |
274
|
|
|
|
275
|
|
|
// operation count + |
276
|
|
|
// merged index write + |
277
|
|
|
// copy merged index to vault + |
278
|
|
|
// save merged index as last local index + |
279
|
|
|
// upload synchronization list + |
280
|
|
|
// release lock |
281
|
|
|
$progressionListener->start(count($operationList) + 5); |
282
|
|
|
|
283
|
|
View Code Duplication |
foreach ($operationList as $operation) |
|
|
|
|
284
|
|
|
{ |
285
|
|
|
/** @var OperationInterface $operation */ |
286
|
|
|
|
287
|
|
|
$success = $operation->execute($this->archivr->getConfiguration()->getPath(), $this->getStorageAdapter()); |
288
|
|
|
|
289
|
|
|
$operationResult = new OperationResult($operation, $success); |
290
|
|
|
$operationResultList->addOperationResult($operationResult); |
291
|
|
|
|
292
|
|
|
$progressionListener->advance(); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
// dump new index |
296
|
|
|
$mergedIndexFilePath = tempnam(sys_get_temp_dir(), 'index'); |
297
|
|
|
$this->writeIndexToFile($mergedIndex, $mergedIndexFilePath); |
298
|
|
|
|
299
|
|
|
$progressionListener->advance(); |
300
|
|
|
|
301
|
|
|
// upload new index |
302
|
|
|
$readStream = fopen($mergedIndexFilePath, 'rb'); |
303
|
|
|
$compressionFilter = stream_filter_append($readStream, 'zlib.deflate'); |
304
|
|
|
$this->storageAdapter->writeStream($synchronization->getBlobId(), $readStream); |
305
|
|
|
rewind($readStream); |
306
|
|
|
stream_filter_remove($compressionFilter); |
307
|
|
|
|
308
|
|
|
$progressionListener->advance(); |
309
|
|
|
|
310
|
|
|
// save new index locally |
311
|
|
|
$writeStream = fopen($this->getLastLocalIndexFilePath(), 'wb'); |
312
|
|
|
stream_copy_to_stream($readStream, $writeStream); |
313
|
|
|
fclose($writeStream); |
314
|
|
|
fclose($readStream); |
315
|
|
|
|
316
|
|
|
$progressionListener->advance(); |
317
|
|
|
|
318
|
|
|
// upload new synchronization list |
319
|
|
|
$synchronizationListFilePath = $this->writeSynchronizationListToTemporaryFile($synchronizationList); |
320
|
|
|
$readStream = fopen($synchronizationListFilePath, 'rb'); |
321
|
|
|
stream_filter_append($readStream, 'zlib.deflate'); |
322
|
|
|
$this->storageAdapter->writeStream(static::SYNCHRONIZATION_LIST_FILE_NAME, $readStream); |
323
|
|
|
fclose($readStream); |
324
|
|
|
|
325
|
|
|
// release lock |
326
|
|
|
if (!$this->getLockAdapter()->releaseLock(static::LOCK_SYNC)) |
327
|
|
|
{ |
328
|
|
|
throw new Exception('Failed to release lock.'); |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
$progressionListener->advance(); |
332
|
|
|
$progressionListener->finish(); |
333
|
|
|
|
334
|
|
|
return $operationResultList; |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* Loads and returns the list of synchronizations from the vault. |
339
|
|
|
* |
340
|
|
|
* @return SynchronizationList |
341
|
|
|
*/ |
342
|
|
|
public function loadSynchronizationList(): SynchronizationList |
343
|
|
|
{ |
344
|
|
|
$storageAdapter = $this->getStorageAdapter(); |
345
|
|
|
$list = null; |
|
|
|
|
346
|
|
|
|
347
|
|
|
if ($storageAdapter->exists(static::SYNCHRONIZATION_LIST_FILE_NAME)) |
348
|
|
|
{ |
349
|
|
|
$stream = $storageAdapter->getReadStream(static::SYNCHRONIZATION_LIST_FILE_NAME); |
350
|
|
|
|
351
|
|
|
stream_filter_append($stream, 'zlib.inflate'); |
352
|
|
|
|
353
|
|
|
$list = $this->readSynchronizationListFromStream($stream); |
354
|
|
|
|
355
|
|
|
fclose($stream); |
356
|
|
|
|
357
|
|
|
return $list; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
return new SynchronizationList(); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Restores the local state at the given revision from the vault. |
365
|
|
|
* |
366
|
|
|
* @param int $revision |
367
|
|
|
* @param SynchronizationProgressListenerInterface $progressionListener |
368
|
|
|
* |
369
|
|
|
* @return OperationResultList |
370
|
|
|
* @throws Exception |
371
|
|
|
*/ |
372
|
|
|
public function restore(int $revision = null, SynchronizationProgressListenerInterface $progressionListener = null): OperationResultList |
373
|
|
|
{ |
374
|
|
|
return $this->doRestore($revision, $progressionListener); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* @param string $targetPath |
379
|
|
|
* @param int $revision |
380
|
|
|
* @param SynchronizationProgressListenerInterface|null $progressListener |
381
|
|
|
* |
382
|
|
|
* @return OperationResultList |
383
|
|
|
* @throws \Exception |
384
|
|
|
*/ |
385
|
|
|
public function dump(string $targetPath, int $revision = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList |
386
|
|
|
{ |
387
|
|
|
return $this->doRestore($revision, $progressListener, true, $targetPath); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
protected function doBuildLocalIndex(string $path = null): Index |
391
|
|
|
{ |
392
|
|
|
$finder = new Finder(); |
393
|
|
|
$finder->in($path ?: $this->archivr->getConfiguration()->getPath()); |
394
|
|
|
$finder->ignoreDotFiles(false); |
395
|
|
|
$finder->ignoreVCS(true); |
396
|
|
|
$finder->exclude(static::METADATA_DIRECTORY_NAME); |
397
|
|
|
$finder->notPath('archivr.json'); |
398
|
|
|
|
399
|
|
|
foreach ($this->archivr->getConfiguration()->getExclude() as $path) |
400
|
|
|
{ |
401
|
|
|
$finder->notPath($path); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
$index = new Index(); |
405
|
|
|
|
406
|
|
|
foreach ($finder->directories() as $fileInfo) |
407
|
|
|
{ |
408
|
|
|
/** @var SplFileInfo $fileInfo */ |
409
|
|
|
|
410
|
|
|
$index->addObject(IndexObject::fromPath($this->archivr->getConfiguration()->getPath(), $fileInfo->getRelativePathname())); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
foreach ($finder->files() as $fileInfo) |
414
|
|
|
{ |
415
|
|
|
/** @var SplFileInfo $fileInfo */ |
416
|
|
|
|
417
|
|
|
$index->addObject(IndexObject::fromPath($this->archivr->getConfiguration()->getPath(), $fileInfo->getRelativePathname())); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
return $index; |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
protected function doLoadRemoteIndex(int $revision, SynchronizationList $synchronizationList = null) |
424
|
|
|
{ |
425
|
|
|
if ($synchronizationList === null) |
426
|
|
|
{ |
427
|
|
|
$synchronizationList = $this->loadSynchronizationList(); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
$synchronization = $synchronizationList->getSynchronizationByRevision($revision); |
431
|
|
|
|
432
|
|
|
if (!$synchronization) |
433
|
|
|
{ |
434
|
|
|
return null; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
$index = null; |
438
|
|
|
|
439
|
|
|
if ($this->storageAdapter->exists($synchronization->getBlobId())) |
440
|
|
|
{ |
441
|
|
|
$stream = $this->storageAdapter->getReadStream($synchronization->getBlobId()); |
442
|
|
|
|
443
|
|
|
stream_filter_append($stream, 'zlib.inflate'); |
444
|
|
|
|
445
|
|
|
$index = $this->readIndexFromStream($stream); |
446
|
|
|
|
447
|
|
|
fclose($stream); |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
return $index; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
protected function doBuildMergedIndex(Index $localIndex = null, Index $lastLocalIndex = null, Index $remoteIndex = null) |
454
|
|
|
{ |
455
|
|
|
$localIndex = $localIndex ?: $this->buildLocalIndex(); |
456
|
|
|
$lastLocalIndex = $lastLocalIndex ?: $this->loadLastLocalIndex(); |
457
|
|
|
$remoteIndex = $remoteIndex ?: $this->loadRemoteIndex(); |
458
|
|
|
|
459
|
|
|
if ($remoteIndex === null) |
460
|
|
|
{ |
461
|
|
|
return $localIndex; |
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
return $this->getIndexMerger()->merge($this->getConflictHandler(), $remoteIndex, $localIndex, $lastLocalIndex); |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
protected function doRestore(int $revision = null, SynchronizationProgressListenerInterface $progressionListener = null, bool $skipLastLocalIndexUpdate = false, string $targetPath = null): OperationResultList |
468
|
|
|
{ |
469
|
|
|
if ($progressionListener === null) |
470
|
|
|
{ |
471
|
|
|
$progressionListener = new DummySynchronizationProgressListener(); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
if (!$this->getLockAdapter()->acquireLock(static::LOCK_SYNC)) |
475
|
|
|
{ |
476
|
|
|
throw new Exception('Failed to acquire lock.'); |
477
|
|
|
} |
478
|
|
|
|
479
|
|
View Code Duplication |
if ($revision === null) |
|
|
|
|
480
|
|
|
{ |
481
|
|
|
$synchronizationList = $this->loadSynchronizationList(); |
482
|
|
|
|
483
|
|
|
if (!$synchronizationList->getLastSynchronization()) |
484
|
|
|
{ |
485
|
|
|
throw new Exception('No revision to restore from.'); |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
$revision = $synchronizationList->getLastSynchronization()->getRevision(); |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
$remoteIndex = $this->loadRemoteIndex($revision); |
492
|
|
|
|
493
|
|
|
if ($remoteIndex === null) |
494
|
|
|
{ |
495
|
|
|
throw new Exception("Unknown revision: {$revision}"); |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
$targetPath = $targetPath ?: $this->archivr->getConfiguration()->getPath(); |
499
|
|
|
|
500
|
|
|
$localIndex = $this->doBuildLocalIndex($targetPath); |
501
|
|
|
|
502
|
|
|
$operationList = $this->getOperationListBuilder()->buildOperationList($remoteIndex, $localIndex, $remoteIndex); |
503
|
|
|
|
504
|
|
|
$operationResultList = new OperationResultList(); |
505
|
|
|
|
506
|
|
|
// operation count + |
507
|
|
|
// save merged index as last local index + |
508
|
|
|
// release lock |
509
|
|
|
$progressionListener->start(count($operationList) + 2); |
510
|
|
|
|
511
|
|
View Code Duplication |
foreach ($operationList as $operation) |
|
|
|
|
512
|
|
|
{ |
513
|
|
|
/** @var OperationInterface $operation */ |
514
|
|
|
|
515
|
|
|
$success = $operation->execute($targetPath, $this->getStorageAdapter()); |
516
|
|
|
|
517
|
|
|
$operationResult = new OperationResult($operation, $success); |
518
|
|
|
$operationResultList->addOperationResult($operationResult); |
519
|
|
|
|
520
|
|
|
$progressionListener->advance(); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
if (!$skipLastLocalIndexUpdate) |
524
|
|
|
{ |
525
|
|
|
$this->writeIndexToFile($remoteIndex, $this->getLastLocalIndexFilePath()); |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
$progressionListener->advance(); |
529
|
|
|
|
530
|
|
|
if (!$this->getLockAdapter()->releaseLock(static::LOCK_SYNC)) |
531
|
|
|
{ |
532
|
|
|
throw new Exception('Failed to release lock.'); |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
$progressionListener->advance(); |
536
|
|
|
$progressionListener->finish(); |
537
|
|
|
|
538
|
|
|
return $operationResultList; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
protected function readIndexFromStream($stream): Index |
542
|
|
|
{ |
543
|
|
|
if (!is_resource($stream)) |
544
|
|
|
{ |
545
|
|
|
throw new Exception(); |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
$index = new Index(); |
549
|
|
|
|
550
|
|
|
while (($row = fgetcsv($stream)) !== false) |
551
|
|
|
{ |
552
|
|
|
$index->addObject(IndexObject::fromIndexRecord($row)); |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
return $index; |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
protected function writeIndexToFile(Index $index, string $path) |
559
|
|
|
{ |
560
|
|
|
$stream = fopen($path, 'wb'); |
561
|
|
|
|
562
|
|
|
foreach ($index as $object) |
563
|
|
|
{ |
564
|
|
|
/** @var IndexObject $object */ |
565
|
|
|
|
566
|
|
|
if (fputcsv($stream, $object->getIndexRecord()) === false) |
567
|
|
|
{ |
568
|
|
|
throw new Exception(); |
569
|
|
|
} |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
fclose($stream); |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
protected function readSynchronizationListFromStream($stream): SynchronizationList |
576
|
|
|
{ |
577
|
|
|
if (!is_resource($stream)) |
578
|
|
|
{ |
579
|
|
|
throw new Exception(); |
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
$list = new SynchronizationList(); |
583
|
|
|
|
584
|
|
|
while (($row = fgetcsv($stream)) !== false) |
585
|
|
|
{ |
586
|
|
|
$list->addSynchronization(Synchronization::fromRecord($row)); |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
return $list; |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
protected function writeSynchronizationListToTemporaryFile(SynchronizationList $synchronizationList): string |
593
|
|
|
{ |
594
|
|
|
$path = tempnam(sys_get_temp_dir(), 'synchronizationList'); |
595
|
|
|
$stream = fopen($path, 'wb'); |
596
|
|
|
|
597
|
|
|
foreach ($synchronizationList as $synchronization) |
598
|
|
|
{ |
599
|
|
|
/** @var Synchronization $synchronization */ |
600
|
|
|
|
601
|
|
|
if (fputcsv($stream, $synchronization->getRecord()) === false) |
602
|
|
|
{ |
603
|
|
|
throw new Exception(); |
604
|
|
|
} |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
fclose($stream); |
608
|
|
|
|
609
|
|
|
return $path; |
610
|
|
|
} |
611
|
|
|
|
612
|
|
|
protected function generateNewBlobId(): string |
613
|
|
|
{ |
614
|
|
|
do |
615
|
|
|
{ |
616
|
|
|
$blobId = Uuid::uuid4()->toString(); |
617
|
|
|
} |
618
|
|
|
while ($this->storageAdapter->exists($blobId)); |
619
|
|
|
|
620
|
|
|
return $blobId; |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
protected function initMetadataDirectory(): string |
624
|
|
|
{ |
625
|
|
|
$path = $this->archivr->getConfiguration()->getPath() . static::METADATA_DIRECTORY_NAME; |
626
|
|
|
|
627
|
|
|
if (!is_dir($path)) |
628
|
|
|
{ |
629
|
|
|
if (!mkdir($path)) |
630
|
|
|
{ |
631
|
|
|
throw new Exception(); |
632
|
|
|
} |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
return $path . DIRECTORY_SEPARATOR; |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
protected function getLastLocalIndexFilePath(): string |
639
|
|
|
{ |
640
|
|
|
return $this->initMetadataDirectory() . sprintf('lastLocalIndex-%s', $this->vaultConfiguration->getTitle()); |
641
|
|
|
} |
642
|
|
|
} |
643
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.