Completed
Pull Request — master (#200)
by Raffael
16:31
created

Smb::getSystemFolder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\Filesystem\Storage\Adapter;
13
14
use Balloon\Filesystem\Node\Collection;
15
use Balloon\Filesystem\Node\File;
16
use Balloon\Filesystem\Node\NodeInterface;
17
use Balloon\Filesystem\Storage\Exception;
18
use Balloon\Server\User;
19
use Icewind\SMB\Exception as SMBException;
20
use Icewind\SMB\IFileInfo;
21
use Icewind\SMB\IShare;
22
use InvalidArgumentException;
23
use MongoDB\BSON\ObjectId;
24
use Psr\Log\LoggerInterface;
25
26
class Smb implements AdapterInterface
27
{
28
    /**
29
     * Options.
30
     */
31
    public const OPTION_SYSTEM_FOLDER = 'system_folder';
32
    public const OPTION_ROOT = 'root';
33
34
    /**
35
     * System folders.
36
     */
37
    protected const SYSTEM_TRASH = 'trash';
38
    protected const SYSTEM_TEMP = 'temp';
39
40
    /**
41
     * SMB share.
42
     *
43
     * @var IShare
44
     */
45
    protected $share;
46
47
    /**
48
     * Logger.
49
     *
50
     * @var LoggerInterface
51
     */
52
    protected $logger;
53
54
    /**
55
     * SMB Root directory within share.
56
     *
57
     * @var string
58
     */
59
    protected $root = '';
60
61
    /**
62
     * Balloon system folder.
63
     *
64
     * @var string
65
     */
66
    protected $system_folder = '.balloon';
67
68
    /**
69
     * SMB storage.
70
     */
71
    public function __construct(IShare $share, LoggerInterface $logger, array $config = [])
72
    {
73
        $this->share = $share;
74
        $this->logger = $logger;
75
        $this->setOptions($config);
76
    }
77
78
    /**
79
     * Get SMB share.
80
     */
81
    public function getShare(): IShare
82
    {
83
        return $this->share;
84
    }
85
86
    /**
87
     * Get root path.
88
     */
89
    public function getRoot(): string
90
    {
91
        return $this->root;
92
    }
93
94
    /**
95
     * Get system folder.
96
     */
97
    public function getSystemFolder(): string
98
    {
99
        return $this->system_folder;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function hasNode(NodeInterface $node): bool
106
    {
107
        $attributes = $node->getAttributes();
108
109
        try {
110
            if (isset($attributes['storage']['path'])) {
111
                return (bool) $this->share->stat($attributes['storage']['path']);
112
            }
113
        } catch (SMBException\NotFoundException $e) {
114
            return false;
115
        }
116
117
        return false;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function deleteFile(File $file, ?int $version = null): ?array
124
    {
125
        return $this->deleteNode($file);
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function readonly(NodeInterface $node, bool $readonly = true): ?array
132
    {
133
        $path = $this->getPath($node);
134
135
        if ($readonly === true) {
136
            $this->share->setMode($path, IFileInfo::MODE_READONLY);
137
138
            return $node->getAttributes()['storage'];
139
        }
140
141
        $this->share->setMode($path, IFileInfo::MODE_NORMAL);
142
143
        return $node->getAttributes()['storage'];
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function forceDeleteFile(File $file, ?int $version = null): bool
150
    {
151
        if (false === $this->hasNode($file)) {
152
            $this->logger->debug('smb blob for file ['.$file->getId().'] was not found', [
153
                'category' => get_class($this),
154
            ]);
155
156
            return false;
157
        }
158
159
        return $this->share->del($this->getPath($file));
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function openReadStream(File $file)
166
    {
167
        return $this->share->read($this->getPath($file));
168
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173
    public function storeFile(File $file, ObjectId $session): array
174
    {
175
        $path = $this->getSystemPath(self::SYSTEM_TEMP).DIRECTORY_SEPARATOR.$session;
176
177
        $current = $file->getPath();
178
        $mount = $file->getFilesystem()->findNodeById($file->getMount())->getPath();
179
        $dest = substr($current, strlen($mount));
180
181
        $this->logger->debug('copy file from session ['.$session.'] in ['.$path.'] to ['.$dest.']', [
182
            'category' => get_class($this),
183
        ]);
184
185
        $hash = hash_init('md5');
186
        $size = 0;
187
        $stream = $this->share->read($path);
188
189
        while (!feof($stream)) {
190
            $buffer = fgets($stream, 65536);
191
192
            if ($buffer === false) {
193
                continue;
194
            }
195
196
            $size += mb_strlen($buffer, '8bit');
197
            hash_update($hash, $buffer);
198
        }
199
200
        fclose($stream);
201
        $md5 = hash_final($hash);
202
203
        $this->logger->debug('calculated hash ['.$md5.'] for temporary file ['.$session.']', [
204
            'category' => get_class($this),
205
        ]);
206
207
        $this->share->rename($path, $dest);
208
209
        return [
210
            'reference' => [
211
                'path' => $dest,
212
            ],
213
            'size' => $size,
214
            'hash' => $md5,
215
        ];
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    public function createCollection(Collection $parent, string $name): array
222
    {
223
        $path = $this->getPath($parent).DIRECTORY_SEPARATOR.$name;
224
        $this->share->mkdir($path);
225
226
        return [
227
            'path' => $path,
228
        ];
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234
    public function deleteCollection(Collection $collection): ?array
235
    {
236
        return $this->deleteNode($collection);
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242
    public function forceDeleteCollection(Collection $collection): bool
243
    {
244
        if (false === $this->hasNode($collection)) {
245
            $this->logger->debug('smb collection ['.$collection->getId().'] was not found', [
246
                'category' => get_class($this),
247
            ]);
248
249
            return false;
250
        }
251
252
        return $this->share->rmdir($this->getPath($collection));
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258
    public function rename(NodeInterface $node, string $new_name): ?array
259
    {
260
        $reference = $node->getAttributes()['storage'];
261
262
        if (!$node->isDeleted()) {
263
            $path = join('/', [rtrim(dirname($this->getPath($node)), '/'), $new_name]);
264
            $this->share->rename($this->getPath($node), $path);
265
            $reference['path'] = $path;
266
        }
267
268
        return $reference;
269
    }
270
271
    /**
272
     * {@inheritdoc}
273
     */
274
    public function move(NodeInterface $node, Collection $parent): ?array
275
    {
276
        $reference = $node->getAttributes()['storage'];
277
278
        if (!$node->isDeleted()) {
279
            $path = join('/', [rtrim($this->getPath($parent), '/'), $node->getName()]);
280
            $this->share->rename($this->getPath($node), $path);
281
            $reference['path'] = $path;
282
        }
283
284
        return $reference;
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290
    public function undelete(NodeInterface $node): ?array
291
    {
292
        if (false === $this->hasNode($node)) {
293
            $this->logger->debug('smb node ['.$this->getPath($node).'] was not found for reference=['.$node->getId().']', [
294
                'category' => get_class($this),
295
            ]);
296
297
            return $node->getAttributes()['storage'];
298
        }
299
300
        $current = $node->getPath();
301
        $mount = $node->getFilesystem()->findNodeById($node->getMount())->getPath();
302
        $restore = substr($current, strlen($mount));
303
304
        $this->share->rename($this->getPath($node), $restore);
305
        $reference = $node->getAttributes()['storage'];
306
        $reference['path'] = $restore;
307
308
        return $reference;
309
    }
310
311
    /**
312
     * {@inheritdoc}
313
     */
314
    public function storeTemporaryFile($stream, User $user, ?ObjectId $session = null): ObjectId
315
    {
316
        $exists = $session;
0 ignored issues
show
Unused Code introduced by
$exists is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
317
318
        if ($session === null) {
319
            $session = new ObjectId();
320
321
            $this->logger->info('create new tempory storage file ['.$session.']', [
322
                'category' => get_class($this),
323
            ]);
324
            $path = $this->getSystemPath(self::SYSTEM_TEMP).DIRECTORY_SEPARATOR.$session;
325
        } else {
326
            $path = $this->getSystemPath(self::SYSTEM_TEMP).DIRECTORY_SEPARATOR.$session;
327
328
            try {
329
                $this->share->stat($path);
330
            } catch (SMBException\NotFoundException $e) {
331
                throw new Exception\SessionNotFound('temporary storage for this file is gone');
332
            }
333
        }
334
335
        $this->storeStream($stream, $path);
336
337
        return $session;
338
    }
339
340
    /**
341
     * Test connection to storage.
342
     */
343
    public function test(): bool
344
    {
345
        $test = $this->share->notify($this->root);
346
        $changes = $test->getChanges();
0 ignored issues
show
Unused Code introduced by
$changes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
347
        $test->stop();
348
349
        return true;
350
    }
351
352
    /**
353
     * Set options.
354
     */
355
    protected function setOptions(array $config = []): self
356
    {
357
        foreach ($config as $option => $value) {
358
            switch ($option) {
359
                case self::OPTION_SYSTEM_FOLDER:
360
                case self::OPTION_ROOT:
361
                    $this->{$option} = (string) $value;
362
363
                break;
364
                default:
365
                    throw new InvalidArgumentException('unknown option '.$option.' given');
366
            }
367
        }
368
369
        return $this;
370
    }
371
372
    /**
373
     * Create system folder if not exists.
374
     */
375
    protected function getSystemPath(string $name): string
376
    {
377
        $path = $this->root.DIRECTORY_SEPARATOR.$this->system_folder;
378
379
        try {
380
            $this->share->stat($path);
381
        } catch (SMBException\NotFoundException $e) {
382
            $this->logger->debug('create smb system folder ['.$path.']', [
383
                'category' => get_class($this),
384
            ]);
385
386
            $this->share->mkdir($path);
387
            $this->share->setMode($path, IFileInfo::MODE_HIDDEN);
388
        }
389
390
        $path .= DIRECTORY_SEPARATOR.$name;
391
392
        try {
393
            $this->share->stat($path);
394
        } catch (SMBException\NotFoundException $e) {
395
            $this->logger->debug('create smb system folder ['.$path.']', [
396
                'category' => get_class($this),
397
            ]);
398
399
            $this->share->mkdir($path);
400
        }
401
402
        return $path;
403
    }
404
405
    /**
406
     * Move node to trash folder.
407
     */
408
    protected function deleteNode(NodeInterface $node): array
409
    {
410
        $reference = $node->getAttributes()['storage'];
411
412
        if ($this->hasNode($node) === false) {
413
            $this->logger->debug('smb node ['.$this->getPath($node).'] was not found for reference=['.$node->getId().']', [
414
                'category' => get_class($this),
415
            ]);
416
417
            return $reference;
418
        }
419
420
        $path = $this->getSystemPath(self::SYSTEM_TRASH).DIRECTORY_SEPARATOR.$node->getId();
421
        $this->share->rename($this->getPath($node), $path);
422
        $reference['path'] = $path;
423
424
        return $reference;
425
    }
426
427
    /**
428
     * Get SMB path from node.
429
     */
430
    protected function getPath(NodeInterface $node): string
431
    {
432
        $attributes = $node->getAttributes();
433
434
        if (isset($attributes['mount']) && count($attributes['mount']) !== 0) {
435
            return $this->root;
436
        }
437
438
        if (!isset($attributes['storage']['path'])) {
439
            throw new Exception\BlobNotFound('no storage.path given for smb storage definiton');
440
        }
441
442
        return $attributes['storage']['path'];
443
    }
444
445
    /**
446
     * Store stream content.
447
     */
448
    protected function storeStream($stream, string $path): int
449
    {
450
        if ($stream === null) {
451
            $dest = $this->share->write($path);
0 ignored issues
show
Unused Code introduced by
$dest is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
452
453
            return 0;
454
        }
455
456
        $dest = $this->share->append($path);
457
        $bytes = stream_copy_to_stream($stream, $dest);
458
        fclose($dest);
459
460
        return $bytes;
461
    }
462
}
463