Add Horde_Stream_Wrapper_Combine::
authorMichael M Slusarz <slusarz@curecanti.org>
Sat, 24 Oct 2009 23:56:44 +0000 (17:56 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Mon, 26 Oct 2009 18:28:43 +0000 (12:28 -0600)
framework/Stream_Wrapper/lib/Horde/Stream/Wrapper/Combine.php [new file with mode: 0644]
framework/Stream_Wrapper/package.xml
framework/Support/lib/Horde/Support/CombineStream.php [new file with mode: 0644]
framework/Support/package.xml
framework/Support/test/Horde/Support/CombineStreamTest.php [new file with mode: 0644]

diff --git a/framework/Stream_Wrapper/lib/Horde/Stream/Wrapper/Combine.php b/framework/Stream_Wrapper/lib/Horde/Stream/Wrapper/Combine.php
new file mode 100644 (file)
index 0000000..68553b5
--- /dev/null
@@ -0,0 +1,239 @@
+<?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);
+    }
+
+}
index a97b932..287e844 100644 (file)
@@ -36,7 +36,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <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="/">
@@ -44,6 +45,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <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 -->
@@ -68,6 +70,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
  </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>
diff --git a/framework/Support/lib/Horde/Support/CombineStream.php b/framework/Support/lib/Horde/Support/CombineStream.php
new file mode 100644 (file)
index 0000000..ebf4297
--- /dev/null
@@ -0,0 +1,86 @@
+<?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;
+    }
+
+}
index 06a6f33..e6f8270 100644 (file)
@@ -25,6 +25,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
  </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
@@ -50,6 +51,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
       </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" />
@@ -82,6 +84,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <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" />
diff --git a/framework/Support/test/Horde/Support/CombineStreamTest.php b/framework/Support/test/Horde/Support/CombineStreamTest.php
new file mode 100644 (file)
index 0000000..b102cf5
--- /dev/null
@@ -0,0 +1,43 @@
+<?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);
+    }
+}