Completed
Push — master ( f2b67d...98a2b4 )
by Arne
01:46
created

ArchivR::buildSynchronizationHistory()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 0
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
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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