Bearsampp 2026.9.23
Loading...
Searching...
No Matches
class.bin.memcached.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2021-2026 Bearsampp
4 * License: GNU General Public License version 3 or later; see LICENSE.txt
5 * Author: Bear
6 * Website: https://bearsampp.com
7 * Github: https://github.com/Bearsampp
8 */
9
16class BinMemcached extends Module
17{
21 const SERVICE_NAME = 'bearsamppmemcached';
25 const SERVICE_PARAMS = '-m %d -p %d -U 0 -vv';
26
30 const ROOT_CFG_ENABLE = 'memcachedEnable';
34 const ROOT_CFG_VERSION = 'memcachedVersion';
35
39 const LOCAL_CFG_EXE = 'memcachedExe';
43 const LOCAL_CFG_MEMORY = 'memcachedMemory';
47 const LOCAL_CFG_PORT = 'memcachedPort';
48
52 private $service;
56 private $log;
57
61 private $exe;
65 private $memory;
69 private $port;
70
77 public function __construct($id, $type)
78 {
79 Log::initClass($this);
80 $this->reload($id, $type);
81 }
82
89 public function reload($id = null, $type = null)
90 {
92 Log::reloadClass($this);
93
94 $this->name = $bearsamppLang->getValue(Lang::MEMCACHED);
95 $this->version = $bearsamppConfig->getRaw(self::ROOT_CFG_VERSION);
96 parent::reload($id, $type);
97
98 $this->enable = $this->enable && $bearsamppConfig->getRaw(self::ROOT_CFG_ENABLE);
99 $this->service = new Win32Service(self::SERVICE_NAME);
100 $this->log = Path::getLogsPath() . '/memcached.log';
101
102 if ($this->bearsamppConfRaw !== false) {
103 $this->exe = $this->symlinkPath . '/' . $this->bearsamppConfRaw[self::LOCAL_CFG_EXE];
104 $this->memory = intval($this->bearsamppConfRaw[self::LOCAL_CFG_MEMORY]);
105 $this->port = intval($this->bearsamppConfRaw[self::LOCAL_CFG_PORT]);
106 }
107
108 if (!$this->enable) {
109 Log::info($this->name . ' is not enabled!');
110
111 return;
112 }
113 if (!is_dir($this->currentPath)) {
114 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_FILE_NOT_FOUND), $this->name . ' ' . $this->version, $this->currentPath));
115
116 return;
117 }
118 if (!is_dir($this->symlinkPath)) {
119 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_FILE_NOT_FOUND), $this->name . ' ' . $this->version, $this->symlinkPath));
120
121 return;
122 }
123 if (!is_file($this->bearsamppConf)) {
124 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_CONF_NOT_FOUND), $this->name . ' ' . $this->version, $this->bearsamppConf));
125
126 return;
127 }
128 if (!is_file($this->exe)) {
129 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_EXE_NOT_FOUND), $this->name . ' ' . $this->version, $this->exe));
130
131 return;
132 }
133 if (empty($this->memory)) {
134 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_MEMORY, $this->memory));
135
136 return;
137 }
138 if (empty($this->port)) {
139 Log::error(sprintf($bearsamppLang->getValue(Lang::ERROR_INVALID_PARAMETER), self::LOCAL_CFG_PORT, $this->port));
140
141 return;
142 }
143
144 $nssm = new Nssm(self::SERVICE_NAME);
145 $nssm->setDisplayName(APP_TITLE . ' ' . $this->getName());
146 $nssm->setBinPath($this->exe);
147 $nssm->setParams(sprintf(self::SERVICE_PARAMS, $this->memory, $this->port));
148 $nssm->setStart(Nssm::SERVICE_DEMAND_START);
149 $nssm->setStdout(Path::getLogsPath() . '/memcached.out.log');
150 $nssm->setStderr(Path::getLogsPath() . '/memcached.err.log');
151
152 $this->service->setNssm($nssm);
153 }
154
160 protected function replaceAll($params)
161 {
162 $content = file_get_contents($this->bearsamppConf);
163
164 foreach ($params as $key => $value) {
165 $content = preg_replace('|' . $key . ' = .*|', $key . ' = ' . '"' . $value . '"', $content);
166 $this->bearsamppConfRaw[$key] = $value;
167 switch ($key) {
168 case self::LOCAL_CFG_MEMORY:
169 $this->memory = intval($value);
170 break;
171 case self::LOCAL_CFG_PORT:
172 $this->port = intval($value);
173 break;
174 }
175 }
176
177 file_put_contents($this->bearsamppConf, $content);
178
179 self::invalidateConfigCacheForPath($this->bearsamppConf);
180 }
181
187 public function rebuildConf()
188 {
189 global $bearsamppRegistry;
190
191 $exists = $bearsamppRegistry->exists(
193 'SYSTEM\CurrentControlSet\Services\\' . self::SERVICE_NAME . '\Parameters',
195 );
196 if ($exists) {
197 return $bearsamppRegistry->setExpandStringValue(
199 'SYSTEM\CurrentControlSet\Services\\' . self::SERVICE_NAME . '\Parameters',
201 sprintf(self::SERVICE_PARAMS, $this->memory, $this->port)
202 );
203 }
204
205 return false;
206 }
207
217 public function changePort($port, $checkUsed = false, $wbProgressBar = null)
218 {
219 global $bearsamppWinbinder;
220
221 if (!Util::isValidPort($port)) {
222 Log::error($this->getName() . ' port not valid: ' . $port);
223
224 return false;
225 }
226
227 $port = intval($port);
228 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
229
230 $isPortInUse = ServiceHelper::isPortInUse($port);
231 if (!$checkUsed || $isPortInUse === false) {
232 // bearsampp.conf
233 $this->setPort($port);
234 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
235
236 // conf
237 $this->update();
238 $bearsamppWinbinder->incrProgressBar($wbProgressBar);
239
240 return true;
241 }
242
243 Log::debug($this->getName() . ' port in used: ' . $port . ' - ' . $isPortInUse);
244
245 return $isPortInUse;
246 }
247
256 public function checkPort($port, $showWindow = false)
257 {
258 global $bearsamppLang, $bearsamppWinbinder;
259 $boxTitle = sprintf($bearsamppLang->getValue(Lang::CHECK_PORT_TITLE), $this->getName(), $port);
260
261 if (!Util::isValidPort($port)) {
262 Log::error($this->getName() . ' port not valid: ' . $port);
263
264 return false;
265 }
266
267 // Use fsockopen instead of memcache_connect to avoid file descriptor leaks in PHP 8.4+
268 $fp = @fsockopen(APP_LOCALHOST, $port, $errno, $errstr, 1);
269 if ($fp) {
270 // Port is open, verify it's memcached by sending a simple command
271 @stream_set_timeout($fp, 1);
272 @fwrite($fp, "version\r\n");
273 $response = @fgets($fp, 128);
274 @fclose($fp);
275
276 if ($response && strpos($response, 'VERSION') === 0) {
277 // Extract version from response (format: "VERSION x.x.x")
278 $version = trim(substr($response, 8));
279 Log::debug($this->getName() . ' port ' . $port . ' is used by: ' . $this->getName() . ' ' . $version);
280 if ($showWindow) {
281 $bearsamppWinbinder->messageBoxInfo(
282 sprintf($bearsamppLang->getValue(Lang::PORT_USED_BY), $port, $this->getName() . ' ' . $version),
283 $boxTitle
284 );
285 }
286
287 return true;
288 } else {
289 // Port is open but not responding as memcached
290 Log::debug($this->getName() . ' port ' . $port . ' is open but not responding as memcached');
291 if ($showWindow) {
292 $bearsamppWinbinder->messageBoxWarning(
293 sprintf($bearsamppLang->getValue(Lang::PORT_NOT_USED_BY), $port),
294 $boxTitle
295 );
296 }
297
298 return false;
299 }
300 } else {
301 // Port is not open
302 Log::debug($this->getName() . ' port ' . $port . ' is not open');
303 if ($showWindow) {
304 $bearsamppWinbinder->messageBoxError(
305 sprintf($bearsamppLang->getValue(Lang::PORT_NOT_USED), $port),
306 $boxTitle
307 );
308 }
309
310 return false;
311 }
312 }
313
322 public function switchVersion($version, $showWindow = false)
323 {
324 Log::debug('Switch ' . $this->name . ' version to ' . $version);
325
326 return $this->updateConfig($version, 0, $showWindow);
327 }
328
338 protected function updateConfig($version = null, $sub = 0, $showWindow = false)
339 {
340 global $bearsamppLang, $bearsamppApps, $bearsamppWinbinder;
341
342 if (!$this->enable) {
343 return true;
344 }
345
346 $version = $version == null ? $this->version : $version;
347 Log::debug(($sub > 0 ? str_repeat(' ', 2 * $sub) : '') . 'Update ' . $this->name . ' ' . $version . ' config');
348
349 $boxTitle = sprintf($bearsamppLang->getValue(Lang::SWITCH_VERSION_TITLE), $this->getName(), $version);
350
351 $bearsamppConf = str_replace('memcached' . $this->getVersion(), 'memcached' . $version, $this->bearsamppConf);
352 if (!file_exists($bearsamppConf)) {
353 Log::error('bearsampp config files not found for ' . $this->getName() . ' ' . $version);
354 if ($showWindow) {
355 $bearsamppWinbinder->messageBoxError(
356 sprintf($bearsamppLang->getValue(Lang::BEARSAMPP_CONF_NOT_FOUND_ERROR), $this->getName() . ' ' . $version),
357 $boxTitle
358 );
359 }
360
361 return false;
362 }
363
364 $bearsamppConfRaw = parse_ini_file($bearsamppConf);
365 if ($bearsamppConfRaw === false || !isset($bearsamppConfRaw[self::ROOT_CFG_VERSION]) || $bearsamppConfRaw[self::ROOT_CFG_VERSION] != $version) {
366 Log::error('bearsampp config file malformed for ' . $this->getName() . ' ' . $version);
367 if ($showWindow) {
368 $bearsamppWinbinder->messageBoxError(
369 sprintf($bearsamppLang->getValue(Lang::BEARSAMPP_CONF_MALFORMED_ERROR), $this->getName() . ' ' . $version),
370 $boxTitle
371 );
372 }
373
374 return false;
375 }
376
377 // bearsampp.conf
378 $this->setVersion($version);
379
380 return true;
381 }
382
388 public function setVersion($version)
389 {
390 global $bearsamppConfig;
391 $this->version = $version;
392 $bearsamppConfig->replace(self::ROOT_CFG_VERSION, $version);
393 $this->reload();
394 }
395
401 public function getService()
402 {
403 return $this->service;
404 }
405
412 public function setEnable($enabled, $showWindow = false)
413 {
414 global $bearsamppConfig, $bearsamppLang, $bearsamppWinbinder;
415
416 if ($enabled == Config::ENABLED && !is_dir($this->currentPath)) {
417 Log::debug($this->getName() . ' cannot be enabled because bundle ' . $this->getVersion() . ' does not exist in ' . $this->currentPath);
418 if ($showWindow) {
419 $bearsamppWinbinder->messageBoxError(
420 sprintf($bearsamppLang->getValue(Lang::ENABLE_BUNDLE_NOT_EXIST), $this->getName(), $this->getVersion(), $this->currentPath),
421 sprintf($bearsamppLang->getValue(Lang::ENABLE_TITLE), $this->getName())
422 );
423 }
424 $enabled = Config::DISABLED;
425 }
426
427 Log::info($this->getName() . ' switched to ' . ($enabled == Config::ENABLED ? 'enabled' : 'disabled'));
428 $this->enable = $enabled == Config::ENABLED;
429 $bearsamppConfig->replace(self::ROOT_CFG_ENABLE, $enabled);
430
431 $this->reload();
432 if ($this->enable) {
433 ServiceHelper::installService($this, $this->port, null, $showWindow);
434 } else {
435 ServiceHelper::removeService($this->service, $this->name);
436 }
437 }
438
444 public function getLog()
445 {
446 return $this->log;
447 }
448
454 public function getExe()
455 {
456 return $this->exe;
457 }
458
464 public function getMemory()
465 {
466 return $this->memory;
467 }
468
474 public function setMemory($memory)
475 {
476 $this->replace(self::LOCAL_CFG_MEMORY, $memory);
477 }
478
484 public function getPort()
485 {
486 return $this->port;
487 }
488
494 public function setPort($port)
495 {
496 $this->replace(self::LOCAL_CFG_PORT, $port);
497 }
498}
499
global $bearsamppLang
global $bearsamppRoot
updateConfig($version=null, $sub=0, $showWindow=false)
switchVersion($version, $showWindow=false)
checkPort($port, $showWindow=false)
changePort($port, $checkUsed=false, $wbProgressBar=null)
setEnable($enabled, $showWindow=false)
reload($id=null, $type=null)
__construct($id, $type)
const DISABLED
const ENABLED
const ENABLE_BUNDLE_NOT_EXIST
const BEARSAMPP_CONF_MALFORMED_ERROR
const ERROR_EXE_NOT_FOUND
const ERROR_CONF_NOT_FOUND
const MEMCACHED
const BEARSAMPP_CONF_NOT_FOUND_ERROR
const PORT_NOT_USED
const ERROR_INVALID_PARAMETER
const ENABLE_TITLE
const PORT_NOT_USED_BY
const SWITCH_VERSION_TITLE
const CHECK_PORT_TITLE
const PORT_USED_BY
const ERROR_FILE_NOT_FOUND
static info($data, $file=null)
static debug($data, $file=null)
static reloadClass($classInstance)
static error($data, $file=null)
static initClass($classInstance)
update($sub=0, $showWindow=false)
static invalidateConfigCacheForPath($sourcePath)
replace($key, $value)
const INFO_APP_PARAMETERS
const SERVICE_DEMAND_START
static getLogsPath($aetrayPath=false)
const HKEY_LOCAL_MACHINE
static installService($bin, $port, $syntaxCheckCmd, $showWindow=false)
static removeService($service, $name)
static isPortInUse($port)
static isValidPort($port)
global $bearsamppConfig
Definition homepage.php:46
const APP_LOCALHOST
Definition root.php:21
const APP_TITLE
Definition root.php:19