Merge pull request #23351 from owncloud/streams-0.4

Update icewind/streams and icewind/smb
This commit is contained in:
Vincent Petry 2016-03-21 17:37:40 +01:00
commit 2309ef9167
35 changed files with 787 additions and 491 deletions

@ -1 +1 @@
Subproject commit 0ee67806d6082675541c77d79b3f494d760e2c28
Subproject commit ec111b90c429356dd99d7944d40478eb642a22f7

View File

@ -2,3 +2,4 @@ example.php
icewind/smb/tests
icewind/smb/install_libsmbclient.sh
icewind/smb/.travis.yml
icewind/streams/tests

View File

@ -8,8 +8,8 @@
"classmap-authoritative": true
},
"require": {
"icewind/smb": "1.0.5",
"icewind/streams": "0.2"
"icewind/smb": "1.0.8",
"icewind/streams": "0.4"
}
}

View File

@ -4,25 +4,25 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "669663944c7232473a1c51a6d160319d",
"content-hash": "7612ced4391f6287fb3e50534500d217",
"hash": "1671a5ec7bef407432d42775f898dc34",
"content-hash": "9d995f0d55bee8a3b344a3c685e7b4a4",
"packages": [
{
"name": "icewind/smb",
"version": "v1.0.5",
"version": "v1.0.8",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
"reference": "acb94a0a85290d653cd64c883175b855ada5022f"
"reference": "764f3fc793a904eb937d619ad097fb076ff199cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/acb94a0a85290d653cd64c883175b855ada5022f",
"reference": "acb94a0a85290d653cd64c883175b855ada5022f",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/764f3fc793a904eb937d619ad097fb076ff199cd",
"reference": "764f3fc793a904eb937d619ad097fb076ff199cd",
"shasum": ""
},
"require": {
"icewind/streams": "0.2.*",
"icewind/streams": ">=0.2.0",
"php": ">=5.3"
},
"require-dev": {
@ -47,27 +47,28 @@
}
],
"description": "php wrapper for smbclient and libsmbclient-php",
"time": "2016-01-20 13:12:36"
"time": "2016-03-17 13:29:58"
},
{
"name": "icewind/streams",
"version": "0.2",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/Streams.git",
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a"
"reference": "9ca40274645a967ecc3408b0ca2e6255ead1d1d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a",
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a",
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/9ca40274645a967ecc3408b0ca2e6255ead1d1d3",
"reference": "9ca40274645a967ecc3408b0ca2e6255ead1d1d3",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"satooshi/php-coveralls": "dev-master"
"phpunit/phpunit": "^4.8",
"satooshi/php-coveralls": "v1.0.0"
},
"type": "library",
"autoload": {
@ -87,7 +88,7 @@
}
],
"description": "A set of generic stream wrappers",
"time": "2014-07-30 23:46:15"
"time": "2016-03-17 12:32:25"
}
],
"packages-dev": [],

View File

@ -13,9 +13,7 @@
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0 class loader
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
@ -39,6 +37,8 @@ namespace Composer\Autoload;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
@ -147,7 +147,7 @@ class ClassLoader
* 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-0 base directories
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException

View File

@ -52,12 +52,23 @@ return array(
'Icewind\\SMB\\TimeZoneProvider' => $vendorDir . '/icewind/smb/src/TimeZoneProvider.php',
'Icewind\\Streams\\CallbackWrapper' => $vendorDir . '/icewind/streams/src/CallbackWrapper.php',
'Icewind\\Streams\\Directory' => $vendorDir . '/icewind/streams/src/Directory.php',
'Icewind\\Streams\\DirectoryFilter' => $vendorDir . '/icewind/streams/src/DirectoryFilter.php',
'Icewind\\Streams\\DirectoryWrapper' => $vendorDir . '/icewind/streams/src/DirectoryWrapper.php',
'Icewind\\Streams\\File' => $vendorDir . '/icewind/streams/src/File.php',
'Icewind\\Streams\\IteratorDirectory' => $vendorDir . '/icewind/streams/src/IteratorDirectory.php',
'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php',
'Icewind\\Streams\\Tests\\CallbackWrapper' => $vendorDir . '/icewind/streams/tests/CallbackWrapper.php',
'Icewind\\Streams\\Tests\\IteratorDirectory' => $vendorDir . '/icewind/streams/tests/IteratorDirectory.php',
'Icewind\\Streams\\Tests\\NullWrapper' => $vendorDir . '/icewind/streams/tests/NullWrapper.php',
'Icewind\\Streams\\Tests\\Wrapper' => $vendorDir . '/icewind/streams/tests/Wrapper.php',
'Icewind\\Streams\\Path' => $vendorDir . '/icewind/streams/src/Path.php',
'Icewind\\Streams\\RetryWrapper' => $vendorDir . '/icewind/streams/src/RetryWrapper.php',
'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php',
'Icewind\\Streams\\Tests\\DirectoryFilter' => $vendorDir . '/icewind/streams/tests/DirectoryFilter.php',
'Icewind\\Streams\\Tests\\DirectoryWrapper' => $vendorDir . '/icewind/streams/tests/DirectoryWrapper.php',
'Icewind\\Streams\\Tests\\DirectoryWrapperDummy' => $vendorDir . '/icewind/streams/tests/DirectoryWrapper.php',
'Icewind\\Streams\\Tests\\DirectoryWrapperNull' => $vendorDir . '/icewind/streams/tests/DirectoryWrapper.php',
'Icewind\\Streams\\Tests\\PartialWrapper' => $vendorDir . '/icewind/streams/tests/RetryWrapper.php',
'Icewind\\Streams\\Tests\\RetryWrapper' => $vendorDir . '/icewind/streams/tests/RetryWrapper.php',
'Icewind\\Streams\\Tests\\SeekableWrapper' => $vendorDir . '/icewind/streams/tests/SeekableWrapper.php',
'Icewind\\Streams\\Tests\\UrlCallBack' => $vendorDir . '/icewind/streams/tests/UrlCallBack.php',
'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php',
'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallBack.php',
'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.php',
);

