2012-09-16 18:52:32 +04:00
< ? php
/**
2015-03-26 13:44:34 +03:00
* @ author Andreas Fischer < bantu @ owncloud . com >
* @ author Bart Visscher < bartv @ thisnet . nl >
* @ author Björn Schießle < schiessle @ owncloud . com >
* @ author Florin Peter < github @ florin - peter . de >
* @ author Joas Schilling < nickvergessen @ owncloud . com >
* @ author Jörn Friedrich Dreyer < jfd @ butonic . de >
* @ author Michael Gapczynski < GapczynskiM @ gmail . com >
* @ author Morris Jobke < hey @ morrisjobke . de >
* @ author Robin Appelman < icewind @ owncloud . com >
* @ author Robin McCorkell < rmccorkell @ karoshi . org . uk >
* @ author TheSFReader < TheSFReader @ gmail . com >
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
* @ author Victor Dubiniuk < dubiniuk @ owncloud . com >
* @ author Vincent Petry < pvince81 @ owncloud . com >
*
* @ copyright Copyright ( c ) 2015 , ownCloud , Inc .
* @ 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 18:52:32 +04:00
*/
2015-02-26 13:37:37 +03:00
2012-09-16 18:52:32 +04:00
namespace OC\Files\Cache ;
2012-10-26 14:30:25 +04:00
/**
2015-05-05 17:06:28 +03:00
* Metadata cache for a storage
2012-10-26 14:30:25 +04:00
*
2015-05-05 17:06:28 +03: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 14:30:25 +04:00
*/
2012-09-16 18:52:32 +04:00
class Cache {
2012-10-08 16:58:21 +04:00
const NOT_FOUND = 0 ;
const PARTIAL = 1 ; //only partial data available, file not cached in the database
const SHALLOW = 2 ; //folder in cache, but not all child files are completely scanned
const COMPLETE = 3 ;
2012-09-16 18:52:32 +04:00
/**
* @ var array partial data for the cache
*/
2014-04-29 17:14:48 +04:00
protected $partial = array ();
2012-09-26 19:52:02 +04:00
2012-11-08 20:59:08 +04:00
/**
* @ var string
*/
2014-04-29 17:14:48 +04:00
protected $storageId ;
2012-09-26 19:52:02 +04:00
2012-12-16 02:28:07 +04:00
/**
2013-04-26 02:00:18 +04:00
* @ var Storage $storageCache
2012-12-16 02:28:07 +04:00
*/
2014-04-29 17:14:48 +04:00
protected $storageCache ;
2012-12-16 02:28:07 +04:00
2014-04-29 17:14:48 +04:00
protected static $mimetypeIds = array ();
protected static $mimetypes = array ();
2013-01-07 04:40:09 +04:00
2012-09-26 19:52:02 +04:00
/**
2012-11-08 20:59:08 +04:00
* @ param \OC\Files\Storage\Storage | string $storage
2012-09-26 19:52:02 +04:00
*/
2012-11-08 20:59:08 +04:00
public function __construct ( $storage ) {
2012-11-23 03:17:18 +04:00
if ( $storage instanceof \OC\Files\Storage\Storage ) {
2012-11-08 20:59:08 +04:00
$this -> storageId = $storage -> getId ();
2012-11-23 03:17:18 +04:00
} else {
2012-11-08 20:59:08 +04:00
$this -> storageId = $storage ;
}
2013-02-16 00:49:40 +04:00
if ( strlen ( $this -> storageId ) > 64 ) {
$this -> storageId = md5 ( $this -> storageId );
}
2012-12-16 02:28:07 +04:00
2013-04-26 02:00:18 +04:00
$this -> storageCache = new Storage ( $storage );
2012-12-16 02:28:07 +04:00
}
2015-05-05 17:06:28 +03:00
/**
* Get the numeric storage id for this cache ' s storage
*
* @ return int
*/
2012-12-16 02:28:07 +04:00
public function getNumericStorageId () {
2013-04-26 02:00:18 +04:00
return $this -> storageCache -> getNumericId ();
2012-09-26 19:52:02 +04:00
}
2012-09-16 18:52:32 +04:00
2013-01-07 04:40:09 +04:00
/**
2015-05-05 17:06:28 +03:00
* Get the numeric id for a mimetype
*
* Mimetypes are stored as integers in the cache to prevent duplicated data of the ( usually ) fairly limited amount of unique mimetypes
* If the supplied mimetype does not yet have a numeric id a new one will be generated
2013-01-07 04:40:09 +04:00
*
* @ param string $mime
* @ return int
*/
public function getMimetypeId ( $mime ) {
2013-10-29 17:18:42 +04:00
if ( empty ( $mime )) {
// Can not insert empty string into Oracle NOT NULL column.
$mime = 'application/octet-stream' ;
}
2013-10-04 19:09:42 +04:00
if ( empty ( self :: $mimetypeIds )) {
2013-10-04 17:17:19 +04:00
$this -> loadMimetypes ();
}
2014-10-30 12:51:25 +03:00
2013-10-04 19:09:42 +04:00
if ( ! isset ( self :: $mimetypeIds [ $mime ])) {
2015-01-15 19:26:12 +03:00
try {
2015-01-29 17:24:53 +03:00
$connection = \OC_DB :: getConnection ();
$connection -> insertIfNotExist ( '*PREFIX*mimetypes' , [
'mimetype' => $mime ,
]);
$this -> loadMimetypes ();
2015-01-15 19:26:12 +03:00
} catch ( \Doctrine\DBAL\DBALException $e ) {
2013-10-21 16:48:08 +04:00
\OC_Log :: write ( 'core' , 'Exception during mimetype insertion: ' . $e -> getmessage (), \OC_Log :: DEBUG );
return - 1 ;
}
2014-10-30 12:51:25 +03:00
}
2013-10-04 19:09:42 +04:00
return self :: $mimetypeIds [ $mime ];
2013-01-07 04:40:09 +04:00
}
2015-05-05 17:06:28 +03:00
/**
* Get the mimetype ( as string ) from a mimetype id
*
* @ param int $id
* @ return string | null the mimetype for the id or null if the id is not known
*/
2013-01-07 04:40:09 +04:00
public function getMimetype ( $id ) {
2013-10-04 19:09:42 +04:00
if ( empty ( self :: $mimetypes )) {
2013-10-04 17:17:19 +04:00
$this -> loadMimetypes ();
2013-01-07 04:40:09 +04:00
}
2013-10-04 17:17:19 +04:00
2013-10-04 19:09:42 +04:00
return isset ( self :: $mimetypes [ $id ]) ? self :: $mimetypes [ $id ] : null ;
2013-10-04 17:17:19 +04:00
}
2013-10-21 16:48:08 +04:00
2015-05-05 17:06:28 +03:00
/**
* Load all known mimetypes and mimetype ids from the database
*
* @ throws \OC\DatabaseException
*/
2015-01-15 19:26:12 +03:00
public function loadMimetypes () {
2015-01-29 17:24:53 +03:00
self :: $mimetypeIds = self :: $mimetypes = array ();
2015-01-15 19:26:12 +03:00
$result = \OC_DB :: executeAudited ( 'SELECT `id`, `mimetype` FROM `*PREFIX*mimetypes`' , array ());
if ( $result ) {
while ( $row = $result -> fetchRow ()) {
self :: $mimetypeIds [ $row [ 'mimetype' ]] = $row [ 'id' ];
self :: $mimetypes [ $row [ 'id' ]] = $row [ 'mimetype' ];
2013-10-04 17:17:19 +04:00
}
2015-01-15 19:26:12 +03:00
}
2013-01-07 04:40:09 +04:00
}
2012-09-16 18:52:32 +04:00
/**
* get the stored metadata of a file or folder
*
2015-05-05 17:06:28 +03:00
* the returned cache entry contains at least the following values :
* [
* 'fileid' => int , the numeric id of a file ( see getId )
* 'storage' => int , the numeric id of the storage the file is stored on
* 'path' => string , the path of the file within the storage ( 'foo/bar.txt' )
* 'name' => string , the basename of a file ( ' bar . txt )
* 'mimetype' => string , the full mimetype of the file ( 'text/plain' )
* 'mimepart' => string , the first half of the mimetype ( 'text' )
* 'size' => int , the size of the file or folder in bytes
* 'mtime' => int , the last modified date of the file as unix timestamp as shown in the ui
* 'storage_mtime' => int , the last modified date of the file as unix timestamp as stored on the storage
* Note that when a file is updated we also update the mtime of all parent folders to make it visible to the user which folder has had updates most recently
* This can differ from the mtime on the underlying storage which usually only changes when a direct child is added , removed or renamed
* 'etag' => string , the etag for the file
* An etag is used for change detection of files and folders , an etag of a file changes whenever the content of the file changes
* Etag for folders change whenever a file in the folder has changed
* 'permissions' int , the permissions for the file stored as bitwise combination of \OCP\PERMISSION_READ , \OCP\PERMISSION_CREATE
* \OCP\PERMISSION_UPDATE , \OCP\PERMISSION_DELETE and \OCP\PERMISSION_SHARE
* ]
*
* @ param string | int $file either the path of a file or folder or the file id for a file or folder
* @ return array | false the cache entry as array of false if the file is not found in the cache
2012-09-16 18:52:32 +04:00
*/
2012-09-26 19:52:02 +04:00
public function get ( $file ) {
2012-11-25 19:30:57 +04:00
if ( is_string ( $file ) or $file == '' ) {
2013-05-23 22:29:46 +04:00
// normalize file
$file = $this -> normalize ( $file );
2012-09-16 18:52:32 +04:00
$where = 'WHERE `storage` = ? AND `path_hash` = ?' ;
2013-04-26 02:00:18 +04:00
$params = array ( $this -> getNumericStorageId (), md5 ( $file ));
2012-09-16 18:52:32 +04:00
} else { //file id
$where = 'WHERE `fileid` = ?' ;
$params = array ( $file );
}
2013-06-07 16:11:05 +04:00
$sql = ' SELECT `fileid` , `storage` , `path` , `parent` , `name` , `mimetype` , `mimepart` , `size` , `mtime` ,
2015-03-30 18:29:05 +03:00
`storage_mtime` , `encrypted` , `etag` , `permissions`
2013-06-07 16:11:05 +04:00
FROM `*PREFIX*filecache` ' . $where ;
$result = \OC_DB :: executeAudited ( $sql , $params );
2012-09-22 17:43:48 +04:00
$data = $result -> fetchRow ();
2012-09-22 16:40:04 +04:00
2013-06-10 13:07:41 +04: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 16:40:04 +04:00
//merge partial data
2015-01-15 19:26:12 +03:00
if ( ! $data and is_string ( $file )) {
2012-09-26 19:52:02 +04:00
if ( isset ( $this -> partial [ $file ])) {
$data = $this -> partial [ $file ];
2012-09-22 17:43:48 +04:00
}
2012-09-26 19:52:02 +04:00
} else {
//fix types
$data [ 'fileid' ] = ( int ) $data [ 'fileid' ];
2014-02-16 03:50:03 +04:00
$data [ 'size' ] = 0 + $data [ 'size' ];
2012-09-26 19:52:02 +04:00
$data [ 'mtime' ] = ( int ) $data [ 'mtime' ];
2013-10-25 14:39:50 +04:00
$data [ 'storage_mtime' ] = ( int ) $data [ 'storage_mtime' ];
2012-10-03 01:34:45 +04:00
$data [ 'encrypted' ] = ( bool ) $data [ 'encrypted' ];
2013-01-22 01:01:22 +04:00
$data [ 'storage' ] = $this -> storageId ;
2013-01-07 04:40:09 +04:00
$data [ 'mimetype' ] = $this -> getMimetype ( $data [ 'mimetype' ]);
$data [ 'mimepart' ] = $this -> getMimetype ( $data [ 'mimepart' ]);
2013-02-10 15:27:35 +04:00
if ( $data [ 'storage_mtime' ] == 0 ) {
$data [ 'storage_mtime' ] = $data [ 'mtime' ];
}
2014-06-03 19:57:56 +04:00
$data [ 'permissions' ] = ( int ) $data [ 'permissions' ];
2012-09-22 16:40:04 +04:00
}
2012-09-26 19:52:02 +04:00
2012-09-22 17:43:48 +04:00
return $data ;
2012-09-16 18:52:32 +04:00
}
2012-09-23 17:25:03 +04:00
/**
* get the metadata of all files stored in $folder
*
2012-09-26 19:52:02 +04:00
* @ param string $folder
2012-09-23 17:25:03 +04:00
* @ return array
*/
2014-02-21 18:36:24 +04:00
public function getFolderContents ( $folder ) {
$fileId = $this -> getId ( $folder );
2014-02-21 18:35:12 +04:00
return $this -> getFolderContentsById ( $fileId );
}
/**
* get the metadata of all files stored in $folder
*
* @ param int $fileId the file id of the folder
* @ return array
*/
public function getFolderContentsById ( $fileId ) {
2012-09-23 17:25:03 +04:00
if ( $fileId > - 1 ) {
2013-06-07 16:11:05 +04:00
$sql = ' SELECT `fileid` , `storage` , `path` , `parent` , `name` , `mimetype` , `mimepart` , `size` , `mtime` ,
2015-03-30 18:29:05 +03:00
`storage_mtime` , `encrypted` , `etag` , `permissions`
2013-06-07 16:11:05 +04:00
FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC ' ;
2015-01-15 19:26:12 +03:00
$result = \OC_DB :: executeAudited ( $sql , array ( $fileId ));
2013-01-07 04:40:09 +04:00
$files = $result -> fetchAll ();
foreach ( $files as & $file ) {
$file [ 'mimetype' ] = $this -> getMimetype ( $file [ 'mimetype' ]);
$file [ 'mimepart' ] = $this -> getMimetype ( $file [ 'mimepart' ]);
2013-02-10 15:27:35 +04:00
if ( $file [ 'storage_mtime' ] == 0 ) {
$file [ 'storage_mtime' ] = $file [ 'mtime' ];
}
2014-06-03 19:57:56 +04:00
$file [ 'permissions' ] = ( int ) $file [ 'permissions' ];
2014-09-10 18:49:58 +04:00
$file [ 'mtime' ] = ( int ) $file [ 'mtime' ];
$file [ 'storage_mtime' ] = ( int ) $file [ 'storage_mtime' ];
$file [ 'size' ] = 0 + $file [ 'size' ];
2013-01-07 04:40:09 +04:00
}
return $files ;
2012-09-23 17:25:03 +04:00
} else {
return array ();
}
}
2012-09-16 18:52:32 +04:00
/**
* store meta data for a file or folder
*
2012-09-26 19:52:02 +04:00
* @ param string $file
2012-09-16 18:52:32 +04:00
* @ param array $data
*
* @ return int file id
2015-03-11 11:33:50 +03:00
* @ throws \RuntimeException
2012-09-16 18:52:32 +04:00
*/
2012-09-26 19:52:02 +04:00
public function put ( $file , array $data ) {
if (( $id = $this -> getId ( $file )) > - 1 ) {
$this -> update ( $id , $data );
2012-09-16 18:52:32 +04:00
return $id ;
} else {
2013-05-23 22:29:46 +04:00
// normalize file
$file = $this -> normalize ( $file );
2012-09-26 19:52:02 +04:00
if ( isset ( $this -> partial [ $file ])) { //add any saved partial data
$data = array_merge ( $this -> partial [ $file ], $data );
unset ( $this -> partial [ $file ]);
2012-09-16 18:52:32 +04:00
}
$requiredFields = array ( 'size' , 'mtime' , 'mimetype' );
foreach ( $requiredFields as $field ) {
if ( ! isset ( $data [ $field ])) { //data not complete save as partial and return
2012-09-26 19:52:02 +04:00
$this -> partial [ $file ] = $data ;
2012-09-16 18:52:32 +04:00
return - 1 ;
}
}
2012-09-26 19:52:02 +04:00
$data [ 'path' ] = $file ;
$data [ 'parent' ] = $this -> getParentId ( $file );
2013-07-29 20:24:05 +04:00
$data [ 'name' ] = \OC_Util :: basename ( $file );
2012-09-16 18:52:32 +04:00
2012-09-26 19:52:02 +04:00
list ( $queryParts , $params ) = $this -> buildParts ( $data );
2012-09-16 18:52:32 +04:00
$queryParts [] = '`storage`' ;
2013-04-26 02:00:18 +04:00
$params [] = $this -> getNumericStorageId ();
2012-09-16 18:52:32 +04:00
2015-03-06 17:32:58 +03:00
$queryParts = array_map ( function ( $item ) {
return trim ( $item , " ` " );
}, $queryParts );
$values = array_combine ( $queryParts , $params );
2015-03-09 20:20:51 +03:00
if ( \OC :: $server -> getDatabaseConnection () -> insertIfNotExist ( '*PREFIX*filecache' , $values , [
'storage' ,
'path_hash' ,
])) {
2015-03-06 17:32:58 +03:00
return ( int ) \OC_DB :: insertid ( '*PREFIX*filecache' );
}
2012-09-16 18:52:32 +04:00
2015-03-09 20:20:51 +03:00
// The file was created in the mean time
2015-03-11 11:33:50 +03:00
if (( $id = $this -> getId ( $file )) > - 1 ) {
$this -> update ( $id , $data );
return $id ;
} else {
2015-04-02 19:37:33 +03:00
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.' );
2015-03-11 11:33:50 +03:00
}
2012-09-16 18:52:32 +04:00
}
}
/**
2015-05-05 17:06:28 +03:00
* update the metadata of an existing file or folder in the cache
2012-09-16 18:52:32 +04:00
*
2015-05-05 17:06:28 +03: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 18:52:32 +04:00
*/
2012-09-26 19:52:02 +04:00
public function update ( $id , array $data ) {
2013-05-25 22:35:12 +04:00
2015-01-15 19:26:12 +03:00
if ( isset ( $data [ 'path' ])) {
2013-05-25 22:35:12 +04:00
// normalize path
$data [ 'path' ] = $this -> normalize ( $data [ 'path' ]);
}
2015-01-15 19:26:12 +03:00
if ( isset ( $data [ 'name' ])) {
2013-05-25 22:35:12 +04:00
// normalize path
$data [ 'name' ] = $this -> normalize ( $data [ 'name' ]);
}
2012-09-26 19:52:02 +04:00
list ( $queryParts , $params ) = $this -> buildParts ( $data );
2015-04-12 15:49:18 +03: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 19:06:21 +03:00
$params = array_merge ( $params , $params );
2012-09-16 18:52:32 +04:00
$params [] = $id ;
2015-04-11 19:06:21 +03: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 ) . '=? ' .
'WHERE (' . implode ( ' <> ? OR ' , $queryParts ) . ' <> ? ) AND `fileid` = ? ' ;
2013-06-13 01:01:52 +04:00
\OC_DB :: executeAudited ( $sql , $params );
2015-04-11 19:06:21 +03:00
2012-09-16 18:52:32 +04:00
}
/**
* extract query parts and params array from data array
*
* @ param array $data
2015-05-05 17:06:28 +03:00
* @ return array [ $queryParts , $params ]
* $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 18:52:32 +04:00
*/
2015-05-05 17:06:28 +03:00
protected function buildParts ( array $data ) {
2014-06-03 19:57:56 +04:00
$fields = array (
2015-03-30 18:29:05 +03:00
'path' , 'parent' , 'name' , 'mimetype' , 'size' , 'mtime' , 'storage_mtime' , 'encrypted' ,
2014-06-03 19:57:56 +04:00
'etag' , 'permissions' );
2012-09-16 18:52:32 +04: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' ) {
2013-01-07 04:40:09 +04:00
$params [] = $this -> getMimetypeId ( substr ( $value , 0 , strpos ( $value , '/' )));
2012-09-16 18:52:32 +04:00
$queryParts [] = '`mimepart`' ;
2013-01-07 04:40:09 +04:00
$value = $this -> getMimetypeId ( $value );
2013-02-10 15:27:35 +04:00
} elseif ( $name === 'storage_mtime' ) {
if ( ! isset ( $data [ 'mtime' ])) {
$params [] = $value ;
$queryParts [] = '`mtime`' ;
}
2013-09-21 04:20:01 +04:00
} elseif ( $name === 'encrypted' ) {
// Boolean to integer conversion
$value = $value ? 1 : 0 ;
2012-09-16 18:52:32 +04:00
}
2013-01-07 04:40:09 +04:00
$params [] = $value ;
$queryParts [] = '`' . $name . '`' ;
2012-09-16 18:52:32 +04:00
}
}
return array ( $queryParts , $params );
}
/**
* get the file id for a file
*
2015-05-05 17:06:28 +03: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 19:52:02 +04:00
* @ param string $file
2012-09-16 18:52:32 +04:00
* @ return int
*/
2012-09-26 19:52:02 +04:00
public function getId ( $file ) {
2013-05-23 22:29:46 +04:00
// normalize file
$file = $this -> normalize ( $file );
2012-09-26 19:52:02 +04:00
$pathHash = md5 ( $file );
2012-09-16 18:52:32 +04:00
2013-06-07 16:11:05 +04:00
$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?' ;
$result = \OC_DB :: executeAudited ( $sql , array ( $this -> getNumericStorageId (), $pathHash ));
2012-09-16 18:52:32 +04:00
if ( $row = $result -> fetchRow ()) {
2012-09-22 17:43:48 +04:00
return $row [ 'fileid' ];
2012-09-16 18:52:32 +04:00
} else {
return - 1 ;
}
}
/**
* get the id of the parent folder of a file
*
2012-09-26 19:52:02 +04:00
* @ param string $file
2012-09-22 17:48:39 +04:00
* @ return int
2012-09-16 18:52:32 +04:00
*/
2012-09-26 19:52:02 +04:00
public function getParentId ( $file ) {
2012-10-03 13:23:33 +04:00
if ( $file === '' ) {
2012-09-16 18:52:32 +04:00
return - 1 ;
} else {
2012-10-03 13:23:33 +04:00
$parent = dirname ( $file );
if ( $parent === '.' ) {
$parent = '' ;
}
return $this -> getId ( $parent );
2012-09-16 18:52:32 +04:00
}
}
/**
* check if a file is available in the cache
*
2012-09-26 19:52:02 +04:00
* @ param string $file
2012-09-16 18:52:32 +04:00
* @ return bool
*/
2012-09-26 19:52:02 +04:00
public function inCache ( $file ) {
return $this -> getId ( $file ) != - 1 ;
2012-09-16 18:52:32 +04:00
}
/**
* remove a file or folder from the cache
*
2015-05-05 17:06:28 +03:00
* when removing a folder from the cache all files and folders inside the folder will be removed as well
*
2012-09-26 19:52:02 +04:00
* @ param string $file
2012-09-16 18:52:32 +04:00
*/
2012-09-26 19:52:02 +04:00
public function remove ( $file ) {
2012-10-27 20:05:40 +04:00
$entry = $this -> get ( $file );
2015-01-15 19:26:12 +03:00
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?' ;
\OC_DB :: executeAudited ( $sql , array ( $entry [ 'fileid' ]));
2012-10-27 20:05:40 +04:00
if ( $entry [ 'mimetype' ] === 'httpd/unix-directory' ) {
2015-01-15 19:26:12 +03:00
$this -> removeChildren ( $entry );
2012-10-27 20:05:40 +04:00
}
2015-01-15 19:26:12 +03:00
}
2014-10-30 12:51:25 +03:00
2015-05-05 17:06:28 +03: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 19:26:12 +03: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 17:06:28 +03: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 19:26:12 +03:00
private function removeChildren ( $entry ) {
$subFolders = $this -> getSubFolders ( $entry );
foreach ( $subFolders as $folder ) {
$this -> removeChildren ( $folder );
}
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?' ;
2013-06-07 16:11:05 +04:00
\OC_DB :: executeAudited ( $sql , array ( $entry [ 'fileid' ]));
2012-09-16 18:52:32 +04:00
}
2012-11-03 01:25:33 +04:00
/**
* Move a file or folder in the cache
*
* @ param string $source
* @ param string $target
*/
public function move ( $source , $target ) {
2015-04-01 16:15:24 +03:00
$this -> moveFromCache ( $this , $source , $target );
2012-11-03 01:25:33 +04:00
}
2015-04-13 18:09:18 +03: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 16:12:59 +03:00
/**
* Move a file or folder in the cache
*
* @ param \OC\Files\Cache\Cache $sourceCache
* @ param string $sourcePath
* @ param string $targetPath
* @ throws \OC\DatabaseException
*/
public function moveFromCache ( Cache $sourceCache , $sourcePath , $targetPath ) {
// normalize source and target
$sourcePath = $this -> normalize ( $sourcePath );
$targetPath = $this -> normalize ( $targetPath );
$sourceData = $sourceCache -> get ( $sourcePath );
$sourceId = $sourceData [ 'fileid' ];
$newParentId = $this -> getParentId ( $targetPath );
2015-04-13 18:09:18 +03:00
list ( $sourceStorageId , $sourcePath ) = $sourceCache -> getMoveInfo ( $sourcePath );
list ( $targetStorageId , $targetPath ) = $this -> getMoveInfo ( $targetPath );
2015-04-01 16:12:59 +03:00
if ( $sourceData [ 'mimetype' ] === 'httpd/unix-directory' ) {
//find all child entries
$sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?' ;
2015-04-13 18:09:18 +03:00
$result = \OC_DB :: executeAudited ( $sql , [ $sourceStorageId , $sourcePath . '/%' ]);
2015-04-01 16:12:59 +03:00
$childEntries = $result -> fetchAll ();
$sourceLength = strlen ( $sourcePath );
\OC_DB :: beginTransaction ();
$query = \OC_DB :: prepare ( 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?' );
foreach ( $childEntries as $child ) {
$newTargetPath = $targetPath . substr ( $child [ 'path' ], $sourceLength );
2015-04-13 18:09:18 +03:00
\OC_DB :: executeAudited ( $query , [ $targetStorageId , $newTargetPath , md5 ( $newTargetPath ), $child [ 'fileid' ]]);
2015-04-01 16:12:59 +03:00
}
\OC_DB :: commit ();
}
$sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?' ;
2015-04-13 18:09:18 +03:00
\OC_DB :: executeAudited ( $sql , [ $targetStorageId , $targetPath , md5 ( $targetPath ), basename ( $targetPath ), $newParentId , $sourceId ]);
2015-04-01 16:12:59 +03:00
}
2012-09-16 18:52:32 +04:00
/**
2012-09-26 19:52:02 +04:00
* remove all entries for files that are stored on the storage from the cache
2012-09-16 18:52:32 +04:00
*/
2012-09-26 19:52:02 +04:00
public function clear () {
2013-06-07 16:11:05 +04:00
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?' ;
\OC_DB :: executeAudited ( $sql , array ( $this -> getNumericStorageId ()));
2012-12-16 02:28:07 +04:00
2013-06-07 16:11:05 +04:00
$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?' ;
\OC_DB :: executeAudited ( $sql , array ( $this -> storageId ));
2012-09-16 18:52:32 +04:00
}
2012-10-08 16:58:21 +04:00
/**
2015-05-05 17:06:28 +03: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 16:58:21 +04:00
* @ param string $file
*
2015-01-16 21:31:15 +03:00
* @ return int Cache :: NOT_FOUND , Cache :: PARTIAL , Cache :: SHALLOW or Cache :: COMPLETE
2012-10-08 16:58:21 +04:00
*/
public function getStatus ( $file ) {
2013-05-25 16:56:00 +04:00
// normalize file
$file = $this -> normalize ( $file );
2012-10-08 16:58:21 +04:00
$pathHash = md5 ( $file );
2013-06-07 16:11:05 +04:00
$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?' ;
$result = \OC_DB :: executeAudited ( $sql , array ( $this -> getNumericStorageId (), $pathHash ));
2012-10-08 16:58:21 +04:00
if ( $row = $result -> fetchRow ()) {
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 15:23:15 +04:00
/**
* search for files matching $pattern
*
2015-05-05 17:06:28 +03:00
* @ param string $pattern the search pattern using SQL search syntax ( e . g . '%searchstring%' )
* @ return array an array of cache entries where the name matches the search pattern
2012-10-26 15:23:15 +04:00
*/
public function search ( $pattern ) {
2013-05-25 22:35:12 +04:00
// normalize pattern
$pattern = $this -> normalize ( $pattern );
2014-07-03 21:01:00 +04:00
$sql = '
SELECT `fileid` , `storage` , `path` , `parent` , `name` ,
`mimetype` , `mimepart` , `size` , `mtime` , `encrypted` ,
2015-03-30 18:29:05 +03:00
`etag` , `permissions`
2014-07-03 21:01:00 +04:00
FROM `*PREFIX*filecache`
2014-09-17 18:12:54 +04:00
WHERE `storage` = ? AND `name` ILIKE ? ' ;
2014-07-03 21:01:00 +04:00
$result = \OC_DB :: executeAudited ( $sql ,
array ( $this -> getNumericStorageId (), $pattern )
);
2012-10-26 15:23:15 +04:00
$files = array ();
while ( $row = $result -> fetchRow ()) {
2013-01-07 04:40:09 +04:00
$row [ 'mimetype' ] = $this -> getMimetype ( $row [ 'mimetype' ]);
$row [ 'mimepart' ] = $this -> getMimetype ( $row [ 'mimepart' ]);
2012-10-26 15:23:15 +04:00
$files [] = $row ;
}
return $files ;
}
2012-10-27 12:01:20 +04:00
2012-10-27 12:34:25 +04:00
/**
* search for files by mimetype
*
2015-05-05 17:06:28 +03:00
* @ param string $mimetype either a full mimetype to search ( 'text/plain' ) or only the first part of a mimetype ( 'image' )
* where it will search for all mimetypes in the group ( 'image/*' )
* @ return array an array of cache entries where the mimetype matches the search
2012-10-27 12:34:25 +04:00
*/
public function searchByMime ( $mimetype ) {
if ( strpos ( $mimetype , '/' )) {
$where = '`mimetype` = ?' ;
} else {
$where = '`mimepart` = ?' ;
}
2015-03-30 18:29:05 +03:00
$sql = ' SELECT `fileid` , `storage` , `path` , `parent` , `name` , `mimetype` , `mimepart` , `size` , `mtime` , `encrypted` , `etag` , `permissions`
2013-06-07 16:11:05 +04:00
FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ? ' ;
2013-01-07 04:40:09 +04:00
$mimetype = $this -> getMimetypeId ( $mimetype );
2013-06-07 16:11:05 +04:00
$result = \OC_DB :: executeAudited ( $sql , array ( $mimetype , $this -> getNumericStorageId ()));
2013-02-03 14:06:26 +04:00
$files = array ();
while ( $row = $result -> fetchRow ()) {
$row [ 'mimetype' ] = $this -> getMimetype ( $row [ 'mimetype' ]);
$row [ 'mimepart' ] = $this -> getMimetype ( $row [ 'mimepart' ]);
$files [] = $row ;
}
return $files ;
2012-10-27 12:34:25 +04:00
}
2014-12-04 16:01:15 +03: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
* @ return array file data
*/
2014-12-12 13:18:35 +03:00
public function searchByTag ( $tag , $userId ) {
2014-12-04 16:01:15 +03:00
$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
'`mimetype`, `mimepart`, `size`, `mtime`, ' .
2015-03-30 18:29:05 +03:00
'`encrypted`, `etag`, `permissions` ' .
2014-12-04 16:01:15 +03:00
'FROM `*PREFIX*filecache` `file`, ' .
'`*PREFIX*vcategory_to_object` `tagmap`, ' .
'`*PREFIX*vcategory` `tag` ' .
// JOIN filecache to vcategory_to_object
2015-01-15 19:26:12 +03:00
'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
2014-12-04 16:01:15 +03:00
// JOIN vcategory_to_object to vcategory
'AND `tagmap`.`type` = `tag`.`type` ' .
'AND `tagmap`.`categoryid` = `tag`.`id` ' .
// conditions
2015-01-15 19:26:12 +03:00
'AND `file`.`storage` = ? ' .
2014-12-04 16:01:15 +03:00
'AND `tag`.`type` = \'files\' ' .
'AND `tag`.`uid` = ? ' ;
if ( is_int ( $tag )) {
$sql .= 'AND `tag`.`id` = ? ' ;
} else {
$sql .= 'AND `tag`.`category` = ? ' ;
}
$result = \OC_DB :: executeAudited (
$sql ,
array (
$this -> getNumericStorageId (),
$userId ,
$tag
)
);
$files = array ();
while ( $row = $result -> fetchRow ()) {
$files [] = $row ;
}
return $files ;
}
2012-11-08 21:10:54 +04:00
/**
2015-05-05 17:06:28 +03:00
* Re - calculate the folder size and the size of all parent folders
2012-11-08 21:10:54 +04:00
*
2014-02-06 19:30:58 +04:00
* @ param string | boolean $path
2014-02-28 17:23:07 +04:00
* @ param array $data ( optional ) meta data of the folder
2012-11-08 21:10:54 +04:00
*/
2014-02-28 17:23:07 +04:00
public function correctFolderSize ( $path , $data = null ) {
$this -> calculateFolderSize ( $path , $data );
2012-11-08 21:10:54 +04:00
if ( $path !== '' ) {
$parent = dirname ( $path );
2013-04-29 17:43:48 +04:00
if ( $parent === '.' or $parent === '/' ) {
2012-11-08 21:10:54 +04:00
$parent = '' ;
}
$this -> correctFolderSize ( $parent );
}
}
2012-10-27 19:02:05 +04:00
/**
2015-05-05 17:06:28 +03:00
* calculate the size of a folder and set it in the cache
2012-10-27 19:02:05 +04:00
*
* @ param string $path
2014-02-28 17:23:07 +04:00
* @ param array $entry ( optional ) meta data of the folder
2012-10-27 19:02:05 +04:00
* @ return int
*/
2014-02-28 17:23:07 +04:00
public function calculateFolderSize ( $path , $entry = null ) {
2012-10-27 19:02:05 +04:00
$totalSize = 0 ;
2014-03-03 19:48:28 +04:00
if ( is_null ( $entry ) or ! isset ( $entry [ 'fileid' ])) {
2014-02-28 17:23:07 +04:00
$entry = $this -> get ( $path );
}
2015-01-19 17:04:53 +03:00
if ( isset ( $entry [ 'mimetype' ]) && $entry [ 'mimetype' ] === 'httpd/unix-directory' ) {
2013-07-29 00:14:49 +04:00
$id = $entry [ 'fileid' ];
2015-03-30 18:29:05 +03:00
$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
2014-01-09 20:27:55 +04:00
'FROM `*PREFIX*filecache` ' .
2013-07-29 18:22:44 +04:00
'WHERE `parent` = ? AND `storage` = ?' ;
2013-07-29 00:14:49 +04:00
$result = \OC_DB :: executeAudited ( $sql , array ( $id , $this -> getNumericStorageId ()));
2013-07-29 18:22:44 +04:00
if ( $row = $result -> fetchRow ()) {
2014-11-28 11:35:31 +03:00
$result -> closeCursor ();
2015-03-30 18:29:05 +03:00
list ( $sum , $min ) = array_values ( $row );
2014-02-16 03:50:03 +04:00
$sum = 0 + $sum ;
$min = 0 + $min ;
2013-07-29 18:22:44 +04:00
if ( $min === - 1 ) {
$totalSize = $min ;
2013-07-29 00:14:49 +04:00
} else {
2013-07-29 18:22:44 +04:00
$totalSize = $sum ;
2013-07-29 00:14:49 +04:00
}
2014-01-09 20:27:55 +04:00
$update = array ();
2013-07-29 18:22:44 +04:00
if ( $entry [ 'size' ] !== $totalSize ) {
2014-01-09 20:27:55 +04:00
$update [ 'size' ] = $totalSize ;
}
if ( count ( $update ) > 0 ) {
$this -> update ( $id , $update );
}
2014-11-28 11:35:31 +03:00
} else {
$result -> closeCursor ();
2012-10-27 19:02:05 +04:00
}
}
return $totalSize ;
}
2012-10-27 12:01:20 +04:00
/**
* get all file ids on the files on the storage
*
* @ return int []
*/
public function getAll () {
2013-06-07 16:11:05 +04:00
$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?' ;
$result = \OC_DB :: executeAudited ( $sql , array ( $this -> getNumericStorageId ()));
2012-10-27 12:01:20 +04:00
$ids = array ();
while ( $row = $result -> fetchRow ()) {
$ids [] = $row [ 'fileid' ];
}
return $ids ;
}
2012-11-22 02:02:43 +04:00
/**
* find a folder in the cache which has not been fully scanned
*
2014-11-10 18:00:08 +03:00
* If multiple incomplete folders are in the cache , the one with the highest id will be returned ,
2012-11-22 02:02:43 +04: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-23 03:17:18 +04:00
public function getIncomplete () {
2013-02-11 20:44:02 +04:00
$query = \OC_DB :: prepare ( 'SELECT `path` FROM `*PREFIX*filecache`'
2015-01-15 19:26:12 +03:00
. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC' , 1 );
2013-06-07 16:11:05 +04:00
$result = \OC_DB :: executeAudited ( $query , array ( $this -> getNumericStorageId ()));
2013-02-15 00:59:24 +04:00
if ( $row = $result -> fetchRow ()) {
2012-11-22 02:02:43 +04:00
return $row [ 'path' ];
2012-11-23 03:17:18 +04:00
} else {
2012-11-22 02:02:43 +04:00
return false ;
}
}
2013-01-27 02:59:29 +04:00
2014-03-27 19:43:34 +04:00
/**
2015-05-05 17:06:28 +03:00
* get the path of a file on this storage by it ' s file id
2014-03-27 19:43:34 +04:00
*
2015-05-05 17:06:28 +03: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 19:43:34 +04:00
*/
public function getPathById ( $id ) {
$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?' ;
$result = \OC_DB :: executeAudited ( $sql , array ( $id , $this -> getNumericStorageId ()));
if ( $row = $result -> fetchRow ()) {
2014-05-08 15:33:55 +04:00
// Oracle stores empty strings as null...
if ( $row [ 'path' ] === null ) {
return '' ;
}
2014-03-27 19:43:34 +04:00
return $row [ 'path' ];
} else {
return null ;
}
}
2013-01-27 02:59:29 +04:00
/**
* get the storage id of the storage for a file and the internal path of the file
2014-03-31 16:29:55 +04: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-27 02:59:29 +04:00
*
2013-04-26 02:00:18 +04:00
* @ param int $id
2015-05-05 17:06:28 +03:00
* @ deprecated use getPathById () instead
2015-01-16 21:31:15 +03:00
* @ return array first element holding the storage id , second the path
2013-01-27 02:59:29 +04:00
*/
static public function getById ( $id ) {
2013-06-07 16:11:05 +04:00
$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?' ;
$result = \OC_DB :: executeAudited ( $sql , array ( $id ));
2013-01-27 02:59:29 +04:00
if ( $row = $result -> fetchRow ()) {
$numericId = $row [ 'storage' ];
$path = $row [ 'path' ];
} else {
return null ;
}
2013-04-26 02:00:18 +04:00
if ( $id = Storage :: getStorageId ( $numericId )) {
return array ( $id , $path );
2013-01-27 02:59:29 +04:00
} else {
return null ;
}
}
2013-05-23 22:29:46 +04:00
/**
* normalize the given path
2015-01-15 19:26:12 +03:00
*
2014-05-12 00:51:30 +04:00
* @ param string $path
2013-05-23 22:29:46 +04:00
* @ return string
*/
public function normalize ( $path ) {
2015-01-08 21:43:02 +03:00
return trim ( \OC_Util :: normalizeUnicode ( $path ), '/' );
2013-05-23 22:29:46 +04:00
}
2012-09-16 18:52:32 +04:00
}