diff --git a/CREDITS b/CREDITS new file mode 100644 index 0000000..53982fc --- /dev/null +++ b/CREDITS @@ -0,0 +1,3 @@ +;; maintainers of SimpleChannelServer +Gregory Beaver [cellog] (lead) +Brett Bieber [saltybeagle] (lead) diff --git a/README b/README new file mode 100644 index 0000000..e893b48 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +A Simple Channel Server for PEAR2 compatible channels + +This package contains a set of scripts to create a simple PEAR compatible +channel server. Included with the package is the pearscs command which you +can use for the command line to manage a pear channel. + +The pearscs utility will be installed in the directory configured by the +bin_dir setting within Pyrus. + +Example usage from the command line: +pearscs create pear.example.com "My Channel" myalias +pearscs release MyPackage-0.0.0.tgz myhandle + +Test \ No newline at end of file diff --git a/RELEASE-0.1.0 b/RELEASE-0.1.0 new file mode 100644 index 0000000..4fd72fb --- /dev/null +++ b/RELEASE-0.1.0 @@ -0,0 +1 @@ +First attempt. diff --git a/TODO b/TODO new file mode 100644 index 0000000..0a98f05 --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +TODO - +Help appreciated! You'll need at least php 5.3! + +* Implement the remaining commands in the CLI utility. +* Build some tests to ensure stability of existing functions. +** Use the SimpleChannelServer's package.xml as a starting point - should be able to build a complete channel for this package. +* Check for phar ini settings to ensure we can open & write phars or .tgzs --- phar.require_hash, diff --git a/customcommand/commands.xml b/customcommand/commands.xml new file mode 100644 index 0000000..fad5818 --- /dev/null +++ b/customcommand/commands.xml @@ -0,0 +1,143 @@ + + + + scs-update + pear2\SimpleChannelServer\CLI + pyrusUpdate + + Simple channel server: Update all releases of a within the get/ directory. + su + + + +This command scans all existing .tar and .tgz files within the get/ directory, +and then uses them to re-generate the REST files in the rest/ directory. + +It must be executed from the channel root directory, the directory must +contain the get/ directory with all releases, and must contain channel.xml. + +In addition, the handle configuration variable must be set to your handle + + + + scs-create + pear2\SimpleChannelServer\CLI + pyrusCreate + + Simple channel server: Create a channel.xml, get/ and rest/ directory for a channel + sc + + + + name + 0 + 0 + Name of the channel to create. This should be the full name, such as pear2.php.net or pear.example.com/path + + + summary + 0 + 0 + Short, 1-line description of the channnel + + + alias + 0 + 1 + Channel alias, such as pear2 for pear2.php.net + + + file + 0 + 1 + full path to the channel.xml to create. Default is channel.xml in the working directory + + + +This command creates a new channel in the current directory, or the directory +specified by the location of the channel.xml file. + + + + scs-add-maintainer + pear2\SimpleChannelServer\CLI + pyrusAddMaintainer + + Simple Channel Server: Add a new maintaing developer to the channel + sam + + + + handle + 0 + 0 + Developer handle as used in package.xml + + + name + 0 + 0 + Developer name + + + uri + 0 + 1 + Developer homepage + + + +This command adds a developer to the channel, and is useful for adding developers +to be listed prior to making their first release. + + + + scs-add-category + pear2\SimpleChannelServer\CLI + pyrusAddCategory + + Simple Channel Server: Add a new category to the channel + sac + + + + category + 0 + 0 + Name of the category. This may contain spaces (use "" to contain them) + + + description + 0 + 0 + Short description of the category's packages + + + +Categories are used to organize channels with large numbers of packages. Use this +command to create a new category. + + + + scs-release + pear2\SimpleChannelServer\CLI + pyrusRelease + + Simple Channel Server: Release a package + sr + + + + path + 0 + 0 + path to the release tarball + + + +Release a package. This command uses the handle configuration variable as the +releasing maintainer's handle, and generates REST files for the release in the +rest/ directory as well as the released tarballs in the get/ directory. + + + \ No newline at end of file diff --git a/examples/update_channel.php b/examples/update_channel.php new file mode 100644 index 0000000..fa46b50 --- /dev/null +++ b/examples/update_channel.php @@ -0,0 +1,36 @@ + + create('Name2', 'Description 2')-> + create('Name3', 'Description 3', 'Alias3')-> + create('Name4', 'Description 4'); + file_put_contents('/tmp/categories.inf', serialize($cat)); +} + +$categories = PEAR2_SimpleChannelServer_Categories::getCategories(); +$categories = $channel->listCategories(); +foreach($categories as $category) { + var_dump($category); +} +*/ +$channel = new pear2\SimpleChannelServer\Channel('pear2.php.net','Brett Bieber\'s PEAR Channel','salty'); + +//$scs = new pear2\SimpleChannelServer\Main($channel,'/Library/WebServer/Documents/pearserver','/home/bbieber/pyrus/php'); +$scs = new pear2\SimpleChannelServer\Main($channel,'/home/cellog/testapache/htdocs',\pear2\Pyrus\Config::current()->location); +$categories = pear2\SimpleChannelServer\Categories::create('Default', 'This is the default category'); +$scs->saveChannel(); +$scs->saveRelease(new \pear2\Pyrus\Package(dirname(__FILE__) . '/../package.xml'), 'cellog'); +echo 'did it'.PHP_EOL; +/* +$manager = new pear2\SimpleChannelServer\REST_Manager('/Library/WebServer/Documents/pearserver','pear2.php.net','rest/',array('cellog')); +var_dump($manager->saveRelease(new \pear2\Pyrus\Package(dirname(__FILE__) . '/../package.xml'),'cellog')); +*/ +?> diff --git a/examples/upgradeFromChiara.php b/examples/upgradeFromChiara.php new file mode 100644 index 0000000..f97fccf --- /dev/null +++ b/examples/upgradeFromChiara.php @@ -0,0 +1,32 @@ +saveChannel(); + +// Path to the get directory. +$dirname = dirname(__FILE__).'/pearchannel/get/'; + +$dir = new DirectoryIterator($dirname); +foreach ($dir as $file) { + if (!$file->isDot() && substr($file->getFilename(), -3) != 'tar') { + $scs->saveRelease(new \pear2\Pyrus\Package($dirname.$file->getFilename()), 'saltybeagle'); + } +} + +?> diff --git a/extrasetup.php b/extrasetup.php new file mode 100644 index 0000000..030b34a --- /dev/null +++ b/extrasetup.php @@ -0,0 +1,11 @@ +setPackagingFilter('pear2\Pyrus\PackageFile\v2Iterator\MinimalPackageFilter'); +$extrafiles = array( + new \pear2\Pyrus\Package(__DIR__ . '/../../HTTP_Request/package.xml'), + $pyrus, + new \pear2\Pyrus\Package(__DIR__ . '/../../Pyrus_Developer/package.xml'), + new \pear2\Pyrus\Package(__DIR__ . '/../../Exception/package.xml'), +); +?> diff --git a/makepackage.php b/makepackage.php new file mode 100644 index 0000000..492b72e --- /dev/null +++ b/makepackage.php @@ -0,0 +1,67 @@ +name.'-'.$package->version['release']; +$a = new \pear2\Pyrus\Package\Creator(array( + //new \pear2\Pyrus\Developer\Creator\Tar($outfile.'.tar', 'none'), + new \pear2\Pyrus\Developer\Creator\Phar($outfile.'.tgz', false, Phar::TAR, Phar::GZ),), + dirname(__FILE__).'/../../Exception/src', + dirname(__FILE__).'/../../Autoload/src', + dirname(__FILE__).'/../../MultiErrors/src'); +$a->render($package); + +$a = new \pear2\Pyrus\Package\Creator(array( + new \pear2\Pyrus\Developer\Creator\Phar(__DIR__ . '/pearscs.phar', 'version['release'] . '/php/\' . implode(\'/\', explode(\'_\', $class)) . \'.php\'; +} +set_include_path(\'phar://\' . PYRUS_PHAR_FILE . \'/PEAR2_SimpleChannelServer-' . + $package->version['release'] . '/php/\'.PATH_SEPARATOR.get_include_path()); +$cli = new \pear2\SimpleChannelServer\CLI(); +$cli->process(); +'),), + dirname(__FILE__) . '/../../Exception/src', + dirname(__FILE__) . '/../../Autoload/src', + dirname(__FILE__) . '/../../MultiErrors/src'); +$b = new \pear2\Pyrus\Package(__DIR__ . '/package.xml'); +$rp = __DIR__ . '/../../HTTP_Request/src/HTTP'; + +$additional_files = array( + 'php/PEAR2/HTTP/Request.php' => $rp . '/Request.php', + 'php/PEAR2/HTTP/Request/Adapter.php' => $rp . '/Request/Adapter.php', + 'php/PEAR2/HTTP/Request/Adapter/Phpsocket.php' => $rp . '/Request/Adapter/Phpsocket.php', + 'php/PEAR2/HTTP/Request/Adapter/Phpstream.php' => $rp . '/Request/Adapter/Phpstream.php', + 'php/PEAR2/HTTP/Request/Exception.php' => $rp . '/Request/Exception.php', + 'php/PEAR2/HTTP/Request/Headers.php' => $rp . '/Request/Headers.php', + 'php/PEAR2/HTTP/Request/Response.php' => $rp . '/Request/Response.php', + 'php/PEAR2/HTTP/Request/Uri.php' => $rp . '/Request/Uri.php', +); +$pyrus = new \pear2\Pyrus\Package(__DIR__ . '/../../Pyrus/package.xml'); +$pyrus_developer = new \pear2\Pyrus\Package(__DIR__ . '/../../Pyrus_Developer/package.xml'); +$exception = new \pear2\Pyrus\Package(__DIR__ . '/../../Exception/package.xml'); +foreach (array('Pyrus' => $pyrus, + 'Pyrus_Developer' => $pyrus_developer, + 'Exception' => $exception) as $add_dir=>$add_package) { + foreach ($add_package->installcontents as $filename=>$details) { + $add_filename = __DIR__ . '/../../'.$add_dir.'/'.$filename; + switch($details['attribs']['role']) { + case 'php': + $additional_files[str_replace('src/','php/PEAR2/', $filename)] = $add_filename; + break; + case 'data': + $additional_files['php/'.$filename] = $add_filename; + $additional_files[$filename] = $add_filename; + break; + } + } +} +$a->render($b, $additional_files); + +?> diff --git a/package.xml b/package.xml new file mode 100644 index 0000000..39ce7b1 --- /dev/null +++ b/package.xml @@ -0,0 +1,90 @@ + + + PEAR2_SimpleChannelServer + pear2.php.net + A Simple Channel Server for PEAR2 compatible channels + + +This package contains a set of scripts to create a simple PEAR compatible +channel server. Included with the package is the pearscs command which you +can use for the command line to manage a pear channel. + +The pearscs utility will be installed in the directory configured by the +bin_dir setting within Pyrus. + +Example usage from the command line: +pearscs create pear.example.com "My Channel" myalias +pearscs release MyPackage-0.0.0.tgz myhandle + +Test + + Gregory Beaver + cellog + cellog@php.net + yes + + + Brett Bieber + saltybeagle + brett.bieber@gmail.com + yes + + 2009-07-13 + + + 0.1.0 + 0.1.0 + + + alpha + alpha + + New BSD License + First attempt. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 2.0.0a1 + + + + + diff --git a/package_compatible.xml b/package_compatible.xml new file mode 100644 index 0000000..4341d35 --- /dev/null +++ b/package_compatible.xml @@ -0,0 +1,95 @@ + + + PEAR2_SimpleChannelServer + pear2.php.net + A Simple Channel Server for PEAR2 compatible channels + + +This package contains a set of scripts to create a simple PEAR compatible +channel server. Included with the package is the pearscs command which you +can use for the command line to manage a pear channel. + +The pearscs utility will be installed in the directory configured by the +bin_dir setting within Pyrus. + +Example usage from the command line: +pearscs create pear.example.com "My Channel" myalias +pearscs release MyPackage-0.0.0.tgz myhandle + +Test + + Gregory Beaver + cellog + cellog@php.net + yes + + + Brett Bieber + saltybeagle + brett.bieber@gmail.com + yes + + 2009-07-13 + + + 0.1.0 + 0.1.0 + + + alpha + alpha + + New BSD License + First attempt. + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.4.8 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pearscs.phar b/pearscs.phar new file mode 100755 index 0000000..727cc89 Binary files /dev/null and b/pearscs.phar differ diff --git a/scripts/pearscs b/scripts/pearscs new file mode 100755 index 0000000..79f1144 --- /dev/null +++ b/scripts/pearscs @@ -0,0 +1,12 @@ +#!/usr/bin/env php +process(); diff --git a/src/SimpleChannelServer/CLI.php b/src/SimpleChannelServer/CLI.php new file mode 100644 index 0000000..7c402fe --- /dev/null +++ b/src/SimpleChannelServer/CLI.php @@ -0,0 +1,246 @@ +channel = new \pear2\Pyrus\Channel(new \pear2\Pyrus\ChannelFile($channel_file)); + } + $this->dir = getcwd(); + } + + public function process() + { + if ($_SERVER['argc'] < 2) { + $this->printUsage(); + return false; + } + switch ($_SERVER['argv'][1]) { + case 'update': + $this->handleUpdate(); + break; + case 'create': + return $this->handleCreate(); + case 'add-maintainer': + $scs = new Main($this->channel, + $this->dir); + // is this even needed? + // yes, new maintainers that have not yet released anything. + break; + case 'add-category': + Categories::create($category, $description); + break; + case 'release': + $this->handleRelease(); + break; + default: + echo 'Please use one of the following commands:'.PHP_EOL; + $this->printUsage(); + break; + } + } + + function pyrusAddCategory($frontend, $args) + { + if (!isset($this->channel)) { + throw new Exception('Unknown channel, run the' . + 'scs-create command first'); + } + Categories::create($args['category'], $args['description']); + $category = new REST\Category($this->dir . '/rest', $this->channel->name); + $category->saveAllCategories(); + $category->savePackagesInfo($args['category']); + echo "Added category ", $args['category'], "\n"; + } + + function pyrusAddMaintainer($frontend, $args) + { + if (!isset($this->channel)) { + throw new Exception('Unknown channel, run the' . + 'scs-create command first'); + } + $maintainer = new REST\Maintainer($this->dir . '/rest', $this->channel->name); + if (isset($args['uri'])) { + $uri = $args['uri']; + } else { + $uri = null; + } + $maintainer->saveInfo($args['handle'], $args['name'], $uri); + $maintainer->saveAll(); + echo "Added maintainer ", $args['handle'], "\n"; + } + + public function handleUpdate() + { + if (!isset($_SERVER['argv'][2])) { + $this->printUpdateUsage(); + return; + } + if (!isset($this->channel)) { + $this->printUpdateUsage(); + return; + } + $this->pyrusUpdate(null, $_SERVER['argv'][2]); + } + + function pyrusUpdate($frontend, $args) + { + if (!isset($this->channel)) { + throw new Exception('Unknown channel, run the' . + 'scs-create command first'); + } + if (null === $frontend) { + $maintainer = $args; + } else { + $chan = \pear2\Pyrus\Config::current()->default_channel; + \pear2\Pyrus\Config::current()->default_channel = $this->channel->name; + $maintainer = \pear2\Pyrus\Config::current()->handle; + \pear2\Pyrus\Config::current()->default_channel = $chan; + } + $scs = new Main($this->channel, $this->dir); + $dirname = $this->dir . '/get/'; + $dir = new DirectoryIterator($dirname); + foreach ($dir as $file) { + if (!$file->isDot() + && !$file->isDir() + && substr($file->getFilename(), -3) != 'tar' + && substr($file->getFilename(), 0, 1) != '.') { + $scs->saveRelease(new \pear2\Pyrus\Package($dirname.$file->getFilename()), $maintainer); + } + } + } + + public function handleRelease() + { + if (!isset($_SERVER['argv'][3])) { + $this->printReleaseUsage(); + return; + } + $this->pyrusRelease(null, array('path' => $_SERVER['argv'][2], + 'maintainer' => $_SERVER['argv'][3])); + } + + function pyrusRelease($frontend, $args) + { + if (null !== $frontend) { + $chan = \pear2\Pyrus\Config::current()->default_channel; + \pear2\Pyrus\Config::current()->default_channel = $this->channel->name; + $args['maintainer'] = \pear2\Pyrus\Config::current()->handle; + \pear2\Pyrus\Config::current()->default_channel = $chan; + } + $scs = new Main($this->channel, $this->dir, $this->pyruspath); + $scs->saveRelease($args['path'], $args['maintainer']); + echo 'Release successfully saved.'.PHP_EOL; + } + + public function printReleaseUsage() + { + echo ' +Usage: pearscs release packagefile maintainer + This will release the package to the channel. + + packagefile The release .tgz file. + maintainer The channel maintainer performing the release. + +'; + } + + public function handleCreate() + { + if ($_SERVER['argc'] < 4) { + $this->printCreateUsage(); + return false; + } + + $name = $_SERVER['argv'][2]; + $summary = $_SERVER['argv'][3]; + $alias = null; + + $args = array('name' => $name, + 'summary' => $summary, + 'alias' => $alias, + ); + if (isset($_SERVER['argv'][4])) { + $args['alias'] = $_SERVER['argv'][4]; + } + if (isset($_SERVER['argv'][5])) { + $args['file'] = $_SERVER['argv'][5]; + } + $this->pyrusCreate(null, $args); + } + + function pyrusCreate($frontend, $args) + { + if (!isset($args['file'])) { + $args['file'] = getcwd() . '/channel.xml'; + } + $this->dir = dirname($args['file']); + $this->channel = new Channel($args['name'], + $args['summary'], + $args['alias']); + $scs = new Main($this->channel, + $this->dir); + $scs->saveChannel(); + echo ' +Created '.$args['name'].' + | ./channel.xml + | ./rest/ + | ./get/'.PHP_EOL; + return true; + } + + public function printCreateUsage() + { + echo ' +Usage: pearscs create pear.example.com summary [alias] [./channel.xml] + This will create a file named channel.xml for the pear channel pear.example.com. + + summary This is the a description for the channel. + alias Channel alias pear users can use as a shorthand. + filename Path to where to create the channel.xml file. Current directory will be + used by default. + +'; + } + + public function printUpdateUsage() + { + echo ' +Usage: pearscs update maintainer [channel.xml] + This will update all releases within the /get/ directory. +'; + } + + public function printUsage() + { + echo ' +Usage: pearscs update|create|add-maintainer|add-category|release [option] + Commands: + update [channel.xml] Update the channel xml files. + create pear.example.com summary [...] Create a new channel. + add-maintainer handle Add a maintainer. + add-category category Add a category. + release package.tgz maintainer Release package. +'; + } +} + +?> \ No newline at end of file diff --git a/src/SimpleChannelServer/Categories.php b/src/SimpleChannelServer/Categories.php new file mode 100644 index 0000000..5450fe7 --- /dev/null +++ b/src/SimpleChannelServer/Categories.php @@ -0,0 +1,213 @@ + + * // make sure the class exists prior to unserialize attempt + * include '/path/to/PEAR2/SimpleChannelServer/Categories.php'; + * if (!@unserialize(file_get_contents('/path/to/serialize/categories.inf'))) { + * $cat = pear\SimpleChannelServer\Categories::create('Name1', + * 'Description 1', 'Alias1')-> + * create('Name2', 'Description 2')-> + * create('Name3', 'Description 3', 'Alias3')-> + * create('Name4', 'Description 4'); + * file_put_contents('/path/to/serialize/categories.inf', serialize($cat)); + * } + * $categories = pear2\SimpleChannelServer\Categories::getCategories(); + * $categories->link('SimpleChannelServer', 'Developer'); + * + * + * @category Developer + * @package PEAR2_SimpleChannelServer + * @author Greg Beaver + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer; +class Categories +{ + /** + * Category information indexed by category name + * @var array('Default' => array('desc' => 'Default Category', 'alias' => 'Default')); + */ + private $_categories = array(); + private $_packages = array(); + /** + * @var PEAR2_SimpleChannelServer_Categories + */ + static private $_category; + + /** + * No direct instantiation allowed + */ + private function __construct() + { + } + + static function exists($category) + { + if (!isset(self::$_category)) { + return false; + } + if (isset(self::$_category->_categories[$category])) { + return true; + } + return false; + } + + /** + * Creates a channel category + * + * @param string $name Category name + * @param string $description Description of the category + * @param string $alias Alias of the category + * + * @return pear2\SimpleChannelServer\Categories + */ + static function create($name, $description, $alias = null) + { + if (!isset(self::$_category)) { + self::$_category = new Categories; + } + return self::$_category->_create($name, $description, $alias); + } + + private function _create($name, $description, $alias = null) + { + if (isset($this->_categories[$name])) { + throw new Categories\Exception( + 'Category "' . $name . '" has already been defined'); + } + if (!$alias) { + $alias = $name; + } + $this->_categories[$name] = array('desc' => $description, 'alias' => $alias); + $this->_info = false; + self::$_category = $this; + $this->_info = $this->getCategories(); + return $this; + } + + /** + * returns categories which are defined + * + * @return array + */ + static function getCategories() + { + if (self::$_category === null) { + throw new Categories\Exception('You must construct a singleton instance with pear2\SimpleChannelServer\Categories::create($name, $description, $alias = null)'); + } else { + return self::$_category->_categories; + } + } + + /** + * get the category for a package + * + * @param string $package Name of package + * + * @return string + */ + static function getPackageCategory($package) + { + if (self::$_category === null) { + throw new Categories\Exception('You must construct a singleton instance with pear2\SimpleChannelServer\Categories::create($name, $description, $alias = null)'); + } else { + return self::$_category->getCategory($package); + } + } + + /** + * link a package to a category + * + * @param string $package name of the package + * @param string $category name of the category + * @param bool $strict if package is already in a category, throw exception + * + * @return unknown + */ + static function linkPackageToCategory($package, $category, $strict = false) + { + return self::$_category->link($package, $category, $strict); + } + + /** + * get the packages in a category + * + * @param string $category name of category + * + * @return array + */ + static function packagesInCategory($category) + { + return self::$_category->packages($category); + } + + /** + * return all known packages in a specific category + * + * @param string $category name of category + * + * @return array(string) Names of packages in the category + */ + public function packages($category) + { + $ret = array(); + foreach ($this->_packages as $p => $c) { + if ($c === $category) { + $ret[] = $p; + } + } + return $ret; + } + + /** + * find what category a package is in - if the category for this package is not + * defined, it will assign it to the default category + * + * @param string $package Name of the package to check + * + * @return string name of the category + */ + public function getCategory($package) + { + if (!isset($this->_packages[$package])) { + $this->link($package, 'Default'); + } + return $this->_packages[$package]; + } + + /** + * Links a package to a specific category + * + * @param string $package name of package + * @param string $category name of category + * @param bool $strict ensure packages are only in one category + * + * @return void + */ + public function link($package, $category, $strict = false) + { + if (isset($this->_packages[$package]) && $strict) { + throw new Categories\Exception( + 'Package "' . $package . '" is already linked to category "' . + $this->_packages[$package] . '"'); + } + if (!isset($this->_categories[$category])) { + throw new Categories\Exception( + 'Unknown category "' . $category . '"'); + } + $this->_packages[$package] = $category; + } + + /** + * called after serialized and woken up + * + * @return void + */ + function __wakeup() + { + self::$_category = $this; + } +} \ No newline at end of file diff --git a/src/SimpleChannelServer/Categories/Exception.php b/src/SimpleChannelServer/Categories/Exception.php new file mode 100644 index 0000000..65a2cf4 --- /dev/null +++ b/src/SimpleChannelServer/Categories/Exception.php @@ -0,0 +1,3 @@ + + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer; +class Channel extends \pear2\Pyrus\ChannelFile +{ + + function __construct($name, $summary, $suggestedalias = null, $restpath = 'rest/') + { + parent::__construct(' + + pear2.php.net + salty + Simple PEAR Channel + + + + http://foo/rest/ + http://foo/rest/ + http://foo/rest/ + + + +', true); + + $this->name = $name; + $this->summary = $summary; + $this->resetREST(); + $this->protocols->rest['REST1.0']->baseurl = 'http://'.$name.'/'.$restpath; + $this->protocols->rest['REST1.1']->baseurl = 'http://'.$name.'/'.$restpath; + $this->protocols->rest['REST1.2']->baseurl = 'http://'.$name.'/'.$restpath; + $this->protocols->rest['REST1.3']->baseurl = 'http://'.$name.'/'.$restpath; + if (!empty($suggestedalias)) { + $this->alias = $suggestedalias; + } else { + $this->alias = self::guessChannelAlias($name); + } + } + + function getChannelFile() + { + return $this->__toString(); + } + + public static function guessChannelAlias($name) + { + if (strpos($name, '/') !== false) { + // www.server.com/fish,simplecas.googlecode.com/svn + $alias = explode('/', $name); + if ($alias[count($alias)-1] != 'pear' + && $alias[count($alias)-1] != 'svn') { + // return fish + return $alias[count($alias)-1]; + } + } + // Something like pear.saltybeagle.com,mychannel.com,localhost,simplecas.googlecode.com/svn + $alias = explode('.', $name); + if (count($alias) > 2 + && $alias[1] != 'googlecode') { + // return saltybeagle + return $alias[1]; + } + // return mychannel or localhost or simplecas + return $alias[0]; + } +} diff --git a/src/SimpleChannelServer/Exception.php b/src/SimpleChannelServer/Exception.php new file mode 100644 index 0000000..74e2144 --- /dev/null +++ b/src/SimpleChannelServer/Exception.php @@ -0,0 +1,3 @@ +get = $savepath; + $this->pyruspath = $pyruspath; + if (!file_exists($savepath)) { + if (!@mkdir($savepath, 0777, true)) { + throw new Exception('Could not initialize' . + 'GET storage directory "' . $savepath . '"'); + } + } + } + + function saveRelease($new, $releaser) + { + $cloner = new \pear2\Pyrus\Package\Cloner($new, $this->get); + $cloner->toTar(); + $cloner->toTgz(); + $cloner->toPhar(); + $cloner->toZip(); + return true; + } + + function deleteRelease(\pear2\Pyrus\Package $release) + { + + } +} diff --git a/src/SimpleChannelServer/Main.php b/src/SimpleChannelServer/Main.php new file mode 100644 index 0000000..082fd5d --- /dev/null +++ b/src/SimpleChannelServer/Main.php @@ -0,0 +1,303 @@ + + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer; +class Main +{ + /** + * @var string + */ + protected $channel; + /** + * @var string + */ + protected $webpath; + /** + * @var string + */ + protected $uri; + /** + * REST manager + * + * @var pear2\SimpleChannelServer\REST\Manager + */ + protected $rest; + + /** + * GET manager + * + * @var pear2\SimpleChannelServer\Get + */ + protected $get; + + /** + * Construct simple channel server + * + * @param pear2\SimpleChannelServer\Channel $channel channel object + * @param string $webpath full path to web files eg: /var/www/pear/ + * @param string $pyruspath Path to the pyrus controlled PEAR installation + */ + function __construct($channel, $webpath, $pyruspath = null) + { + if (!realpath($webpath) || !is_writable($webpath)) { + throw new Exception('Path to channel web files ' . + $webpath . + ' must exist and be writable'); + } else { + $this->webpath = $webpath; + } + if (!$pyruspath) { + $pyruspath = __DIR__; + } + $rest = $channel->protocols->rest; + foreach ($rest as $restpath) { + $restpath = str_replace('http://'.$channel->name.'/', '', $restpath); + break; + } + if (dirname($restpath . 'a') . '/' !== $restpath) { + $restpath .= '/'; + } + $this->uri = 'http://' . $channel->name . '/'; + $this->channel = $channel; + $this->rest = new REST\Manager($webpath.'/'.$restpath, $channel->name, + $restpath); + $this->get = new Get($webpath.'/get', $pyruspath); + try { + $a = \pear2\Pyrus\Config::singleton($pyruspath); + } catch (Exception $e) { + throw new Exception('Cannot initialize Pyrus Config', + $e); + } + } + + function saveRelease($package, $releaser) + { + $rest = $this->rest->saveRelease(new \pear2\Pyrus\Package($package), $releaser); + $get = $this->get->saveRelease($package, $releaser); + return $rest && $get; + } + + function saveChannel() + { + file_put_contents($this->webpath . '/channel.xml', $this->channel->getChannelFile()); + chmod($this->webpath . '/channel.xml', 0666); + } + + /** + * List all categories (unsorted) + * + * @return array + */ + function listCategories() + { + return Categories::getCategories(); + } + + /** + * List all packages, organized by category (unsorted) + * + * @return array + */ + function listPackagesByCategory() + { + $ret = array(); + foreach (Categories::getCategories() as $cat) { + $ret[$cat] = Categories::packagesInCategory($cat); + } + return $ret; + } + + /** + * List all packages, or all packages in a category + * + * @param string|null $category null to list all packages + * + * @return array + */ + function listPackages($category = null) + { + if ($category) { + return Categories::packagesInCategory($category); + } + if (!file_exists($this->rest->getRESTPath('p', 'allpackages.xml'))) { + return array(); + } + try { + $list = $reader->parse($this->rest->getRESTPath('p', 'allpackages.xml')); + } catch (\Exception $e) { + throw new Exception('Unable to list packages', + $e); + } + return $list['a']['p']; + } + + /** + * List all maintainers, or maintainers of a specific package + * + * @param string|null $package null to list all maintainers + * + * @return array + */ + function listMaintainers($package = null) + { + if ($package === null) { + if (!file_exists($this->rest->getRESTPath('p', 'allpackages.xml'))) { + return array(); + } + try { + $list = $reader->parse($this->rest->getRESTPath('m', 'allmaintainers.xml')); + $maint = new REST\Maintainer($this->webpath, + $this->channel, $this->uri); + $ret = array(); + foreach ($list['m']['h'] as $info) { + $inf = $maint->getInfo($info['_content']); + if (!$inf) { + throw new Exception('Maintainer ' . + $info['_content'] . ' is listed as a maintainer, ' . + 'but does not have an info file'); + } + $ret[] = array( + 'user' => $info['_content'], + 'name' => $inf['n'] + ); + } + return $ret; + } catch (\Exception $e) { + throw new Exception('Unable to list maintainers', + $e); + } + } + $reader = new \pear2\Pyrus\XMLParser; + $path = $this->rest->getRESTPath('r', strtolower($package) . '/maintainers2.xml'); + if (!file_exists($path)) { + return array(); + } + try { + $list = $reader->parse($path); + } catch (\Exception $e) { + throw new Exception('Unable to list maintainers for' . + ' package ' . $package, + $e); + } + $ret = array(); + if (!isset($list['m']['m'][0])) { + $list['m']['m'] = array($list['m']['m']); + } + $maint = new REST\Maintainer($this->webpath, $this->channel, + $this->uri); + foreach ($list['m']['m'] as $maintainer) { + $info = $maint->getInfo($maintainer['h']); + $inf = array( + 'user' => $maintainer['h'], + ); + if ($info) { + $inf['name'] = $info['n']; + } + $inf['active'] = $maintainer['a']; + $ret[] = $inf; + } + return $ret; + } + + /** + * List release info with dependencies formatted for easy processing + * by a web frontend. + * + * @param string $package Package name eg: PEAR2_SimpleChannelServer + * + * @return array + */ + function listReleases($package) + { + $path = $this->rest->getRESTPath('r', strtolower($package) . '/allreleases2.xml'); + if (!file_exists($path)) { + return array(); + } + try { + $list = $reader->parse($path); + } catch (\Exception $e) { + throw new Exception('Unable to list releases of ' . + $package . ' package', + $e); + } + if (!isset($list['a']['r'][0])) { + $list['a']['r'] = array($list['a']['r']); + } + $ret = array(); + foreach ($list['a']['r'] as $info) { + $inf = array( + 'version' => $info['v'], + 'stability' => $info['s'], + 'minimum PHP version' => $info['m'] + ); + + $deps = unserialize(file_get_contents($this->rest->getPath('r', + strtolower($package) . '/deps.' . $info['v'] . '.txt'))); + + $inf['required'] = array(); + if (isset($deps['required']['package'])) { + $inf['required']['package'] = $deps['required']['package']; + if (!isset($inf['required']['package'][0])) { + $inf['required']['package'] = array($inf['required']['package']); + } + } + if (isset($deps['required']['subpackage'])) { + if (!isset($deps['required']['subpackage'][0])) { + $deps['required']['subpackage'] = array($deps['required']['subpackage']); + } + foreach ($deps['required']['subpackage'] as $s) { + $inf['required']['package'][] = $s; + } + } + if (isset($deps['required']['extension'])) { + if (!isset($deps['required']['extension'][0])) { + $deps['required']['extension'] = array($deps['required']['extension']); + } + foreach ($deps['required']['extension'] as $s) { + $inf['required']['extension'][] = $s; + } + } + if (isset($deps['optional']['package'])) { + $inf['optional'] = array(); + $inf['optional']['package'] = $deps['optional']['package']; + if (!isset($inf['optional']['package'][0])) { + $inf['optional']['package'] = array($inf['optional']['package']); + } + } + if (isset($deps['optional']['extension'])) { + if (!isset($deps['optional']['extension'][0])) { + $deps['optional']['extension'] = array($deps['optional']['extension']); + } + foreach ($deps['optional']['extension'] as $s) { + $inf['optional']['extension'][] = $s; + } + } + if (isset($deps['optional']['subpackage'])) { + if (!isset($deps['optional']['subpackage'][0])) { + $deps['optional']['subpackage'] = array($deps['optional']['subpackage']); + } + foreach ($deps['optional']['subpackage'] as $s) { + $inf['optional']['package'][] = $s; + } + } + if (isset($deps['group'])) { + $inf['groups'] = array(); + if (!isset($deps['group'][0])) { + $deps['group'] = array($deps['group']); + } + foreach ($deps['group'] as $group) { + $inf['groups'] = $group['attribs']; + } + } + $ret[] = $inf; + } + return $ret; + } +} diff --git a/src/SimpleChannelServer/REST/Category.php b/src/SimpleChannelServer/REST/Category.php new file mode 100644 index 0000000..1d88cc9 --- /dev/null +++ b/src/SimpleChannelServer/REST/Category.php @@ -0,0 +1,170 @@ + + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer\REST; +use pear2\SimpleChannelServer\Categories; +class Category extends Manager +{ + /** + * Construct a new rest category object + * + * @param string $savepath full path to REST files + * @param string $channel the channel name + * @param string $serverpath relative path within URI to REST files + */ + function __construct($savepath, $channel, $serverpath = 'rest/') + { + parent::__construct($savepath, $channel, $serverpath); + } + + /** + * Save a package release's REST-related information + * + * @param \pear2\Pyrus\Package $new Package to save category for + * + * @return void + */ + function save(\pear2\Pyrus\Package $new) + { + $category = Categories::getPackageCategory($new->name); + $this->savePackagesInfo($category); + $this->saveAllCategories(); + } + + /** + * Delete a package release's REST-related information + * + * @param \pear2\Pyrus\Package $new Package to rease rest info for + * + * @return void + */ + function erase(\pear2\Pyrus\Package $new) + { + $category = Categories::getPackageCategory($new->name); + $this->savePackagesInfo($category); + } + + /** + * Save REST xml information for all categories + * + * This is not release-dependent + * + * @return void + */ + function saveAllCategories() + { + $categories = Categories::getCategories(); + $xml = $this->_getProlog('a', 'allcategories'); + $xml['a']['ch'] = $this->channel; + $xml['a']['c'] = array(); + if (count($categories) == 1) { + $xml['a']['c'] = array('attribs' => + array('xlink:href' => + $this->getCategoryRESTLink(urlencode(key($categories)) + . '/info.xml')), + '_content' => key($categories)); + $this->saveInfo(key($categories), + $categories[key($categories)]['desc'], + $categories[key($categories)]['alias']); + } else { + foreach ($categories as $category => $data) { + $xml['a']['c'][] = array( + 'attribs' => array( + 'xlink:href' => + $this->getCategoryRESTLink(urlencode($category) . '/info.xml')), + '_content' => $category, + ); + $this->saveInfo($category, $data['desc'], $data['alias']); + } + } + $this->saveCategoryREST('categories.xml', $xml); + } + + /** + * Save information on a category + * + * This is not release-dependent + * + * @param string $category The name of the category eg:Services + * @param string $desc Basic description for the category + * @param string $alias Optional alias category name + * + * @return void + */ + function saveInfo($category, $desc, $alias = false) + { + if (!Categories::exists($category)) { + Categories::create($category, $desc, $alias); + } + $xml = $this->_getProlog('c', 'category'); + $xml['c']['n'] = $category; + $xml['c']['a'] = $alias ? $category : $alias; + $xml['c']['c'] = $this->channel; + $xml['c']['d'] = $desc; + $this->saveCategoryREST(urlencode($category) . '/info.xml', $xml); + } + + /** + * Save packagesinfo.xml for a category + * + * @param string $category Category to update packages info for + * + * @return void + */ + function savePackagesInfo($category) + { + $xml = array(); + $pdir = $this->rest . 'p'; + $rdir = $this->rest . 'r'; + + $packages = Categories::packagesInCategory($category); + $reader = new \pear2\Pyrus\XMLParser; + clearstatcache(); + $xml['pi'] = array(); + foreach ($packages as $package) { + $next = array(); + if (!file_exists($pdir . DIRECTORY_SEPARATOR . strtolower($package) . + DIRECTORY_SEPARATOR . 'info.xml')) { + continue; + } + $f = $reader->parse($pdir . DIRECTORY_SEPARATOR . strtolower($package) . + DIRECTORY_SEPARATOR . 'info.xml'); + unset($f['p']['attribs']); + $next['p'] = $f['p']; + if (file_exists($rdir . DIRECTORY_SEPARATOR . strtolower($package) . + DIRECTORY_SEPARATOR . 'allreleases.xml')) { + $r = $reader->parse($rdir . DIRECTORY_SEPARATOR . + strtolower($package) . DIRECTORY_SEPARATOR . + 'allreleases.xml'); + unset($r['a']['attribs']); + unset($r['a']['p']); + unset($r['a']['c']); + $next['a'] = $r['a']; + $dirhandle = opendir($rdir . DIRECTORY_SEPARATOR . + strtolower($package)); + while (false !== ($entry = readdir($dirhandle))) { + if (strpos($entry, 'deps.') === 0) { + $version = str_replace(array('deps.', '.txt'), array('', ''), $entry); + + $next['deps'] = array(); + $next['deps']['v'] = $version; + $next['deps']['d'] = file_get_contents($rdir . DIRECTORY_SEPARATOR . + strtolower($package) . DIRECTORY_SEPARATOR . + $entry); + } + } + } + $xml['pi'][] = $next; + } + $xmlinf = $this->_getProlog('f', 'categorypackageinfo'); + $xmlinf['f'][] = $xml; + $this->saveCategoryREST(urlencode($category) . DIRECTORY_SEPARATOR . 'packagesinfo.xml', $xmlinf); + } +} \ No newline at end of file diff --git a/src/SimpleChannelServer/REST/Maintainer.php b/src/SimpleChannelServer/REST/Maintainer.php new file mode 100644 index 0000000..a4f0a5b --- /dev/null +++ b/src/SimpleChannelServer/REST/Maintainer.php @@ -0,0 +1,88 @@ + + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer\REST; +use pear2\SimpleChannelServer\Exception; +class Maintainer extends Manager +{ + function save(\pear2\Pyrus\Package $new) + { + foreach ($new->allmaintainers as $role => $maintainers) { + foreach ($maintainers as $dev) { + $this->saveInfo($dev->user, $dev->name); + } + } + $this->saveAll(); + } + + /** + * Save an individual maintainer's REST + * + * @param string $handle Maintainer's handle eg: cellog + * @param string $name The maintainers real name eg: Gregory Beaver + * @param string $uri URI to the person's blog etc. + */ + function saveInfo($handle, $name, $uri = false) + { + $xml = $this->_getProlog('m', 'maintainer'); + $xml['m']['n'] = $handle; + $xml['m']['h'] = $name; + if ($uri) { + $xml['m']['u'] = $uri; + } + $this->saveMaintainerREST(strtolower($handle) . '/info.xml', $xml); + } + + /** + * Grab information on a maintainer + * + * @param string $handle The user's handle eg: cellog + * + * @return array + */ + function getInfo($handle) + { + $path = $this->getRESTPath('m', strtolower($handle) . '/info.xml'); + $reader = new \pear2\Pyrus\XMLParser; + if (!file_exists($path)) { + return false; + } + try { + $info = $reader->parse($path); + return $info['m']; + } catch (\Exception $e) { + throw new Exception('Cannot read information on ' . + 'developer ' . $handle, $e); + } + } + + /** + * Save a list of all maintainers in REST + */ + function saveAll() + { + $xml = $this->_getProlog('m', 'allmaintainers'); + $xml['m']['h'] = array(); + foreach (new \DirectoryIterator($this->rest . '/m') as $file) { + if ($file->isDot()) continue; + if ($file->isDir() + && $file->getBasename() != 'CVS' + && $file->getBasename() != '.svn') { + $xml['m']['h'][] = array( + 'attribs' => array( + 'xlink:href' => $this->uri . 'm/' . $file + ), + '_content' => $file->__toString() + ); + } + } + $this->saveMaintainerREST('allmaintainers.xml', $xml); + } +} \ No newline at end of file diff --git a/src/SimpleChannelServer/REST/Manager.php b/src/SimpleChannelServer/REST/Manager.php new file mode 100644 index 0000000..6f7a7b6 --- /dev/null +++ b/src/SimpleChannelServer/REST/Manager.php @@ -0,0 +1,219 @@ + + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer\REST; +use pear2\SimpleChannelServer\Exception; +class Manager +{ + /** + * Full path on the filesystem to the REST files + * + * @var string + */ + protected $rest; + /** + * Relative path to REST files for URI link construction + * + * @var string + */ + protected $uri; + /** + * Channel name this REST server applies to + * + * @var string + */ + protected $chan; + + /** + * @param string $savepath full path to REST files + * @param string $channel the channel name + * @param string $serverpath relative path within URI to REST files + * @param array $admins an array of handles that are channel administrators + * and can release/delete any package + */ + function __construct($savepath, $channel, $serverpath = 'rest/') + { + $this->rest = $savepath; + if (!file_exists($savepath)) { + if (!@mkdir($savepath, 0777, true)) { + throw new Exception('Could not initialize' . + 'REST storage directory "' . $savepath . '"'); + } + } + $this->uri = $serverpath; + $this->chan = $channel; + } + + /** + * Save release REST for a new package release. + * + * Prior to calling this, categories and category package links + * should be set up, otherwise the package will be released under + * the "Default" category. + * + * @param \pear2\Pyrus\Package $release + * @param string $releaser handle of person who is uploading this release + */ + function saveRelease(\pear2\Pyrus\Package $new, $releaser) + { + if ($new->channel !== $this->chan) { + throw new Exception('Cannot release ' . + $new->name . '-' . $new->version['release'] . ', we are managing ' . + $this->chan . ' channel, and package is in ' . + $new->channel . ' channel'); + } + if (!isset($new->maintainer[$releaser]) || + $new->maintainer[$releaser]->role !== 'lead') { + throw new Exception($releaser . ' is not a ' . + 'lead maintainer of this package, and cannot release'); + } + $category = new Category($this->rest, $this->chan, + $this->uri); + $package = new Package($this->rest, $this->chan, + $this->uri); + $maintainer = new Maintainer($this->rest, $this->chan, + $this->uri); + $release = new Release($this->rest, $this->chan, + $this->uri); + $maintainer->save($new); + $package->save($new); + $release->save($new, $releaser); + $category->save($new); + } + + /** + * Remove a release from package REST + * + * Removes REST. If $deleteorphaned is true, then + * maintainers who no longer maintain a package will be + * deleted from package maintainer REST. + * @param \pear2\Pyrus\Package $release + * @param string $deleter handle of maintainer deleting this release + * @param bool $deleteorphaned + */ + function deleteRelease(\pear2\Pyrus\Package $release, $deleter, $deleteorphaned = true) + { + if ($new->channel !== $this->chan) { + throw new Exception('Cannot delete release ' . + $new->name . '-' . $new->version['release'] . ', we are managing ' . + $this->chan . ' channel, and package is in ' . + $new->channel . ' channel'); + } + if (!isset($this->_admins[$releaser]) && (!isset($new->maintainer[$releaser]) || + $new->maintainer[$releaser]->role !== 'lead')) { + throw new Exception($releaser . ' is not a ' . + 'lead maintainer of this package, and cannot delete the release'); + } + $category = new Category($this->rest, $this->chan, + $this->uri); + $package = new Package($this->rest, $this->chan, + $this->uri); + $maintainer = new Maintainer($this->rest, $this->chan, + $this->uri); + $release = new Release($this->rest, $this->chan, + $this->uri); + $maintainer->erase($new, $deleteorphaned); + $package->erase($new); + $release->erase($new); + $category->erase($new); + } + + function __get($var) + { + if ($var == 'path') { + return $this->uri; + } + if ($var == 'channel') { + return $this->chan; + } + } + + protected function _getProlog($basetag, $schema) + { + return array($basetag => array( + 'attribs' => + array( + 'xmlns' => 'http://pear.php.net/dtd/rest.' . $schema, + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xlink' => 'http://www.w3.org/1999/xlink', + 'xsi:schemaLocation' => 'http://pear.php.net/dtd/rest.' . + $schema . ' http://pear.php.net/dtd/rest.' . + $schema . '.xsd', + ), + )); + } + + function getCategoryRESTLink($file) + { + return $this->uri . 'c/' . $file; + } + + function getPackageRESTLink($file) + { + return $this->uri . 'p/' . $file; + } + + function getReleaseRESTLink($file) + { + return $this->uri . 'r/' . $file; + } + + function getMaintainerRESTLink($file) + { + return $this->uri . 'm/' . $file; + } + + function getRESTPath($type, $file) + { + return $this->rest . DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . + $file; + } + + private function _initDir($dir, $dirname = false) + { + if (!$dirname) $dir = dirname($dir); + if (!file_exists($dir)) { + if (!@mkdir($dir, 0777, true)) { + throw new Exception('Could not initialize' . + 'REST category storage directory "' . $dir . '"'); + } + } + } + + private function _saveREST($path, $contents, $isxml, $type) + { + $this->_initDir($this->rest . '/' . $type . '/' . $path); + if ($isxml) { + $contents = (string) new \pear2\Pyrus\XMLWriter($contents); + } + file_put_contents($this->rest . '/' . $type . '/' . $path, $contents); + chmod($this->rest . '/' . $type . '/' . $path, 0666); + } + + function saveReleaseREST($path, $contents, $isxml = true) + { + $this->_saveREST($path, $contents, $isxml, 'r'); + } + + function saveCategoryREST($path, $contents, $isxml = true) + { + $this->_saveREST($path, $contents, $isxml, 'c'); + } + + function savePackageREST($path, $contents, $isxml = true) + { + $this->_saveREST($path, $contents, $isxml, 'p'); + } + + function saveMaintainerREST($path, $contents, $isxml = true) + { + $this->_saveREST($path, $contents, $isxml, 'm'); + } +} \ No newline at end of file diff --git a/src/SimpleChannelServer/REST/Package.php b/src/SimpleChannelServer/REST/Package.php new file mode 100644 index 0000000..abe4a74 --- /dev/null +++ b/src/SimpleChannelServer/REST/Package.php @@ -0,0 +1,177 @@ + + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer\REST; +use pear2\SimpleChannelServer\Categories; +class Package extends Manager +{ + private $_packages = array(); + + /** + * Save package REST based on a release + * + * @param \pear2\Pyrus\package $new + */ + function save(\pear2\Pyrus\Package $new) + { + $this->_packages[$new->name] = true; + $this->saveInfo($new); + $this->saveAllPackages(); + $this->saveMaintainers($new); + } + + /** + * Remove package REST based on a release + * + * This does nothing + * @param \pear2\Pyrus\package $new + */ + function erase(\pear2\Pyrus\Package $new) + { + } + + /** + * Mark a package as deprecated in favor of another package + * + * @param string $name + * @param string $newpackage + * @param string $newchannel + */ + function deprecatePackage($name, $newpackage, $newchannel) + { + if (file_exists($this->rest . DIRECTORY_SEPARATOR . 'p' . DIRECTORY_SEPARATOR . + strtolower($name) . DIRECTORY_SEPARATOR . 'info.xml')) { + $oldinfo = $reader->parse($this->rest . DIRECTORY_SEPARATOR . 'p' . + DIRECTORY_SEPARATOR . 'info.xml'); + } + $oldinfo['p']['dc'] = $newchannel; + $oldinfo['p']['dp'] = $newpackage; + $this->savePackageREST(strtolower($name) . DIRECTORY_SEPARATOR . 'info.xml', + $oldinfo); + } + + /** + * Remove a package from the REST list + * + * @param unknown_type $name + */ + function deletePackage($name) + { + unset($this->_packages[$name]); + @unlink($this->rest . DIRECTORY_SEPARATOR . 'p' . + DIRECTORY_SEPARATOR . 'info.xml'); + $this->saveAllPackages(); + } + + /** + * Save package REST based on a release + * + * @param \pear2\Pyrus\Package $new + */ + function saveInfo(\pear2\Pyrus\Package $new) + { + $reader = new \pear2\Pyrus\XMLParser; + $deprecated = false; + if (file_exists($this->rest . DIRECTORY_SEPARATOR . 'p' . DIRECTORY_SEPARATOR . + 'info.xml')) { + $oldinfo = $reader->parse($this->rest . DIRECTORY_SEPARATOR . 'p' . + DIRECTORY_SEPARATOR . 'info.xml'); + if (isset($oldinfo['p']['dp'])) { + $deprecated = array('dp' => $oldinfo['p']['dp'], 'dc' => $oldinfo['p']['dc']); + } + } + $xml = array(); + $xml['n'] = $new->name; + $xml['c'] = $this->channel; + try { + $category = Categories::getPackageCategory($new->name); + } catch (Categories\Exception $e) { + $categories = Categories::create('Default', 'This is the default category'); + $categories->linkPackageToCategory($new->name,'Default'); + $category = Categories::getPackageCategory($new->name); + } + $xml['ca'] = array( + 'attribs' => array('xlink:href' => $this->getCategoryRESTLink(urlencode($category))), + '_content' => $category, + ); + $xml['l'] = $new->license['name']; + $xml['s'] = $new->summary; + $xml['d'] = $new->description; + $xml['r'] = array('attribs' => + $this->getReleaseRESTLink(strtolower($new->name))); + if ($a = $new->extends) { + $xml['pa'] = array('attribs' => + array('xlink:href' => $this->getPackageRESTLink(strtolower($a) . '/info.xml')), + '_content' => $a); + } + $xmlinf = $this->_getProlog('p', 'package'); + $xml['attribs'] = $xmlinf['p']['attribs']; + $xml = array('p' => $xml); + $this->savePackageREST(strtolower($new->name) . '/info.xml', $xml); + } + + /** + * Save a list of all packages in REST + * + * This is not release dependent. + */ + function saveAllPackages() + { + $xml = $this->_getProlog('a', 'allpackages'); + $xml['a']['p'] = array(); + foreach (new \DirectoryIterator($this->rest . 'p') as $file) { + if ($file->isDot()) continue; + $a = (string) $file; + if ($file->isDir() + && $a[0] != '.' + && $a != 'CVS') { + $xml['a']['p'][] = $a; + } + } + usort($xml['a']['p'], 'strnatcmp'); + $this->savePackageREST('packages.xml', $xml); + } + + /** + * Save package maintainers information for this release + * + * @param \pear2\Pyrus\Package $new package to be saved + * + * @return void + */ + function saveMaintainers(\pear2\Pyrus\Package $new) + { + $m = $this->_getProlog('m', 'packagemaintainers'); + $m2 = $this->_getProlog('m', 'packagemaintainers2'); + + $m['m']['p'] = $m2['m']['p'] = $new->name; + $m['m']['c'] = $m2['m']['c'] = $this->chan; + $m['m']['m'] = $m2['m']['m'] = array(); + + foreach ($new->allmaintainers as $role => $maintainers) { + if (!$maintainers) continue; + foreach ($maintainers as $dev) { + $m['m']['m'][] = array('h' => $dev->user, 'a' => $dev->active); + $m2['m']['m'][] = array( + 'h' => $dev->user, + 'a' => $dev->active, + 'r' => $role + ); + } + } + $this->savePackageREST(strtolower($new->name) . '/maintainers.xml', + $m); + $this->savePackageREST(strtolower($new->name) . '/maintainers2.xml', + $m2); + } +} \ No newline at end of file diff --git a/src/SimpleChannelServer/REST/Release.php b/src/SimpleChannelServer/REST/Release.php new file mode 100644 index 0000000..d7b1a40 --- /dev/null +++ b/src/SimpleChannelServer/REST/Release.php @@ -0,0 +1,305 @@ + + * @license New BSD? + * @link http://svn.pear.php.net/wsvn/PEARSVN/sandbox/SimpleChannelServer/ + */ +namespace pear2\SimpleChannelServer\REST; +use pear2\SimpleChannelServer\Categories; +class Release extends Manager +{ + /** + * Save a package release's REST information + * + * @param \pear2\Pyrus\Package $new new package to be released + * @param string $releaser handle of the maintainer who released this package + * + * @return void + */ + function save(\pear2\Pyrus\Package $new, $releaser) + { + $this->saveInfo($new, $releaser); + $this->saveInfo2($new, $releaser); + $this->saveAll($new); + $this->saveAll2($new); + $this->saveStability($new); + $this->savePackageDeps($new); + $this->savePackageXml($new); + } + + /** + * Delete a release from REST information + * + * @param \pear2\Pyrus\Package $new Package to be erased + * + * @return void + */ + function erase(\pear2\Pyrus\Package $new) + { + $this->saveAll($new, true); + $xml = $this->saveAll2($new, true); + if (!count($xml['a']['r'])) return; + // reconstruct stability stuff + foreach ($xml['a']['r'] as $release) { + if (!isset($latest)) { + $latest = $release['v']; + } + if ($release['s'] == 'stable' && !isset($stable)) { + $stable = $release['v']; + } + if ($release['s'] == 'beta' && !isset($beta)) { + $beta = $release['v']; + } + if ($release['s'] == 'alpha' && !isset($alpha)) { + $alpha = $release['v']; + } + } + $this->saveReleaseREST(strtolower($new->name) . '/latest.txt', $latest, false); + if (isset($stable)) { + $this->saveReleaseREST(strtolower($new->name) . '/stable.txt', $stable, false); + } + if (isset($beta)) { + $this->saveReleaseREST(strtolower($new->name) . '/beta.txt', $beta, false); + } + if (isset($alpha)) { + $this->saveReleaseREST(strtolower($new->name) . '/alpha.txt', $alpha, false); + } + } + + /** + * save rest.release release REST + * + * @param \pear2\Pyrus\Package $new package to save info for + * @param string $releaser releasing maintainer's handle + * + * @return void + */ + function saveInfo(\pear2\Pyrus\Package $new, $releaser) + { + $xml = $this->_getProlog('r', 'release'); + $xml['r']['p'] = array( + 'attribs' => array( + 'xlink:href' => + $this->getPackageRESTLink(strtolower($new->name)) + ), + '_content' => $new->name); + $xml['r']['c'] = $this->channel; + $category = Categories::getPackageCategory($new->name); + $xml['r']['v'] = $new->version['release']; + $xml['r']['st'] = $new->stability['release']; + $xml['r']['l'] = $new->license['name']; + $xml['r']['m'] = $releaser; + $xml['r']['s'] = $new->summary; + $xml['r']['d'] = $new->description; + $xml['r']['da'] = $new->date. ' ' . $new->time; + $xml['r']['n'] = $new->notes; + $xml['r']['f'] = filesize($new->archivefile); + $xml['r']['g'] = 'http://' . $this->channel . '/get/' . $new->name . + '-' . $new->version['release']; + $xml['r']['x'] = array('attribs' => array( + 'xlink:href' => 'package.' . $new->version['release'] . '.xml' + )); + $this->saveReleaseREST(strtolower($new->name) . '/' . + $new->version['release'] . '.xml', $xml); + } + + /** + * save rest.release2 release REST + * + * @param \pear2\Pyrus\Package $new package to be saved + * @param string $releaser releasing maintainer's handle + * + * @return void + */ + function saveInfo2(\pear2\Pyrus\Package $new, $releaser) + { + $xml = $this->_getProlog('r', 'release2'); + $xml['r']['p'] = array( + 'attribs' => array( + 'xlink:href' => + $this->getPackageRESTLink(strtolower($new->name)) + ), + '_content' => $new->name); + $xml['r']['c'] = $this->channel; + $category = Categories::getPackageCategory($new->name); + $xml['r']['v'] = $new->version['release']; + $xml['r']['a'] = $new->version['api']; + $xml['r']['mp'] = $new->dependencies['required']->php->min; + $xml['r']['st'] = $new->stability['release']; + $xml['r']['l'] = $new->license['name']; + $xml['r']['m'] = $releaser; + $xml['r']['s'] = $new->summary; + $xml['r']['d'] = $new->description; + $xml['r']['da'] = $new->date . ' ' . $new->time; + $xml['r']['n'] = $new->notes; + $xml['r']['f'] = filesize($new->archivefile); + $xml['r']['g'] = 'http://' . $this->channel . '/get/' . $new->name . + '-' . $new->version['release']; + $xml['r']['x'] = array('attribs' => array( + 'xlink:href' => 'package.' . $new->version['release'] . '.xml' + )); + $this->saveReleaseREST(strtolower($new->name) . '/v2.' . + $new->version['release'] . '.xml', $xml); + } + + /** + * Save a release's package.xml contents + * + * @param \pear2\Pyrus\Package $new package to be saved + * + * @return void + */ + function savePackageXml(\pear2\Pyrus\Package $new) + { + $this->saveReleaseREST(strtolower($new->name) . '/package.' . + $new->version['release'] . '.xml', file_get_contents($new->packagefile), + false); + } + + /** + * Save a serialized representation of a package's dependencies + * + * @param \pear2\Pyrus\Package $new package to be saved + * + * @return void + */ + function savePackageDeps(\pear2\Pyrus\Package $new) + { + $this->saveReleaseREST(strtolower($new->name) . '/deps.' . + $new->version['release'] . '.txt', serialize($new->rawdeps), + false); + } + + /** + * save REST information for all releases of this package + * + * @param \pear2\Pyrus\Package $new package to save all release info for + * @param bool $erase if true, the release represented by the + * version of $new will be removed. + * + * @return void + */ + function saveAll(\pear2\Pyrus\Package $new, $erase = false, $is2 = false) + { + if ($is2) { + $is2 = '2'; + } else { + $is2 = ''; + } + $reader = new \pear2\Pyrus\XMLParser; + $path = $this->getRESTPath('r', strtolower($new->name) . + DIRECTORY_SEPARATOR . 'allreleases' . $is2 . '.xml'); + if (file_exists($path)) { + $xml = $reader->parse($path); + if (isset($xml['a']['r']) && !isset($xml['a']['r'][0])) { + $xml['a']['r'] = array($xml['a']['r']); + } + } else { + $xml = $this->_getProlog('a', 'allreleases' . $is2); + $xml['a']['p'] = $new->name; + $xml['a']['c'] = $this->chan; + $xml['a']['r'] = array(); + } + if ($erase) { + foreach ($xml['a']['r'] as $i => $release) { + if ($release['v'] === $new->version['release']) { + unset($xml['a']['r'][$i]); + $xml['a']['r'] = array_values($xml['a']['r']); + break; + } + } + if (!count($xml['a']['r'])) { + // no releases, erase all traces + foreach (new DirectoryIterator($this->getRESTPath('r', + strtolower($new->name))) as $name => $info) { + if ($info->isDot()) continue; + unlink($name); + } + } + } else { + $info = array( + 'v' => $new->version['release'], + 's' => $new->stability['release'], + ); + if ($is2) { + $info['m'] = $new->dependencies['required']->php->min; + } + if (count($new->compatible)) { + $info['co'] = array(); + foreach ($new->compatible as $package=>$cinfo) { + if (strpos($package, '/')) { + $c = substr($package, 0, strpos($package, '/')); + $package = str_replace(array($c, '/'), '', $package); + } else { + $c = 'pear.php.net'; + } + unset($cinfo['channel']); + unset($cinfo['package']); + if (isset($cinfo['exclude'])) { + $info['x'] = $cinfo['exclude']; + unset($cinfo['exclude']); + } + + $info['co'][] = array_merge(array('c' => $c, 'p' => $package), $cinfo); + } + } + $test = $xml['a']['r']; + if (count($test) && !isset($test[0])) { + if ($test['v'] != $info['v']) { + $test = array($info, $test); + } + } else { + $found = false; + foreach ($test as $i => $rel) { + if ($rel['v'] == $info['v']) { + $found = true; + $test[$i] = $info; + break; + } + } + if (!$found) { + array_unshift($test, $info); + } + } + if (count($test) == 1) { + $test = $test[0]; + } + $xml['a']['r'] = $test; + } + $this->saveReleaseREST(strtolower($new->name) . '/allreleases' . $is2 . '.xml', $xml); + return $xml; + } + + /** + * save REST information for all releases (version 2) of this package + * + * @param \pear2\Pyrus\Package $new package to save all releases for + * @param bool $erase if true, the release represented by the + * version of $new will be removed. + * + * @return void + */ + function saveAll2(\pear2\Pyrus\Package $new, $erase = false) + { + return $this->saveAll($new, $erase, true); + } + + /** + * save REST stability version info in .txt files + * + * @param \pear2\Pyrus\Package $new package to save stability for + * + * @return void + */ + function saveStability(\pear2\Pyrus\Package $new) + { + $this->saveReleaseREST(strtolower($new->name) . '/latest.txt', + $new->version['release'], false); + $this->saveReleaseREST(strtolower($new->name) . '/' . $new->state . '.txt', + $new->version['release'], false); + } +} diff --git a/stub.php b/stub.php new file mode 100644 index 0000000..fb14c68 --- /dev/null +++ b/stub.php @@ -0,0 +1,27 @@ +#!/usr/bin/env php +process(); +__HALT_COMPILER();