View File

@ -23,16 +23,6 @@ class ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3', 'loadClassLoader'));
$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);
@ -44,8 +34,3 @@ class ComposerAutoloaderInit98fe9b281934250b3a93f69a5ce843b3
return $loader;
}
}
function composerRequire98fe9b281934250b3a93f69a5ce843b3($file)
{
require $file;
}

View File

@ -1,26 +1,27 @@
[
{
"name": "icewind/streams",
"version": "0.2",
"version_normalized": "0.2.0.0",
"version": "0.4.0",
"version_normalized": "0.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/Streams.git",
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a"
"reference": "9ca40274645a967ecc3408b0ca2e6255ead1d1d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a",
"reference": "5aae45f2ddd3d1a6e2a496dd5d1e7857bfeb605a",
"url": "https://api.github.com/repos/icewind1991/Streams/zipball/9ca40274645a967ecc3408b0ca2e6255ead1d1d3",
"reference": "9ca40274645a967ecc3408b0ca2e6255ead1d1d3",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"satooshi/php-coveralls": "dev-master"
"phpunit/phpunit": "^4.8",
"satooshi/php-coveralls": "v1.0.0"
},
"time": "2014-07-30 23:46:15",
"time": "2016-03-17 12:32:25",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -43,28 +44,28 @@
},
{
"name": "icewind/smb",
"version": "v1.0.5",
"version_normalized": "1.0.5.0",
"version": "v1.0.8",
"version_normalized": "1.0.8.0",
"source": {
"type": "git",
"url": "https://github.com/icewind1991/SMB.git",
"reference": "acb94a0a85290d653cd64c883175b855ada5022f"
"reference": "764f3fc793a904eb937d619ad097fb076ff199cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/acb94a0a85290d653cd64c883175b855ada5022f",
"reference": "acb94a0a85290d653cd64c883175b855ada5022f",
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/764f3fc793a904eb937d619ad097fb076ff199cd",
"reference": "764f3fc793a904eb937d619ad097fb076ff199cd",
"shasum": ""
},
"require": {
"icewind/streams": "0.2.*",
"icewind/streams": ">=0.2.0",
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "^4.8",
"satooshi/php-coveralls": "v1.0.0"
},
"time": "2016-01-20 13:12:36",
"time": "2016-03-17 13:29:58",
"type": "library",
"installation-source": "source",
"autoload": {

View File

@ -75,7 +75,7 @@ $share = $server->getShare('test');
$content = $share->dir('test');
foreach ($content as $info) {
echo $name->getName() . "\n";
echo $info->getName() . "\n";
echo "\tsize :" . $info->getSize() . "\n";
}
```

View File

@ -10,7 +10,7 @@
],
"require" : {
"php": ">=5.3",
"icewind/streams": "0.2.*"
"icewind/streams": ">=0.2.0"
},
"require-dev": {
"satooshi/php-coveralls" : "v1.0.0",

View File

@ -239,8 +239,9 @@ class NativeShare extends AbstractShare {
*/
public function read($source) {
$this->connect();
$handle = $this->state->open($this->buildUrl($source), 'r');
return NativeStream::wrap($this->state, $handle, 'r');
$url = $this->buildUrl($source);
$handle = $this->state->open($url, 'r');
return NativeStream::wrap($this->state, $handle, 'r', $url);
}
/**
@ -254,8 +255,9 @@ class NativeShare extends AbstractShare {
*/
public function write($source) {
$this->connect();
$handle = $this->state->create($this->buildUrl($source));
return NativeStream::wrap($this->state, $handle, 'w');
$url = $this->buildUrl($source);
$handle = $this->state->create($url);
return NativeStream::wrap($this->state, $handle, 'w', $url);
}
/**

View File

@ -7,6 +7,7 @@
namespace Icewind\SMB;
use Icewind\SMB\Exception\Exception;
use Icewind\SMB\Exception\InvalidRequestException;
use Icewind\Streams\File;
@ -31,20 +32,27 @@ class NativeStream implements File {
*/
private $eof = false;
/**
* @var string
*/
private $url;
/**
* Wrap a stream from libsmbclient-php into a regular php stream
*
* @param \Icewind\SMB\NativeState $state
* @param resource $smbStream
* @param string $mode
* @param string $url
* @return resource
*/
public static function wrap($state, $smbStream, $mode) {
public static function wrap($state, $smbStream, $mode, $url) {
stream_wrapper_register('nativesmb', '\Icewind\SMB\NativeStream');
$context = stream_context_create(array(
'nativesmb' => array(
'state' => $state,
'handle' => $smbStream
'handle' => $smbStream,
'url' => $url
)
));
$fh = fopen('nativesmb://', $mode, false, $context);
@ -68,6 +76,7 @@ class NativeStream implements File {
$context = stream_context_get_options($this->context);
$this->state = $context['nativesmb']['state'];
$this->handle = $context['nativesmb']['handle'];
$this->url = $context['nativesmb']['url'];
return true;
}
@ -89,7 +98,11 @@ class NativeStream implements File {
}
public function stream_stat() {
return $this->state->fstat($this->handle);
try {
return $this->state->stat($this->url);
} catch (Exception $e) {
return false;
}
}
public function stream_tell() {

View File

@ -0,0 +1,7 @@
{
"name": "icewind/streams-dummy",
"provide": {
"icewind/streams": "0.2"
}
}

View File

@ -1,8 +1,9 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Robin Appelman
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.

View File

@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/icewind1991/Streams.svg?branch=master)](https://travis-ci.org/icewind1991/Streams)
[![Coverage Status](https://img.shields.io/coveralls/icewind1991/Streams.svg)](https://coveralls.io/r/icewind1991/Streams?branch=master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/icewind1991/Streams/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/icewind1991/Streams/?branch=master)
Generic stream wrappers for php.

View File

@ -12,7 +12,8 @@
"php": ">=5.3"
},
"require-dev" : {
"satooshi/php-coveralls": "dev-master"
"satooshi/php-coveralls": "v1.0.0",
"phpunit/phpunit": "^4.8"
},
"autoload" : {
"psr-4": {

View File

@ -17,6 +17,7 @@ namespace Icewind\Streams;
* 'read' => function($count){} (optional)
* 'write' => function($data){} (optional)
* 'close' => function(){} (optional)
* 'readdir' => function(){} (optional)
* ]
* ]
*
@ -38,6 +39,11 @@ class CallbackWrapper extends Wrapper {
*/
protected $closeCallback;
/**
* @var callable
*/
protected $readDirCallBack;
/**
* Wraps a stream with the provided callbacks
*
@ -45,48 +51,45 @@ class CallbackWrapper extends Wrapper {
* @param callable $read (optional)
* @param callable $write (optional)
* @param callable $close (optional)
* @param callable $readDir (optional)
* @return resource
*
* @throws \BadMethodCallException
*/
public static function wrap($source, $read = null, $write = null, $close = null) {
public static function wrap($source, $read = null, $write = null, $close = null, $readDir = null) {
$context = stream_context_create(array(
'callback' => array(
'source' => $source,
'read' => $read,
'write' => $write,
'close' => $close
'close' => $close,
'readDir' => $readDir
)
));
stream_wrapper_register('callback', '\Icewind\Streams\CallbackWrapper');
try {
$wrapped = fopen('callback://', 'r+', false, $context);
} catch (\BadMethodCallException $e) {
stream_wrapper_unregister('callback');
throw $e;
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\CallbackWrapper');
}
stream_wrapper_unregister('callback');
return $wrapped;
protected function open() {
$context = $this->loadContext('callback');
$this->readCallback = $context['read'];
$this->writeCallback = $context['write'];
$this->closeCallback = $context['close'];
$this->readDirCallBack = $context['readDir'];
return true;
}
public function dir_opendir($path, $options) {
return $this->open();
}
public function stream_open($path, $mode, $options, &$opened_path) {
$context = $this->loadContext('callback');
if (isset($context['read']) and is_callable($context['read'])) {
$this->readCallback = $context['read'];
}
if (isset($context['write']) and is_callable($context['write'])) {
$this->writeCallback = $context['write'];
}
if (isset($context['close']) and is_callable($context['close'])) {
$this->closeCallback = $context['close'];
}
return true;
return $this->open();
}
public function stream_read($count) {
$result = parent::stream_read($count);
if ($this->readCallback) {
if (is_callable($this->readCallback)) {
call_user_func($this->readCallback, $count);
}
return $result;
@ -94,7 +97,7 @@ class CallbackWrapper extends Wrapper {
public function stream_write($data) {
$result = parent::stream_write($data);
if ($this->writeCallback) {
if (is_callable($this->writeCallback)) {
call_user_func($this->writeCallback, $data);
}
return $result;
@ -102,9 +105,17 @@ class CallbackWrapper extends Wrapper {
public function stream_close() {
$result = parent::stream_close();
if ($this->closeCallback) {
if (is_callable($this->closeCallback)) {
call_user_func($this->closeCallback);
}
return $result;
}
public function dir_readdir() {
$result = parent::dir_readdir();
if (is_callable($this->readDirCallBack)) {
call_user_func($this->readDirCallBack);
}
return $result;
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* Copyright (c) 2015 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Wrapper allows filtering of directories
*
* The filter callback will be called for each entry in the folder
* when the callback return false the entry will be filtered out
*/
class DirectoryFilter extends DirectoryWrapper {
/**
* @var callable
*/
private $filter;
/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options) {
$context = $this->loadContext('filter');
$this->filter = $context['filter'];
return true;
}
/**
* @return string
*/
public function dir_readdir() {
$file = readdir($this->source);
$filter = $this->filter;
// keep reading untill we have an accepted entry or we're at the end of the folder
while ($file !== false && $filter($file) === false) {
$file = readdir($this->source);
}
return $file;
}
/**
* @param resource $source
* @param callable $filter
* @return resource
*/
public static function wrap($source, callable $filter) {
$options = array(
'filter' => array(
'source' => $source,
'filter' => $filter
)
);
return self::wrapWithOptions($options, '\Icewind\Streams\DirectoryFilter');
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* Copyright (c) 2015 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
class DirectoryWrapper implements Directory {
/**
* @var resource
*/
public $context;
/**
* @var resource
*/
protected $source;
/**
* Load the source from the stream context and return the context options
*
* @param string $name
* @return array
* @throws \Exception
*/
protected function loadContext($name) {
$context = stream_context_get_options($this->context);
if (isset($context[$name])) {
$context = $context[$name];
} else {
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
}
if (isset($context['source']) and is_resource($context['source'])) {
$this->source = $context['source'];
} else {
throw new \BadMethodCallException('Invalid context, source not set');
}
return $context;
}
/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options) {
$this->loadContext('dir');
return true;
}
/**
* @return string
*/
public function dir_readdir() {
return readdir($this->source);
}
/**
* @return bool
*/
public function dir_closedir() {
closedir($this->source);
return true;
}
/**
* @return bool
*/
public function dir_rewinddir() {
rewinddir($this->source);
return true;
}
/**
* @param array $options the options for the context to wrap the stream with
* @param string $class
* @return resource
*/
protected static function wrapWithOptions($options, $class) {
$context = stream_context_create($options);
stream_wrapper_register('dirwrapper', $class);
$wrapped = opendir('dirwrapper://', $context);
stream_wrapper_unregister('dirwrapper');
return $wrapped;
}
}

View File

@ -21,7 +21,7 @@ interface File {
public function stream_open($path, $mode, $options, &$opened_path);
/**
* @param string $offset
* @param int $offset
* @param int $whence
* @return bool
*/

View File

@ -45,9 +45,9 @@ class IteratorDirectory implements Directory {
} else {
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
}
if (isset($context['iterator']) and $context['iterator'] instanceof \Iterator) {
if (isset($context['iterator'])) {
$this->iterator = $context['iterator'];
} else if (isset($context['array']) and is_array($context['array'])) {
} else if (isset($context['array'])) {
$this->iterator = new \ArrayIterator($context['array']);
} else {
throw new \BadMethodCallException('Invalid context, iterator or array not set');

View File

@ -24,19 +24,16 @@ class NullWrapper extends Wrapper {
'null' => array(
'source' => $source)
));
stream_wrapper_register('null', '\Icewind\Streams\NullWrapper');
try {
$wrapped = fopen('null://', 'r+', false, $context);
} catch (\BadMethodCallException $e) {
stream_wrapper_unregister('null');
throw $e;
}
stream_wrapper_unregister('null');
return $wrapped;
return Wrapper::wrapSource($source, $context, 'null', '\Icewind\Streams\NullWrapper');
}
public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext('null');
return true;
}
public function dir_opendir($path, $options) {
$this->loadContext('null');
return true;
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* A string-like object that automatically registers a stream wrapper when used and removes the stream wrapper when no longer used
*
* Can optionally pass context options to the stream wrapper
*/
class Path {
/**
* @var bool
*/
protected $registered = false;
/**
* @var string
*/
protected $protocol;
/**
* @var string
*/
protected $class;
/**
* @var array
*/
protected $contextOptions;
/**
* @param string $class
* @param array $contextOptions
*/
public function __construct($class, $contextOptions = array()) {
$this->class = $class;
$this->contextOptions = $contextOptions;
}
public function getProtocol() {
if (!$this->protocol) {
$this->protocol = 'auto' . uniqid();
}
return $this->protocol;
}
public function wrapPath($path) {
return $this->getProtocol() . '://' . $path;
}
protected function register() {
if (!$this->registered) {
$this->appendDefaultContent($this->getProtocol(), $this->contextOptions);
stream_wrapper_register($this->getProtocol(), $this->class);
$this->registered = true;
}
}
protected function unregister() {
stream_wrapper_unregister($this->getProtocol());
$this->unsetDefaultContent($this->getProtocol());
$this->registered = false;
}
/**
* Add values to the default stream context
*
* @param string $key
* @param array $values
*/
protected function appendDefaultContent($key, $values) {
$context = stream_context_get_default();
$defaults = stream_context_get_options($context);
$defaults[$key] = $values;
stream_context_set_default($defaults);
}
/**
* Remove values from the default stream context
*
* @param string $key
*/
protected function unsetDefaultContent($key) {
$context = stream_context_get_default();
$defaults = stream_context_get_options($context);
unset($defaults[$key]);
stream_context_set_default($defaults);
}
public function __toString() {
$this->register();
return $this->protocol . '://';
}
public function __destruct() {
$this->unregister();
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* Copyright (c) 2016 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Wrapper that retries reads/writes to remote streams that dont deliver/recieve all requested data at once
*/
class RetryWrapper extends Wrapper {
/**
* Wraps a stream with the provided callbacks
*
* @param resource $source
* @return resource
*/
public static function wrap($source) {
$context = stream_context_create(array(
'retry' => array(
'source' => $source
)
));
return Wrapper::wrapSource($source, $context, 'retry', '\Icewind\Streams\RetryWrapper');
}
protected function open() {
$this->loadContext('retry');
return true;
}
public function dir_opendir($path, $options) {
return false;
}
public function stream_open($path, $mode, $options, &$opened_path) {
return $this->open();
}
public function stream_read($count) {
$result = parent::stream_read($count);
$bytesReceived = strlen($result);
while ($bytesReceived < $count && !$this->stream_eof()) {
$result .= parent::stream_read($count - $bytesReceived);
$bytesReceived = strlen($result);
}
return $result;
}
public function stream_write($data) {
$bytesToSend = strlen($data);
$result = parent::stream_write($data);
while ($result < $bytesToSend && !$this->stream_eof()) {
$dataLeft = substr($data, $result);
$result += parent::stream_write($dataLeft);
}
return $result;
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Wrapper that provides callbacks for write, read and close
*
* The following options should be passed in the context when opening the stream
* [
* 'callback' => [
* 'source' => resource
* ]
* ]
*
* All callbacks are called after the operation is executed on the source stream
*/
class SeekableWrapper extends Wrapper {
/**
* @var resource
*/
protected $cache;
/**
* Wraps a stream to make it seekable
*
* @param resource $source
* @return resource
*
* @throws \BadMethodCallException
*/
public static function wrap($source) {
$context = stream_context_create(array(
'callback' => array(
'source' => $source
)
));
return Wrapper::wrapSource($source, $context, 'callback', '\Icewind\Streams\SeekableWrapper');
}
public function dir_opendir($path, $options) {
return false;
}
public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext('callback');
$this->cache = fopen('php://temp', 'w+');
return true;
}
protected function readTill($position) {
$current = ftell($this->source);
if ($position > $current) {
$data = parent::stream_read($position - $current);
$cachePosition = ftell($this->cache);
fseek($this->cache, $current);
fwrite($this->cache, $data);
fseek($this->cache, $cachePosition);
}
}
public function stream_read($count) {
$current = ftell($this->cache);
$this->readTill($current + $count);
return fread($this->cache, $count);
}
public function stream_seek($offset, $whence = SEEK_SET) {
if ($whence === SEEK_SET) {
$target = $offset;
} else if ($whence === SEEK_CUR) {
$current = ftell($this->cache);
$target = $current + $offset;
} else {
return false;
}
$this->readTill($target);
return fseek($this->cache, $target) === 0;
}
public function stream_tell() {
return ftell($this->cache);
}
public function stream_eof() {
return parent::stream_eof() and (ftell($this->source) === ftell($this->cache));
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Interface for stream wrappers that implement url functions such as unlink, stat
*/
interface Url {
/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options);
/**
* @param string $path
* @param string $mode
* @param int $options
* @param string &$opened_path
* @return bool
*/
public function stream_open($path, $mode, $options, &$opened_path);
/**
* @param string $path
* @param int $mode
* @param int $options
* @return bool
*/
public function mkdir($path, $mode, $options);
/**
* @param string $source
* @param string $target
* @return bool
*/
public function rename($source, $target);
/**
* @param string $path
* @param int $options
* @return bool
*/
public function rmdir($path, $options);
/**
* @param string
* @return bool
*/
public function unlink($path);
/**
* @param string $path
* @param int $flags
* @return array
*/
public function url_stat($path, $flags);
}

View File

@ -0,0 +1,121 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams;
/**
* Wrapper that provides callbacks for url actions such as fopen, unlink, rename
*
* Usage:
*
* $path = UrlCallBack('/path/so/source', function(){
* echo 'fopen';
* }, function(){
* echo 'opendir';
* }, function(){
* echo 'mkdir';
* }, function(){
* echo 'rename';
* }, function(){
* echo 'rmdir';
* }, function(){
* echo 'unlink';
* }, function(){
* echo 'stat';
* });
*
* mkdir($path);
* ...
*
* All callbacks are called after the operation is executed on the source stream
*/
class UrlCallback extends Wrapper implements Url {
/**
* @param string $source
* @param callable $fopen
* @param callable $opendir
* @param callable $mkdir
* @param callable $rename
* @param callable $rmdir
* @param callable $unlink
* @param callable $stat
* @return \Icewind\Streams\Path
*
* @throws \BadMethodCallException
* @throws \Exception
*/
public static function wrap($source, $fopen = null, $opendir = null, $mkdir = null, $rename = null, $rmdir = null,
$unlink = null, $stat = null) {
$options = array(
'source' => $source,
'fopen' => $fopen,
'opendir' => $opendir,
'mkdir' => $mkdir,
'rename' => $rename,
'rmdir' => $rmdir,
'unlink' => $unlink,
'stat' => $stat
);
return new Path('\Icewind\Streams\UrlCallBack', $options);
}
protected function loadContext($url) {
list($protocol) = explode('://', $url);
$options = stream_context_get_options($this->context);
return $options[$protocol];
}
protected function callCallBack($context, $callback) {
if (is_callable($context[$callback])) {
call_user_func($context[$callback]);
}
}
public function stream_open($path, $mode, $options, &$opened_path) {
$context = $this->loadContext($path);
$this->callCallBack($context, 'fopen');
$this->setSourceStream(fopen($context['source'], $mode));
return true;
}
public function dir_opendir($path, $options) {
$context = $this->loadContext($path);
$this->callCallBack($context, 'opendir');
$this->setSourceStream(opendir($context['source']));
return true;
}
public function mkdir($path, $mode, $options) {
$context = $this->loadContext($path);
$this->callCallBack($context, 'mkdir');
return mkdir($context['source'], $mode, $options & STREAM_MKDIR_RECURSIVE);
}
public function rmdir($path, $options) {
$context = $this->loadContext($path);
$this->callCallBack($context, 'rmdir');
return rmdir($context['source']);
}
public function rename($source, $target) {
$context = $this->loadContext($source);
$this->callCallBack($context, 'rename');
list(, $target) = explode('://', $target);
return rename($context['source'], $target);
}
public function unlink($path) {
$context = $this->loadContext($path);
$this->callCallBack($context, 'unlink');
return unlink($context['source']);
}
public function url_stat($path, $flags) {
throw new \Exception('stat is not supported due to php bug 50526');
}
}

View File

@ -12,7 +12,7 @@ namespace Icewind\Streams;
*
* This wrapper itself doesn't implement any functionality but is just a base class for other wrappers to extend
*/
abstract class Wrapper implements File {
abstract class Wrapper implements File, Directory {
/**
* @var resource
*/
@ -25,6 +25,22 @@ abstract class Wrapper implements File {
*/
protected $source;
protected static function wrapSource($source, $context, $protocol, $class) {
try {
stream_wrapper_register($protocol, $class);
if (@rewinddir($source) === false) {
$wrapped = fopen($protocol . '://', 'r+', false, $context);
} else {
$wrapped = opendir($protocol . '://', $context);
}
} catch (\BadMethodCallException $e) {
stream_wrapper_unregister($protocol);
throw $e;
}
stream_wrapper_unregister($protocol);
return $wrapped;
}
/**
* Load the source from the stream context and return the context options
*
@ -107,4 +123,17 @@ abstract class Wrapper implements File {
public function stream_close() {
return fclose($this->source);
}
public function dir_readdir() {
return readdir($this->source);
}
public function dir_closedir() {
closedir($this->source);
return true;
}
public function dir_rewinddir() {
return rewind($this->source);
}
}

View File

@ -1,72 +0,0 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams\Tests;
class CallbackWrapper extends Wrapper {
/**
* @param resource $source
* @param callable $read
* @param callable $write
* @param callable $close
* @return resource
*/
protected function wrapSource($source, $read = null, $write = null, $close = null) {
return \Icewind\Streams\CallbackWrapper::wrap($source, $read, $write, $close);
}
/**
* @expectedException \BadMethodCallException
*/
public function testWrapInvalidSource() {
$this->wrapSource('foo');
}
public function testReadCallback() {
$called = false;
$callBack = function () use (&$called) {
$called = true;
};
$source = fopen('php://temp', 'r+');
fwrite($source, 'foobar');
rewind($source);
$wrapped = $this->wrapSource($source, $callBack);
$this->assertEquals('foo', fread($wrapped, 3));
$this->assertTrue($called);
}
public function testWriteCallback() {
$lastData = '';
$callBack = function ($data) use (&$lastData) {
$lastData = $data;
};
$source = fopen('php://temp', 'r+');
$wrapped = $this->wrapSource($source, null, $callBack);
fwrite($wrapped, 'foobar');
$this->assertEquals('foobar', $lastData);
}
public function testCloseCallback() {
$called = false;
$callBack = function () use (&$called) {
$called = true;
};
$source = fopen('php://temp', 'r+');
fwrite($source, 'foobar');
rewind($source);
$wrapped = $this->wrapSource($source, null, null, $callBack);
fclose($wrapped);
$this->assertTrue($called);
}
}

View File

@ -1,130 +0,0 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams\Tests;
class IteratorDirectory extends \PHPUnit_Framework_TestCase {
/**
* @param \Iterator | array $source
* @return resource
*/
protected function wrapSource($source) {
return \Icewind\Streams\IteratorDirectory::wrap($source);
}
/**
* @expectedException \BadMethodCallException
*/
public function testNoContext() {
$context = stream_context_create(array());
stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory');
try {
opendir('iterator://', $context);
stream_wrapper_unregister('iterator');
} catch (\Exception $e) {
stream_wrapper_unregister('iterator');
throw $e;
}
}
/**
* @expectedException \BadMethodCallException
*/
public function testInvalidSource() {
$context = stream_context_create(array(
'dir' => array(
'array' => 2
)
));
stream_wrapper_register('iterator', '\Icewind\Streams\IteratorDirectory');
try {
opendir('iterator://', $context);
stream_wrapper_unregister('iterator');
} catch (\Exception $e) {
stream_wrapper_unregister('iterator');
throw $e;
}
}
/**
* @expectedException \BadMethodCallException
*/
public function testWrapInvalidSource() {
$this->wrapSource(2);
}
public function fileListProvider() {
$longList = array_fill(0, 500, 'foo');
return array(
array(
array(
'foo',
'bar',
'qwerty'
)
),
array(
array(
'with spaces',
'under_scores',
'日本語',
'character %$_',
'.',
'0',
'double "quotes"',
"single 'quotes'"
)
),
array(
array(
'single item'
)
),
array(
$longList
),
array(
array()
)
);
}
protected function basicTest($fileList, $dh) {
$result = array();
while (($file = readdir($dh)) !== false) {
$result[] = $file;
}
$this->assertEquals($fileList, $result);
rewinddir($dh);
if (count($fileList)) {
$this->assertEquals($fileList[0], readdir($dh));
} else {
$this->assertFalse(readdir($dh));
}
}
/**
* @dataProvider fileListProvider
*/
public function testBasicIterator($fileList) {
$iterator = new \ArrayIterator($fileList);
$dh = $this->wrapSource($iterator);
$this->basicTest($fileList, $dh);
}
/**
* @dataProvider fileListProvider
*/
public function testBasicArray($fileList) {
$dh = $this->wrapSource($fileList);
$this->basicTest($fileList, $dh);
}
}

View File

@ -1,59 +0,0 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams\Tests;
class NullWrapper extends Wrapper {
/**
* @param resource $source
* @return resource
*/
protected function wrapSource($source) {
return \Icewind\Streams\NullWrapper::wrap($source);
}
/**
* @expectedException \BadMethodCallException
*/
public function testNoContext() {
stream_wrapper_register('null', '\Icewind\Streams\NullWrapper');
$context = stream_context_create(array());
try {
fopen('null://', 'r+', false, $context);
stream_wrapper_unregister('null');
} catch (\Exception $e) {
stream_wrapper_unregister('null');
throw $e;
}
}
/**
* @expectedException \BadMethodCallException
*/
public function testNoSource() {
stream_wrapper_register('null', '\Icewind\Streams\NullWrapper');
$context = stream_context_create(array(
'null' => array(
'source' => 'bar'
)
));
try {
fopen('null://', 'r+', false, $context);
} catch (\Exception $e) {
stream_wrapper_unregister('null');
throw $e;
}
}
/**
* @expectedException \BadMethodCallException
*/
public function testWrapInvalidSource() {
$this->wrapSource('foo');
}
}

View File

@ -1,105 +0,0 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
namespace Icewind\Streams\Tests;
abstract class Wrapper extends \PHPUnit_Framework_TestCase {
/**
* @param resource $source
* @return resource
*/
abstract protected function wrapSource($source);
public function testRead() {
$source = fopen('php://temp', 'r+');
fwrite($source, 'foobar');
rewind($source);
$wrapped = $this->wrapSource($source);
$this->assertEquals('foo', fread($wrapped, 3));
$this->assertEquals('bar', fread($wrapped, 3));
$this->assertEquals('', fread($wrapped, 3));
}
public function testWrite() {
$source = fopen('php://temp', 'r+');
rewind($source);
$wrapped = $this->wrapSource($source);
$this->assertEquals(6, fwrite($wrapped, 'foobar'));
rewind($source);
$this->assertEquals('foobar', stream_get_contents($source));
}
public function testClose() {
$source = fopen('php://temp', 'r+');
rewind($source);
$wrapped = $this->wrapSource($source);
fclose($wrapped);
$this->assertFalse(is_resource($source));
}
public function testSeekTell() {
$source = fopen('php://temp', 'r+');
fwrite($source, 'foobar');
rewind($source);
$wrapped = $this->wrapSource($source);
$this->assertEquals(0, ftell($wrapped));
fseek($wrapped, 2);
$this->assertEquals(2, ftell($source));
$this->assertEquals(2, ftell($wrapped));
fseek($wrapped, 2, SEEK_CUR);
$this->assertEquals(4, ftell($source));
$this->assertEquals(4, ftell($wrapped));
fseek($wrapped, -1, SEEK_END);
$this->assertEquals(5, ftell($source));
$this->assertEquals(5, ftell($wrapped));
}
public function testStat() {
$source = fopen(__FILE__, 'r+');
$wrapped = $this->wrapSource($source);
$this->assertEquals(stat(__FILE__), fstat($wrapped));
}
public function testTruncate() {
if (version_compare(phpversion(), '5.4.0', '<')) {
$this->markTestSkipped('php <5.4 doesn\'t support truncate for stream wrappers');
}
$source = fopen('php://temp', 'r+');
fwrite($source, 'foobar');
rewind($source);
$wrapped = $this->wrapSource($source);
ftruncate($wrapped, 2);
$this->assertEquals('fo', fread($wrapped, 10));
}
public function testLock() {
$source = tmpfile();
$wrapped = $this->wrapSource($source);
if (!flock($wrapped, LOCK_EX)) {
$this->fail('Unable to acquire lock');
}
}
public function testStreamOptions() {
$source = fopen('php://temp', 'r+');
$wrapped = $this->wrapSource($source);
stream_set_blocking($wrapped, 0);
stream_set_timeout($wrapped, 1, 0);
stream_set_write_buffer($wrapped, 0);
}
}

View File

@ -1,9 +0,0 @@
<?php
/**
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
* This file is licensed under the Licensed under the MIT license:
* http://opensource.org/licenses/MIT
*/
date_default_timezone_set('UTC');
require_once __DIR__ . '/../vendor/autoload.php';

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<phpunit bootstrap="bootstrap.php">
<testsuite name='Stream'>
<directory suffix='.php'>./</directory>
</testsuite>
</phpunit>