Completed
Push — master ( a40d21...065b57 )
by Maxim
05:48
created

removeRecentCommandsFromLockFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 0
1
<?php
2
3
namespace Weew\Console;
4
5
use DateTime;
6
use Exception;
7
use Weew\Console\Exceptions\CommandIsAlreadyRunningException;
8
use Weew\ConsoleArguments\ICommand;
9
use Weew\JsonEncoder\JsonEncoder;
10
11
class CommandExecutionLock implements ICommandExecutionLock {
12
    /**
13
     * @var string
14
     */
15
    protected $lockFile;
16
17
    /**
18
     * @var array
19
     */
20
    protected $recentCommands = [];
21
22
    /**
23
     * CommandExecutionLock constructor.
24
     *
25
     * @param string $lockFile
26
     */
27
    public function __construct($lockFile = null) {
28
        if ($lockFile === null) {
29
            $lockFile = $this->getDefaultLockFile();
30
        }
31
32
        $this->setLockFile($lockFile);
33
        $this->handleShutdowns();
34
    }
35
36
    /**
37
     * @return string
38
     */
39
    public function getLockFile() {
40
        return $this->lockFile;
41
    }
42
43
    /**
44
     * @param string $lockFile
45
     */
46
    public function setLockFile($lockFile) {
47
        $this->lockFile = $lockFile;
48
    }
49
50
    /**
51
     * @return array
52
     */
53
    public function readLockFile() {
54
        $lockFile = $this->getLockFile();
55
        $data = [];
56
57
        if (file_exists($lockFile)) {
58
            try {
59
                $encoder = new JsonEncoder();
60
                $data = $encoder->decode(file_read($lockFile));
61
62
                if ($data === null) {
63
                    $data = [];
64
                }
65
            } catch (Exception $ex) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
66
        }
67
68
        return $data;
69
    }
70
71
    /**
72
     * @param array $data
73
     */
74
    public function writeLockFile(array $data) {
75
        $lockFile = $this->getLockFile();
76
        $encoder = new JsonEncoder();
77
78
        file_write($lockFile, $encoder->encode($data));
79
    }
80
81
    /**
82
     * @param string $value
83
     *
84
     * @return bool
85
     */
86
    public function isInLockFile($value) {
87
        return array_has($this->readLockFile(), $value);
88
    }
89
90
    /**
91
     * @param string $value
92
     */
93
    public function addToLockFile($value) {
94
        // use key -> value here for easier lookup
95
        $this->recentCommands[$value] = true;
96
97
        $data = $this->readLockFile();
98
        $data[$value] = (new DateTime())->format(DateTime::ATOM);
99
        $this->writeLockFile($data);
100
    }
101
102
    /**
103
     * @param string $value
104
     */
105
    public function removeFromLockFile($value) {
106
        unset($this->recentCommands[$value]);
107
108
        $data = $this->readLockFile();
109
        array_remove($data, $value);
110
        $this->writeLockFile($data);
111
    }
112
113
    /**
114
     * Remove all locks for commands called trough
115
     * this particular lock instance.
116
     */
117
    public function removeRecentCommandsFromLockFile() {
118
        foreach ($this->recentCommands as $commandName => $status) {
119
            $this->removeFromLockFile($commandName);
120
        }
121
    }
122
123
    /**
124
     * @param IConsole $console
125
     * @param ICommand $command
126
     */
127
    public function lockCommand(IConsole $console, ICommand $command) {
128
        if ($command->isParallel() && $console->getAllowParallel()) {
129
            return;
130
        }
131
132
        if ($this->isInLockFile($command->getName())) {
133
            throw new CommandIsAlreadyRunningException(s(
134
                'Command "%s" is already being executed. ' .
135
                'Parallel execution for this command has been forbidden. ' .
136
                'This is the corresponding lock file "%s".',
137
                $command->getName(),
138
                $this->getLockFile()
139
            ));
140
        }
141
142
        $this->addToLockFile($command->getName());
143
    }
144
145
    /**
146
     * Handle shutdown events and clean up lock files.
147
     */
148
    protected function handleShutdowns() {
149
        declare(ticks = 1);
150
151
        $self = $this;
152
153
        $cleanup = function($signal = null) use ($self) {
154
            if ($signal === SIGTERM) {
155
                fprintf(STDERR, 'Received SIGTERM...');
156
            } else if ($signal === SIGINT) {
157
                fprintf(STDERR, 'Received SIGINT...');
158
            } else if ($signal === SIGTSTP) {
159
                fprintf(STDERR, 'Received SIGTSTP...');
160
            }
161
162
            $self->removeRecentCommandsFromLockFile();
163
        };
164
165
        if (extension_loaded('pcntl')) {
166
            pcntl_signal(SIGTERM, $cleanup, false);
167
            pcntl_signal(SIGINT, $cleanup, false);
168
            pcntl_signal(SIGTSTP, $cleanup, false);
169
        }
170
171
        register_shutdown_function($cleanup);
172
    }
173
174
    /**
175
     * @return string
176
     */
177
    protected function getDefaultLockFile() {
178
        return path(sys_get_temp_dir(), md5(__DIR__), '_console_lock');
179
    }
180
}
181