Completed
Push — master ( 287393...7ff50d )
by Raffael
18:27 queued 14:12
created

src/lib/Async/SmbScanner.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Node\NodeInterface;
17
use Balloon\Filesystem\Storage\Adapter\Blackhole;
18
use Balloon\Filesystem\Storage\Adapter\Smb;
19
use Balloon\Helper;
20
use Balloon\Server;
21
use Balloon\Server\User;
22
use Icewind\SMB\Exception\NotFoundException;
23
use Icewind\SMB\IFileInfo;
24
use Icewind\SMB\INotifyHandler;
25
use Icewind\SMB\IShare;
26
use MongoDB\BSON\UTCDateTime;
27
use Normalizer;
28
use Psr\Log\LoggerInterface;
29
use TaskScheduler\AbstractJob;
30
31
class SmbScanner extends AbstractJob
32
{
33
    /**
34
     * Server.
35
     *
36
     * @var Server
37
     */
38
    protected $server;
39
40
    /**
41
     * Logger.
42
     *
43
     * @var LoggerInterface
44
     */
45
    protected $logger;
46
47
    /**
48
     * Constructor.
49
     */
50
    public function __construct(Server $server, LoggerInterface $logger)
51
    {
52
        $this->server = $server;
53
        $this->logger = $logger;
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function start(): bool
60
    {
61
        $dummy = new Blackhole();
62
        $fs = $this->server->getFilesystem();
63
        $mount = $collection = $fs->findNodeById($this->data['id']);
64
        $user = $this->server->getUserById($collection->getOwner());
65
        $user_fs = $user->getFilesystem();
66
        $smb = $collection->getStorage();
67
        $share = $smb->getShare();
68
        $action = INotifyHandler::NOTIFY_ADDED;
69
70
        if (isset($this->data['action'])) {
71
            $action = $this->data['action'];
72
        }
73
74
        $collection
75
            ->setFilesystem($user_fs)
76
            ->setStorage($dummy);
77
78
        $path = $smb->getRoot();
79
        if (isset($this->data['path'])) {
80
            $path = $this->data['path'];
81
        }
82
83
        if ($path === '' || $path === '.') {
84
            $path = DIRECTORY_SEPARATOR;
85
        }
86
87
        if ($path !== DIRECTORY_SEPARATOR) {
88
            $parent_path = dirname($path);
89
            if ($path !== '.') {
90
                $collection = $this->getParent($collection, $share, $parent_path, $action);
91
                $collection->setStorage($dummy);
92
            }
93
        }
94
95
        $recursive = true;
96
        if (isset($this->data['recursive'])) {
97
            $recursive = $this->data['recursive'];
98
        }
99
100
        $this->recursiveIterator($collection, $mount, $share, $dummy, $user, $smb, $path, $recursive, $action);
101
102
        return true;
103
    }
104
105
    /**
106
     * Get parent node from sub path.
107
     */
108
    protected function getParent(Collection $mount, IShare $share, string $path, int $action): Collection
109
    {
110
        $parent = $mount;
111
        $nodes = explode(DIRECTORY_SEPARATOR, $path);
112
        $sub = '';
113
        $dummy = $mount->getStorage();
114
115
        foreach ($nodes as $child) {
116
            if ($child === '.') {
117
                continue;
118
            }
119
120
            try {
121
                $sub .= DIRECTORY_SEPARATOR.$child;
122
                $parent = $parent->getChild($child);
123
                $parent->setStorage($dummy);
124
            } catch (\Exception $e) {
125
                if ($action === INotifyHandler::NOTIFY_REMOVED) {
126
                    throw $e;
127
                }
128
                $this->logger->debug('child node ['.$child.'] does not exits, add folder', [
129
                        'category' => get_class($this),
130
                        'exception' => $e,
131
                    ]);
132
133
                $node = $share->stat($sub);
134
                $parent = $parent->addDirectory($child, $this->getAttributes($mount, $share, $node));
135
            }
136
        }
137
138
        return $parent;
139
    }
140
141
    /**
142
     * Delete node.
143
     */
144
    protected function deleteNode(NodeInterface $node, Blackhole $dummy): bool
145
    {
146
        if ($node instanceof Collection) {
147
            $node->doRecursiveAction(function (NodeInterface $node) use ($dummy) {
148
                if ($node instanceof Collection) {
149
                    $node->setStorage($dummy);
150
                }
151
            });
152
        }
153
154
        $node->delete(true);
155
156
        return true;
157
    }
158
159
    /**
160
     * Iterate recursively through smb share.
161
     */
162
    protected function recursiveIterator(Collection $parent, Collection $mount, IShare $share, Blackhole $dummy, User $user, Smb $smb, string $path, bool $recursive, int $action): void
163
    {
164
        $this->logger->debug('sync smb path ['.$path.'] in mount ['.$mount->getId().'] from operation ['.$action.']', [
165
            'category' => get_class($this),
166
            'recursive' => $recursive,
167
        ]);
168
169
        $system_path = $path.DIRECTORY_SEPARATOR.$smb->getSystemFolder();
170
171
        if ($path === $system_path) {
172
            return;
173
        }
174
175
        try {
176
            $node = $share->stat($path);
177
        } catch (NotFoundException $e) {
0 ignored issues
show
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...
178
            if ($action === INotifyHandler::NOTIFY_REMOVED) {
179
                $node = $parent->getChild(Helper::mb_basename($path));
180
                $node->getParent()->setStorage($dummy);
181
                $this->deleteNode($node, $dummy);
182
            }
183
184
            return;
185
        }
186
187
        if ($node->isDirectory()) {
188
            $parent->setStorage($dummy);
189
190
            if ($path === DIRECTORY_SEPARATOR) {
191
                $child = $parent;
192
            } else {
193
                if ($parent->childExists($node->getName())) {
194
                    $child = $parent->getChild($node->getName());
195
                } else {
196
                    $child = $parent->addDirectory($node->getName(), $this->getAttributes($mount, $share, $node));
197
                }
198
            }
199
200
            if ($recursive === true) {
201
                $child->setStorage($dummy);
202
                $nodes = [];
203
204
                foreach ($share->dir($path) as $node) {
205
                    if ($node->getPath() === $system_path) {
206
                        continue;
207
                    }
208
209
                    $nodes[] = $node->getName();
210
                    $child_path = ($path === DIRECTORY_SEPARATOR) ? $path.$node->getName() : $path.DIRECTORY_SEPARATOR.$node->getName();
211
212
                    try {
213
                        $this->recursiveIterator($child, $mount, $share, $dummy, $user, $smb, $child_path, $recursive, $action);
214
                    } catch (\Exception $e) {
215
                        $this->logger->error('failed sync child node ['.$child_path.'] in smb mount', [
216
                            'category' => get_class($this),
217
                            'exception' => $e,
218
                        ]);
219
                    }
220
                }
221
222
                foreach ($child->getChildren() as $sub_child) {
223
                    $sub_name = Normalizer::normalize($sub_child->getName());
224
225
                    if (!in_array($sub_name, $nodes)) {
226
                        $this->deleteNode($sub_child, $dummy);
227
                    }
228
                }
229
            }
230
        } else {
231
            $this->syncFile($parent, $mount, $node, $action, $share, $user);
232
        }
233
    }
234
235
    /**
236
     * Sync file.
237
     */
238
    protected function syncFile(Collection $parent, Collection $mount, IFileInfo $node, int $action, IShare $share, User $user): bool
239
    {
240
        $this->logger->debug('update smb file meta data from ['.$node->getPath().'] in parent node ['.$parent->getId().']', [
241
            'category' => get_class($this),
242
        ]);
243
244
        $attributes = $this->getAttributes($mount, $share, $node);
245
246
        if ($parent->childExists($node->getName())) {
247
            $file = $parent->getChild($node->getName());
248
            $file->getParent()->setStorage($parent->getStorage());
249
            if ($this->fileUpdateRequired($file, $attributes)) {
250
                $this->updateFileContent($parent, $share, $node, $file, $user, $attributes);
251
            }
252
253
            return true;
254
        }
255
256
        $file = $parent->addFile($node->getName(), null, $attributes);
257
        $file->getParent()->setStorage($parent->getStorage());
258
        $this->updateFileContent($parent, $share, $node, $file, $user, $attributes);
259
260
        return true;
261
    }
262
263
    /**
264
     * Set file content.
265
     */
266
    protected function updateFileContent(Collection $parent, IShare $share, IFileInfo $node, File $file, User $user, array $attributes): bool
267
    {
268
        $storage = $parent->getStorage();
269
        $stream = $share->read($node->getPath());
270
        $session = $storage->storeTemporaryFile($stream, $user);
271
        $file->setContent($session, $attributes);
272
273
        fclose($stream);
274
275
        return true;
276
    }
277
278
    /**
279
     * Check if file content needs to be updated.
280
     */
281
    protected function fileUpdateRequired(File $file, array $smb_attributes): bool
282
    {
283
        $meta_attributes = $file->getAttributes();
284
285
        return $smb_attributes['size'] != $meta_attributes['size'] || $smb_attributes['changed'] != $meta_attributes['changed'];
286
    }
287
288
    /**
289
     * Prepare node attributes.
290
     */
291
    protected function getAttributes(Collection $collection, IShare $share, IFileInfo $node): array
292
    {
293
        $stats = $share->stat($node->getPath());
294
        $mtime = new UTCDateTime($stats->getMTime() * 1000);
295
296
        $attributes = [
297
            'created' => $mtime,
298
            'changed' => $mtime,
299
            'storage_reference' => $collection->getId(),
300
            'storage' => [
301
                'path' => '/'.ltrim($node->getPath(), '/'),
302
            ],
303
        ];
304
305
        if (!$node->isDirectory()) {
306
            $attributes['size'] = $node->getSize();
307
        }
308
309
        return $attributes;
310
    }
311
}
312