Completed
Push — master ( c79300...9eb332 )
by Arne
02:48
created

Storeman::initMetadataDirectory()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 3
nc 3
nop 0
1
<?php
2
3
namespace Storeman;
4
5
use Storeman\Config\Configuration;
6
use Storeman\Index\Index;
7
use Storeman\IndexBuilder\IndexBuilderInterface;
8
use Storeman\SynchronizationProgressListener\SynchronizationProgressListenerInterface;
9
10
/**
11
 * This class coordinates executions with multiple vaults involved.
12
 */
13
class Storeman
14
{
15
    public const CONFIG_FILE_NAME = 'storeman.json';
16
    public const METADATA_DIRECTORY_NAME = '.storeman';
17
18
    /**
19
     * @var Container
20
     */
21
    protected $container;
22
23
    public function __construct(Container $container = null)
24
    {
25
        $this->container = $container ?: new Container();
26
        $this->container->injectStoreman($this);
27
    }
28
29
    /**
30
     * Returns the DI container of this storeman instance.
31
     * Some service names resolve to different implementations depending on the current vault which can be set as a context.
32
     * E.g. "storageAdapter" resolves to the storage adapter implementation configured for the current vault.
33
     *
34
     * @param Vault $vault
35
     * @return Container
36
     */
37
    public function getContainer(Vault $vault = null): Container
38
    {
39
        return $this->container->selectVault($vault);
40
    }
41
42
    /**
43
     * Returns the configuration.
44
     *
45
     * @return Configuration
46
     */
47
    public function getConfiguration(): Configuration
48
    {
49
        return $this->container->get('configuration');
50
    }
51
52
    /**
53
     * Returns a container of the configured vaults.
54
     *
55
     * @return VaultContainer
56
     */
57
    public function getVaultContainer(): VaultContainer
58
    {
59
        return $this->container->getVaultContainer();
60
    }
61
62
    /**
63
     * Returns configured index builder.
64
     *
65
     * @return IndexBuilderInterface
66
     */
67
    public function getIndexBuilder(): IndexBuilderInterface
68
    {
69
        return $this->container->get('indexBuilder');
70
    }
71
72
    /**
73
     * @return FileReader
74
     */
75
    public function getFileReader(): FileReader
76
    {
77
        return $this->container->get('fileReader');
78
    }
79
80
    /**
81
     * Builds and returns an index representing the current local state.
82
     *
83
     * @param string $path
84
     * @return Index
85
     */
86
    public function getLocalIndex(string $path = null): Index
87
    {
88
        return $this->getIndexBuilder()->buildIndex(
89
            $path ?: $this->getConfiguration()->getPath(),
90
            $this->getLocalIndexExclusionPatterns()
91
        );
92
    }
93
94
    public function synchronize(array $vaultTitles = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList
95
    {
96
        /** @var Vault[] $vaults */
97
        $vaults = ($vaultTitles === null) ? $this->getVaultContainer() : $this->getVaultContainer()->getVaultsByTitles($vaultTitles);
98
99
        $this->acquireLocks($vaults, Vault::LOCK_SYNC);
0 ignored issues
show
Documentation introduced by
$vaults is of type array<integer,object<Storeman\Vault>>, but the function expects a object<Traversable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
100
101
        $newRevision = ($this->getLastRevision() ?: 0) + 1;
102
103
        $return = new OperationResultList();
104
        foreach ($vaults as $vault)
105
        {
106
            $return->append($vault->synchronize($newRevision, $progressListener));
107
        }
108
109
        $this->releaseLocks($vaults, Vault::LOCK_SYNC);
0 ignored issues
show
Documentation introduced by
$vaults is of type array<integer,object<Storeman\Vault>>, but the function expects a object<Traversable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
110
111
        return $return;
112
    }
113
114
    public function restore(int $toRevision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList
115
    {
116
        $vault = $this->getVaultForDownload($toRevision, $fromVault);
117
118
        if ($vault === null)
119
        {
120
            return new OperationResultList();
121
        }
122
123
        $operationResultList = $vault->restore($toRevision, $progressListener);
124
125
        return $operationResultList;
126
    }
127
128
    public function dump(string $targetPath, int $revision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList
129
    {
130
        $vault = $this->getVaultForDownload($revision, $fromVault);
131
132
        if ($vault === null)
133
        {
134
            return new OperationResultList();
135
        }
136
137
        $operationResultList = $vault->dump($targetPath, $revision, $progressListener);
138
139
        return $operationResultList;
140
    }
141
142
    /**
143
     * Returns the highest revision number across all vaults.
144
     *
145
     * @return int
146
     */
147
    public function getLastRevision(): ?int
148
    {
149
        $max = 0;
150
151
        foreach ($this->getVaultContainer() as $vault)
152
        {
153
            /** @var Vault $vault */
154
155
            if ($lastSynchronization = $vault->getVaultLayout()->getLastSynchronization())
156
            {
157
                $max = max($max, $lastSynchronization->getRevision());
158
            }
159
        }
160
161
        return $max ?: null;
162
    }
163
164
    public function getMetadataDirectoryPath(): string
165
    {
166
        return $this->initMetadataDirectory();
167
    }
168
169
    /**
170
     * @param Vault[] $vaults
171
     * @param string $lockName
172
     */
173
    protected function acquireLocks(\Traversable $vaults, string $lockName)
174
    {
175
        foreach ($vaults as $vault)
176
        {
177
            if (!$vault->getLockAdapter()->acquireLock($lockName))
178
            {
179
                throw new Exception("Failed to acquire lock for vault {$vault->getVaultConfiguration()->getTitle()}");
180
            }
181
        }
182
    }
183
184
    /**
185
     * @param Vault[] $vaults
186
     * @param string $lockName
187
     */
188
    protected function releaseLocks(\Traversable $vaults, string $lockName)
189
    {
190
        foreach ($vaults as $vault)
191
        {
192
            if (!$vault->getLockAdapter()->releaseLock($lockName))
193
            {
194
                throw new Exception("Failed to release lock for vault {$vault->getVaultConfiguration()->getTitle()}");
195
            }
196
        }
197
    }
198
199
    protected function getVaultForDownload(?int $revision, ?string $vaultTitle): ?Vault
200
    {
201
        $revision = $revision ?: $this->getLastRevision();
202
        if ($revision === null)
203
        {
204
            return null;
205
        }
206
207
        $vaultContainer = $this->getVaultContainer();
208
209
        if ($vaultTitle)
210
        {
211
            $vault = $vaultContainer->getVaultByTitle($vaultTitle);
212
        }
213
        else
214
        {
215
            $vaults = $vaultContainer->getVaultsHavingRevision($revision);
216
            $vault = $vaultContainer->getPrioritizedVault($vaults);
217
        }
218
219
        if ($vault === null)
220
        {
221
            throw new Exception("Cannot find requested revision #{$revision} in any configured vault.");
222
        }
223
224
        return $vault;
225
    }
226
227
    protected function initMetadataDirectory(): string
228
    {
229
        $path = $this->getConfiguration()->getPath() . static::METADATA_DIRECTORY_NAME;
230
231
        if (!is_dir($path))
232
        {
233
            if (!mkdir($path))
234
            {
235
                throw new Exception("mkdir() failed for {$path}");
236
            }
237
        }
238
239
        return $path . DIRECTORY_SEPARATOR;
240
    }
241
242
    /**
243
     * @return string[]
244
     */
245
    protected function getLocalIndexExclusionPatterns(): array
246
    {
247
        return array_merge($this->getConfiguration()->getExclude(), [
248
            static::CONFIG_FILE_NAME,
249
            static::METADATA_DIRECTORY_NAME,
250
        ]);
251
    }
252
}
253