|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Storeman; |
|
4
|
|
|
|
|
5
|
|
|
use Storeman\Exception\ConfigurationException; |
|
6
|
|
|
use Storeman\SynchronizationProgressListener\SynchronizationProgressListenerInterface; |
|
7
|
|
|
|
|
8
|
|
|
/** |
|
9
|
|
|
* This class coordinates executions with multiple vaults involved. |
|
10
|
|
|
*/ |
|
11
|
|
|
class Storeman |
|
12
|
|
|
{ |
|
13
|
|
|
/** |
|
14
|
|
|
* @var Configuration |
|
15
|
|
|
*/ |
|
16
|
|
|
protected $configuration; |
|
17
|
|
|
|
|
18
|
|
|
/** |
|
19
|
|
|
* @var Container |
|
20
|
|
|
*/ |
|
21
|
|
|
protected $container; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* @var Vault[] |
|
25
|
|
|
*/ |
|
26
|
|
|
protected $vaults = []; |
|
27
|
|
|
|
|
28
|
|
|
public function __construct(Configuration $configuration, Container $container = null) |
|
29
|
|
|
{ |
|
30
|
|
|
$this->configuration = $configuration; |
|
31
|
|
|
$this->container = $container ?: new Container(); |
|
32
|
|
|
} |
|
33
|
|
|
|
|
34
|
|
|
public function getConfiguration(): Configuration |
|
35
|
|
|
{ |
|
36
|
|
|
return $this->configuration; |
|
37
|
|
|
} |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* Returns the DI container of this storeman instance. |
|
41
|
|
|
* Some service names resolve to different implementations depending on the current vault which can be set as a context. |
|
42
|
|
|
* E.g. "storageAdapter" resolves to the storage adapter implementation configured for the current vault. |
|
43
|
|
|
* |
|
44
|
|
|
* @param Vault $vault |
|
45
|
|
|
* @return Container |
|
46
|
|
|
*/ |
|
47
|
|
|
public function getContainer(Vault $vault = null): Container |
|
48
|
|
|
{ |
|
49
|
|
|
return $this->container->selectVault($vault); |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* Returns a specific vault by title. |
|
54
|
|
|
* |
|
55
|
|
|
* @param string $vaultTitle |
|
56
|
|
|
* @return Vault |
|
57
|
|
|
*/ |
|
58
|
|
|
public function getVault(string $vaultTitle): Vault |
|
59
|
|
|
{ |
|
60
|
|
|
if (!isset($this->vaults[$vaultTitle])) |
|
61
|
|
|
{ |
|
62
|
|
|
$vaultConfiguration = $this->getConfiguration()->getVaultByTitle($vaultTitle); |
|
63
|
|
|
|
|
64
|
|
|
$this->vaults[$vaultTitle] = new Vault($this, $vaultConfiguration); |
|
65
|
|
|
} |
|
66
|
|
|
|
|
67
|
|
|
return $this->vaults[$vaultTitle]; |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* Returns all vaults. |
|
72
|
|
|
* |
|
73
|
|
|
* @return Vault[] |
|
74
|
|
|
*/ |
|
75
|
|
|
public function getVaults(): array |
|
76
|
|
|
{ |
|
77
|
|
|
return array_values(array_map(function(VaultConfiguration $vaultConfiguration) { |
|
78
|
|
|
|
|
79
|
|
|
return $this->getVault($vaultConfiguration->getTitle()); |
|
80
|
|
|
|
|
81
|
|
|
}, $this->configuration->getVaults())); |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* Builds and returns an OperationList instance containing all operations required to a full synchronization. |
|
86
|
|
|
* |
|
87
|
|
|
* @return OperationList |
|
88
|
|
|
*/ |
|
89
|
|
|
public function buildOperationList(): OperationList |
|
90
|
|
|
{ |
|
91
|
|
|
$return = new OperationList(); |
|
92
|
|
|
|
|
93
|
|
|
foreach ($this->getVaults() as $vault) |
|
94
|
|
|
{ |
|
95
|
|
|
$return->append($vault->getOperationList()); |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
return $return; |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
|
|
public function synchronize(array $vaultTitles = [], SynchronizationProgressListenerInterface $progressListener = null): OperationResultList |
|
102
|
|
|
{ |
|
103
|
|
|
$lastRevision = 0; |
|
104
|
|
|
|
|
105
|
|
|
// fallback to all vaults |
|
106
|
|
|
$vaultTitles = $vaultTitles ?: array_map(function(Vault $vault) { return $vault->getVaultConfiguration()->getTitle(); }, $this->getVaults()); |
|
107
|
|
|
|
|
108
|
|
|
// acquire all locks and retrieve highest existing revision |
|
109
|
|
|
foreach ($this->getVaults() as $vault) |
|
110
|
|
|
{ |
|
111
|
|
|
// lock is only required for vaults that we want to synchronize with |
|
112
|
|
|
if (in_array($vault->getVaultConfiguration()->getTitle(), $vaultTitles)) |
|
113
|
|
|
{ |
|
114
|
|
|
$vault->getLockAdapter()->acquireLock(Vault::LOCK_SYNC); |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
// highest revision should be build across all vaults |
|
118
|
|
|
$synchronizationList = $vault->loadSynchronizationList(); |
|
119
|
|
|
if ($synchronizationList->getLastSynchronization()) |
|
120
|
|
|
{ |
|
121
|
|
|
$lastRevision = max($lastRevision, $synchronizationList->getLastSynchronization()->getRevision()); |
|
122
|
|
|
} |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
// new revision is one plus the highest existing revision across all vaults |
|
126
|
|
|
$newRevision = $lastRevision + 1; |
|
127
|
|
|
|
|
128
|
|
|
// actual synchronization |
|
129
|
|
|
$return = new OperationResultList(); |
|
130
|
|
|
foreach ($vaultTitles as $vaultTitle) |
|
131
|
|
|
{ |
|
132
|
|
|
$return->append($this->getVault($vaultTitle)->synchronize($newRevision, $progressListener)); |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
// release lock at the last moment to further reduce change of deadlocks |
|
136
|
|
|
foreach ($vaultTitles as $vaultTitle) |
|
137
|
|
|
{ |
|
138
|
|
|
$this->getVault($vaultTitle)->getLockAdapter()->releaseLock(Vault::LOCK_SYNC); |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
return $return; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
View Code Duplication |
public function restore(int $toRevision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList |
|
|
|
|
|
|
145
|
|
|
{ |
|
146
|
|
|
$vault = $fromVault ? $this->getVault($fromVault) : $this->getAnyVault(); |
|
147
|
|
|
$lockAdapter = $vault->getLockAdapter(); |
|
148
|
|
|
|
|
149
|
|
|
$lockAdapter->acquireLock(Vault::LOCK_SYNC); |
|
150
|
|
|
|
|
151
|
|
|
$operationResultList = $vault->restore($toRevision, $progressListener); |
|
152
|
|
|
|
|
153
|
|
|
$lockAdapter->releaseLock(Vault::LOCK_SYNC); |
|
154
|
|
|
|
|
155
|
|
|
return $operationResultList; |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
View Code Duplication |
public function dump(string $targetPath, int $revision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList |
|
|
|
|
|
|
159
|
|
|
{ |
|
160
|
|
|
$vault = $fromVault ? $this->getVault($fromVault) : $this->getAnyVault(); |
|
161
|
|
|
$lockAdapter = $vault->getLockAdapter(); |
|
162
|
|
|
|
|
163
|
|
|
$lockAdapter->acquireLock(Vault::LOCK_SYNC); |
|
164
|
|
|
|
|
165
|
|
|
$operationResultList = $vault->dump($targetPath, $revision, $progressListener); |
|
166
|
|
|
|
|
167
|
|
|
$lockAdapter->releaseLock(Vault::LOCK_SYNC); |
|
168
|
|
|
|
|
169
|
|
|
return $operationResultList; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
/** |
|
173
|
|
|
* Builds and returns a history of all synchronizations on record for this archive. |
|
174
|
|
|
* |
|
175
|
|
|
* @return Synchronization[][] |
|
176
|
|
|
*/ |
|
177
|
|
|
public function buildSynchronizationHistory(): array |
|
178
|
|
|
{ |
|
179
|
|
|
$return = []; |
|
180
|
|
|
|
|
181
|
|
|
foreach ($this->getVaults() as $vault) |
|
182
|
|
|
{ |
|
183
|
|
|
$vaultConfig = $vault->getVaultConfiguration(); |
|
184
|
|
|
$list = $vault->loadSynchronizationList(); |
|
185
|
|
|
|
|
186
|
|
|
foreach ($list as $synchronization) |
|
187
|
|
|
{ |
|
188
|
|
|
/** @var Synchronization $synchronization */ |
|
189
|
|
|
|
|
190
|
|
|
$return[$synchronization->getRevision()][$vaultConfig->getTitle()] = $synchronization; |
|
191
|
|
|
} |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
ksort($return); |
|
195
|
|
|
|
|
196
|
|
|
return $return; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
protected function getAnyVault(): Vault |
|
200
|
|
|
{ |
|
201
|
|
|
$vaults = array_values($this->getVaults()); |
|
202
|
|
|
|
|
203
|
|
|
if (empty($vaults)) |
|
204
|
|
|
{ |
|
205
|
|
|
throw new ConfigurationException('No vaults defined.'); |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
return $vaults[0]; |
|
209
|
|
|
} |
|
210
|
|
|
} |
|
211
|
|
|
|
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.