--- /dev/null
+<?php
+/**
+ * A stream wrapper that will combine multiple strings/streams into a single
+ * stream.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Stream_Wrapper
+ */
+
+/**
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Stream_Wrapper
+ */
+class Horde_Stream_Wrapper_Combine
+{
+ /**
+ * Context.
+ *
+ * @var resource
+ */
+ public $context;
+
+ /**
+ * Array that holds the various streams.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * The combined length of the stream.
+ *
+ * @var integer
+ */
+ protected $_length = 0;
+
+ /**
+ * The current position in the string.
+ *
+ * @var integer
+ */
+ protected $_position = 0;
+
+ /**
+ * The current position in the data array.
+ *
+ * @var integer
+ */
+ protected $_datapos = 0;
+
+ /**
+ * Have we reached EOF?
+ *
+ * @var boolean
+ */
+ protected $_ateof = false;
+
+ /**
+ * @see streamWrapper::stream_open()
+ *
+ * @param string $path
+ * @param string $mode
+ * @param integer $options
+ * @param string &$opened_path
+ *
+ * @throws Exception
+ */
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $opts = stream_context_get_options($this->context);
+ if (empty($opts['horde-combine']['data']) ||
+ !($opts['horde-combine']['data'] instanceof Horde_Support_CombineStream)) {
+ throw new Exception('A combined stream must be created using the Horde_Support_CombineStream class.');
+ }
+
+ $data = &$opts['horde-combine']['data']->getData();
+
+ reset($data);
+ while (list(,$val) = each($data)) {
+ if (is_string($val)) {
+ $fp = fopen('php://temp', 'r+');
+ fwrite($fp, $val);
+ } else {
+ $fp = $val;
+ }
+
+ fseek($fp, 0, SEEK_END);
+ $length = ftell($fp);
+ rewind($fp);
+
+ $this->_data[] = array(
+ 'fp' => $fp,
+ 'l' => $length,
+ 'p' => 0
+ );
+
+ $this->_length += $length;
+ }
+
+ return true;
+ }
+
+ /**
+ * @see streamWrapper::stream_read()
+ *
+ * @param integer $count
+ *
+ * @return mixed
+ */
+ public function stream_read($count)
+ {
+ if ($this->stream_eof()) {
+ return false;
+ }
+
+ $out = '';
+
+ while ($count) {
+ $tmp = &$this->_data[$this->_datapos];
+ $curr_read = min($count, $tmp['l'] - $tmp['p']);
+ $out .= fread($tmp['fp'], $curr_read);
+ $count -= $curr_read;
+ $this->_position += $curr_read;
+
+ if ($this->_position == $this->_length) {
+ if ($count) {
+ $this->_ateof = true;
+ break;
+ } else {
+ $tmp['p'] += $curr_read;
+ }
+ } else {
+ $tmp = &$this->_data[++$this->_datapos];
+ rewind($tmp['fp']);
+ $tmp['p'] = 0;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * @see streamWrapper::stream_write()
+ *
+ * @param string $data
+ *
+ * @return integer
+ */
+ public function stream_write($data)
+ {
+ $tmp = &$this->_data[$this->_datapos];
+
+ $oldlen = $tmp['l'];
+ $res = fwrite($tmp['fp'], $data);
+ if ($res === false) {
+ return false;
+ }
+
+ $tmp['p'] = ftell($tmp['fp']);
+ if ($tmp['p'] > $oldlen) {
+ $tmp['l'] = $tmp['p'];
+ $this->_length += ($tmp['l'] - $oldlen);
+ }
+
+ return $res;
+ }
+
+ /**
+ * @see streamWrapper::stream_tell()
+ *
+ * @return integer
+ */
+ public function stream_tell()
+ {
+ return $this->_position;
+ }
+
+ /**
+ * @see streamWrapper::stream_eof()
+ *
+ * @return boolean
+ */
+ public function stream_eof()
+ {
+ return $this->_ateof;
+ }
+
+ /**
+ * @see streamWrapper::stream_seek()
+ *
+ * @param integer $offset
+ * @param integer $whence SEEK_SET, SEEK_CUR, or SEEK_END
+ *
+ * @return boolean
+ */
+ public function stream_seek($offset, $whence)
+ {
+ $oldpos = $this->_position;
+ $this->_ateof = false;
+
+ switch ($whence) {
+ case SEEK_SET:
+ $offset = $offset;
+ break;
+
+ case SEEK_CUR:
+ $offset = $this->_position + $offset;
+ break;
+
+ case SEEK_END:
+ $offset = $this->_length + $offset;
+ break;
+
+ default:
+ return false;
+ }
+
+ $count = $this->_position = min($this->_length, $offset);
+
+ foreach ($this->_data as $key => $val) {
+ if ($count < $val['l']) {
+ $this->_datapos = $key;
+ $val['p'] = $count;
+ fseek($val['fp'], $count, SEEK_SET);
+ break;
+ }
+ $count -= $val['l'];
+ }
+
+ return ($oldpos != $this->_position);
+ }
+
+}
<api>beta</api>
</stability>
<license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>* Initial release.
+ <notes>* Add Combine wrapper.
+ * Initial release.
</notes>
<contents>
<dir name="/">
<dir name="Horde">
<dir name="Stream">
<dir name="Wrapper">
+ <file name="Combine.php" role="php" />
<file name="String.php" role="php" />
</dir> <!-- /lib/Horde/Stream/Wrapper -->
</dir> <!-- /lib/Horde/Stream -->
</dependencies>
<phprelease>
<filelist>
+ <install name="lib/Horde/Stream/Wrapper/Combine.php" as="Horde/Stream/Wrapper/Combine.php" />
<install name="lib/Horde/Stream/Wrapper/String.php" as="Horde/Stream/Wrapper/String.php" />
</filelist>
</phprelease>
--- /dev/null
+<?php
+/**
+ * Provides access to the Combine stream wrapper.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Support
+ */
+
+/**
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Support
+ */
+class Horde_Support_CombineStream
+{
+ /**
+ * Data.
+ *
+ * @var array
+ */
+ protected $_data;
+
+ /**
+ * Constructor
+ *
+ * @param array $data An array of strings and/or streams to combine into
+ * a single stream.
+ */
+ public function __construct($data)
+ {
+ $this->installWrapper();
+ $this->_data = $data;
+ }
+
+ /**
+ * Return a stream handle to this stream.
+ *
+ * @return resource
+ */
+ public function fopen()
+ {
+ $context = stream_context_create(array('horde-combine' => array('data' => $this)));
+ return fopen('horde-combine://' . spl_object_hash($this), 'rb', false, $context);
+ }
+
+ /**
+ * Return an SplFileObject representing this stream
+ *
+ * @return SplFileObject
+ */
+ public function getFileObject()
+ {
+ $context = stream_context_create(array('horde-combine' => array('data' => $this)));
+ return new SplFileObject('horde-combine://' . spl_object_hash($this), 'rb', false, $context);
+ }
+
+ /**
+ * Install the horde-combine stream wrapper if it isn't already
+ * registered.
+ *
+ * @throws Exception
+ */
+ public function installWrapper()
+ {
+ if (!in_array('horde-combine', stream_get_wrappers()) &&
+ !stream_wrapper_register('horde-combine', 'Horde_Stream_Wrapper_Combine')) {
+ throw new Exception('Unable to register horde-combine stream wrapper.');
+ }
+ }
+
+ /**
+ * Return a reference to the data.
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ return $this->_data;
+ }
+
+}
</stability>
<license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
<notes>
+ * Add Horde_Support_CombineStream::.
* Initial horde/support package
* Initial Horde_Support_Array object
* Initial Horde_Support_Backtrace object
</dir> <!-- /lib/Horde/Support/Numerizer -->
<file name="Array.php" role="php" />
<file name="Backtrace.php" role="php" />
+ <file name="CombineStream.php" role="php" />
<file name="ConsistentHash.php" role="php" />
<file name="Guid.php" role="php" />
<file name="Inflector.php" role="php" />
<filelist>
<install name="lib/Horde/Support/Array.php" as="Horde/Support/Array.php" />
<install name="lib/Horde/Support/Backtrace.php" as="Horde/Support/Backtrace.php" />
+ <install name="lib/Horde/Support/CombineStream.php" as="Horde/Support/CombineStream.php" />
<install name="lib/Horde/Support/ConsistentHash.php" as="Horde/Support/ConsistentHash.php" />
<install name="lib/Horde/Support/Guid.php" as="Horde/Support/Guid.php" />
<install name="lib/Horde/Support/Inflector.php" as="Horde/Support/Inflector.php" />
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2009 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+
+/**
+ * @group support
+ * @category Horde
+ * @package Support
+ * @subpackage UnitTests
+ * @copyright 2009 The Horde Project (http://www.horde.org/)
+ * @license http://opensource.org/licenses/bsd-license.php
+ */
+class Horde_Support_CombineStreamTest extends PHPUnit_Framework_TestCase
+{
+ public function testUsage()
+ {
+ $fp = fopen('php://temp', 'r+');
+ fwrite($fp, '12345');
+
+ $data = array('ABCDE', $fp, 'fghij');
+ $ob = new Horde_Support_CombineStream($data);
+ $stream = $ob->fopen();
+
+ $this->assertEquals('ABCDE12345fghij', fread($stream, 1024));
+ $this->assertEquals(true, feof($stream));
+ $this->assertEquals(0, fseek($stream, 0));
+ $this->assertEquals(-1, fseek($stream, 0));
+ $this->assertEquals(0, ftell($stream));
+ $this->assertEquals(0, fseek($stream, 5, SEEK_CUR));
+ $this->assertEquals(5, ftell($stream));
+ $this->assertEquals(10, fwrite($stream, '0000000000'));
+ $this->assertEquals(0, fseek($stream, 0, SEEK_END));
+ $this->assertEquals(20, ftell($stream));
+ $this->assertEquals(false, feof($stream));
+
+ fclose($stream);
+ }
+}