aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--LICENSE25
-rw-r--r--README.md36
-rw-r--r--composer.json15
-rw-r--r--example/composer.json11
-rw-r--r--example/composer.lock37
-rw-r--r--example/create-tasks.php17
-rw-r--r--example/example.php14
-rw-r--r--example/vendor/autoload.php7
l---------example/vendor/ch1p/jobd-client1
-rw-r--r--example/vendor/composer/ClassLoader.php479
-rw-r--r--example/vendor/composer/InstalledVersions.php292
-rw-r--r--example/vendor/composer/LICENSE21
-rw-r--r--example/vendor/composer/autoload_classmap.php10
-rw-r--r--example/vendor/composer/autoload_namespaces.php9
-rw-r--r--example/vendor/composer/autoload_psr4.php10
-rw-r--r--example/vendor/composer/autoload_real.php55
-rw-r--r--example/vendor/composer/autoload_static.php36
-rw-r--r--example/vendor/composer/installed.json27
-rw-r--r--example/vendor/composer/installed.php33
-rw-r--r--src/Client.php153
-rw-r--r--src/Message.php36
-rw-r--r--src/RequestMessage.php40
-rw-r--r--src/ResponseMessage.php43
24 files changed, 1408 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..485dee6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4ea1ed1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2021, Evgeny Zinoviev
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..80ab121
--- /dev/null
+++ b/README.md
@@ -0,0 +1,36 @@
+# php-jobd-client
+
+This is a simple PHP client for [jobd](https://github.com/gch1p/jobd). It can send
+requests and read responses to/from *jobd* and *jobd-master* instances.
+
+## Installation
+
+```
+composer require ch1p/jobd-client
+```
+
+## Usage
+
+The API is compact and simple, just read the code of `Client.php`.
+
+Here's a small example.
+
+```php
+try {
+ $jobd = new jobd\Client(jobd\Client::MASTER_PORT, '127.0.0.1');
+} catch (Exception $e) {
+ die('Failed to connect.');
+}
+
+// poke master to send poll requests to workers
+$response = $jobd->poke(['target_name', 'another_name']);
+
+// get status from master
+$response = $status = $jobd->status();
+
+$jobd->close();
+```
+
+## License
+
+BSD-2c \ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..8670223
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,15 @@
+{
+ "name": "ch1p/jobd-client",
+ "version": "1.0",
+ "repositories": [
+ {
+ "type": "git",
+ "url": "https://github.com/gch1p/php-jobd-client"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "jobd\\": "src/"
+ }
+ }
+} \ No newline at end of file
diff --git a/example/composer.json b/example/composer.json
new file mode 100644
index 0000000..8bde4e7
--- /dev/null
+++ b/example/composer.json
@@ -0,0 +1,11 @@
+{
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../"
+ }
+ ],
+ "require": {
+ "ch1p/jobd-client": "^1.0"
+ }
+}
diff --git a/example/composer.lock b/example/composer.lock
new file mode 100644
index 0000000..c8416ff
--- /dev/null
+++ b/example/composer.lock
@@ -0,0 +1,37 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "50c0d7581897dc28f75a92f41008c613",
+ "packages": [
+ {
+ "name": "ch1p/jobd-client",
+ "version": "1.0",
+ "dist": {
+ "type": "path",
+ "url": "..",
+ "reference": "084d0d0e0ec68c71f414fa62c6c089aae51c1a9b"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "jobd\\": "src/"
+ }
+ },
+ "transport-options": {
+ "relative": true
+ }
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.0.0"
+}
diff --git a/example/create-tasks.php b/example/create-tasks.php
new file mode 100644
index 0000000..35968a9
--- /dev/null
+++ b/example/create-tasks.php
@@ -0,0 +1,17 @@
+<?php
+
+$db = new mysqli();
+if (!$db->real_connect('10.211.55.6', 'jobd', 'password', 'jobd'))
+ die('Failed to connect.');
+
+$target = 'server1';
+$slots = ['low', 'normal', 'high'];
+
+for ($i = 0; $i < 100; $i++) {
+ $slot = $slots[array_rand($slots)];
+ $time = time();
+ if (!$db->query("INSERT INTO jobs (target, slot, time_created, status) VALUES ('$target', '$slot', $time, 'waiting')"))
+ echo "{$db->error}\n";
+}
+
+$db->close(); \ No newline at end of file
diff --git a/example/example.php b/example/example.php
new file mode 100644
index 0000000..cc9cbd6
--- /dev/null
+++ b/example/example.php
@@ -0,0 +1,14 @@
+<?php
+
+require_once 'vendor/autoload.php';
+
+try {
+ $client = new jobd\Client(jobd\Client::MASTER_PORT);
+} catch (Exception $e) {
+ die($e->getMessage());
+}
+
+// $status = $client->status();
+// var_dump($status->getData());
+
+var_dump($client->poke(['server1']));
diff --git a/example/vendor/autoload.php b/example/vendor/autoload.php
new file mode 100644
index 0000000..c167c9a
--- /dev/null
+++ b/example/vendor/autoload.php
@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit5a87c37afe98e529b06f7be034379eab::getLoader();
diff --git a/example/vendor/ch1p/jobd-client b/example/vendor/ch1p/jobd-client
new file mode 120000
index 0000000..1b20c9f
--- /dev/null
+++ b/example/vendor/ch1p/jobd-client
@@ -0,0 +1 @@
+../../../ \ No newline at end of file
diff --git a/example/vendor/composer/ClassLoader.php b/example/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000..247294d
--- /dev/null
+++ b/example/vendor/composer/ClassLoader.php
@@ -0,0 +1,479 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ private $vendorDir;
+
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ private static $registeredLoaders = array();
+
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ }
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
+ *
+ * @return self[]
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/example/vendor/composer/InstalledVersions.php b/example/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000..bbb8f24
--- /dev/null
+++ b/example/vendor/composer/InstalledVersions.php
@@ -0,0 +1,292 @@
+<?php
+
+
+
+
+
+
+
+
+
+
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+
+
+
+
+
+class InstalledVersions
+{
+private static $installed = array (
+ 'root' =>
+ array (
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'aliases' =>
+ array (
+ ),
+ 'reference' => NULL,
+ 'name' => '__root__',
+ ),
+ 'versions' =>
+ array (
+ '__root__' =>
+ array (
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'aliases' =>
+ array (
+ ),
+ 'reference' => NULL,
+ ),
+ 'ch1p/jobd-client' =>
+ array (
+ 'pretty_version' => '1.0',
+ 'version' => '1.0.0.0',
+ 'aliases' =>
+ array (
+ ),
+ 'reference' => '084d0d0e0ec68c71f414fa62c6c089aae51c1a9b',
+ ),
+ ),
+);
+private static $canGetVendors;
+private static $installedByVendor = array();
+
+
+
+
+
+
+
+public static function getInstalledPackages()
+{
+$packages = array();
+foreach (self::getInstalled() as $installed) {
+$packages[] = array_keys($installed['versions']);
+}
+
+
+if (1 === \count($packages)) {
+return $packages[0];
+}
+
+return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+}
+
+
+
+
+
+
+
+
+
+public static function isInstalled($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (isset($installed['versions'][$packageName])) {
+return true;
+}
+}
+
+return false;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+public static function satisfies(VersionParser $parser, $packageName, $constraint)
+{
+$constraint = $parser->parseConstraints($constraint);
+$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+return $provided->matches($constraint);
+}
+
+
+
+
+
+
+
+
+
+
+public static function getVersionRanges($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+$ranges = array();
+if (isset($installed['versions'][$packageName]['pretty_version'])) {
+$ranges[] = $installed['versions'][$packageName]['pretty_version'];
+}
+if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+}
+if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+}
+if (array_key_exists('provided', $installed['versions'][$packageName])) {
+$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+}
+
+return implode(' || ', $ranges);
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getVersion($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+if (!isset($installed['versions'][$packageName]['version'])) {
+return null;
+}
+
+return $installed['versions'][$packageName]['version'];
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getPrettyVersion($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+return null;
+}
+
+return $installed['versions'][$packageName]['pretty_version'];
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getReference($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+if (!isset($installed['versions'][$packageName]['reference'])) {
+return null;
+}
+
+return $installed['versions'][$packageName]['reference'];
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getRootPackage()
+{
+$installed = self::getInstalled();
+
+return $installed[0]['root'];
+}
+
+
+
+
+
+
+
+public static function getRawData()
+{
+return self::$installed;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+public static function reload($data)
+{
+self::$installed = $data;
+self::$installedByVendor = array();
+}
+
+
+
+
+private static function getInstalled()
+{
+if (null === self::$canGetVendors) {
+self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+}
+
+$installed = array();
+
+if (self::$canGetVendors) {
+foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+if (isset(self::$installedByVendor[$vendorDir])) {
+$installed[] = self::$installedByVendor[$vendorDir];
+} elseif (is_file($vendorDir.'/composer/installed.php')) {
+$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+}
+}
+}
+
+$installed[] = self::$installed;
+
+return $installed;
+}
+}
diff --git a/example/vendor/composer/LICENSE b/example/vendor/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/example/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/example/vendor/composer/autoload_classmap.php b/example/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..b26f1b1
--- /dev/null
+++ b/example/vendor/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/example/vendor/composer/autoload_namespaces.php b/example/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..b7fc012
--- /dev/null
+++ b/example/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/example/vendor/composer/autoload_psr4.php b/example/vendor/composer/autoload_psr4.php
new file mode 100644
index 0000000..98f76a0
--- /dev/null
+++ b/example/vendor/composer/autoload_psr4.php
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'jobd\\' => array($vendorDir . '/ch1p/jobd-client/src'),
+);
diff --git a/example/vendor/composer/autoload_real.php b/example/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..f67c9e6
--- /dev/null
+++ b/example/vendor/composer/autoload_real.php
@@ -0,0 +1,55 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit5a87c37afe98e529b06f7be034379eab
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ spl_autoload_register(array('ComposerAutoloaderInit5a87c37afe98e529b06f7be034379eab', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
+ spl_autoload_unregister(array('ComposerAutoloaderInit5a87c37afe98e529b06f7be034379eab', 'loadClassLoader'));
+
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit5a87c37afe98e529b06f7be034379eab::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/example/vendor/composer/autoload_static.php b/example/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..f83e924
--- /dev/null
+++ b/example/vendor/composer/autoload_static.php
@@ -0,0 +1,36 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit5a87c37afe98e529b06f7be034379eab
+{
+ public static $prefixLengthsPsr4 = array (
+ 'j' =>
+ array (
+ 'jobd\\' => 5,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'jobd\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ch1p/jobd-client/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit5a87c37afe98e529b06f7be034379eab::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit5a87c37afe98e529b06f7be034379eab::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInit5a87c37afe98e529b06f7be034379eab::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/example/vendor/composer/installed.json b/example/vendor/composer/installed.json
new file mode 100644
index 0000000..2b56af1
--- /dev/null
+++ b/example/vendor/composer/installed.json
@@ -0,0 +1,27 @@
+{
+ "packages": [
+ {
+ "name": "ch1p/jobd-client",
+ "version": "1.0",
+ "version_normalized": "1.0.0.0",
+ "dist": {
+ "type": "path",
+ "url": "..",
+ "reference": "084d0d0e0ec68c71f414fa62c6c089aae51c1a9b"
+ },
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "jobd\\": "src/"
+ }
+ },
+ "transport-options": {
+ "relative": true
+ },
+ "install-path": "../ch1p/jobd-client"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/example/vendor/composer/installed.php b/example/vendor/composer/installed.php
new file mode 100644
index 0000000..340e0fe
--- /dev/null
+++ b/example/vendor/composer/installed.php
@@ -0,0 +1,33 @@
+<?php return array (
+ 'root' =>
+ array (
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'aliases' =>
+ array (
+ ),
+ 'reference' => NULL,
+ 'name' => '__root__',
+ ),
+ 'versions' =>
+ array (
+ '__root__' =>
+ array (
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'aliases' =>
+ array (
+ ),
+ 'reference' => NULL,
+ ),
+ 'ch1p/jobd-client' =>
+ array (
+ 'pretty_version' => '1.0',
+ 'version' => '1.0.0.0',
+ 'aliases' =>
+ array (
+ ),
+ 'reference' => '084d0d0e0ec68c71f414fa62c6c089aae51c1a9b',
+ ),
+ ),
+);
diff --git a/src/Client.php b/src/Client.php
new file mode 100644
index 0000000..54427f4
--- /dev/null
+++ b/src/Client.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace jobd;
+
+
+class Client {
+
+ const WORKER_PORT = 13596;
+ const MASTER_PORT = 13597;
+
+ const EOT = "\4";
+
+ protected $host;
+ protected $port;
+ protected $password;
+ protected $sock;
+
+ /**
+ * JobdClient constructor.
+ * @param int $port
+ * @param string $host
+ * @param string $password
+ */
+ public function __construct(int $port, string $host = '127.0.0.1', string $password = '') {
+ $this->port = $port;
+ $this->host = $host;
+ $this->password = $password;
+
+ $this->sock = fsockopen($this->host, $this->port);
+ if (!$this->sock)
+ throw new \Exception("Failed to connect to {$this->host}:{$this->port}");
+ }
+
+ /**
+ * @return mixed
+ */
+ public function ping() {
+ $this->send(new RequestMessage('ping'));
+ return $this->recv();
+ }
+
+ /**
+ * @param array $targets
+ * @return mixed
+ */
+ public function poke(array $targets) {
+ $this->send(new RequestMessage('poke', ['targets' => $targets]));
+ return $this->recv();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function status() {
+ $this->send(new RequestMessage('status'));
+ return $this->recv();
+ }
+
+ public function poll(array $targets) {
+ $this->send(new RequestMessage('poll', ['targets' => $targets]));
+ return $this->recv();
+ }
+
+ /**
+ * @param int $id
+ * @return mixed
+ */
+ public function runManual(int $id) {
+ $this->send(new RequestMessage('run-manual', ['id' => $id]));
+ return $this->recv();
+ }
+
+ /**
+ * @param RequestMessage $request
+ */
+ public function send(RequestMessage $request) {
+ if ($this->password)
+ $request->setPassword($this->password);
+
+ $serialized = $request->serialize();
+
+ fwrite($this->sock, $serialized . self::EOT);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function recv() {
+ $messages = [];
+ $buf = '';
+ while (!feof($this->sock)) {
+ $buf .= fread($this->sock, 1024);
+ $buflen = strlen($buf);
+ if ($buflen > 0 && $buf[$buflen-1] == self::EOT)
+ break;
+ }
+
+ $offset = 0;
+ $eot_pos = 0;
+ do {
+ $eot_pos = strpos($buf, self::EOT, $offset);
+ if ($eot_pos !== false) {
+ $message = substr($buf, $offset, $eot_pos);
+ $messages[] = $message;
+
+ $offset = $eot_pos + 1;
+ }
+ } while ($eot_pos !== false && $offset < $buflen-1);
+
+ if (empty($message))
+ throw new \Exception("Malformed response: no messages found. Response: {$buf}");
+
+ if (count($messages) > 1)
+ trigger_error(__METHOD__.": received more than one message");
+
+ return self::parseMessage($messages[0]);
+ }
+
+ protected static function parseMessage(string $raw_string) {
+ $raw = json_decode($raw_string, true);
+ if (!is_array($raw) || count($raw) != 2)
+ throw new \Exception("Malformed response: {$raw_string}");
+
+ list($type, $data) = $raw;
+
+ switch ($type) {
+ case Message::REQUEST:
+ if (!$data || !is_array($data) || !isset($data['type']) || !is_string($data['type']))
+ throw new \Exception('Malformed REQUEST message');
+
+ $message = new RequestMessage($data['type'], $data['data'] ?? null);
+ if (isset($data['password']))
+ $message->setPassword($data['password']);
+
+ return $message;
+
+ case Message::RESPONSE:
+ if (!is_array($data) || count($data) < 2)
+ throw new \Exception('Malformed RESPONSE message');
+
+ $message = new ResponseMessage(...$data);
+ return $message;
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function close() {
+ return fclose($this->sock);
+ }
+
+} \ No newline at end of file
diff --git a/src/Message.php b/src/Message.php
new file mode 100644
index 0000000..647afad
--- /dev/null
+++ b/src/Message.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace jobd;
+
+
+abstract class Message {
+
+ const REQUEST = 0;
+ const RESPONSE = 1;
+
+ protected $type;
+
+ /**
+ * Message constructor.
+ * @param int $type
+ */
+ public function __construct(int $type) {
+ $this->type = $type;
+ }
+
+ /**
+ * @return array
+ */
+ abstract protected function getContent(): array;
+
+ /**
+ * @return string
+ */
+ public function serialize(): string {
+ return json_encode([
+ $this->type,
+ $this->getContent()
+ ]);
+ }
+
+} \ No newline at end of file
diff --git a/src/RequestMessage.php b/src/RequestMessage.php
new file mode 100644
index 0000000..46dfba3
--- /dev/null
+++ b/src/RequestMessage.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace jobd;
+
+class RequestMessage extends Message {
+
+ protected $requestType;
+ protected $requestData;
+ protected $password;
+
+ /**
+ * Request constructor.
+ * @param string $request_type
+ * @param null $request_data
+ */
+ public function __construct(string $request_type, $request_data = null) {
+ parent::__construct(Message::REQUEST);
+
+ $this->requestData = $request_data;
+ $this->requestType = $request_type;
+ }
+
+ /**
+ * @param string $password
+ */
+ public function setPassword(string $password) {
+ $this->password = $password;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getContent(): array {
+ $request = ['type' => $this->requestType];
+ if (!is_null($this->requestData))
+ $request['data'] = $this->requestData;
+ return $request;
+ }
+
+} \ No newline at end of file
diff --git a/src/ResponseMessage.php b/src/ResponseMessage.php
new file mode 100644
index 0000000..fa54c29
--- /dev/null
+++ b/src/ResponseMessage.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace jobd;
+
+class ResponseMessage extends Message {
+
+ protected $error;
+ protected $data;
+
+ /**
+ * Response constructor.
+ * @param null $error
+ * @param null $data
+ */
+ public function __construct($error = null, $data = null) {
+ parent::__construct(Message::RESPONSE);
+
+ $this->error = $error;
+ $this->data = $data;
+ }
+
+ /**
+ * @return array
+ */
+ public function getContent(): array {
+ return [$this->error, $this->data];
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getError() {
+ return $this->error;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getData() {
+ return $this->data;
+ }
+
+} \ No newline at end of file