2012-09-16 14:52:32 +00:00
< ? php
/**
2016-07-21 15:07:57 +00:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2015-03-26 10:44:34 +00:00
* @ author Andreas Fischer < bantu @ owncloud . com >
2016-05-26 17:56:05 +00:00
* @ author Björn Schießle < bjoern @ schiessle . org >
2015-03-26 10:44:34 +00:00
* @ author Florin Peter < github @ florin - peter . de >
2015-06-25 09:43:55 +00:00
* @ author Jens - Christian Fischer < jens - christian . fischer @ switch . ch >
2016-07-21 15:07:57 +00:00
* @ author Joas Schilling < coding @ schilljs . com >
2015-03-26 10:44:34 +00:00
* @ author Jörn Friedrich Dreyer < jfd @ butonic . de >
2016-05-26 17:56:05 +00:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
2015-03-26 10:44:34 +00:00
* @ author Michael Gapczynski < GapczynskiM @ gmail . com >
* @ author Morris Jobke < hey @ morrisjobke . de >
2016-07-21 16:13:36 +00:00
* @ author Robin Appelman < robin @ icewind . nl >
2016-01-12 14:02:16 +00:00
* @ author Robin McCorkell < robin @ mccorkell . me . uk >
2016-07-21 15:07:57 +00:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2015-03-26 10:44:34 +00:00
* @ author TheSFReader < TheSFReader @ gmail . com >
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
* @ author Vincent Petry < pvince81 @ owncloud . com >
2017-03-14 05:27:17 +00:00
* @ author Xuanwo < xuanwo @ yunify . com >
2015-03-26 10:44:34 +00:00
*
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License , version 3 ,
* along with this program . If not , see < http :// www . gnu . org / licenses />
*
2012-09-16 14:52:32 +00:00
*/
2015-02-26 10:37:37 +00:00
2012-09-16 14:52:32 +00:00
namespace OC\Files\Cache ;
2017-01-16 15:20:53 +00:00
use OCP\DB\QueryBuilder\IQueryBuilder ;
2017-02-02 17:20:08 +00:00
use Doctrine\DBAL\Driver\Statement ;
2015-12-01 15:41:28 +00:00
use OCP\Files\Cache\ICache ;
2015-12-02 13:59:13 +00:00
use OCP\Files\Cache\ICacheEntry ;
2015-09-03 18:48:42 +00:00
use \OCP\Files\IMimeTypeLoader ;
2017-02-02 17:20:08 +00:00
use OCP\Files\Search\ISearchQuery ;
2015-11-05 15:25:02 +00:00
use OCP\IDBConnection ;
2015-09-03 18:48:42 +00:00
2012-10-26 10:30:25 +00:00
/**
2015-05-05 14:06:28 +00:00
* Metadata cache for a storage
2012-10-26 10:30:25 +00:00
*
2015-05-05 14:06:28 +00:00
* The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms :
*
* - Scanner : scans the storage and updates the cache where needed
* - Watcher : checks for changes made to the filesystem outside of the ownCloud instance and rescans files and folder when a change is detected
* - Updater : listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed
* - ChangePropagator : updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
2012-10-26 10:30:25 +00:00
*/
2015-12-01 15:41:28 +00:00
class Cache implements ICache {
2016-01-27 14:45:19 +00:00
use MoveFromCacheTrait {
MoveFromCacheTrait :: moveFromCache as moveFromCacheFallback ;
}
2012-09-16 14:52:32 +00:00
/**
* @ var array partial data for the cache
*/
2014-04-29 13:14:48 +00:00
protected $partial = array ();
2012-09-26 15:52:02 +00:00
2012-11-08 16:59:08 +00:00
/**
* @ var string
*/
2014-04-29 13:14:48 +00:00
protected $storageId ;
2012-09-26 15:52:02 +00:00
2012-12-15 22:28:07 +00:00
/**
2013-04-25 22:00:18 +00:00
* @ var Storage $storageCache
2012-12-15 22:28:07 +00:00
*/
2014-04-29 13:14:48 +00:00
protected $storageCache ;
2012-12-15 22:28:07 +00:00
2015-09-03 18:48:42 +00:00
/** @var IMimeTypeLoader */
protected $mimetypeLoader ;
2013-01-07 00:40:09 +00:00
2015-11-05 15:25:02 +00:00
/**
* @ var IDBConnection
*/
protected $connection ;
2017-02-02 17:20:08 +00:00
/** @var QuerySearchHelper */
protected $querySearchHelper ;
2012-09-26 15:52:02 +00:00
/**
2012-11-08 16:59:08 +00:00
* @ param \OC\Files\Storage\Storage | string $storage
2012-09-26 15:52:02 +00:00
*/
2012-11-08 16:59:08 +00:00
public function __construct ( $storage ) {
2012-11-22 23:17:18 +00:00
if ( $storage instanceof \OC\Files\Storage\Storage ) {
2012-11-08 16:59:08 +00:00
$this -> storageId = $storage -> getId ();
2012-11-22 23:17:18 +00:00
} else {
2012-11-08 16:59:08 +00:00
$this -> storageId = $storage ;
}
2013-02-15 20:49:40 +00:00
if ( strlen ( $this -> storageId ) > 64 ) {
$this -> storageId = md5 ( $this -> storageId );
}
2012-12-15 22:28:07 +00:00
2013-04-25 22:00:18 +00:00
$this -> storageCache = new Storage ( $storage );
2015-09-03 18:48:42 +00:00
$this -> mimetypeLoader = \OC :: $server -> getMimeTypeLoader ();
2015-11-05 15:25:02 +00:00
$this -> connection = \OC :: $server -> getDatabaseConnection ();
2017-02-02 17:20:08 +00:00
$this -> querySearchHelper = new QuerySearchHelper ( $this -> mimetypeLoader );
2012-12-15 22:28:07 +00:00
}
2015-05-05 14:06:28 +00:00
/**
* Get the numeric storage id for this cache ' s storage
*
* @ return int
*/
2012-12-15 22:28:07 +00:00
public function getNumericStorageId () {
2013-04-25 22:00:18 +00:00
return $this -> storageCache -> getNumericId ();
2012-09-26 15:52:02 +00:00
}
2012-09-16 14:52:32 +00:00
/**
* get the stored metadata of a file or folder
*
2015-05-05 14:06:28 +00:00
* @ param string | int $file either the path of a file or folder or the file id for a file or folder
2015-12-02 13:59:13 +00:00
* @ return ICacheEntry | false the cache entry as array of false if the file is not found in the cache
2012-09-16 14:52:32 +00:00
*/
2012-09-26 15:52:02 +00:00
public function get ( $file ) {
2012-11-25 15:30:57 +00:00
if ( is_string ( $file ) or $file == '' ) {
2013-05-23 18:29:46 +00:00
// normalize file
$file = $this -> normalize ( $file );
2012-09-16 14:52:32 +00:00
$where = 'WHERE `storage` = ? AND `path_hash` = ?' ;
2013-04-25 22:00:18 +00:00
$params = array ( $this -> getNumericStorageId (), md5 ( $file ));
2012-09-16 14:52:32 +00:00
} else { //file id
$where = 'WHERE `fileid` = ?' ;
$params = array ( $file );
}
2017-01-16 15:20:53 +00:00
$sql = ' SELECT `fileid` , `storage` , `path` , `path_hash` , `parent` , `name` , `mimetype` , `mimepart` , `size` , `mtime` ,
2016-01-29 20:50:48 +00:00
`storage_mtime` , `encrypted` , `etag` , `permissions` , `checksum`
2013-06-07 12:11:05 +00:00
FROM `*PREFIX*filecache` ' . $where ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , $params );
$data = $result -> fetch ();
2012-09-22 12:40:04 +00:00
2013-06-10 09:07:41 +00:00
//FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
//PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
if ( $data === null ) {
$data = false ;
}
2012-09-22 12:40:04 +00:00
//merge partial data
2015-01-15 16:26:12 +00:00
if ( ! $data and is_string ( $file )) {
2012-09-26 15:52:02 +00:00
if ( isset ( $this -> partial [ $file ])) {
$data = $this -> partial [ $file ];
2012-09-22 13:43:48 +00:00
}
2015-12-02 13:59:13 +00:00
return $data ;
2012-09-26 15:52:02 +00:00
} else {
2016-12-13 11:53:38 +00:00
return self :: cacheEntryFromData ( $data , $this -> mimetypeLoader );
2016-11-17 13:18:47 +00:00
}
}
/**
* Create a CacheEntry from database row
*
* @ param array $data
* @ param IMimeTypeLoader $mimetypeLoader
* @ return CacheEntry
*/
2016-12-13 11:53:38 +00:00
public static function cacheEntryFromData ( $data , IMimeTypeLoader $mimetypeLoader ) {
2016-11-17 13:18:47 +00:00
//fix types
$data [ 'fileid' ] = ( int ) $data [ 'fileid' ];
$data [ 'parent' ] = ( int ) $data [ 'parent' ];
$data [ 'size' ] = 0 + $data [ ' size ' ];
$data [ 'mtime' ] = ( int ) $data [ 'mtime' ];
$data [ 'storage_mtime' ] = ( int ) $data [ 'storage_mtime' ];
$data [ 'encryptedVersion' ] = ( int ) $data [ 'encrypted' ];
$data [ 'encrypted' ] = ( bool ) $data [ 'encrypted' ];
2016-12-13 11:50:17 +00:00
$data [ 'storage_id' ] = $data [ 'storage' ];
2017-02-21 03:07:37 +00:00
$data [ 'storage' ] = ( int ) $data [ 'storage' ];
2016-11-17 13:18:47 +00:00
$data [ 'mimetype' ] = $mimetypeLoader -> getMimetypeById ( $data [ 'mimetype' ]);
$data [ 'mimepart' ] = $mimetypeLoader -> getMimetypeById ( $data [ 'mimepart' ]);
if ( $data [ 'storage_mtime' ] == 0 ) {
$data [ 'storage_mtime' ] = $data [ 'mtime' ];
2012-09-22 12:40:04 +00:00
}
2016-11-17 13:18:47 +00:00
$data [ 'permissions' ] = ( int ) $data [ 'permissions' ];
return new CacheEntry ( $data );
2012-09-16 14:52:32 +00:00
}
2012-09-23 13:25:03 +00:00
/**
* get the metadata of all files stored in $folder
*
2012-09-26 15:52:02 +00:00
* @ param string $folder
2015-12-02 13:59:13 +00:00
* @ return ICacheEntry []
2012-09-23 13:25:03 +00:00
*/
2014-02-21 14:36:24 +00:00
public function getFolderContents ( $folder ) {
$fileId = $this -> getId ( $folder );
2014-02-21 14:35:12 +00:00
return $this -> getFolderContentsById ( $fileId );
}
/**
* get the metadata of all files stored in $folder
*
* @ param int $fileId the file id of the folder
2015-12-02 13:59:13 +00:00
* @ return ICacheEntry []
2014-02-21 14:35:12 +00:00
*/
public function getFolderContentsById ( $fileId ) {
2012-09-23 13:25:03 +00:00
if ( $fileId > - 1 ) {
2013-06-07 12:11:05 +00:00
$sql = ' SELECT `fileid` , `storage` , `path` , `parent` , `name` , `mimetype` , `mimepart` , `size` , `mtime` ,
2016-01-29 20:50:48 +00:00
`storage_mtime` , `encrypted` , `etag` , `permissions` , `checksum`
2013-06-07 12:11:05 +00:00
FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC ' ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , [ $fileId ]);
2013-01-07 00:40:09 +00:00
$files = $result -> fetchAll ();
2015-12-02 13:59:13 +00:00
return array_map ( function ( array $data ) {
2017-02-23 01:03:32 +00:00
return self :: cacheEntryFromData ( $data , $this -> mimetypeLoader );;
2015-12-02 13:59:13 +00:00
}, $files );
2012-09-23 13:25:03 +00:00
} else {
return array ();
}
}
2012-09-16 14:52:32 +00:00
/**
2016-02-02 13:41:14 +00:00
* insert or update meta data for a file or folder
2012-09-16 14:52:32 +00:00
*
2012-09-26 15:52:02 +00:00
* @ param string $file
2012-09-16 14:52:32 +00:00
* @ param array $data
*
* @ return int file id
2015-03-11 08:33:50 +00:00
* @ throws \RuntimeException
2012-09-16 14:52:32 +00:00
*/
2012-09-26 15:52:02 +00:00
public function put ( $file , array $data ) {
if (( $id = $this -> getId ( $file )) > - 1 ) {
$this -> update ( $id , $data );
2012-09-16 14:52:32 +00:00
return $id ;
} else {
2016-02-02 13:41:14 +00:00
return $this -> insert ( $file , $data );
}
}
2013-05-23 18:29:46 +00:00
2016-02-02 13:41:14 +00:00
/**
* insert meta data for a new file or folder
*
* @ param string $file
* @ param array $data
*
* @ return int file id
* @ throws \RuntimeException
*/
public function insert ( $file , array $data ) {
// normalize file
$file = $this -> normalize ( $file );
2012-09-16 14:52:32 +00:00
2016-02-02 13:41:14 +00:00
if ( isset ( $this -> partial [ $file ])) { //add any saved partial data
$data = array_merge ( $this -> partial [ $file ], $data );
unset ( $this -> partial [ $file ]);
}
$requiredFields = array ( 'size' , 'mtime' , 'mimetype' );
foreach ( $requiredFields as $field ) {
if ( ! isset ( $data [ $field ])) { //data not complete save as partial and return
$this -> partial [ $file ] = $data ;
return - 1 ;
2012-09-16 14:52:32 +00:00
}
2016-02-02 13:41:14 +00:00
}
2012-09-16 14:52:32 +00:00
2016-02-02 13:41:14 +00:00
$data [ 'path' ] = $file ;
$data [ 'parent' ] = $this -> getParentId ( $file );
$data [ 'name' ] = \OC_Util :: basename ( $file );
2012-09-16 14:52:32 +00:00
2016-02-02 13:41:14 +00:00
list ( $queryParts , $params ) = $this -> buildParts ( $data );
$queryParts [] = '`storage`' ;
$params [] = $this -> getNumericStorageId ();
2012-09-16 14:52:32 +00:00
2016-02-02 13:41:14 +00:00
$queryParts = array_map ( function ( $item ) {
return trim ( $item , " ` " );
}, $queryParts );
$values = array_combine ( $queryParts , $params );
if ( \OC :: $server -> getDatabaseConnection () -> insertIfNotExist ( '*PREFIX*filecache' , $values , [
'storage' ,
'path_hash' ,
])
) {
return ( int ) $this -> connection -> lastInsertId ( '*PREFIX*filecache' );
}
2012-09-16 14:52:32 +00:00
2016-02-02 13:41:14 +00:00
// The file was created in the mean time
if (( $id = $this -> getId ( $file )) > - 1 ) {
$this -> update ( $id , $data );
return $id ;
} else {
throw new \RuntimeException ( 'File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.' );
2012-09-16 14:52:32 +00:00
}
}
/**
2015-05-05 14:06:28 +00:00
* update the metadata of an existing file or folder in the cache
2012-09-16 14:52:32 +00:00
*
2015-05-05 14:06:28 +00:00
* @ param int $id the fileid of the existing file or folder
* @ param array $data [ $key => $value ] the metadata to update , only the fields provided in the array will be updated , non - provided values will remain unchanged
2012-09-16 14:52:32 +00:00
*/
2012-09-26 15:52:02 +00:00
public function update ( $id , array $data ) {
2013-05-25 18:35:12 +00:00
2015-01-15 16:26:12 +00:00
if ( isset ( $data [ 'path' ])) {
2013-05-25 18:35:12 +00:00
// normalize path
$data [ 'path' ] = $this -> normalize ( $data [ 'path' ]);
}
2015-01-15 16:26:12 +00:00
if ( isset ( $data [ 'name' ])) {
2013-05-25 18:35:12 +00:00
// normalize path
$data [ 'name' ] = $this -> normalize ( $data [ 'name' ]);
}
2012-09-26 15:52:02 +00:00
list ( $queryParts , $params ) = $this -> buildParts ( $data );
2015-04-12 12:49:18 +00:00
// duplicate $params because we need the parts twice in the SQL statement
// once for the SET part, once in the WHERE clause
2015-04-11 16:06:21 +00:00
$params = array_merge ( $params , $params );
2012-09-16 14:52:32 +00:00
$params [] = $id ;
2015-04-11 16:06:21 +00:00
// don't update if the data we try to set is the same as the one in the record
// some databases (Postgres) don't like superfluous updates
$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode ( ' = ?, ' , $queryParts ) . '=? ' .
2016-01-29 20:50:48 +00:00
'WHERE (' .
implode ( ' <> ? OR ' , $queryParts ) . ' <> ? OR ' .
implode ( ' IS NULL OR ' , $queryParts ) . ' IS NULL' .
') AND `fileid` = ? ' ;
2015-11-05 15:25:02 +00:00
$this -> connection -> executeQuery ( $sql , $params );
2015-04-11 16:06:21 +00:00
2012-09-16 14:52:32 +00:00
}
/**
* extract query parts and params array from data array
*
* @ param array $data
2015-05-05 14:06:28 +00:00
* @ return array [ $queryParts , $params ]
2015-11-05 15:25:02 +00:00
* $queryParts : string [], the ( escaped ) column names to be set in the query
* $params : mixed [], the new values for the columns , to be passed as params to the query
2012-09-16 14:52:32 +00:00
*/
2015-05-05 14:06:28 +00:00
protected function buildParts ( array $data ) {
2014-06-03 15:57:56 +00:00
$fields = array (
2015-03-30 15:29:05 +00:00
'path' , 'parent' , 'name' , 'mimetype' , 'size' , 'mtime' , 'storage_mtime' , 'encrypted' ,
2016-01-29 20:50:48 +00:00
'etag' , 'permissions' , 'checksum' );
2015-10-16 16:28:45 +00:00
$doNotCopyStorageMTime = false ;
if ( array_key_exists ( 'mtime' , $data ) && $data [ 'mtime' ] === null ) {
// this horrific magic tells it to not copy storage_mtime to mtime
unset ( $data [ 'mtime' ]);
$doNotCopyStorageMTime = true ;
}
2012-09-16 14:52:32 +00:00
$params = array ();
$queryParts = array ();
foreach ( $data as $name => $value ) {
if ( array_search ( $name , $fields ) !== false ) {
if ( $name === 'path' ) {
$params [] = md5 ( $value );
$queryParts [] = '`path_hash`' ;
} elseif ( $name === 'mimetype' ) {
2015-09-03 18:48:42 +00:00
$params [] = $this -> mimetypeLoader -> getId ( substr ( $value , 0 , strpos ( $value , '/' )));
2012-09-16 14:52:32 +00:00
$queryParts [] = '`mimepart`' ;
2015-09-03 18:48:42 +00:00
$value = $this -> mimetypeLoader -> getId ( $value );
2013-02-10 11:27:35 +00:00
} elseif ( $name === 'storage_mtime' ) {
2015-10-16 16:28:45 +00:00
if ( ! $doNotCopyStorageMTime && ! isset ( $data [ 'mtime' ])) {
2013-02-10 11:27:35 +00:00
$params [] = $value ;
$queryParts [] = '`mtime`' ;
}
2013-09-21 00:20:01 +00:00
} elseif ( $name === 'encrypted' ) {
2017-02-02 17:20:08 +00:00
if ( isset ( $data [ 'encryptedVersion' ])) {
2016-02-08 19:35:33 +00:00
$value = $data [ 'encryptedVersion' ];
} else {
// Boolean to integer conversion
$value = $value ? 1 : 0 ;
}
2012-09-16 14:52:32 +00:00
}
2013-01-07 00:40:09 +00:00
$params [] = $value ;
$queryParts [] = '`' . $name . '`' ;
2012-09-16 14:52:32 +00:00
}
}
return array ( $queryParts , $params );
}
/**
* get the file id for a file
*
2015-05-05 14:06:28 +00:00
* A file id is a numeric id for a file or folder that ' s unique within an owncloud instance which stays the same for the lifetime of a file
*
* File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
*
2012-09-26 15:52:02 +00:00
* @ param string $file
2012-09-16 14:52:32 +00:00
* @ return int
*/
2012-09-26 15:52:02 +00:00
public function getId ( $file ) {
2013-05-23 18:29:46 +00:00
// normalize file
$file = $this -> normalize ( $file );
2012-09-26 15:52:02 +00:00
$pathHash = md5 ( $file );
2012-09-16 14:52:32 +00:00
2013-06-07 12:11:05 +00:00
$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?' ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , array ( $this -> getNumericStorageId (), $pathHash ));
if ( $row = $result -> fetch ()) {
2012-09-22 13:43:48 +00:00
return $row [ 'fileid' ];
2012-09-16 14:52:32 +00:00
} else {
return - 1 ;
}
}
/**
* get the id of the parent folder of a file
*
2012-09-26 15:52:02 +00:00
* @ param string $file
2012-09-22 13:48:39 +00:00
* @ return int
2012-09-16 14:52:32 +00:00
*/
2012-09-26 15:52:02 +00:00
public function getParentId ( $file ) {
2012-10-03 09:23:33 +00:00
if ( $file === '' ) {
2012-09-16 14:52:32 +00:00
return - 1 ;
} else {
2015-11-10 15:14:08 +00:00
$parent = $this -> getParentPath ( $file );
2015-12-02 13:59:13 +00:00
return ( int ) $this -> getId ( $parent );
2015-11-10 15:14:08 +00:00
}
}
private function getParentPath ( $path ) {
$parent = dirname ( $path );
if ( $parent === '.' ) {
$parent = '' ;
2012-09-16 14:52:32 +00:00
}
2015-11-10 15:14:08 +00:00
return $parent ;
2012-09-16 14:52:32 +00:00
}
/**
* check if a file is available in the cache
*
2012-09-26 15:52:02 +00:00
* @ param string $file
2012-09-16 14:52:32 +00:00
* @ return bool
*/
2012-09-26 15:52:02 +00:00
public function inCache ( $file ) {
return $this -> getId ( $file ) != - 1 ;
2012-09-16 14:52:32 +00:00
}
/**
* remove a file or folder from the cache
*
2015-05-05 14:06:28 +00:00
* when removing a folder from the cache all files and folders inside the folder will be removed as well
*
2012-09-26 15:52:02 +00:00
* @ param string $file
2012-09-16 14:52:32 +00:00
*/
2012-09-26 15:52:02 +00:00
public function remove ( $file ) {
2012-10-27 16:05:40 +00:00
$entry = $this -> get ( $file );
2015-01-15 16:26:12 +00:00
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?' ;
2015-11-05 15:25:02 +00:00
$this -> connection -> executeQuery ( $sql , array ( $entry [ 'fileid' ]));
2012-10-27 16:05:40 +00:00
if ( $entry [ 'mimetype' ] === 'httpd/unix-directory' ) {
2015-01-15 16:26:12 +00:00
$this -> removeChildren ( $entry );
2012-10-27 16:05:40 +00:00
}
2015-01-15 16:26:12 +00:00
}
2014-10-30 09:51:25 +00:00
2015-05-05 14:06:28 +00:00
/**
* Get all sub folders of a folder
*
* @ param array $entry the cache entry of the folder to get the subfolders for
* @ return array [] the cache entries for the subfolders
*/
2015-01-15 16:26:12 +00:00
private function getSubFolders ( $entry ) {
$children = $this -> getFolderContentsById ( $entry [ 'fileid' ]);
return array_filter ( $children , function ( $child ) {
return $child [ 'mimetype' ] === 'httpd/unix-directory' ;
});
}
2015-05-05 14:06:28 +00:00
/**
* Recursively remove all children of a folder
*
* @ param array $entry the cache entry of the folder to remove the children of
* @ throws \OC\DatabaseException
*/
2015-01-15 16:26:12 +00:00
private function removeChildren ( $entry ) {
$subFolders = $this -> getSubFolders ( $entry );
foreach ( $subFolders as $folder ) {
$this -> removeChildren ( $folder );
}
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?' ;
2015-11-05 15:25:02 +00:00
$this -> connection -> executeQuery ( $sql , array ( $entry [ 'fileid' ]));
2012-09-16 14:52:32 +00:00
}
2012-11-02 21:25:33 +00:00
/**
* Move a file or folder in the cache
*
* @ param string $source
* @ param string $target
*/
public function move ( $source , $target ) {
2015-04-01 13:15:24 +00:00
$this -> moveFromCache ( $this , $source , $target );
2012-11-02 21:25:33 +00:00
}
2015-04-13 15:09:18 +00:00
/**
* Get the storage id and path needed for a move
*
* @ param string $path
* @ return array [ $storageId , $internalPath ]
*/
protected function getMoveInfo ( $path ) {
return [ $this -> getNumericStorageId (), $path ];
}
2015-04-01 13:12:59 +00:00
/**
* Move a file or folder in the cache
*
2015-12-02 13:59:13 +00:00
* @ param \OCP\Files\Cache\ICache $sourceCache
2015-04-01 13:12:59 +00:00
* @ param string $sourcePath
* @ param string $targetPath
* @ throws \OC\DatabaseException
2017-02-16 10:47:16 +00:00
* @ throws \Exception if the given storages have an invalid id
2015-04-01 13:12:59 +00:00
*/
2015-12-02 13:59:13 +00:00
public function moveFromCache ( ICache $sourceCache , $sourcePath , $targetPath ) {
2016-01-27 14:45:19 +00:00
if ( $sourceCache instanceof Cache ) {
// normalize source and target
$sourcePath = $this -> normalize ( $sourcePath );
$targetPath = $this -> normalize ( $targetPath );
$sourceData = $sourceCache -> get ( $sourcePath );
$sourceId = $sourceData [ 'fileid' ];
$newParentId = $this -> getParentId ( $targetPath );
list ( $sourceStorageId , $sourcePath ) = $sourceCache -> getMoveInfo ( $sourcePath );
list ( $targetStorageId , $targetPath ) = $this -> getMoveInfo ( $targetPath );
2017-02-16 10:47:16 +00:00
if ( is_null ( $sourceStorageId ) || $sourceStorageId === false ) {
throw new \Exception ( 'Invalid source storage id: ' . $sourceStorageId );
}
if ( is_null ( $targetStorageId ) || $targetStorageId === false ) {
throw new \Exception ( 'Invalid target storage id: ' . $targetStorageId );
}
2017-01-16 15:20:53 +00:00
$this -> connection -> beginTransaction ();
2016-01-27 14:45:19 +00:00
if ( $sourceData [ 'mimetype' ] === 'httpd/unix-directory' ) {
2017-01-16 15:20:53 +00:00
//update all child entries
2016-01-27 14:45:19 +00:00
$sourceLength = strlen ( $sourcePath );
2017-01-16 15:20:53 +00:00
$query = $this -> connection -> getQueryBuilder ();
2017-03-26 18:45:49 +00:00
$fun = $query -> func ();
2017-01-16 15:20:53 +00:00
$newPathFunction = $fun -> concat (
$query -> createNamedParameter ( $targetPath ),
$fun -> substring ( 'path' , $query -> createNamedParameter ( $sourceLength + 1 , IQueryBuilder :: PARAM_INT )) // +1 for the leading slash
);
$query -> update ( 'filecache' )
-> set ( 'storage' , $query -> createNamedParameter ( $targetStorageId , IQueryBuilder :: PARAM_INT ))
-> set ( 'path_hash' , $fun -> md5 ( $newPathFunction ))
-> set ( 'path' , $newPathFunction )
-> where ( $query -> expr () -> eq ( 'storage' , $query -> createNamedParameter ( $sourceStorageId , IQueryBuilder :: PARAM_INT )))
-> andWhere ( $query -> expr () -> like ( 'path' , $query -> createNamedParameter ( $this -> connection -> escapeLikeParameter ( $sourcePath ) . '/%' )));
try {
$query -> execute ();
2017-02-21 13:48:00 +00:00
} catch ( \OC\DatabaseException $e ) {
2017-01-16 15:20:53 +00:00
$this -> connection -> rollBack ();
throw $e ;
2016-01-27 14:45:19 +00:00
}
2015-04-01 13:12:59 +00:00
}
2017-01-16 15:20:53 +00:00
$sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?' ;
$this -> connection -> executeQuery ( $sql , array ( $targetStorageId , $targetPath , md5 ( $targetPath ), \OC_Util :: basename ( $targetPath ), $newParentId , $sourceId ));
$this -> connection -> commit ();
2015-10-26 15:31:26 +00:00
} else {
2016-01-27 14:45:19 +00:00
$this -> moveFromCacheFallback ( $sourceCache , $sourcePath , $targetPath );
2015-04-01 13:12:59 +00:00
}
}
2012-09-16 14:52:32 +00:00
/**
2012-09-26 15:52:02 +00:00
* remove all entries for files that are stored on the storage from the cache
2012-09-16 14:52:32 +00:00
*/
2012-09-26 15:52:02 +00:00
public function clear () {
2013-06-07 12:11:05 +00:00
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?' ;
2015-11-05 15:25:02 +00:00
$this -> connection -> executeQuery ( $sql , array ( $this -> getNumericStorageId ()));
2012-12-15 22:28:07 +00:00
2013-06-07 12:11:05 +00:00
$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?' ;
2015-11-05 15:25:02 +00:00
$this -> connection -> executeQuery ( $sql , array ( $this -> storageId ));
2012-09-16 14:52:32 +00:00
}
2012-10-08 12:58:21 +00:00
/**
2015-05-05 14:06:28 +00:00
* Get the scan status of a file
*
* - Cache :: NOT_FOUND : File is not in the cache
* - Cache :: PARTIAL : File is not stored in the cache but some incomplete data is known
* - Cache :: SHALLOW : The folder and it ' s direct children are in the cache but not all sub folders are fully scanned
* - Cache :: COMPLETE : The file or folder , with all it ' s children ) are fully scanned
*
2012-10-08 12:58:21 +00:00
* @ param string $file
*
2015-01-16 18:31:15 +00:00
* @ return int Cache :: NOT_FOUND , Cache :: PARTIAL , Cache :: SHALLOW or Cache :: COMPLETE
2012-10-08 12:58:21 +00:00
*/
public function getStatus ( $file ) {
2013-05-25 12:56:00 +00:00
// normalize file
$file = $this -> normalize ( $file );
2012-10-08 12:58:21 +00:00
$pathHash = md5 ( $file );
2013-06-07 12:11:05 +00:00
$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?' ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , array ( $this -> getNumericStorageId (), $pathHash ));
if ( $row = $result -> fetch ()) {
2012-10-08 12:58:21 +00:00
if (( int ) $row [ 'size' ] === - 1 ) {
return self :: SHALLOW ;
} else {
return self :: COMPLETE ;
}
} else {
if ( isset ( $this -> partial [ $file ])) {
return self :: PARTIAL ;
} else {
return self :: NOT_FOUND ;
}
}
}
2012-10-26 11:23:15 +00:00
/**
* search for files matching $pattern
*
2015-05-05 14:06:28 +00:00
* @ param string $pattern the search pattern using SQL search syntax ( e . g . '%searchstring%' )
2015-12-02 13:59:13 +00:00
* @ return ICacheEntry [] an array of cache entries where the name matches the search pattern
2012-10-26 11:23:15 +00:00
*/
public function search ( $pattern ) {
2013-05-25 18:35:12 +00:00
// normalize pattern
$pattern = $this -> normalize ( $pattern );
2017-03-13 15:06:19 +00:00
if ( $pattern === '%%' ) {
return [];
}
2014-07-03 17:01:00 +00:00
$sql = '
SELECT `fileid` , `storage` , `path` , `parent` , `name` ,
2017-02-23 01:03:32 +00:00
`mimetype` , `storage_mtime` , `mimepart` , `size` , `mtime` ,
`encrypted` , `etag` , `permissions` , `checksum`
2014-07-03 17:01:00 +00:00
FROM `*PREFIX*filecache`
2014-09-17 14:12:54 +00:00
WHERE `storage` = ? AND `name` ILIKE ? ' ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql ,
[ $this -> getNumericStorageId (), $pattern ]
2014-07-03 17:01:00 +00:00
);
2017-02-02 17:20:08 +00:00
return $this -> searchResultToCacheEntries ( $result );
}
/**
* @ param Statement $result
* @ return CacheEntry []
*/
private function searchResultToCacheEntries ( Statement $result ) {
2017-02-23 01:03:32 +00:00
$files = $result -> fetchAll ();
2017-02-02 17:20:08 +00:00
return array_map ( function ( array $data ) {
2017-02-23 01:03:32 +00:00
return self :: cacheEntryFromData ( $data , $this -> mimetypeLoader );
2015-12-02 13:59:13 +00:00
}, $files );
2012-10-26 11:23:15 +00:00
}
2012-10-27 08:01:20 +00:00
2012-10-27 08:34:25 +00:00
/**
* search for files by mimetype
*
2015-05-05 14:06:28 +00:00
* @ param string $mimetype either a full mimetype to search ( 'text/plain' ) or only the first part of a mimetype ( 'image' )
2015-11-05 15:25:02 +00:00
* where it will search for all mimetypes in the group ( 'image/*' )
2015-12-02 13:59:13 +00:00
* @ return ICacheEntry [] an array of cache entries where the mimetype matches the search
2012-10-27 08:34:25 +00:00
*/
public function searchByMime ( $mimetype ) {
if ( strpos ( $mimetype , '/' )) {
$where = '`mimetype` = ?' ;
} else {
$where = '`mimepart` = ?' ;
}
2017-02-23 01:03:32 +00:00
$sql = ' SELECT `fileid` , `storage` , `path` , `parent` , `name` , `mimetype` , `mimepart` , `size` , `storage_mtime` , `mtime` , `encrypted` , `etag` , `permissions` , `checksum`
2013-06-07 12:11:05 +00:00
FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ? ' ;
2015-09-03 18:48:42 +00:00
$mimetype = $this -> mimetypeLoader -> getId ( $mimetype );
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , array ( $mimetype , $this -> getNumericStorageId ()));
2017-02-23 01:03:32 +00:00
2017-02-02 17:20:08 +00:00
return $this -> searchResultToCacheEntries ( $result );
}
2017-02-23 01:03:32 +00:00
2017-02-02 17:20:08 +00:00
public function searchQuery ( ISearchQuery $searchQuery ) {
$builder = \OC :: $server -> getDatabaseConnection () -> getQueryBuilder ();
$query = $builder -> select ([ 'fileid' , 'storage' , 'path' , 'parent' , 'name' , 'mimetype' , 'mimepart' , 'size' , 'mtime' , 'storage_mtime' , 'encrypted' , 'etag' , 'permissions' , 'checksum' ])
2017-03-08 14:17:39 +00:00
-> from ( 'filecache' , 'file' );
$query -> where ( $builder -> expr () -> eq ( 'storage' , $builder -> createNamedParameter ( $this -> getNumericStorageId ())));
if ( $this -> querySearchHelper -> shouldJoinTags ( $searchQuery -> getSearchOperation ())) {
$query
-> innerJoin ( 'file' , 'vcategory_to_object' , 'tagmap' , $builder -> expr () -> eq ( 'file.fileid' , 'tagmap.objid' ))
-> innerJoin ( 'tagmap' , 'vcategory' , 'tag' , $builder -> expr () -> andX (
$builder -> expr () -> eq ( 'tagmap.type' , 'tag.type' ),
$builder -> expr () -> eq ( 'tagmap.categoryid' , 'tag.id' )
))
-> andWhere ( $builder -> expr () -> eq ( 'tag.type' , $builder -> createNamedParameter ( 'files' )))
-> andWhere ( $builder -> expr () -> eq ( 'tag.uid' , $builder -> createNamedParameter ( $searchQuery -> getUser () -> getUID ())));
}
$query -> andWhere ( $this -> querySearchHelper -> searchOperatorToDBExpr ( $builder , $searchQuery -> getSearchOperation ()));
2017-02-02 17:20:08 +00:00
2017-03-15 13:46:23 +00:00
$this -> querySearchHelper -> addSearchOrdersToQuery ( $query , $searchQuery -> getOrder ());
2017-02-02 17:20:08 +00:00
if ( $searchQuery -> getLimit ()) {
$query -> setMaxResults ( $searchQuery -> getLimit ());
}
if ( $searchQuery -> getOffset ()) {
$query -> setFirstResult ( $searchQuery -> getOffset ());
}
$result = $query -> execute ();
return $this -> searchResultToCacheEntries ( $result );
2012-10-27 08:34:25 +00:00
}
2017-03-08 14:17:39 +00:00
/**
2014-12-04 13:01:15 +00:00
* Search for files by tag of a given users .
*
* Note that every user can tag files differently .
*
* @ param string | int $tag name or tag id
* @ param string $userId owner of the tags
2015-12-02 13:59:13 +00:00
* @ return ICacheEntry [] file data
2014-12-04 13:01:15 +00:00
*/
2014-12-12 10:18:35 +00:00
public function searchByTag ( $tag , $userId ) {
2014-12-04 13:01:15 +00:00
$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
2017-02-23 01:03:32 +00:00
'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
2016-01-29 20:50:48 +00:00
'`encrypted`, `etag`, `permissions`, `checksum` ' .
2014-12-04 13:01:15 +00:00
'FROM `*PREFIX*filecache` `file`, ' .
'`*PREFIX*vcategory_to_object` `tagmap`, ' .
'`*PREFIX*vcategory` `tag` ' .
// JOIN filecache to vcategory_to_object
2015-01-15 16:26:12 +00:00
'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
2014-12-04 13:01:15 +00:00
// JOIN vcategory_to_object to vcategory
'AND `tagmap`.`type` = `tag`.`type` ' .
'AND `tagmap`.`categoryid` = `tag`.`id` ' .
// conditions
2015-01-15 16:26:12 +00:00
'AND `file`.`storage` = ? ' .
2014-12-04 13:01:15 +00:00
'AND `tag`.`type` = \'files\' ' .
'AND `tag`.`uid` = ? ' ;
if ( is_int ( $tag )) {
$sql .= 'AND `tag`.`id` = ? ' ;
} else {
$sql .= 'AND `tag`.`category` = ? ' ;
}
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery (
2014-12-04 13:01:15 +00:00
$sql ,
2015-11-05 15:25:02 +00:00
[
2014-12-04 13:01:15 +00:00
$this -> getNumericStorageId (),
$userId ,
$tag
2015-11-05 15:25:02 +00:00
]
2014-12-04 13:01:15 +00:00
);
2017-02-23 01:03:32 +00:00
$files = $result -> fetchAll ();
2015-12-02 13:59:13 +00:00
return array_map ( function ( array $data ) {
2017-02-23 01:03:32 +00:00
return self :: cacheEntryFromData ( $data , $this -> mimetypeLoader );
2015-12-02 13:59:13 +00:00
}, $files );
2014-12-04 13:01:15 +00:00
}
2012-11-08 17:10:54 +00:00
/**
2015-05-05 14:06:28 +00:00
* Re - calculate the folder size and the size of all parent folders
2012-11-08 17:10:54 +00:00
*
2014-02-06 15:30:58 +00:00
* @ param string | boolean $path
2014-02-28 13:23:07 +00:00
* @ param array $data ( optional ) meta data of the folder
2012-11-08 17:10:54 +00:00
*/
2014-02-28 13:23:07 +00:00
public function correctFolderSize ( $path , $data = null ) {
$this -> calculateFolderSize ( $path , $data );
2012-11-08 17:10:54 +00:00
if ( $path !== '' ) {
$parent = dirname ( $path );
2013-04-29 13:43:48 +00:00
if ( $parent === '.' or $parent === '/' ) {
2012-11-08 17:10:54 +00:00
$parent = '' ;
}
$this -> correctFolderSize ( $parent );
}
}
2012-10-27 15:02:05 +00:00
/**
2015-05-05 14:06:28 +00:00
* calculate the size of a folder and set it in the cache
2012-10-27 15:02:05 +00:00
*
* @ param string $path
2014-02-28 13:23:07 +00:00
* @ param array $entry ( optional ) meta data of the folder
2012-10-27 15:02:05 +00:00
* @ return int
*/
2014-02-28 13:23:07 +00:00
public function calculateFolderSize ( $path , $entry = null ) {
2012-10-27 15:02:05 +00:00
$totalSize = 0 ;
2014-03-03 15:48:28 +00:00
if ( is_null ( $entry ) or ! isset ( $entry [ 'fileid' ])) {
2014-02-28 13:23:07 +00:00
$entry = $this -> get ( $path );
}
2015-01-19 14:04:53 +00:00
if ( isset ( $entry [ 'mimetype' ]) && $entry [ 'mimetype' ] === 'httpd/unix-directory' ) {
2013-07-28 20:14:49 +00:00
$id = $entry [ 'fileid' ];
2015-03-30 15:29:05 +00:00
$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
2014-01-09 16:27:55 +00:00
'FROM `*PREFIX*filecache` ' .
2013-07-29 14:22:44 +00:00
'WHERE `parent` = ? AND `storage` = ?' ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , array ( $id , $this -> getNumericStorageId ()));
if ( $row = $result -> fetch ()) {
2014-11-28 08:35:31 +00:00
$result -> closeCursor ();
2015-03-30 15:29:05 +00:00
list ( $sum , $min ) = array_values ( $row );
2014-02-15 23:50:03 +00:00
$sum = 0 + $sum ;
$min = 0 + $min ;
2013-07-29 14:22:44 +00:00
if ( $min === - 1 ) {
$totalSize = $min ;
2013-07-28 20:14:49 +00:00
} else {
2013-07-29 14:22:44 +00:00
$totalSize = $sum ;
2013-07-28 20:14:49 +00:00
}
2014-01-09 16:27:55 +00:00
$update = array ();
2013-07-29 14:22:44 +00:00
if ( $entry [ 'size' ] !== $totalSize ) {
2014-01-09 16:27:55 +00:00
$update [ 'size' ] = $totalSize ;
}
if ( count ( $update ) > 0 ) {
$this -> update ( $id , $update );
}
2014-11-28 08:35:31 +00:00
} else {
$result -> closeCursor ();
2012-10-27 15:02:05 +00:00
}
}
return $totalSize ;
}
2012-10-27 08:01:20 +00:00
/**
* get all file ids on the files on the storage
*
* @ return int []
*/
public function getAll () {
2013-06-07 12:11:05 +00:00
$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?' ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , array ( $this -> getNumericStorageId ()));
2012-10-27 08:01:20 +00:00
$ids = array ();
2015-11-05 15:25:02 +00:00
while ( $row = $result -> fetch ()) {
2012-10-27 08:01:20 +00:00
$ids [] = $row [ 'fileid' ];
}
return $ids ;
}
2012-11-21 22:02:43 +00:00
/**
* find a folder in the cache which has not been fully scanned
*
2014-11-10 15:00:08 +00:00
* If multiple incomplete folders are in the cache , the one with the highest id will be returned ,
2012-11-21 22:02:43 +00:00
* use the one with the highest id gives the best result with the background scanner , since that is most
* likely the folder where we stopped scanning previously
*
* @ return string | bool the path of the folder or false when no folder matched
*/
2012-11-22 23:17:18 +00:00
public function getIncomplete () {
2015-11-05 15:25:02 +00:00
$query = $this -> connection -> prepare ( 'SELECT `path` FROM `*PREFIX*filecache`'
2015-01-15 16:26:12 +00:00
. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC' , 1 );
2015-11-05 15:25:02 +00:00
$query -> execute ([ $this -> getNumericStorageId ()]);
if ( $row = $query -> fetch ()) {
2012-11-21 22:02:43 +00:00
return $row [ 'path' ];
2012-11-22 23:17:18 +00:00
} else {
2012-11-21 22:02:43 +00:00
return false ;
}
}
2013-01-26 22:59:29 +00:00
2014-03-27 15:43:34 +00:00
/**
2015-05-05 14:06:28 +00:00
* get the path of a file on this storage by it ' s file id
2014-03-27 15:43:34 +00:00
*
2015-05-05 14:06:28 +00:00
* @ param int $id the file id of the file or folder to search
* @ return string | null the path of the file ( relative to the storage ) or null if a file with the given id does not exists within this cache
2014-03-27 15:43:34 +00:00
*/
public function getPathById ( $id ) {
$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?' ;
2015-11-05 15:25:02 +00:00
$result = $this -> connection -> executeQuery ( $sql , array ( $id , $this -> getNumericStorageId ()));
if ( $row = $result -> fetch ()) {
2014-05-08 11:33:55 +00:00
// Oracle stores empty strings as null...
if ( $row [ 'path' ] === null ) {
return '' ;
}
2014-03-27 15:43:34 +00:00
return $row [ 'path' ];
} else {
return null ;
}
}
2013-01-26 22:59:29 +00:00
/**
* get the storage id of the storage for a file and the internal path of the file
2014-03-31 12:29:55 +00:00
* unlike getPathById this does not limit the search to files on this storage and
* instead does a global search in the cache table
2013-01-26 22:59:29 +00:00
*
2013-04-25 22:00:18 +00:00
* @ param int $id
2015-05-05 14:06:28 +00:00
* @ deprecated use getPathById () instead
2015-01-16 18:31:15 +00:00
* @ return array first element holding the storage id , second the path
2013-01-26 22:59:29 +00:00
*/
static public function getById ( $id ) {
2015-11-05 15:25:02 +00:00
$connection = \OC :: $server -> getDatabaseConnection ();
2013-06-07 12:11:05 +00:00
$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?' ;
2015-11-05 15:25:02 +00:00
$result = $connection -> executeQuery ( $sql , array ( $id ));
if ( $row = $result -> fetch ()) {
2013-01-26 22:59:29 +00:00
$numericId = $row [ 'storage' ];
$path = $row [ 'path' ];
} else {
return null ;
}
2013-04-25 22:00:18 +00:00
if ( $id = Storage :: getStorageId ( $numericId )) {
return array ( $id , $path );
2013-01-26 22:59:29 +00:00
} else {
return null ;
}
}
2013-05-23 18:29:46 +00:00
/**
* normalize the given path
2015-01-15 16:26:12 +00:00
*
2014-05-11 20:51:30 +00:00
* @ param string $path
2013-05-23 18:29:46 +00:00
* @ return string
*/
public function normalize ( $path ) {
2015-01-08 18:43:02 +00:00
return trim ( \OC_Util :: normalizeUnicode ( $path ), '/' );
2013-05-23 18:29:46 +00:00
}
2012-09-16 14:52:32 +00:00
}