InterProcessData::setDeviceUserData()   D
last analyzed

Complexity

Conditions 19
Paths 9

Size

Total Lines 78
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 19
eloc 41
c 1
b 0
f 0
nc 9
nop 7
dl 0
loc 78
rs 4.5166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * Class takes care of interprocess communication for different purposes
9
 * using a backend implementing IIpcBackend
10
 */
11
12
abstract class InterProcessData {
13
	public const CLEANUPTIME = 1;
14
15
	protected static $devid;
16
	protected static $pid;
17
	protected static $user;
18
	protected static $start;
19
	protected $type;
20
	protected $allocate;
21
22
	/**
23
	 * @var IIpcProvider
24
	 */
25
	private $ipcProvider;
26
27
	/**
28
	 * Constructor.
29
	 */
30
	public function __construct() {
31
		if (!isset($this->type) || !isset($this->allocate)) {
32
			throw new FatalNotImplementedException(sprintf("Class InterProcessData can not be initialized. Subclass %s did not initialize type and allocable memory.", static::class));
33
		}
34
35
		try {
36
			// use an own mutex + storage key for each device on non-shared-memory IPC
37
			// this method is not suitable for the TopCollector atm
38
			$type = Request::GetDeviceID();
0 ignored issues
show
Unused Code introduced by
The assignment to $type is dead and can be removed.
Loading history...
39
			$this->ipcProvider = GSync::GetRedis();
40
		}
41
		catch (Exception $e) {
42
			// ipcProvider could not initialise
43
			SLog::Write(LOGLEVEL_ERROR, sprintf("%s could not initialise IPC Redis provider: %s", static::class, $e->getMessage()));
44
		}
45
	}
46
47
	/**
48
	 * Initializes internal parameters.
49
	 *
50
	 * @return bool
51
	 */
52
	protected function initializeParams() {
53
		if (!isset(self::$devid)) {
54
			self::$devid = Request::GetDeviceID();
55
			self::$pid = @getmypid();
56
			self::$user = Request::GetAuthUserString(); // we want to see everything here
57
			self::$start = time();
58
			if (!self::$devid) {
59
				self::$devid = "none";
60
			}
61
		}
62
63
		return true;
64
	}
65
66
	/**
67
	 * Returns the underlying redis connection object.
68
	 *
69
	 * @return RedisConnection
70
	 */
71
	protected function getRedis() {
72
		return $this->ipcProvider;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->ipcProvider returns the type IIpcProvider which is incompatible with the documented return type RedisConnection.
Loading history...
73
	}
74
75
	/**
76
	 * Reinitializes the IPC data by removing, detaching and re-allocating it.
77
	 *
78
	 * @return bool
79
	 */
80
	public function ReInitIPC() {
81
		// TODO: do we need this?
82
		return false;
83
	}
84
85
	/**
86
	 * Cleans up the IPC data block.
87
	 *
88
	 * @return bool
89
	 */
90
	public function Clean() {
91
		// TODO: do we need this?
92
		return false;
93
	}
94
95
	/**
96
	 * Indicates if the IPC is active.
97
	 *
98
	 * @return bool
99
	 */
100
	public function IsActive() {
101
		if (!$this->getRedis()) {
102
			return false;
103
		}
104
105
		return true;
106
	}
107
108
	/**
109
	 * Blocks the class mutex.
110
	 * Method blocks until mutex is available!
111
	 * ATTENTION: make sure that you *always* release a blocked mutex!
112
	 *
113
	 * @return bool
114
	 */
115
	protected function blockMutex() {
116
		return true;
117
	}
118
119
	/**
120
	 * Releases the class mutex.
121
	 * After the release other processes are able to block the mutex themselves.
122
	 *
123
	 * @return bool
124
	 */
125
	protected function releaseMutex() {
126
		return true;
127
	}
128
129
	/**
130
	 * Indicates if the requested variable is available in IPC data.
131
	 *
132
	 * @param int $id int indicating the variable
133
	 *
134
	 * @return bool
135
	 */
136
	protected function hasData($id = 2) {
137
		return $this->ipcProvider ? $this->ipcProvider->get()->exists($id) : false;
0 ignored issues
show
Bug introduced by
The method get() does not exist on IIpcProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

137
		return $this->ipcProvider ? $this->ipcProvider->/** @scrutinizer ignore-call */ get()->exists($id) : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
138
	}
139
140
	/**
141
	 * Returns the requested variable from IPC data.
142
	 *
143
	 * @param int $id int indicating the variable
144
	 *
145
	 * @return mixed
146
	 */
147
	protected function getData($id = 2) {
148
		return $this->ipcProvider ? json_decode((string) $this->ipcProvider->getKey($id), true) : null;
0 ignored issues
show
Bug introduced by
The method getKey() does not exist on IIpcProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
		return $this->ipcProvider ? json_decode((string) $this->ipcProvider->/** @scrutinizer ignore-call */ getKey($id), true) : null;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
149
	}
150
151
	/**
152
	 * Writes the transmitted variable to IPC data.
153
	 * Subclasses may never use an id < 2!
154
	 *
155
	 * @param mixed $data data which should be saved into IPC data
156
	 * @param int   $id   int indicating the variable (bigger than 2!)
157
	 * @param mixed $ttl
158
	 *
159
	 * @return bool
160
	 */
161
	protected function setData($data, $id = 2, $ttl = -1) {
162
		return $this->ipcProvider ? $this->ipcProvider->setKey($id, json_encode($data), $ttl) : false;
0 ignored issues
show
Bug introduced by
The method setKey() does not exist on IIpcProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

162
		return $this->ipcProvider ? $this->ipcProvider->/** @scrutinizer ignore-call */ setKey($id, json_encode($data), $ttl) : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
163
	}
164
165
	protected function setDeviceUserData($key, $data, $devid, $user, $subkey = -1, $doCas = false, $rawdata = false) {
166
		if (!$this->ipcProvider) {
167
			return false;
168
		}
169
		$compKey = $this->getComposedKey($devid, $user, $subkey);
170
		$ok = false;
171
172
		// overwrite
173
		if (!$doCas) {
174
			$ok = ($this->ipcProvider->get()->hset($key, $compKey, json_encode($data)) !== false);
175
		}
176
		// merge data and do CAS on the $compKey
177
		elseif ($doCas == "merge") {
178
			$okCount = 0;
179
			// TODO: make this configurable (retrycount)?
180
			while (!$ok && $okCount < 5) {
181
				$newData = $data;
182
				// step 1:  get current data
183
				$_rawdata = $this->ipcProvider->get()->hget($key, $compKey);
184
				// step 2:  if data exists, merge it with the new data. Keys within
185
				//          within the new data overwrite possible existing old data (update).
186
				if ($_rawdata && $_rawdata !== null) {
187
					$_old = json_decode((string) $_rawdata, true);
188
					$newData = array_merge($_old, $data);
189
				}
190
				// step 3:  replace old with new data
191
				$ok = $this->ipcProvider->CASHash($key, $compKey, $_rawdata, json_encode($newData));
0 ignored issues
show
Bug introduced by
The method CASHash() does not exist on IIpcProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

191
				/** @scrutinizer ignore-call */ 
192
    $ok = $this->ipcProvider->CASHash($key, $compKey, $_rawdata, json_encode($newData));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
192
				if (!$ok) {
193
					++$okCount;
194
					// retry in 0.1s
195
					// TODO: make this configurable?
196
					usleep(1000000);
197
				}
198
			}
199
		}
200
		// replace data and do CAS on the $compKey - fail hard if CAS fails
201
		elseif ($doCas == "replace") {
202
			if (!$rawdata) {
203
				$ok = ($this->ipcProvider->get()->hset($key, $compKey, json_encode($data)) !== false);
204
			}
205
			else {
206
				$ok = $this->ipcProvider->CASHash($key, $compKey, $rawdata, json_encode($data));
207
			}
208
		}
209
		// delete keys of data and do CAS on the $compKey
210
		elseif ($doCas == "deletekeys") {
211
			$okCount = 0;
212
			// TODO: make this configurable (retrycount)?
213
			while (!$ok && $okCount < 5) {
214
				// step 1:  get current data
215
				$_rawdata = $this->ipcProvider->get()->hget($key, $compKey);
216
				$newData = json_decode((string) $_rawdata, true);
217
				# no data to delete from, done
218
				if (!is_array($newData) || count($newData) == 0) {
219
					break;
220
				}
221
				// step 2:  if data exists, delete the keys of $data from it
222
				foreach ($data as $delKey) {
223
					unset($newData[$delKey]);
224
				}
225
				$rawNewData = json_encode($newData);
226
				// step 3:  check if the data actually changed (not the correctest way to compare these, but still valid for our data)
227
				if ($_rawdata == $rawNewData) {
228
					break;
229
				}
230
				// step 4:  replace old with new data
231
				$ok = $this->ipcProvider->CASHash($key, $compKey, $_rawdata, $rawNewData);
232
				if (!$ok) {
233
					++$okCount;
234
					// TODO: make this configurable?
235
					// retry in 0.1s
236
					usleep(100000);
237
					SLog::Write(LOGLEVEL_DEBUG, "InterProcessData: setDeviceUserData CAS failed, retrying...");
238
				}
239
			}
240
		}
241
242
		return $ok;
243
	}
244
245
	protected function getDeviceUserData($key, $devid, $user, $subkey = -1, $returnRaw = false) {
246
		$compKey = $this->getComposedKey($devid, $user, $subkey);
247
		$_rawdata = false;
248
		if ($this->ipcProvider) {
249
			$_rawdata = $this->ipcProvider->get()->hget($key, $compKey);
250
		}
251
		if ($returnRaw) {
252
			if ($_rawdata) {
253
				return [json_decode((string) $_rawdata, true), $_rawdata];
254
			}
255
256
			return [[], false];
257
		}
258
259
		if ($_rawdata) {
260
			return json_decode((string) $_rawdata, true);
261
		}
262
263
		return [];
264
	}
265
266
	protected function delDeviceUserData($key, $devid, $user, $subkey = -1) {
267
		$compKey = $this->getComposedKey($devid, $user, $subkey);
268
269
		return $this->ipcProvider->get()->hdel($key, $compKey);
270
	}
271
272
	protected function getAllDeviceUserData($key) {
273
		$_data = [];
274
		$raw = $this->ipcProvider->get()->hGetAll($key);
275
		foreach ($raw as $compKey => $status) {
276
			$_linedata = json_decode((string) $status, true);
277
			if (!is_array($_linedata)) {
278
				$_linedata = [];
279
			}
280
			[$devid, $user, $subkey] = array_pad(explode("|-|", (string) $compKey, 3), 3, null);
281
			if ($devid === null || $devid === '') {
282
				continue;
283
			}
284
			if (!isset($_data[$devid])) {
285
				$_data[$devid] = [];
286
			}
287
			if ($user === null) {
288
				$user = '';
289
			}
290
			if ($subkey === null || $subkey === '') {
291
				$_data[$devid][$user] = $_linedata;
292
				continue;
293
			}
294
			if (!isset($_data[$devid][$user]) || !is_array($_data[$devid][$user])) {
295
				$_data[$devid][$user] = [];
296
			}
297
			$_data[$devid][$user][$subkey] = $_linedata;
298
		}
299
300
		return $_data;
301
	}
302
303
	protected function getRawDeviceUserData($key) {
304
		return $this->ipcProvider->get()->hGetAll($key);
305
	}
306
307
	protected function getComposedKey($key1, $key2, $key3 = -1) {
308
		$_k = $key1;
309
		if ($key2 > -1) {
310
			$_k .= "|-|" . $key2;
311
		}
312
313
		if ($key3 > -1) {
314
			$_k .= "|-|" . $key3;
315
		}
316
317
		return $_k;
318
	}
319
}
320