Completed
Push — master ( e610ee...c79300 )
by Arne
04:09
created

synchronizationToScalarArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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