|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Archivr; |
|
4
|
|
|
|
|
5
|
|
|
use Archivr\ConnectionAdapter\ConnectionAdapterFactoryContainer; |
|
6
|
|
|
use Archivr\ConnectionAdapter\ConnectionAdapterInterface; |
|
7
|
|
|
use Archivr\ConnectionAdapter\FlysystemConnectionAdapter; |
|
8
|
|
|
use Archivr\Exception\ConfigurationException; |
|
9
|
|
|
use Archivr\Exception\Exception; |
|
10
|
|
|
use Archivr\LockAdapter\ConnectionBasedLockAdapter; |
|
11
|
|
|
use Archivr\LockAdapter\LockAdapterFactoryContainer; |
|
12
|
|
|
use Archivr\LockAdapter\LockAdapterInterface; |
|
13
|
|
|
use League\Flysystem\Adapter\Local; |
|
14
|
|
|
use League\Flysystem\Filesystem; |
|
15
|
|
|
|
|
16
|
|
|
class ArchivR |
|
17
|
|
|
{ |
|
18
|
|
|
use TildeExpansionTrait; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* @var Configuration |
|
22
|
|
|
*/ |
|
23
|
|
|
protected $configuration; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @var ConnectionAdapterFactoryContainer |
|
27
|
|
|
*/ |
|
28
|
|
|
protected $connectionAdapterFactoryContainer; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @var LockAdapterFactoryContainer |
|
32
|
|
|
*/ |
|
33
|
|
|
protected $lockAdapterFactoryContainer; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* @var Vault[] |
|
37
|
|
|
*/ |
|
38
|
|
|
protected $vaults = []; |
|
39
|
|
|
|
|
40
|
|
|
public function __construct(Configuration $configuration) |
|
41
|
|
|
{ |
|
42
|
|
|
$this->configuration = $configuration; |
|
43
|
|
|
$this->connectionAdapterFactoryContainer = new ConnectionAdapterFactoryContainer([ |
|
44
|
|
|
|
|
45
|
|
|
'path' => function(ConnectionConfiguration $connectionConfiguration) |
|
46
|
|
|
{ |
|
47
|
|
|
$path = $connectionConfiguration->getSetting('path'); |
|
48
|
|
|
$path = $this->expandTildePath($path); |
|
49
|
|
|
|
|
50
|
|
|
if (!is_dir($path) || !is_writable($path)) |
|
51
|
|
|
{ |
|
52
|
|
|
throw new ConfigurationException(sprintf('Path "%s" does not exist or is not writable.', $path)); |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
$adapter = new Local($path); |
|
56
|
|
|
$filesystem = new Filesystem($adapter); |
|
57
|
|
|
|
|
58
|
|
|
return new FlysystemConnectionAdapter($filesystem); |
|
59
|
|
|
}, |
|
60
|
|
|
]); |
|
61
|
|
|
$this->lockAdapterFactoryContainer = new LockAdapterFactoryContainer([ |
|
62
|
|
|
|
|
63
|
|
|
'connection' => function(ConnectionConfiguration $connectionConfiguration) |
|
64
|
|
|
{ |
|
65
|
|
|
$connection = $this->getConnection($connectionConfiguration->getTitle()); |
|
66
|
|
|
|
|
67
|
|
|
return new ConnectionBasedLockAdapter($connection); |
|
68
|
|
|
} |
|
69
|
|
|
]); |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
public function getConfiguration(): Configuration |
|
73
|
|
|
{ |
|
74
|
|
|
return $this->configuration; |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
public function getConnectionAdapterFactoryContainer(): AbstractServiceFactoryContainer |
|
78
|
|
|
{ |
|
79
|
|
|
return $this->connectionAdapterFactoryContainer; |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
public function getLockAdapterFactoryContainer(): AbstractServiceFactoryContainer |
|
83
|
|
|
{ |
|
84
|
|
|
return $this->lockAdapterFactoryContainer; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
View Code Duplication |
public function getConnection(string $vaultTitle): ConnectionAdapterInterface |
|
88
|
|
|
{ |
|
89
|
|
|
$connectionConfiguration = $this->configuration->getConnectionConfigurationByTitle($vaultTitle); |
|
90
|
|
|
|
|
91
|
|
|
if ($connectionConfiguration === null) |
|
92
|
|
|
{ |
|
93
|
|
|
throw new Exception(sprintf('Unknown connection title: "%s".', $vaultTitle)); |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
$connection = $this->connectionAdapterFactoryContainer->get($connectionConfiguration->getVaultAdapter(), $connectionConfiguration); |
|
97
|
|
|
|
|
98
|
|
|
if ($connection === null) |
|
99
|
|
|
{ |
|
100
|
|
|
throw new ConfigurationException(sprintf('Unknown connection adapter: "%s".', $connectionConfiguration->getVaultAdapter())); |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
return $connection; |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
View Code Duplication |
public function getLockAdapter(string $vaultTitle): LockAdapterInterface |
|
107
|
|
|
{ |
|
108
|
|
|
$connectionConfiguration = $this->configuration->getConnectionConfigurationByTitle($vaultTitle); |
|
109
|
|
|
|
|
110
|
|
|
if ($connectionConfiguration === null) |
|
111
|
|
|
{ |
|
112
|
|
|
throw new Exception(sprintf('Unknown connection title: "%s".', $vaultTitle)); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
$lockAdapter = $this->lockAdapterFactoryContainer->get($connectionConfiguration->getLockAdapter(), $connectionConfiguration); |
|
116
|
|
|
|
|
117
|
|
|
if ($lockAdapter === null) |
|
118
|
|
|
{ |
|
119
|
|
|
throw new ConfigurationException(sprintf('Unknown lock adapter: "%s".', $connectionConfiguration->getLockAdapter())); |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
return $lockAdapter; |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
public function buildOperationCollection(): OperationCollection |
|
126
|
|
|
{ |
|
127
|
|
|
$return = new OperationCollection(); |
|
128
|
|
|
|
|
129
|
|
|
foreach ($this->getVaults() as $vault) |
|
130
|
|
|
{ |
|
131
|
|
|
$return->append($vault->getOperationCollection()); |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
return $return; |
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
|
/** |
|
138
|
|
|
* @return Vault[] |
|
139
|
|
|
*/ |
|
140
|
|
|
public function getVaults(): array |
|
141
|
|
|
{ |
|
142
|
|
|
return array_map(function(ConnectionConfiguration $connectionConfiguration) { |
|
143
|
|
|
|
|
144
|
|
|
return $this->getVault($connectionConfiguration->getTitle()); |
|
145
|
|
|
|
|
146
|
|
|
}, $this->configuration->getConnectionConfigurations()); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
public function getVault(string $vaultTitle): Vault |
|
150
|
|
|
{ |
|
151
|
|
|
if (!isset($this->vaults[$vaultTitle])) |
|
152
|
|
|
{ |
|
153
|
|
|
$vault = new Vault( |
|
154
|
|
|
$vaultTitle, |
|
155
|
|
|
$this->configuration->getLocalPath(), |
|
156
|
|
|
$this->getConnection($vaultTitle) |
|
157
|
|
|
); |
|
158
|
|
|
$vault->setLockAdapter($this->getLockAdapter($vaultTitle)); |
|
159
|
|
|
$vault->setExclusions($this->configuration->getExclusions()); |
|
160
|
|
|
$vault->setIdentity($this->configuration->getIdentity()); |
|
161
|
|
|
|
|
162
|
|
|
$this->vaults[$vaultTitle] = $vault; |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
return $this->vaults[$vaultTitle]; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
public function synchronize(array $vaultTitles = [], SynchronizationProgressListenerInterface $progressListener = null): OperationResultCollection |
|
169
|
|
|
{ |
|
170
|
|
|
$lastRevision = 0; |
|
171
|
|
|
|
|
172
|
|
|
// fallback to all vaults |
|
173
|
|
|
$vaultTitles = $vaultTitles ?: array_map(function(Vault $vault) { return $vault->getTitle(); }, $this->getVaults()); |
|
174
|
|
|
|
|
175
|
|
|
// acquire all locks and retrieve highest existing revision |
|
176
|
|
|
foreach ($this->getVaults() as $vault) |
|
177
|
|
|
{ |
|
178
|
|
|
// lock is only required for vaults that we want to synchronize with |
|
179
|
|
|
if (in_array($vault->getTitle(), $vaultTitles)) |
|
180
|
|
|
{ |
|
181
|
|
|
$this->waitForLock($vault, Vault::LOCK_SYNC); |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
// highest revision should be build across all vaults |
|
185
|
|
|
$synchronizationList = $vault->loadSynchronizationList(); |
|
186
|
|
|
if ($synchronizationList->getLastSynchronization()) |
|
187
|
|
|
{ |
|
188
|
|
|
$lastRevision = max($lastRevision, $synchronizationList->getLastSynchronization()->getRevision()); |
|
189
|
|
|
} |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
// new revision is one plus the highest existing revision across all vaults |
|
193
|
|
|
$newRevision = $lastRevision + 1; |
|
194
|
|
|
|
|
195
|
|
|
// actual synchronization |
|
196
|
|
|
$return = new OperationResultCollection(); |
|
197
|
|
|
foreach ($vaultTitles as $vaultTitle) |
|
198
|
|
|
{ |
|
199
|
|
|
$return->append($this->getVault($vaultTitle)->synchronize($newRevision, $progressListener)); |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
// release lock at the last moment to further reduce change of deadlocks |
|
203
|
|
|
foreach ($vaultTitles as $vaultTitle) |
|
204
|
|
|
{ |
|
205
|
|
|
$this->getVault($vaultTitle)->getLockAdapter()->releaseLock(Vault::LOCK_SYNC); |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
return $return; |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* @return Synchronization[][] |
|
213
|
|
|
*/ |
|
214
|
|
|
public function buildSynchronizationHistory(): array |
|
215
|
|
|
{ |
|
216
|
|
|
$return = []; |
|
217
|
|
|
|
|
218
|
|
|
foreach ($this->getVaults() as $vault) |
|
219
|
|
|
{ |
|
220
|
|
|
$list = $vault->loadSynchronizationList(); |
|
221
|
|
|
|
|
222
|
|
|
foreach ($list as $synchronization) |
|
223
|
|
|
{ |
|
224
|
|
|
/** @var Synchronization $synchronization */ |
|
225
|
|
|
|
|
226
|
|
|
$return[$synchronization->getRevision()][$vault->getTitle()] = $synchronization; |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
ksort($return); |
|
231
|
|
|
|
|
232
|
|
|
return $return; |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
View Code Duplication |
public function restore(int $toRevision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultCollection |
|
|
|
|
|
|
236
|
|
|
{ |
|
237
|
|
|
$vault = $fromVault ? $this->getVault($fromVault) : $this->getAnyVault(); |
|
238
|
|
|
|
|
239
|
|
|
$this->waitForLock($vault, Vault::LOCK_SYNC); |
|
240
|
|
|
|
|
241
|
|
|
$resultCollection = $vault->restore($toRevision, $progressListener); |
|
242
|
|
|
|
|
243
|
|
|
$vault->getLockAdapter()->releaseLock(Vault::LOCK_SYNC); |
|
244
|
|
|
|
|
245
|
|
|
return $resultCollection; |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
View Code Duplication |
public function dump(string $targetPath, int $revision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultCollection |
|
|
|
|
|
|
249
|
|
|
{ |
|
250
|
|
|
$vault = $fromVault ? $this->getVault($fromVault) : $this->getAnyVault(); |
|
251
|
|
|
|
|
252
|
|
|
$this->waitForLock($vault, Vault::LOCK_SYNC); |
|
253
|
|
|
|
|
254
|
|
|
$resultCollection = $vault->dump($targetPath, $revision, $progressListener); |
|
255
|
|
|
|
|
256
|
|
|
$vault->getLockAdapter()->releaseLock(Vault::LOCK_SYNC); |
|
257
|
|
|
|
|
258
|
|
|
return $resultCollection; |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
|
|
protected function getAnyVault(): Vault |
|
262
|
|
|
{ |
|
263
|
|
|
$vaults = $this->getVaults(); |
|
264
|
|
|
|
|
265
|
|
|
if (empty($vaults)) |
|
266
|
|
|
{ |
|
267
|
|
|
throw new ConfigurationException('No vaults defined.'); |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
return $vaults[0]; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
protected function waitForLock(Vault $vault, string $name) |
|
274
|
|
|
{ |
|
275
|
|
|
while (!$vault->getLockAdapter()->acquireLock($name)) |
|
276
|
|
|
{ |
|
277
|
|
|
sleep(5); |
|
278
|
|
|
} |
|
279
|
|
|
} |
|
280
|
|
|
} |
|
281
|
|
|
|
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.