forked from extern/egroupware
"finished implementation of storing vfs/sqlfs files in the database:
- the used storage (default filesystem) can be switched via a get-parameter in the url mounted (eg. sqlfs://default/?storage=db) - please note the current (php5.2.6) problems: a) retriving files via streams does NOT work for PDO_mysql (bindColum(,,PDO::PARAM_LOB) does NOT work, string returned) (there's a workaround implemented, but it requires to allocate memory for the whole file!) b) uploading/writing files > 1M fail on PDOStatement::execute() (setting PDO::MYSQL_ATTR_MAX_BUFFER_SIZE does NOT help) (not sure if that's a bug in PDO/PDO_mysql or an accepted limitation) --> now we need to implement an easy switch in setup to allow admins to use the db backend (does NOT require an directory outside the docroot) currently you need to use filemanager/cli.php mount"
This commit is contained in:
parent
f128866a37
commit
0baa7f22e3
@ -16,7 +16,12 @@
|
|||||||
*
|
*
|
||||||
* The sqlfs stream wrapper has 3 operation modi:
|
* The sqlfs stream wrapper has 3 operation modi:
|
||||||
* - content of files is stored in the filesystem (eGW's files_dir) (default)
|
* - content of files is stored in the filesystem (eGW's files_dir) (default)
|
||||||
* - content of files is stored as BLOB in the DB
|
* - content of files is stored as BLOB in the DB (can be enabled by mounting sqlfs://...?storage=db)
|
||||||
|
* please note the current (php5.2.6) problems:
|
||||||
|
* a) retriving files via streams does NOT work for PDO_mysql (bindColum(,,PDO::PARAM_LOB) does NOT work, string returned)
|
||||||
|
* (there's a workaround implemented, but it requires to allocate memory for the whole file!)
|
||||||
|
* b) uploading/writing files > 1M fail on PDOStatement::execute() (setting PDO::MYSQL_ATTR_MAX_BUFFER_SIZE does NOT help)
|
||||||
|
* (not sure if that's a bug in PDO/PDO_mysql or an accepted limitation)
|
||||||
* - content of files is versioned (and stored in the DB) NOT YET IMPLEMENTED
|
* - content of files is versioned (and stored in the DB) NOT YET IMPLEMENTED
|
||||||
* In the future it will be possible to activate eg. versioning in parts of the filesystem, via mount options in the vfs
|
* In the future it will be possible to activate eg. versioning in parts of the filesystem, via mount options in the vfs
|
||||||
*
|
*
|
||||||
@ -25,8 +30,6 @@
|
|||||||
* The stream wrapper interface is according to the docu on php.net
|
* The stream wrapper interface is according to the docu on php.net
|
||||||
*
|
*
|
||||||
* @link http://de.php.net/manual/de/function.stream-wrapper-register.php
|
* @link http://de.php.net/manual/de/function.stream-wrapper-register.php
|
||||||
* @ToDo compare (and optimize) performance with old vfs system (eg. via webdav)
|
|
||||||
* @ToDo pass $operation parameter via context from vfs stream-wrappers mount table, to eg. allow to mount parts with(out) versioning
|
|
||||||
* @ToDo versioning
|
* @ToDo versioning
|
||||||
*/
|
*/
|
||||||
class sqlfs_stream_wrapper implements iface_stream_wrapper
|
class sqlfs_stream_wrapper implements iface_stream_wrapper
|
||||||
@ -79,9 +82,14 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
/**
|
/**
|
||||||
* default for operation, change that if you want to test with STORE2DB atm
|
* default for operation, change that if you want to test with STORE2DB atm
|
||||||
*/
|
*/
|
||||||
const DEFAULT_OPERATION = 2;
|
const DEFAULT_OPERATION = self::STORE2FS;
|
||||||
|
|
||||||
var $operation = self::DEFAULT_OPERATION;
|
/**
|
||||||
|
* operation mode of the opened file
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $operation = self::DEFAULT_OPERATION;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* optional context param when opening the stream, null if no context passed
|
* optional context param when opening the stream, null if no context passed
|
||||||
@ -152,6 +160,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)");
|
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)");
|
||||||
|
|
||||||
$path = parse_url($url,PHP_URL_PATH);
|
$path = parse_url($url,PHP_URL_PATH);
|
||||||
|
$this->operation = self::url2operation($url);
|
||||||
$dir = egw_vfs::dirname($url);
|
$dir = egw_vfs::dirname($url);
|
||||||
|
|
||||||
$this->opened_path = $path;
|
$this->opened_path = $path;
|
||||||
@ -174,17 +183,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// new file --> create it in the DB
|
// new file --> create it in the DB
|
||||||
if ($this->operation == self::STORE2FS)
|
$stmt = self::$pdo->prepare($query='INSERT INTO '.self::TABLE.' (fs_name,fs_dir,fs_mode,fs_uid,fs_gid,fs_created,fs_modified,fs_creator,fs_mime'.
|
||||||
{
|
') VALUES (:fs_name,:fs_dir,:fs_mode,:fs_uid,:fs_gid,:fs_created,:fs_modified,:fs_creator,:fs_mime)');
|
||||||
$stmt = self::$pdo->prepare($query='INSERT INTO '.self::TABLE.' (fs_name,fs_dir,fs_mode,fs_uid,fs_gid,fs_created,fs_modified,fs_creator'.
|
|
||||||
') VALUES (:fs_name,:fs_dir,:fs_mode,:fs_uid,:fs_gid,:fs_created,:fs_modified,:fs_creator)');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$stmt = self::$pdo->prepare($query='INSERT INTO '.self::TABLE.' (fs_name,fs_dir,fs_mode,fs_uid,fs_gid,fs_created,fs_modified,fs_creator,fs_content'.
|
|
||||||
') VALUES (:fs_name,:fs_dir,:fs_mode,:fs_uid,:fs_gid,:fs_created,:fs_modified,:fs_creator,:fs_content)');
|
|
||||||
$stmt->bindParam(':fs_content',$this->opened_stream,PDO::PARAM_LOB);
|
|
||||||
}
|
|
||||||
$values = array(
|
$values = array(
|
||||||
'fs_name' => basename($path),
|
'fs_name' => basename($path),
|
||||||
'fs_dir' => $dir_stat['ino'],
|
'fs_dir' => $dir_stat['ino'],
|
||||||
@ -197,6 +197,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
'fs_created' => self::_pdo_timestamp(time()),
|
'fs_created' => self::_pdo_timestamp(time()),
|
||||||
'fs_modified' => self::_pdo_timestamp(time()),
|
'fs_modified' => self::_pdo_timestamp(time()),
|
||||||
'fs_creator' => egw_vfs::$user,
|
'fs_creator' => egw_vfs::$user,
|
||||||
|
'fs_mime' => 'application/octet-stream', // required NOT NULL!
|
||||||
);
|
);
|
||||||
foreach($values as $name => &$val)
|
foreach($values as $name => &$val)
|
||||||
{
|
{
|
||||||
@ -205,9 +206,14 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
if (!$stmt->execute() || !($this->opened_fs_id = self::$pdo->lastInsertId('fs_id')))
|
if (!$stmt->execute() || !($this->opened_fs_id = self::$pdo->lastInsertId('fs_id')))
|
||||||
{
|
{
|
||||||
$this->opened_stream = $this->opened_path = $this->opened_mode = null;
|
$this->opened_stream = $this->opened_path = $this->opened_mode = null;
|
||||||
echo self::$pdo->errorInfo()."\n";
|
error_log(__METHOD__."($url,$mode,$options) execute() failed: ".self::$pdo->errorInfo());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if ($this->operation == self::STORE2DB)
|
||||||
|
{
|
||||||
|
// we buffer all write operations in a temporary file, which get's written on close
|
||||||
|
$this->opened_stream = tmpfile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -223,6 +229,25 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$this->opened_fs_id = $stat['ino'];
|
$this->opened_fs_id = $stat['ino'];
|
||||||
|
|
||||||
|
if ($this->operation == self::STORE2DB)
|
||||||
|
{
|
||||||
|
$stmt = self::$pdo->prepare($sql='SELECT fs_content FROM '.self::TABLE.' WHERE fs_id=?');
|
||||||
|
$stmt->execute(array($stat['ino']));
|
||||||
|
$stmt->bindColumn(1,$this->opened_stream,PDO::PARAM_LOB);
|
||||||
|
$stmt->fetch(PDO::FETCH_BOUND);
|
||||||
|
// hack to work around a current php bug (http://bugs.php.net/bug.php?id=40913)
|
||||||
|
// PDOStatement::bindColumn(,,PDO::PARAM_LOB) is not working for MySQL, content is returned as string :-(
|
||||||
|
if (is_string($this->opened_stream))
|
||||||
|
{
|
||||||
|
$name = md5($url);
|
||||||
|
$GLOBALS[$name] =& $this->opened_stream; unset($this->opened_stream);
|
||||||
|
require_once(EGW_API_INC.'/class.global_stream_wrapper.inc.php');
|
||||||
|
$this->opened_stream = fopen('global://'.$name,'r');
|
||||||
|
unset($GLOBALS[$name]); // unset it, so it does not use up memory, once the stream is closed
|
||||||
|
}
|
||||||
|
//echo 'gettype($this->opened_stream)='; var_dump($this->opened_stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// do we operate directly on the filesystem
|
// do we operate directly on the filesystem
|
||||||
if ($this->operation == self::STORE2FS)
|
if ($this->operation == self::STORE2FS)
|
||||||
@ -233,6 +258,8 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
{
|
{
|
||||||
$this->stream_seek(0,SEEK_END);
|
$this->stream_seek(0,SEEK_END);
|
||||||
}
|
}
|
||||||
|
if (!is_resource($this->opened_stream)) error_log(__METHOD__."($url,$mode,$options) NO stream, returning false!");
|
||||||
|
|
||||||
return is_resource($this->opened_stream);
|
return is_resource($this->opened_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,24 +286,37 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
{
|
{
|
||||||
$mime_magic = new mime_magic();
|
$mime_magic = new mime_magic();
|
||||||
}
|
}
|
||||||
|
// we need to update the mime-type, size and content (if STORE2DB)
|
||||||
// we need to update the mime-type and size
|
|
||||||
$values = array(
|
$values = array(
|
||||||
':fs_size' => $this->stream_tell(),
|
':fs_size' => $this->stream_tell(),
|
||||||
// todo: analyse the file for the mime-type
|
// todo: analyse the file for the mime-type
|
||||||
':fs_mime' => $mime_magic->filename2mime($this->opened_path),
|
':fs_mime' => $mime_magic->filename2mime($this->opened_path),
|
||||||
':fs_id' => $this->opened_fs_id,
|
':fs_id' => $this->opened_fs_id,
|
||||||
);
|
);
|
||||||
$ret = fclose($this->opened_stream);
|
|
||||||
|
|
||||||
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_size=:fs_size,fs_mime=:fs_mime WHERE fs_id=:fs_id');
|
if ($this->operation == self::STORE2FS)
|
||||||
$stmt->execute($values);
|
{
|
||||||
}
|
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_size=:fs_size,fs_mime=:fs_mime WHERE fs_id=:fs_id');
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
$ret = fclose($this->opened_stream);
|
{
|
||||||
|
$stmt = self::$pdo->prepare('UPDATE '.self::TABLE.' SET fs_size=:fs_size,fs_mime=:fs_mime,fs_content=:fs_content WHERE fs_id=:fs_id');
|
||||||
|
$this->stream_seek(0,SEEK_SET); // rewind to the start
|
||||||
|
$stmt->bindParam(':fs_content', $this->opened_stream, PDO::PARAM_LOB);
|
||||||
|
}
|
||||||
|
foreach($values as $name => &$value)
|
||||||
|
{
|
||||||
|
$stmt->bindParam($name,$value);
|
||||||
|
}
|
||||||
|
if (!($ret = $stmt->execute()))
|
||||||
|
{
|
||||||
|
error_log(__METHOD__."() execute() failed! errorInfo()=".array2string(self::$pdo->errorInfo()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$ret = fclose($this->opened_stream) && $ret;
|
||||||
|
|
||||||
$this->opened_stream = $this->opened_path = $this->opened_mode = $this->opend_fs_id = null;
|
$this->opened_stream = $this->opened_path = $this->opened_mode = $this->opend_fs_id = null;
|
||||||
|
$this->operation = self::DEFAULT_OPERATION;
|
||||||
|
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
@ -433,7 +473,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
* @param string $url
|
* @param string $url
|
||||||
* @return boolean TRUE on success or FALSE on failure
|
* @return boolean TRUE on success or FALSE on failure
|
||||||
*/
|
*/
|
||||||
static function unlink ( $url,$operation=self::DEFAULT_OPERATION )
|
static function unlink ( $url )
|
||||||
{
|
{
|
||||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)");
|
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)");
|
||||||
|
|
||||||
@ -448,7 +488,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
$stmt = self::$pdo->prepare('DELETE FROM '.self::TABLE.' WHERE fs_id=:fs_id');
|
$stmt = self::$pdo->prepare('DELETE FROM '.self::TABLE.' WHERE fs_id=:fs_id');
|
||||||
unset(self::$stat_cache[$path]);
|
unset(self::$stat_cache[$path]);
|
||||||
|
|
||||||
if (($ret = $stmt->execute(array(':fs_id' => $stat['ino']))) && $operation == self::STORE2FS)
|
if (($ret = $stmt->execute(array(':fs_id' => $stat['ino']))) && self::url2operation($url) == self::STORE2FS)
|
||||||
{
|
{
|
||||||
unlink(self::_fs_path($path));
|
unlink(self::_fs_path($path));
|
||||||
}
|
}
|
||||||
@ -467,13 +507,14 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
* @param string $url_to
|
* @param string $url_to
|
||||||
* @return boolean TRUE on success or FALSE on failure
|
* @return boolean TRUE on success or FALSE on failure
|
||||||
*/
|
*/
|
||||||
static function rename ( $url_from, $url_to, $operation=self::DEFAULT_OPERATION )
|
static function rename ( $url_from, $url_to )
|
||||||
{
|
{
|
||||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url_from,$url_to)");
|
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url_from,$url_to)");
|
||||||
|
|
||||||
$path_from = parse_url($url_from,PHP_URL_PATH);
|
$path_from = parse_url($url_from,PHP_URL_PATH);
|
||||||
$path_to = parse_url($url_to,PHP_URL_PATH);
|
$path_to = parse_url($url_to,PHP_URL_PATH);
|
||||||
$to_dir = dirname($path_to);
|
$to_dir = dirname($path_to);
|
||||||
|
$operation = self::url2operation($url_from);
|
||||||
|
|
||||||
if (!($from_stat = self::url_stat($path_from,0)) || !egw_vfs::check_access(dirname($path_from),egw_vfs::WRITABLE))
|
if (!($from_stat = self::url_stat($path_from,0)) || !egw_vfs::check_access(dirname($path_from),egw_vfs::WRITABLE))
|
||||||
{
|
{
|
||||||
@ -532,7 +573,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
* @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE
|
* @param int $options Posible values include STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE
|
||||||
* @return boolean TRUE on success or FALSE on failure
|
* @return boolean TRUE on success or FALSE on failure
|
||||||
*/
|
*/
|
||||||
static function mkdir ( $url, $mode, $options, $operation=self::DEFAULT_OPERATION )
|
static function mkdir ( $url, $mode, $options )
|
||||||
{
|
{
|
||||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)");
|
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url,$mode,$options)");
|
||||||
|
|
||||||
@ -584,7 +625,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
':fs_created' => self::_pdo_timestamp(time()),
|
':fs_created' => self::_pdo_timestamp(time()),
|
||||||
':fs_modified' => self::_pdo_timestamp(time()),
|
':fs_modified' => self::_pdo_timestamp(time()),
|
||||||
':fs_creator' => egw_vfs::$user,
|
':fs_creator' => egw_vfs::$user,
|
||||||
))) && $operation == self::STORE2FS)
|
))) && self::url2operation($url) == self::STORE2FS)
|
||||||
{
|
{
|
||||||
$ok = mkdir($dir = self::_fs_path($path),0700,true); // we create all missing parent dirs, as eg. /home might not yet exist
|
$ok = mkdir($dir = self::_fs_path($path),0700,true); // we create all missing parent dirs, as eg. /home might not yet exist
|
||||||
}
|
}
|
||||||
@ -601,7 +642,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
* @param int $options Possible values include STREAM_REPORT_ERRORS.
|
* @param int $options Possible values include STREAM_REPORT_ERRORS.
|
||||||
* @return boolean TRUE on success or FALSE on failure.
|
* @return boolean TRUE on success or FALSE on failure.
|
||||||
*/
|
*/
|
||||||
static function rmdir ( $url, $options, $operation=self::DEFAULT_OPERATION )
|
static function rmdir ( $url, $options )
|
||||||
{
|
{
|
||||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)");
|
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($url)");
|
||||||
|
|
||||||
@ -635,7 +676,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
unset($stmt); // free statement object, on some installs a new prepare fails otherwise!
|
unset($stmt); // free statement object, on some installs a new prepare fails otherwise!
|
||||||
|
|
||||||
$stmt = self::$pdo->prepare('DELETE FROM '.self::TABLE.' WHERE fs_id=?');
|
$stmt = self::$pdo->prepare('DELETE FROM '.self::TABLE.' WHERE fs_id=?');
|
||||||
if (($ret = $stmt->execute(array($stat['ino']))) && $operation == self::STORE2FS)
|
if (($ret = $stmt->execute(array($stat['ino']))) && self::url2operation($url) == self::STORE2FS)
|
||||||
{
|
{
|
||||||
self::eacl($path,null,false,$stat['ino']); // remove all (=false) evtl. existing extended acl for that dir
|
self::eacl($path,null,false,$stat['ino']); // remove all (=false) evtl. existing extended acl for that dir
|
||||||
rmdir(self::_fs_path($path));
|
rmdir(self::_fs_path($path));
|
||||||
@ -824,14 +865,6 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
$this->opened_dir = array();
|
$this->opened_dir = array();
|
||||||
$query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified FROM '.self::TABLE.' WHERE fs_dir=?';
|
$query = 'SELECT fs_id,fs_name,fs_mode,fs_uid,fs_gid,fs_size,fs_mime,fs_created,fs_modified FROM '.self::TABLE.' WHERE fs_dir=?';
|
||||||
|
|
||||||
// only return readable files, if dir is not writable by user
|
|
||||||
/* to value/check the extended acl later on, we have to return all files!
|
|
||||||
if (egw_vfs::HIDE_UNREADABLES && !egw_vfs::check_access($url,egw_vfs::WRITABLE,$stat))
|
|
||||||
{
|
|
||||||
$query .= ' AND '.self::_sql_readable();
|
|
||||||
}*/
|
|
||||||
//$query = "/* sqlfs::dir_opendir($path) */ ".$query;
|
|
||||||
|
|
||||||
$stmt = self::$pdo->prepare($query);
|
$stmt = self::$pdo->prepare($query);
|
||||||
$stmt->setFetchMode(PDO::FETCH_ASSOC);
|
$stmt->setFetchMode(PDO::FETCH_ASSOC);
|
||||||
if ($stmt->execute(array($stat['ino'])))
|
if ($stmt->execute(array($stat['ino'])))
|
||||||
@ -1277,6 +1310,7 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
|
|
||||||
switch($egw_db->Type)
|
switch($egw_db->Type)
|
||||||
{
|
{
|
||||||
|
case 'mysqli':
|
||||||
case 'mysqlt':
|
case 'mysqlt':
|
||||||
$type = 'mysql';
|
$type = 'mysql';
|
||||||
break;
|
break;
|
||||||
@ -1383,6 +1417,34 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
|
|||||||
$parts['host'].$parts['path'];
|
$parts['host'].$parts['path'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get storage mode from url (get parameter 'storage', eg. ?storage=db)
|
||||||
|
*
|
||||||
|
* @param string|array $url complete url or array of url-parts from parse_url
|
||||||
|
* @return int self::STORE2FS or self::STORE2DB
|
||||||
|
*/
|
||||||
|
static function url2operation($url)
|
||||||
|
{
|
||||||
|
$operation = self::DEFAULT_OPERATION;
|
||||||
|
|
||||||
|
if (strpos(is_array($url) ? $url['query'] : $url,'storage=') !== false)
|
||||||
|
{
|
||||||
|
parse_str(is_array($url) ? $url['query'] : parse_url($url,PHP_URL_QUERY),$query);
|
||||||
|
switch ($query['storage'])
|
||||||
|
{
|
||||||
|
case 'db':
|
||||||
|
$operation = self::STORE2DB;
|
||||||
|
break;
|
||||||
|
case 'fs':
|
||||||
|
default:
|
||||||
|
$operation = self::STORE2FS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//error_log(__METHOD__."('$url') = $operation (1=DB, 2=FS)");
|
||||||
|
return $operation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stream_register_wrapper(sqlfs_stream_wrapper::SCHEME ,'sqlfs_stream_wrapper');
|
stream_register_wrapper(sqlfs_stream_wrapper::SCHEME ,'sqlfs_stream_wrapper');
|
||||||
|
Loading…
Reference in New Issue
Block a user