createIndexObjectFromScalarArray()   A
last analyzed

Complexity

Conditions 5
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4555
c 0
b 0
f 0
cc 5
nc 1
nop 1
1
<?php
2
3
namespace Storeman\VaultLayout\Amberjack;
4
5
use Ramsey\Uuid\Uuid;
6
use Storeman\Exception;
7
use Storeman\FileReader;
8
use Storeman\Hash\HashContainer;
9
use Storeman\Index\Index;
10
use Storeman\Index\IndexObject;
11
use Storeman\StorageAdapter\StorageAdapterInterface;
12
use Storeman\Synchronization;
13
use Storeman\SynchronizationList;
14
use Storeman\VaultLayout\LazyLoadedIndex;
15
use Storeman\VaultLayout\VaultLayoutInterface;
16
17
/**
18
 * The Amberjack vault layout is the simplest layout shipped with storeman and exists primarily for testing & debugging
19
 * as it does not feature encryption, de-duplication and requires the ability to replace already existing files on the
20
 * vault.
21
 */
22
class AmberjackVaultLayout implements VaultLayoutInterface
23
{
24
    protected const SYNCHRONIZATION_LIST_FILE_NAME = 'sync.log';
25
26
    /**
27
     * @var StorageAdapterInterface
28
     */
29
    protected $storageAdapter;
30
31
    public function __construct(StorageAdapterInterface $storageAdapter)
32
    {
33
        $this->storageAdapter = $storageAdapter;
34
    }
35
36
    /**
37
     * {@inheritdoc}
38
     */
39
    public function getSynchronizations(): SynchronizationList
40
    {
41
        if ($this->storageAdapter->exists(static::SYNCHRONIZATION_LIST_FILE_NAME))
42
        {
43
            $stream = $this->storageAdapter->getReadStream(static::SYNCHRONIZATION_LIST_FILE_NAME);
44
45
            stream_filter_append($stream, 'zlib.inflate', STREAM_FILTER_READ);
46
47
            $list = new SynchronizationList();
48
49
            while (is_array($row = fgetcsv($stream)))
50
            {
51
                $synchronization = $this->createSynchronizationFromScalarArray($row);
52
                $synchronization->setIndex(new LazyLoadedIndex(function() use ($synchronization) {
53
54
                    return $this->readIndex($synchronization);
55
                }));
56
57
                $list->addSynchronization($synchronization);
58
            }
59
60
            if (!feof($stream))
61
            {
62
                throw new Exception("Corrupt synchronization list detected");
63
            }
64
65
            fclose($stream);
66
67
            return $list;
68
        }
69
70
        return new SynchronizationList();
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function getLastSynchronization(): ?Synchronization
77
    {
78
        return $this->getSynchronizations()->getLastSynchronization();
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function getSynchronization(int $revision): Synchronization
85
    {
86
        return $this->getSynchronizations()->getSynchronization($revision);
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function readBlob(string $blobId)
93
    {
94
        $stream = $this->storageAdapter->getReadStream($blobId);
95
96
        return $stream;
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function writeSynchronization(Synchronization $synchronization, FileReader $fileReader)
103
    {
104
        foreach ($synchronization->getIndex() as $indexObject)
105
        {
106
            /** @var IndexObject $indexObject */
107
108
            if ($indexObject->isFile() && $indexObject->getBlobId() === null)
109
            {
110
                $indexObject->setBlobId($this->generateNewBlobId($synchronization->getIndex()));
111
112
                $this->storageAdapter->writeStream($indexObject->getBlobId(), $fileReader->getReadStream($indexObject));
113
            }
114
        }
115
116
        $this->writeIndex($synchronization);
117
118
        $synchronizationList = $this->getSynchronizations();
119
        $synchronizationList->addSynchronization($synchronization);
120
121
        $this->writeSynchronizationList($synchronizationList);
122
    }
123
124
    protected function readIndex(Synchronization $synchronization): Index
125
    {
126
        $stream = $this->storageAdapter->getReadStream($this->getIndexFileName($synchronization));
127
128
        stream_filter_append($stream, 'zlib.inflate');
129
130
        $index = new Index();
131
        while (($row = fgetcsv($stream)) !== false)
132
        {
133
            $index->addObject($this->createIndexObjectFromScalarArray($row));
134
        }
135
136
        fclose($stream);
137
138
        return $index;
139
    }
140
141
    protected function writeIndex(Synchronization $synchronization)
142
    {
143
        // write to local temp file
144
        $stream = tmpfile();
145
        $filterHandle = stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_WRITE);
146
        foreach ($synchronization->getIndex() as $object)
147
        {
148
            /** @var IndexObject $object */
149
150
            if (fputcsv($stream, $this->indexObjectToScalarArray($object)) === false)
151
            {
152
                throw new \RuntimeException();
153
            }
154
        }
155
        stream_filter_remove($filterHandle);
156
        rewind($stream);
157
158
        // upload local file to vault
159
        $this->storageAdapter->writeStream($this->getIndexFileName($synchronization), $stream);
160
161
        fclose($stream);
162
    }
163
164
    protected function writeSynchronizationList(SynchronizationList $synchronizationList)
165
    {
166
        // write to local temp file
167
        $stream = tmpfile();
168
        $filterHandle = stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_WRITE);
169
        foreach ($synchronizationList as $synchronization)
170
        {
171
            /** @var Synchronization $synchronization */
172
173
            if (fputcsv($stream, $this->synchronizationToScalarArray($synchronization)) === false)
174
            {
175
                throw new \RuntimeException();
176
            }
177
        }
178
        stream_filter_remove($filterHandle);
179
        rewind($stream);
180
181
182
        // upload local file to vault
183
        $this->storageAdapter->writeStream(static::SYNCHRONIZATION_LIST_FILE_NAME, $stream);
184
185
        fclose($stream);
186
    }
187
188
    /**
189
     * Transforms an IndexObject instance into a scalar array suitable for fputcsv().
190
     *
191
     * @param IndexObject $indexObject
192
     * @return array
193
     */
194
    protected function indexObjectToScalarArray(IndexObject $indexObject): array
195
    {
196
        return [
197
            $indexObject->getRelativePath(),
198
            $indexObject->getType(),
199
            sprintf('%.9f', $indexObject->getMtime()),
200
            $indexObject->getPermissions(),
201
            $indexObject->getSize(),
202
            $indexObject->getLinkTarget(),
203
            $indexObject->getBlobId(),
204
            $indexObject->getHashes() ? $indexObject->getHashes()->serialize() : null,
205
        ];
206
    }
207
208
    /**
209
     * Reconstructs an IndexObject instance from a scalar array read by fgetcsv().
210
     *
211
     * @param array $array
212
     * @return IndexObject
213
     */
214
    protected function createIndexObjectFromScalarArray(array $array): IndexObject
215
    {
216
        return new IndexObject(
217
            $array[0],
218
            (int)$array[1],
219
            (float)$array[2],
220
            null,
221
            (int)$array[3],
222
            ($array[4] !== '') ? (int)$array[4] : null,
223
            null,
224
            $array[5] ?: null,
225
            $array[6] ?: null,
226
            $array[7] ? (new HashContainer())->unserialize($array[7]) : null
227
        );
228
    }
229
230
    protected function synchronizationToScalarArray(Synchronization $synchronization): array
231
    {
232
        return [
233
            $synchronization->getRevision(),
234
            $synchronization->getTime()->getTimestamp(),
235
            $synchronization->getIdentity(),
236
        ];
237
    }
238
239
    protected function createSynchronizationFromScalarArray(array $array): Synchronization
240
    {
241
        return new Synchronization(
242
            $array[0],
243
            \DateTime::createFromFormat('U', $array[1]),
0 ignored issues
show
Security Bug introduced by
It seems like \DateTime::createFromFormat('U', $array[1]) targeting DateTime::createFromFormat() can also be of type false; however, Storeman\Synchronization::__construct() does only seem to accept object<DateTime>, did you maybe forget to handle an error condition?
Loading history...
244
            $array[2]
245
        );
246
    }
247
248
    protected function generateNewBlobId(Index $index = null): string
249
    {
250
        do
251
        {
252
            $blobId = Uuid::uuid4()->toString();
253
        }
254
        while (($index && $index->getObjectByBlobId($blobId)) || $this->storageAdapter->exists($blobId));
255
256
        return $blobId;
257
    }
258
259
    protected function getIndexFileName(Synchronization $synchronization): string
260
    {
261
        return "index_{$synchronization->getRevision()}";
262
    }
263
}
264