Test Failed
Push — master ( 923f89...121dd8 )
by Raffael
01:20 queued 11s
created

SmbScanner::getAttributes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 0
cts 17
cp 0
rs 9.6
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\Async;
13
14
use Balloon\Filesystem\Node\Collection;
15
use Balloon\Filesystem\Node\File;
16
use Balloon\Filesystem\Storage\Adapter\Blackhole;
17
use Balloon\Filesystem\Storage\Adapter\Smb;
18
use Balloon\Helper;
19
use Balloon\Server;
20
use Balloon\Server\User;
21
use Icewind\SMB\Exception\NotFoundException;
22
use Icewind\SMB\IFileInfo;
23
use Icewind\SMB\INotifyHandler;
24
use Icewind\SMB\IShare;
25
use MongoDB\BSON\UTCDateTime;
26
use Normalizer;
27
use Psr\Log\LoggerInterface;
28
use TaskScheduler\AbstractJob;
29
30
class SmbScanner extends AbstractJob
31
{
32
    /**
33
     * Server.
34
     *
35
     * @var Server
36
     */
37
    protected $server;
38
39
    /**
40
     * Logger.
41
     *
42
     * @var LoggerInterface
43
     */
44
    protected $logger;
45
46
    /**
47
     * Dummy storage.
48
     */
49
    protected $dummy;
50
51
    /**
52
     * Constructor.
53
     */
54
    public function __construct(Server $server, LoggerInterface $logger)
55
    {
56
        $this->server = $server;
57
        $this->logger = $logger;
58
        $this->dummy = new Blackhole();
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function start(): bool
65
    {
66
        $fs = $this->server->getFilesystem();
67
        $mount = $collection = $fs->findNodeById($this->data['id']);
68
        $user = $this->server->getUserById($collection->getOwner());
69
        $user_fs = $user->getFilesystem();
70
        $smb = $collection->getStorage();
71
        $share = $smb->getShare();
72
        $action = INotifyHandler::NOTIFY_ADDED;
73
74
        if (isset($this->data['action'])) {
75
            $action = $this->data['action'];
76
        }
77
78
        $collection
79
            ->setFilesystem($user_fs)
80
            ->setStorage($this->dummy);
81
82
        $path = $smb->getRoot();
83
        if (isset($this->data['path'])) {
84
            $path = $this->data['path'];
85
        }
86
87
        if ($path === '' || $path === '.') {
88
            $path = DIRECTORY_SEPARATOR;
89
        }
90
91
        if ($path !== DIRECTORY_SEPARATOR) {
92
            $parent_path = dirname($path);
93
            if ($path !== '.') {
94
                $collection = $this->getParent($collection, $share, $parent_path, $action);
95
                $collection->setStorage($this->dummy);
96
            }
97
        }
98
99
        $recursive = true;
100
        if (isset($this->data['recursive'])) {
101
            $recursive = $this->data['recursive'];
102
        }
103
104
        $this->recursiveIterator($collection, $mount, $share, $user, $smb, $path, $recursive, $action);
105
106
        return true;
107
    }
108
109
    /**
110
     * Get parent node from sub path.
111
     */
112
    protected function getParent(Collection $mount, IShare $share, string $path, int $action): Collection
113
    {
114
        $parent = $mount;
115
        $nodes = explode(DIRECTORY_SEPARATOR, $path);
116
        $sub = '';
117
118
        foreach ($nodes as $child) {
119
            if ($child === '.') {
120
                continue;
121
            }
122
123
            try {
124
                $sub .= DIRECTORY_SEPARATOR.$child;
125
                $parent = $parent->getChild($child);
126
                $parent->setStorage($this->dummy);
127
            } catch (\Exception $e) {
128
                if ($action === INotifyHandler::NOTIFY_REMOVED) {
129
                    throw $e;
130
                }
131
                $this->logger->debug('child node ['.$child.'] does not exits, add folder', [
132
                        'category' => get_class($this),
133
                        'exception' => $e,
134
                    ]);
135
136
                $node = $share->stat($sub);
137
                $parent = $parent->addDirectory($child, $this->getAttributes($mount, $share, $node));
138
            }
139
        }
140
141
        return $parent;
142
    }
143
144
    /**
145
     * Iterate recursively through smb share.
146
     */
147
    protected function recursiveIterator(Collection $parent, Collection $mount, IShare $share, User $user, Smb $smb, string $path, bool $recursive, int $action): void
148
    {
149
        $system_path = $path.DIRECTORY_SEPARATOR.$smb->getSystemFolder();
150
151
        if ($path === $system_path) {
152
            return;
153
        }
154
155
        $this->logger->debug('sync smb path ['.$path.'] in mount ['.$mount->getId().'] from operation ['.$action.']', [
156
            'category' => get_class($this),
157
            'recursive' => $recursive,
158
        ]);
159
160
        $system_path = $path.DIRECTORY_SEPARATOR.$smb->getSystemFolder();
161
162
        if ($path === $system_path) {
163
            return;
164
        }
165
166
        try {
167
            $node = $share->stat($path);
168
        } catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class Icewind\SMB\Exception\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
169
            if ($action === INotifyHandler::NOTIFY_REMOVED) {
170
                $node = $parent->getChild(Helper::mb_basename($path));
171
                $node->getParent()->setStorage($this->dummy);
172
                $node->delete(true);
173
            }
174
175
            return;
176
        }
177
178
        if ($node->isDirectory()) {
179
            if ($path === DIRECTORY_SEPARATOR) {
180
                $child = $parent;
181
            } else {
182
                if ($parent->childExists($node->getName())) {
183
                    $child = $parent->getChild($node->getName());
184
                } else {
185
                    $child = $parent->addDirectory($node->getName(), $this->getAttributes($mount, $share, $node));
186
                }
187
            }
188
189
            if ($recursive === true) {
190
                $child->setStorage($this->dummy);
191
                $nodes = [];
192
193
                foreach ($share->dir($path) as $node) {
194
                    if ($node->getPath() === $system_path) {
195
                        continue;
196
                    }
197
198
                    $nodes[] = $node->getName();
199
                    $child_path = ($path === DIRECTORY_SEPARATOR) ? $path.$node->getName() : $path.DIRECTORY_SEPARATOR.$node->getName();
200
201
                    try {
202
                        $this->recursiveIterator($child, $mount, $share, $user, $smb, $child_path, $recursive, $action);
203
                    } catch (\Exception $e) {
204
                        $this->logger->error('failed sync child node ['.$child_path.'] in smb mount', [
205
                            'category' => get_class($this),
206
                            'exception' => $e,
207
                        ]);
208
                    }
209
                }
210
211
                foreach ($child->getChildNodes() as $sub_child) {
212
                    $sub_name = Normalizer::normalize($sub_child->getName());
213
214
                    if (!in_array($sub_name, $nodes)) {
215
                        $sub_child->delete(true);
216
                    }
217
                }
218
            }
219
        } else {
220
            $this->syncFile($parent, $mount, $node, $action, $share, $user);
221
        }
222
    }
223
224
    /**
225
     * Sync file.
226
     */
227
    protected function syncFile(Collection $parent, Collection $mount, IFileInfo $node, int $action, IShare $share, User $user): bool
228
    {
229
        $this->logger->debug('update smb file meta data from ['.$node->getPath().'] in parent node ['.$parent->getId().']', [
230
            'category' => get_class($this),
231
        ]);
232
233
        $attributes = $this->getAttributes($mount, $share, $node);
234
235
        if ($parent->childExists($node->getName())) {
236
            $file = $parent->getChild($node->getName());
237
            $file->getParent()->setStorage($parent->getStorage());
238
            if ($this->fileUpdateRequired($file, $attributes)) {
239
                $this->updateFileContent($parent, $share, $node, $file, $user, $attributes);
240
            }
241
242
            return true;
243
        }
244
245
        $file = $parent->addFile($node->getName(), null, $attributes);
246
        $file->getParent()->setStorage($parent->getStorage());
247
        $this->updateFileContent($parent, $share, $node, $file, $user, $attributes);
248
249
        return true;
250
    }
251
252
    /**
253
     * Set file content.
254
     */
255
    protected function updateFileContent(Collection $parent, IShare $share, IFileInfo $node, File $file, User $user, array $attributes): bool
256
    {
257
        $storage = $parent->getStorage();
258
        $stream = $share->read($node->getPath());
259
        $session = $storage->storeTemporaryFile($stream, $user);
260
        $file->setContent($session, $attributes);
261
262
        fclose($stream);
263
264
        return true;
265
    }
266
267
    /**
268
     * Check if file content needs to be updated.
269
     */
270
    protected function fileUpdateRequired(File $file, array $smb_attributes): bool
271
    {
272
        $meta_attributes = $file->getAttributes();
273
274
        return $smb_attributes['size'] != $meta_attributes['size'] || $smb_attributes['changed'] != $meta_attributes['changed'];
275
    }
276
277
    /**
278
     * Prepare node attributes.
279
     */
280
    protected function getAttributes(Collection $collection, IShare $share, IFileInfo $node): array
281
    {
282
        $stats = $share->stat($node->getPath());
283
        $mtime = new UTCDateTime($stats->getMTime() * 1000);
284
285
        $attributes = [
286
            'created' => $mtime,
287
            'changed' => $mtime,
288
            'storage_reference' => $collection->getId(),
289
            'storage' => [
290
                'path' => '/'.ltrim($node->getPath(), '/'),
291
            ],
292
        ];
293
294
        if (!$node->isDirectory()) {
295
            $attributes['size'] = $node->getSize();
296
        }
297
298
        return $attributes;
299
    }
300
}
301