FileWatcher::onFileChanged()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
dl 0
loc 14
rs 9.2222
c 0
b 0
f 0
nc 5
nop 1
1
<?php
2
namespace PHPDaemon\FS;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Timer;
6
7
/**
8
 * Implementation of the file watcher
9
 * @package PHPDaemon\FS
10
 * @author  Vasily Zorin <[email protected]>
11
 */
12
class FileWatcher
13
{
14
    use \PHPDaemon\Traits\ClassWatchdog;
15
    use \PHPDaemon\Traits\StaticObjectWatchdog;
16
17
    /**
18
     * @var array Associative array of the files being observed
19
     */
20
    public $files = [];
21
22
    /**
23
     * @var resource Resource returned by inotify_init()
24
     */
25
    public $inotify;
26
27
    /**
28
     * @var Array of inotify descriptors
29
     */
30
    public $descriptors = [];
31
32
    /**
33
     * Constructor
34
     */
35
    public function __construct()
36
    {
37
        if (Daemon::loadModuleIfAbsent('inotify')) {
38
            $this->inotify = inotify_init();
39
            stream_set_blocking($this->inotify, 0);
40
        }
41
42
        Timer::add(
43
            function ($event) {
44
                Daemon::$process->fileWatcher->watch();
45
                if (sizeof(Daemon::$process->fileWatcher->files) > 0) {
46
                    $event->timeout();
47
                }
48
            },
49
            1e6 * 1,
50
            'fileWatcher'
51
        );
52
    }
53
54
    /**
55
     * Check the file system, triggered by timer
56
     * @return void
57
     */
58
    public function watch()
59
    {
60
        if ($this->inotify) {
61
            $events = inotify_read($this->inotify);
62
            if (!$events) {
63
                return;
64
            }
65
            foreach ($events as $ev) {
66
                $path = $this->descriptors[$ev['wd']];
67
                if (!isset($this->files[$path])) {
68
                    continue;
69
                }
70
                $this->onFileChanged($path);
71
            }
72
        } else {
73
            static $hash = [];
74
75
            foreach ($this->files as $path => $v) {
76
                if (!file_exists($path)) {
77
                    // file can be deleted
78
                    unset($this->files[$path]);
79
                    continue;
80
                }
81
82
                $mt = filemtime($path);
83
84
                if (isset($hash[$path]) && ($mt > $hash[$path])) {
85
                    $this->onFileChanged($path);
86
                }
87
88
                $hash[$path] = $mt;
89
            }
90
        }
91
    }
92
93
    /**
94
     * Called when file $path is changed
95
     * @param  string $path Path
96
     * @return void
97
     */
98
    public function onFileChanged($path)
99
    {
100
        if (!Daemon::lintFile($path)) {
101
            Daemon::log(__METHOD__ . ': Detected parse error in ' . $path);
102
            return;
103
        }
104
        foreach ($this->files[$path] as $cb) {
105
            if (is_callable($cb) || is_array($cb)) {
106
                $cb($path);
107
            } elseif (!Daemon::$process->IPCManager->importFile($cb, $path)) {
108
                $this->rmWatch($path, $cb);
109
            }
110
        }
111
    }
112
113
    /**
114
     * Cancels your subscription on object in FS
115
     * @param  string $path Path
116
     * @param  mixed $cb Callback
117
     * @return boolean
118
     */
119
    public function rmWatch($path, $cb)
120
    {
121
        $path = realpath($path);
122
123
        if (!isset($this->files[$path])) {
124
            return false;
125
        }
126
        if (($k = array_search($cb, $this->files[$path], true)) !== false) {
127
            unset($this->files[$path][$k]);
128
        }
129
        if (sizeof($this->files[$path]) === 0) {
130
            if ($this->inotify) {
131
                if (($descriptor = array_search($path, $this->descriptors)) !== false) {
132
                    inotify_rm_watch($this->inotify, $cb);
133
                }
134
            }
135
            unset($this->files[$path]);
136
        }
137
        return true;
138
    }
139
140
    /**
141
     * Adds your subscription on object in FS
142
     * @param  string $path Path
143
     * @param  mixed $cb Callback
144
     * @param  integer $flags Look inotify_add_watch()
0 ignored issues
show
Documentation introduced by
Should the type for parameter $flags not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
145
     * @return true
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
146
     */
147
    public function addWatch($path, $cb, $flags = null)
148
    {
149
        $path = realpath($path);
150
        if (!isset($this->files[$path])) {
151
            $this->files[$path] = [];
152
            if ($this->inotify) {
153
                $this->descriptors[inotify_add_watch($this->inotify, $path, $flags ?: IN_MODIFY)] = $path;
154
            }
155
        }
156
        $this->files[$path][] = $cb;
157
        Timer::setTimeout('fileWatcher');
158
        return true;
159
    }
160
}
161