mirror of
https://codeberg.org/icewind/streams.git
synced 2026-06-03 16:44:07 +02:00
Add UrlCallBack wrapper that provides callbacks for fopen, unlink, rename, etc
This commit is contained in:
parent
53cfe7f4f1
commit
e5854bfd06
4 changed files with 421 additions and 0 deletions
104
src/Path.php
Normal file
104
src/Path.php
Normal 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();
|
||||
}
|
||||
}
|
||||
64
src/Url.php
Normal file
64
src/Url.php
Normal 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);
|
||||
}
|
||||
121
src/UrlCallBack.php
Normal file
121
src/UrlCallBack.php
Normal 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);
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
132
tests/UrlCallBack.php
Normal file
132
tests/UrlCallBack.php
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?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 UrlCallBack extends \PHPUnit_Framework_TestCase {
|
||||
protected $tempDirs = array();
|
||||
|
||||
protected function getTempDir() {
|
||||
$dir = sys_get_temp_dir() . '/streams_' . uniqid();
|
||||
mkdir($dir);
|
||||
$this->tempDirs[] = $dir;
|
||||
return $dir;
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
foreach ($this->tempDirs as $dir) {
|
||||
$this->rmdir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
protected function rmdir($path) {
|
||||
$directory = new \RecursiveDirectoryIterator($path);
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::CHILD_FIRST);
|
||||
/**
|
||||
* @var \SplFileInfo $file
|
||||
*/
|
||||
foreach ($iterator as $file) {
|
||||
if (in_array($file->getBasename(), array('.', '..'))) {
|
||||
continue;
|
||||
} elseif ($file->isDir()) {
|
||||
rmdir($file->getPathname());
|
||||
} elseif ($file->isFile() || $file->isLink()) {
|
||||
unlink($file->getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testFOpenCallBack() {
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
};
|
||||
$path = \Icewind\Streams\UrlCallBack::wrap('php://temp', $callback);
|
||||
$fh = fopen($path, 'r');
|
||||
fclose($fh);
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testOpenDirCallBack() {
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
};
|
||||
$path = \Icewind\Streams\UrlCallBack::wrap($this->getTempDir(), null, $callback);
|
||||
$fh = opendir($path);
|
||||
closedir($fh);
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testMKDirCallBack() {
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
};
|
||||
$dir = $this->getTempDir() . '/test';
|
||||
$path = \Icewind\Streams\UrlCallBack::wrap($dir, null, null, $callback);
|
||||
mkdir($path);
|
||||
$this->assertTrue(file_exists($dir));
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testRMDirCallBack() {
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
};
|
||||
$dir = $this->getTempDir() . '/test';
|
||||
mkdir($dir);
|
||||
$path = \Icewind\Streams\UrlCallBack::wrap($dir, null, null, null, null, $callback);
|
||||
rmdir($path);
|
||||
$this->assertFalse(file_exists($dir));
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testRenameCallBack() {
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
};
|
||||
$source = $this->getTempDir() . '/test';
|
||||
touch($source);
|
||||
$path = \Icewind\Streams\UrlCallBack::wrap($source, null, null, null, $callback);
|
||||
$target = $path->wrapPath($source . '_rename');
|
||||
rename($path, $target);
|
||||
$this->assertTrue(file_exists($source . '_rename'));
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testUnlinkCallBack() {
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
};
|
||||
$file = $this->getTempDir() . '/test';
|
||||
touch($file);
|
||||
$path = \Icewind\Streams\UrlCallBack::wrap($file, null, null, null, null, null, $callback);
|
||||
unlink($path);
|
||||
$this->assertFalse(file_exists($file));
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function testStatCallBack() {
|
||||
$called = false;
|
||||
$callback = function () use (&$called) {
|
||||
$called = true;
|
||||
};
|
||||
$file = $this->getTempDir() . '/test';
|
||||
touch($file);
|
||||
$path = \Icewind\Streams\UrlCallBack::wrap($file, null, null, null, null, null, null, $callback);
|
||||
try {
|
||||
stat($path);
|
||||
} catch (\Exception $e) {
|
||||
$this->markTestSkipped('url_stat doesn\'t receive the context parameter, see php bug 50526');
|
||||
}
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue