2012-09-16 10:52:32 -04:00
< ? php
2024-05-23 03:26:56 -04:00
2012-09-16 10:52:32 -04:00
/**
2024-05-23 03:26:56 -04:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2012-09-16 10:52:32 -04:00
*/
namespace OC\Files\Cache ;
2018-11-12 07:43:46 -05:00
use Doctrine\DBAL\Exception\UniqueConstraintViolationException ;
2024-06-10 12:01:39 -04:00
use OC\DB\Exceptions\DbalException ;
2021-05-04 13:06:02 -04:00
use OC\Files\Search\SearchComparison ;
use OC\Files\Search\SearchQuery ;
2022-12-28 11:17:45 -05:00
use OC\Files\Storage\Wrapper\Encryption ;
2023-08-15 12:41:53 -04:00
use OC\SystemConfig ;
2017-01-16 10:20:53 -05:00
use OCP\DB\QueryBuilder\IQueryBuilder ;
2020-11-26 09:39:46 -05:00
use OCP\EventDispatcher\IEventDispatcher ;
use OCP\Files\Cache\CacheEntryInsertedEvent ;
2023-11-23 04:22:34 -05:00
use OCP\Files\Cache\CacheEntryRemovedEvent ;
2020-11-26 09:39:46 -05:00
use OCP\Files\Cache\CacheEntryUpdatedEvent ;
2019-01-22 11:08:32 -05:00
use OCP\Files\Cache\CacheInsertEvent ;
2019-02-18 09:47:54 -05:00
use OCP\Files\Cache\CacheUpdateEvent ;
2015-12-01 10:41:28 -05:00
use OCP\Files\Cache\ICache ;
2015-12-02 08:59:13 -05:00
use OCP\Files\Cache\ICacheEntry ;
2019-10-25 10:55:53 -04:00
use OCP\Files\FileInfo ;
2019-11-22 14:52:10 -05:00
use OCP\Files\IMimeTypeLoader ;
2021-05-04 13:06:02 -04:00
use OCP\Files\Search\ISearchComparison ;
2021-05-05 12:09:53 -04:00
use OCP\Files\Search\ISearchOperator ;
2017-02-02 12:20:08 -05:00
use OCP\Files\Search\ISearchQuery ;
2019-01-22 11:08:32 -05:00
use OCP\Files\Storage\IStorage ;
2023-11-08 06:35:01 -05:00
use OCP\FilesMetadata\IFilesMetadataManager ;
2015-11-05 10:25:02 -05:00
use OCP\IDBConnection ;
2023-05-11 06:46:16 -04:00
use OCP\Util ;
2022-03-17 12:26:27 -04:00
use Psr\Log\LoggerInterface ;
2015-09-03 14:48:42 -04:00
2012-10-26 06:30:25 -04:00
/**
2015-05-05 10:06:28 -04:00
* Metadata cache for a storage
2012-10-26 06:30:25 -04:00
*
2022-07-27 08:51:42 -04:00
* The cache stores the metadata for all files and folders in a storage and is kept up to date through the following mechanisms :
2015-05-05 10:06:28 -04:00
*
* - Scanner : scans the storage and updates the cache where needed
2021-07-29 09:56:30 -04:00
* - Watcher : checks for changes made to the filesystem outside of the Nextcloud instance and rescans files and folder when a change is detected
* - Updater : listens to changes made to the filesystem inside of the Nextcloud instance and updates the cache where needed
2015-05-05 10:06:28 -04:00
* - 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 06:30:25 -04:00
*/
2015-12-01 10:41:28 -05:00
class Cache implements ICache {
2016-01-27 09:45:19 -05:00
use MoveFromCacheTrait {
MoveFromCacheTrait :: moveFromCache as moveFromCacheFallback ;
}
2012-09-16 10:52:32 -04:00
/**
* @ var array partial data for the cache
*/
2023-08-15 12:41:53 -04:00
protected array $partial = [];
protected string $storageId ;
protected Storage $storageCache ;
protected IMimeTypeLoader $mimetypeLoader ;
protected IDBConnection $connection ;
protected SystemConfig $systemConfig ;
protected LoggerInterface $logger ;
protected QuerySearchHelper $querySearchHelper ;
protected IEventDispatcher $eventDispatcher ;
protected IFilesMetadataManager $metadataManager ;
public function __construct (
private IStorage $storage ,
// this constructor is used in to many pleases to easily do proper di
// so instead we group it all together
2024-03-28 11:13:19 -04:00
? CacheDependencies $dependencies = null ,
2023-08-15 12:41:53 -04:00
) {
2019-01-22 11:08:32 -05:00
$this -> storageId = $storage -> getId ();
2013-02-15 15:49:40 -05:00
if ( strlen ( $this -> storageId ) > 64 ) {
$this -> storageId = md5 ( $this -> storageId );
}
2023-08-15 12:41:53 -04:00
if ( ! $dependencies ) {
$dependencies = \OC :: $server -> get ( CacheDependencies :: class );
}
$this -> storageCache = new Storage ( $this -> storage , true , $dependencies -> getConnection ());
$this -> mimetypeLoader = $dependencies -> getMimeTypeLoader ();
$this -> connection = $dependencies -> getConnection ();
$this -> systemConfig = $dependencies -> getSystemConfig ();
$this -> logger = $dependencies -> getLogger ();
$this -> querySearchHelper = $dependencies -> getQuerySearchHelper ();
$this -> eventDispatcher = $dependencies -> getEventDispatcher ();
$this -> metadataManager = $dependencies -> getMetadataManager ();
2012-12-15 17:28:07 -05:00
}
2021-01-14 13:03:39 -05:00
protected function getQueryBuilder () {
2019-10-25 10:55:53 -04:00
return new CacheQueryBuilder (
2024-07-04 13:20:46 -04:00
$this -> connection -> getQueryBuilder (),
2023-08-15 12:41:53 -04:00
$this -> metadataManager ,
2019-10-25 10:55:53 -04:00
);
}
2023-08-15 12:41:53 -04:00
public function getStorageCache () : Storage {
return $this -> storageCache ;
}
2015-05-05 10:06:28 -04:00
/**
* Get the numeric storage id for this cache ' s storage
*
* @ return int
*/
2012-12-15 17:28:07 -05:00
public function getNumericStorageId () {
2013-04-25 18:00:18 -04:00
return $this -> storageCache -> getNumericId ();
2012-09-26 11:52:02 -04:00
}
2012-09-16 10:52:32 -04:00
/**
* get the stored metadata of a file or folder
*
2015-05-05 10:06:28 -04:00
* @ param string | int $file either the path of a file or folder or the file id for a file or folder
2023-08-16 08:12:40 -04:00
* @ return ICacheEntry | false the cache entry as array or false if the file is not found in the cache
2012-09-16 10:52:32 -04:00
*/
2012-09-26 11:52:02 -04:00
public function get ( $file ) {
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> selectFileCache ();
2023-11-08 06:35:01 -05:00
$metadataQuery = $query -> selectMetadata ();
2019-10-25 10:55:53 -04:00
2021-12-03 10:05:19 -05:00
if ( is_string ( $file ) || $file == '' ) {
2013-05-23 14:29:46 -04:00
// normalize file
$file = $this -> normalize ( $file );
2021-05-04 13:06:02 -04:00
$query -> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> wherePath ( $file );
2012-09-16 10:52:32 -04:00
} else { //file id
2019-10-25 10:55:53 -04:00
$query -> whereFileId ( $file );
2012-09-16 10:52:32 -04:00
}
2012-09-22 08:40:04 -04:00
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
$data = $result -> fetch ();
$result -> closeCursor ();
2013-06-10 05:07:41 -04:00
2012-09-22 08:40:04 -04:00
//merge partial data
2021-12-03 10:05:19 -05:00
if ( ! $data && is_string ( $file ) && isset ( $this -> partial [ $file ])) {
2019-10-25 10:55:53 -04:00
return $this -> partial [ $file ];
2020-04-10 04:35:09 -04:00
} elseif ( ! $data ) {
2018-11-16 11:26:42 -05:00
return $data ;
2012-09-26 11:52:02 -04:00
} else {
2024-02-14 09:30:11 -05:00
$data [ 'metadata' ] = $metadataQuery -> extractMetadata ( $data ) -> asArray ();
2016-12-13 06:53:38 -05:00
return self :: cacheEntryFromData ( $data , $this -> mimetypeLoader );
2016-11-17 08:18:47 -05:00
}
}
/**
* Create a CacheEntry from database row
*
* @ param array $data
* @ param IMimeTypeLoader $mimetypeLoader
* @ return CacheEntry
*/
2016-12-13 06:53:38 -05:00
public static function cacheEntryFromData ( $data , IMimeTypeLoader $mimetypeLoader ) {
2016-11-17 08:18:47 -05:00
//fix types
2023-02-02 05:50:29 -05:00
$data [ 'name' ] = ( string ) $data [ 'name' ];
$data [ 'path' ] = ( string ) $data [ 'path' ];
2016-11-17 08:18:47 -05:00
$data [ 'fileid' ] = ( int ) $data [ 'fileid' ];
$data [ 'parent' ] = ( int ) $data [ 'parent' ];
2023-05-11 06:46:16 -04:00
$data [ 'size' ] = Util :: numericToNumber ( $data [ 'size' ]);
$data [ 'unencrypted_size' ] = Util :: numericToNumber ( $data [ 'unencrypted_size' ] ? ? 0 );
2016-11-17 08:18:47 -05:00
$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 06:50:17 -05:00
$data [ 'storage_id' ] = $data [ 'storage' ];
2017-02-20 22:07:37 -05:00
$data [ 'storage' ] = ( int ) $data [ 'storage' ];
2016-11-17 08:18:47 -05: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 08:40:04 -04:00
}
2016-11-17 08:18:47 -05:00
$data [ 'permissions' ] = ( int ) $data [ 'permissions' ];
2019-10-25 12:21:57 -04:00
if ( isset ( $data [ 'creation_time' ])) {
2021-03-18 12:16:28 -04:00
$data [ 'creation_time' ] = ( int ) $data [ 'creation_time' ];
2019-10-25 12:21:57 -04:00
}
if ( isset ( $data [ 'upload_time' ])) {
2021-03-18 12:16:28 -04:00
$data [ 'upload_time' ] = ( int ) $data [ 'upload_time' ];
2019-10-25 12:21:57 -04:00
}
2016-11-17 08:18:47 -05:00
return new CacheEntry ( $data );
2012-09-16 10:52:32 -04:00
}
2012-09-23 09:25:03 -04:00
/**
* get the metadata of all files stored in $folder
*
2012-09-26 11:52:02 -04:00
* @ param string $folder
2015-12-02 08:59:13 -05:00
* @ return ICacheEntry []
2012-09-23 09:25:03 -04:00
*/
2014-02-21 09:36:24 -05:00
public function getFolderContents ( $folder ) {
$fileId = $this -> getId ( $folder );
2014-02-21 09:35:12 -05: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 08:59:13 -05:00
* @ return ICacheEntry []
2014-02-21 09:35:12 -05:00
*/
public function getFolderContentsById ( $fileId ) {
2012-09-23 09:25:03 -04:00
if ( $fileId > - 1 ) {
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> selectFileCache ()
-> whereParent ( $fileId )
2024-07-04 13:21:03 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> orderBy ( 'name' , 'ASC' );
2023-11-08 06:35:01 -05:00
$metadataQuery = $query -> selectMetadata ();
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
$files = $result -> fetchAll ();
$result -> closeCursor ();
2023-11-08 06:35:01 -05:00
return array_map ( function ( array $data ) use ( $metadataQuery ) {
2024-02-14 09:30:11 -05:00
$data [ 'metadata' ] = $metadataQuery -> extractMetadata ( $data ) -> asArray ();
2018-01-26 17:46:40 -05:00
return self :: cacheEntryFromData ( $data , $this -> mimetypeLoader );
2015-12-02 08:59:13 -05:00
}, $files );
2012-09-23 09:25:03 -04:00
}
2018-01-25 17:01:03 -05:00
return [];
2012-09-23 09:25:03 -04:00
}
2012-09-16 10:52:32 -04:00
/**
2016-02-02 08:41:14 -05:00
* insert or update meta data for a file or folder
2012-09-16 10:52:32 -04:00
*
2012-09-26 11:52:02 -04:00
* @ param string $file
2012-09-16 10:52:32 -04:00
* @ param array $data
*
* @ return int file id
2015-03-11 04:33:50 -04:00
* @ throws \RuntimeException
2012-09-16 10:52:32 -04:00
*/
2012-09-26 11:52:02 -04:00
public function put ( $file , array $data ) {
if (( $id = $this -> getId ( $file )) > - 1 ) {
2019-05-07 08:53:02 -04:00
$this -> update ( $id , $data );
2012-09-16 10:52:32 -04:00
return $id ;
} else {
2016-02-02 08:41:14 -05:00
return $this -> insert ( $file , $data );
}
}
2013-05-23 14:29:46 -04:00
2016-02-02 08:41:14 -05: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 10:52:32 -04:00
2016-02-02 08:41:14 -05:00
if ( isset ( $this -> partial [ $file ])) { //add any saved partial data
$data = array_merge ( $this -> partial [ $file ], $data );
unset ( $this -> partial [ $file ]);
}
2019-10-25 10:55:53 -04:00
$requiredFields = [ 'size' , 'mtime' , 'mimetype' ];
2016-02-02 08:41:14 -05:00
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 10:52:32 -04:00
}
2016-02-02 08:41:14 -05:00
}
2012-09-16 10:52:32 -04:00
2016-02-02 08:41:14 -05:00
$data [ 'path' ] = $file ;
2020-02-14 16:08:13 -05:00
if ( ! isset ( $data [ 'parent' ])) {
$data [ 'parent' ] = $this -> getParentId ( $file );
}
2017-11-23 06:35:47 -05:00
$data [ 'name' ] = basename ( $file );
2012-09-16 10:52:32 -04:00
2019-10-25 12:21:57 -04:00
[ $values , $extensionValues ] = $this -> normalizeData ( $data );
2020-11-13 11:04:36 -05:00
$storageId = $this -> getNumericStorageId ();
$values [ 'storage' ] = $storageId ;
2018-11-12 07:43:46 -05:00
try {
$builder = $this -> connection -> getQueryBuilder ();
$builder -> insert ( 'filecache' );
foreach ( $values as $column => $value ) {
$builder -> setValue ( $column , $builder -> createNamedParameter ( $value ));
}
if ( $builder -> execute ()) {
2019-10-25 12:21:57 -04:00
$fileId = $builder -> getLastInsertId ();
2019-10-31 09:22:32 -04:00
if ( count ( $extensionValues )) {
$query = $this -> getQueryBuilder ();
$query -> insert ( 'filecache_extended' );
$query -> setValue ( 'fileid' , $query -> createNamedParameter ( $fileId , IQueryBuilder :: PARAM_INT ));
foreach ( $extensionValues as $column => $value ) {
$query -> setValue ( $column , $query -> createNamedParameter ( $value ));
}
$query -> execute ();
2019-10-25 12:21:57 -04:00
}
2020-11-26 09:39:46 -05:00
$event = new CacheEntryInsertedEvent ( $this -> storage , $file , $fileId , $storageId );
$this -> eventDispatcher -> dispatch ( CacheInsertEvent :: class , $event );
$this -> eventDispatcher -> dispatchTyped ( $event );
2019-01-22 11:08:32 -05:00
return $fileId ;
2018-11-12 07:43:46 -05:00
}
2019-01-22 11:08:32 -05:00
} catch ( UniqueConstraintViolationException $e ) {
2018-11-12 07:43:46 -05:00
// entry exists already
2020-02-28 11:36:38 -05:00
if ( $this -> connection -> inTransaction ()) {
$this -> connection -> commit ();
$this -> connection -> beginTransaction ();
}
2016-02-02 08:41:14 -05:00
}
2012-09-16 10:52:32 -04:00
2016-02-02 08:41:14 -05:00
// The file was created in the mean time
if (( $id = $this -> getId ( $file )) > - 1 ) {
$this -> update ( $id , $data );
return $id ;
} else {
2018-11-12 07:43:46 -05:00
throw new \RuntimeException ( 'File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.' );
2012-09-16 10:52:32 -04:00
}
}
/**
2015-05-05 10:06:28 -04:00
* update the metadata of an existing file or folder in the cache
2012-09-16 10:52:32 -04:00
*
2015-05-05 10:06:28 -04: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 10:52:32 -04:00
*/
2012-09-26 11:52:02 -04:00
public function update ( $id , array $data ) {
2015-01-15 11:26:12 -05:00
if ( isset ( $data [ 'path' ])) {
2013-05-25 14:35:12 -04:00
// normalize path
$data [ 'path' ] = $this -> normalize ( $data [ 'path' ]);
}
2015-01-15 11:26:12 -05:00
if ( isset ( $data [ 'name' ])) {
2013-05-25 14:35:12 -04:00
// normalize path
$data [ 'name' ] = $this -> normalize ( $data [ 'name' ]);
}
2019-10-25 12:21:57 -04:00
[ $values , $extensionValues ] = $this -> normalizeData ( $data );
2019-10-25 10:55:53 -04:00
2019-10-25 12:21:57 -04:00
if ( count ( $values )) {
$query = $this -> getQueryBuilder ();
2019-10-25 10:55:53 -04:00
2019-10-25 12:21:57 -04:00
$query -> update ( 'filecache' )
-> whereFileId ( $id )
2024-07-04 13:21:03 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 12:21:57 -04:00
-> andWhere ( $query -> expr () -> orX ( ... array_map ( function ( $key , $value ) use ( $query ) {
return $query -> expr () -> orX (
$query -> expr () -> neq ( $key , $query -> createNamedParameter ( $value )),
$query -> expr () -> isNull ( $key )
);
}, array_keys ( $values ), array_values ( $values ))));
foreach ( $values as $key => $value ) {
$query -> set ( $key , $query -> createNamedParameter ( $value ));
}
2019-10-25 10:55:53 -04:00
2019-10-25 12:21:57 -04:00
$query -> execute ();
2019-10-25 10:55:53 -04:00
}
2012-09-16 10:52:32 -04:00
2019-10-25 12:21:57 -04:00
if ( count ( $extensionValues )) {
2019-10-31 09:22:32 -04:00
try {
$query = $this -> getQueryBuilder ();
$query -> insert ( 'filecache_extended' );
2019-10-25 12:21:57 -04:00
2019-10-31 09:22:32 -04:00
$query -> setValue ( 'fileid' , $query -> createNamedParameter ( $id , IQueryBuilder :: PARAM_INT ));
foreach ( $extensionValues as $column => $value ) {
$query -> setValue ( $column , $query -> createNamedParameter ( $value ));
}
2019-10-25 12:21:57 -04:00
2019-10-31 09:22:32 -04:00
$query -> execute ();
} catch ( UniqueConstraintViolationException $e ) {
$query = $this -> getQueryBuilder ();
$query -> update ( 'filecache_extended' )
-> whereFileId ( $id )
-> andWhere ( $query -> expr () -> orX ( ... array_map ( function ( $key , $value ) use ( $query ) {
return $query -> expr () -> orX (
$query -> expr () -> neq ( $key , $query -> createNamedParameter ( $value )),
$query -> expr () -> isNull ( $key )
);
}, array_keys ( $extensionValues ), array_values ( $extensionValues ))));
foreach ( $extensionValues as $key => $value ) {
$query -> set ( $key , $query -> createNamedParameter ( $value ));
}
$query -> execute ();
}
2019-10-25 12:21:57 -04:00
}
2015-04-11 12:06:21 -04:00
2019-02-18 09:47:54 -05:00
$path = $this -> getPathById ( $id );
// path can still be null if the file doesn't exist
if ( $path !== null ) {
2020-11-26 09:39:46 -05:00
$event = new CacheEntryUpdatedEvent ( $this -> storage , $path , $id , $this -> getNumericStorageId ());
$this -> eventDispatcher -> dispatch ( CacheUpdateEvent :: class , $event );
$this -> eventDispatcher -> dispatchTyped ( $event );
2019-02-18 09:47:54 -05:00
}
2012-09-16 10:52:32 -04:00
}
/**
* extract query parts and params array from data array
*
* @ param array $data
2019-10-25 10:55:53 -04:00
* @ return array
2012-09-16 10:52:32 -04:00
*/
2019-10-25 10:55:53 -04:00
protected function normalizeData ( array $data ) : array {
$fields = [
2015-03-30 11:29:05 -04:00
'path' , 'parent' , 'name' , 'mimetype' , 'size' , 'mtime' , 'storage_mtime' , 'encrypted' ,
2022-04-13 10:05:45 -04:00
'etag' , 'permissions' , 'checksum' , 'storage' , 'unencrypted_size' ];
2019-10-25 12:21:57 -04:00
$extensionFields = [ 'metadata_etag' , 'creation_time' , 'upload_time' ];
2015-10-16 12:28:45 -04: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 ;
}
2019-10-25 10:55:53 -04:00
$params = [];
2019-10-25 12:21:57 -04:00
$extensionParams = [];
2012-09-16 10:52:32 -04:00
foreach ( $data as $name => $value ) {
2023-09-17 04:43:23 -04:00
if ( in_array ( $name , $fields )) {
2012-09-16 10:52:32 -04:00
if ( $name === 'path' ) {
2019-10-25 10:55:53 -04:00
$params [ 'path_hash' ] = md5 ( $value );
2020-04-10 04:35:09 -04:00
} elseif ( $name === 'mimetype' ) {
2019-10-25 10:55:53 -04:00
$params [ 'mimepart' ] = $this -> mimetypeLoader -> getId ( substr ( $value , 0 , strpos ( $value , '/' )));
2015-09-03 14:48:42 -04:00
$value = $this -> mimetypeLoader -> getId ( $value );
2020-04-10 04:35:09 -04:00
} elseif ( $name === 'storage_mtime' ) {
2015-10-16 12:28:45 -04:00
if ( ! $doNotCopyStorageMTime && ! isset ( $data [ 'mtime' ])) {
2019-10-25 10:55:53 -04:00
$params [ 'mtime' ] = $value ;
2013-02-10 06:27:35 -05:00
}
2020-04-10 04:35:09 -04:00
} elseif ( $name === 'encrypted' ) {
2017-02-02 12:20:08 -05:00
if ( isset ( $data [ 'encryptedVersion' ])) {
2016-02-08 14:35:33 -05:00
$value = $data [ 'encryptedVersion' ];
} else {
// Boolean to integer conversion
$value = $value ? 1 : 0 ;
}
2012-09-16 10:52:32 -04:00
}
2019-10-25 10:55:53 -04:00
$params [ $name ] = $value ;
2012-09-16 10:52:32 -04:00
}
2023-09-17 04:43:23 -04:00
if ( in_array ( $name , $extensionFields )) {
2019-10-25 12:21:57 -04:00
$extensionParams [ $name ] = $value ;
}
2012-09-16 10:52:32 -04:00
}
2019-11-13 05:28:14 -05:00
return [ $params , array_filter ( $extensionParams )];
2012-09-16 10:52:32 -04:00
}
/**
* get the file id for a file
*
2015-05-05 10:06:28 -04: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 11:52:02 -04:00
* @ param string $file
2012-09-16 10:52:32 -04:00
* @ return int
*/
2012-09-26 11:52:02 -04:00
public function getId ( $file ) {
2013-05-23 14:29:46 -04:00
// normalize file
$file = $this -> normalize ( $file );
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> select ( 'fileid' )
-> from ( 'filecache' )
2021-05-04 13:06:02 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> wherePath ( $file );
2012-09-16 10:52:32 -04:00
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
2021-01-03 09:28:31 -05:00
$id = $result -> fetchOne ();
2020-11-05 04:50:53 -05:00
$result -> closeCursor ();
2019-10-25 10:55:53 -04:00
return $id === false ? - 1 : ( int ) $id ;
2012-09-16 10:52:32 -04:00
}
/**
* get the id of the parent folder of a file
*
2012-09-26 11:52:02 -04:00
* @ param string $file
2012-09-22 09:48:39 -04:00
* @ return int
2012-09-16 10:52:32 -04:00
*/
2012-09-26 11:52:02 -04:00
public function getParentId ( $file ) {
2012-10-03 05:23:33 -04:00
if ( $file === '' ) {
2012-09-16 10:52:32 -04:00
return - 1 ;
} else {
2015-11-10 10:14:08 -05:00
$parent = $this -> getParentPath ( $file );
2015-12-02 08:59:13 -05:00
return ( int ) $this -> getId ( $parent );
2015-11-10 10:14:08 -05:00
}
}
private function getParentPath ( $path ) {
$parent = dirname ( $path );
if ( $parent === '.' ) {
$parent = '' ;
2012-09-16 10:52:32 -04:00
}
2015-11-10 10:14:08 -05:00
return $parent ;
2012-09-16 10:52:32 -04:00
}
/**
* check if a file is available in the cache
*
2012-09-26 11:52:02 -04:00
* @ param string $file
2012-09-16 10:52:32 -04:00
* @ return bool
*/
2012-09-26 11:52:02 -04:00
public function inCache ( $file ) {
return $this -> getId ( $file ) != - 1 ;
2012-09-16 10:52:32 -04:00
}
/**
* remove a file or folder from the cache
*
2015-05-05 10:06:28 -04:00
* when removing a folder from the cache all files and folders inside the folder will be removed as well
*
2012-09-26 11:52:02 -04:00
* @ param string $file
2012-09-16 10:52:32 -04:00
*/
2012-09-26 11:52:02 -04:00
public function remove ( $file ) {
2012-10-27 12:05:40 -04:00
$entry = $this -> get ( $file );
2019-10-25 10:55:53 -04:00
2022-06-28 10:27:09 -04:00
if ( $entry instanceof ICacheEntry ) {
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> delete ( 'filecache' )
2024-07-04 13:21:03 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> whereFileId ( $entry -> getId ());
$query -> execute ();
2019-10-25 12:21:57 -04:00
$query = $this -> getQueryBuilder ();
$query -> delete ( 'filecache_extended' )
-> whereFileId ( $entry -> getId ());
$query -> execute ();
2019-10-25 10:55:53 -04:00
if ( $entry -> getMimeType () == FileInfo :: MIMETYPE_FOLDER ) {
$this -> removeChildren ( $entry );
}
2020-11-13 11:04:36 -05:00
2020-12-02 10:15:02 -05:00
$this -> eventDispatcher -> dispatchTyped ( new CacheEntryRemovedEvent ( $this -> storage , $entry -> getPath (), $entry -> getId (), $this -> getNumericStorageId ()));
2012-10-27 12:05:40 -04:00
}
2015-01-15 11:26:12 -05:00
}
2014-10-30 05:51:25 -04:00
2015-05-05 10:06:28 -04:00
/**
2022-10-24 10:17:27 -04:00
* Remove all children of a folder
2015-05-05 10:06:28 -04:00
*
2019-10-25 10:55:53 -04:00
* @ param ICacheEntry $entry the cache entry of the folder to remove the children of
2015-05-05 10:06:28 -04:00
* @ throws \OC\DatabaseException
*/
2019-10-25 10:55:53 -04:00
private function removeChildren ( ICacheEntry $entry ) {
2020-04-29 10:07:51 -04:00
$parentIds = [ $entry -> getId ()];
$queue = [ $entry -> getId ()];
2022-10-24 10:17:27 -04:00
$deletedIds = [];
$deletedPaths = [];
2020-04-29 10:07:51 -04:00
2022-07-27 08:51:42 -04:00
// we walk depth first through the file tree, removing all filecache_extended attributes while we walk
2020-04-29 10:07:51 -04:00
// and collecting all folder ids to later use to delete the filecache entries
while ( $entryId = array_pop ( $queue )) {
$children = $this -> getFolderContentsById ( $entryId );
$childIds = array_map ( function ( ICacheEntry $cacheEntry ) {
return $cacheEntry -> getId ();
}, $children );
2022-10-24 10:17:27 -04:00
$childPaths = array_map ( function ( ICacheEntry $cacheEntry ) {
return $cacheEntry -> getPath ();
}, $children );
2024-03-04 12:13:45 -05:00
foreach ( $childIds as $childId ) {
$deletedIds [] = $childId ;
}
foreach ( $childPaths as $childPath ) {
$deletedPaths [] = $childPath ;
}
2020-04-29 10:07:51 -04:00
$query = $this -> getQueryBuilder ();
$query -> delete ( 'filecache_extended' )
2021-10-17 14:52:38 -04:00
-> where ( $query -> expr () -> in ( 'fileid' , $query -> createParameter ( 'childIds' )));
2022-03-17 12:26:27 -04:00
2021-10-17 14:52:38 -04:00
foreach ( array_chunk ( $childIds , 1000 ) as $childIdChunk ) {
$query -> setParameter ( 'childIds' , $childIdChunk , IQueryBuilder :: PARAM_INT_ARRAY );
2021-10-17 13:08:27 -04:00
$query -> execute ();
}
2020-04-29 10:07:51 -04:00
/** @var ICacheEntry[] $childFolders */
2023-11-03 11:31:02 -04:00
$childFolders = [];
foreach ( $children as $child ) {
if ( $child -> getMimeType () == FileInfo :: MIMETYPE_FOLDER ) {
$childFolders [] = $child ;
}
}
2020-04-29 10:07:51 -04:00
foreach ( $childFolders as $folder ) {
$parentIds [] = $folder -> getId ();
$queue [] = $folder -> getId ();
}
2015-01-15 11:26:12 -05:00
}
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> delete ( 'filecache' )
2024-07-04 13:21:03 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2021-10-17 14:52:38 -04:00
-> whereParentInParameter ( 'parentIds' );
2024-04-26 09:38:29 -04:00
// Sorting before chunking allows the db to find the entries close to each
// other in the index
sort ( $parentIds , SORT_NUMERIC );
2021-10-17 14:52:38 -04:00
foreach ( array_chunk ( $parentIds , 1000 ) as $parentIdChunk ) {
$query -> setParameter ( 'parentIds' , $parentIdChunk , IQueryBuilder :: PARAM_INT_ARRAY );
2021-10-17 13:08:27 -04:00
$query -> execute ();
}
2022-10-24 10:17:27 -04:00
foreach ( array_combine ( $deletedIds , $deletedPaths ) as $fileId => $filePath ) {
$cacheEntryRemovedEvent = new CacheEntryRemovedEvent (
$this -> storage ,
$filePath ,
$fileId ,
$this -> getNumericStorageId ()
);
$this -> eventDispatcher -> dispatchTyped ( $cacheEntryRemovedEvent );
2021-10-17 13:08:27 -04:00
}
2012-09-16 10:52:32 -04:00
}
2012-11-02 17: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 09:15:24 -04:00
$this -> moveFromCache ( $this , $source , $target );
2012-11-02 17:25:33 -04:00
}
2015-04-13 11:09:18 -04: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 ];
}
2022-12-28 11:17:45 -05:00
protected function hasEncryptionWrapper () : bool {
return $this -> storage -> instanceOfStorage ( Encryption :: class );
}
2015-04-01 09:12:59 -04:00
/**
* Move a file or folder in the cache
*
2021-03-08 12:49:08 -05:00
* @ param ICache $sourceCache
2015-04-01 09:12:59 -04:00
* @ param string $sourcePath
* @ param string $targetPath
* @ throws \OC\DatabaseException
2017-02-16 05:47:16 -05:00
* @ throws \Exception if the given storages have an invalid id
2015-04-01 09:12:59 -04:00
*/
2015-12-02 08:59:13 -05:00
public function moveFromCache ( ICache $sourceCache , $sourcePath , $targetPath ) {
2016-01-27 09:45:19 -05:00
if ( $sourceCache instanceof Cache ) {
// normalize source and target
$sourcePath = $this -> normalize ( $sourcePath );
$targetPath = $this -> normalize ( $targetPath );
$sourceData = $sourceCache -> get ( $sourcePath );
2023-08-05 06:05:36 -04:00
if ( ! $sourceData ) {
2021-03-24 05:36:51 -04:00
throw new \Exception ( 'Invalid source storage path: ' . $sourcePath );
}
2016-01-27 09:45:19 -05:00
$sourceId = $sourceData [ 'fileid' ];
$newParentId = $this -> getParentId ( $targetPath );
2020-02-28 11:36:38 -05:00
[ $sourceStorageId , $sourcePath ] = $sourceCache -> getMoveInfo ( $sourcePath );
[ $targetStorageId , $targetPath ] = $this -> getMoveInfo ( $targetPath );
2016-01-27 09:45:19 -05:00
2017-02-16 05:47:16 -05: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 );
}
2016-01-27 09:45:19 -05:00
if ( $sourceData [ 'mimetype' ] === 'httpd/unix-directory' ) {
2017-01-16 10:20:53 -05:00
//update all child entries
2017-07-20 10:29:50 -04:00
$sourceLength = mb_strlen ( $sourcePath );
2024-02-12 09:52:07 -05:00
$childIds = $this -> getChildIds ( $sourceStorageId , $sourcePath );
$childChunks = array_chunk ( $childIds , 1000 );
2017-01-16 10:20:53 -05:00
$query = $this -> connection -> getQueryBuilder ();
2017-03-26 14:45:49 -04:00
$fun = $query -> func ();
2017-01-16 10:20:53 -05: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 )))
2024-02-12 09:52:07 -05:00
-> andWhere ( $query -> expr () -> in ( 'fileid' , $query -> createParameter ( 'files' )));
2017-01-16 10:20:53 -05:00
2022-12-28 11:17:45 -05:00
// when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark
if ( $sourceCache -> hasEncryptionWrapper () && ! $this -> hasEncryptionWrapper ()) {
$query -> set ( 'encrypted' , $query -> createNamedParameter ( 0 , IQueryBuilder :: PARAM_INT ));
}
2024-03-27 11:13:51 -04:00
// Retry transaction in case of RetryableException like deadlocks.
// Retry up to 4 times because we should receive up to 4 concurrent requests from the frontend
$retryLimit = 4 ;
for ( $i = 1 ; $i <= $retryLimit ; $i ++ ) {
try {
$this -> connection -> beginTransaction ();
2024-02-12 09:52:07 -05:00
foreach ( $childChunks as $chunk ) {
$query -> setParameter ( 'files' , $chunk , IQueryBuilder :: PARAM_INT_ARRAY );
$query -> executeStatement ();
}
2024-03-27 11:13:51 -04:00
break ;
} catch ( \OC\DatabaseException $e ) {
$this -> connection -> rollBack ();
throw $e ;
2024-06-10 12:01:39 -04:00
} catch ( DbalException $e ) {
2024-02-12 09:52:07 -05:00
$this -> connection -> rollBack ();
2024-06-10 12:01:39 -04:00
if ( ! $e -> isRetryable ()) {
throw $e ;
}
2024-03-27 11:13:51 -04:00
// Simply throw if we already retried 4 times.
if ( $i === $retryLimit ) {
throw $e ;
}
// Sleep a bit to give some time to the other transaction to finish.
usleep ( 100 * 1000 * $i );
}
2016-01-27 09:45:19 -05:00
}
2024-03-27 11:13:51 -04:00
} else {
$this -> connection -> beginTransaction ();
2015-04-01 09:12:59 -04:00
}
2017-01-16 10:20:53 -05:00
2019-10-25 12:21:57 -04:00
$query = $this -> getQueryBuilder ();
2019-10-25 10:55:53 -04:00
$query -> update ( 'filecache' )
-> set ( 'storage' , $query -> createNamedParameter ( $targetStorageId ))
-> set ( 'path' , $query -> createNamedParameter ( $targetPath ))
-> set ( 'path_hash' , $query -> createNamedParameter ( md5 ( $targetPath )))
-> set ( 'name' , $query -> createNamedParameter ( basename ( $targetPath )))
-> set ( 'parent' , $query -> createNamedParameter ( $newParentId , IQueryBuilder :: PARAM_INT ))
2019-10-25 12:21:57 -04:00
-> whereFileId ( $sourceId );
2022-12-28 11:17:45 -05:00
// when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark
if ( $sourceCache -> hasEncryptionWrapper () && ! $this -> hasEncryptionWrapper ()) {
$query -> set ( 'encrypted' , $query -> createNamedParameter ( 0 , IQueryBuilder :: PARAM_INT ));
}
2019-10-25 10:55:53 -04:00
$query -> execute ();
2019-10-25 12:21:57 -04:00
2017-01-16 10:20:53 -05:00
$this -> connection -> commit ();
2020-11-13 11:04:36 -05:00
if ( $sourceCache -> getNumericStorageId () !== $this -> getNumericStorageId ()) {
2020-11-26 09:39:46 -05:00
$this -> eventDispatcher -> dispatchTyped ( new CacheEntryRemovedEvent ( $this -> storage , $sourcePath , $sourceId , $sourceCache -> getNumericStorageId ()));
$event = new CacheEntryInsertedEvent ( $this -> storage , $targetPath , $sourceId , $this -> getNumericStorageId ());
$this -> eventDispatcher -> dispatch ( CacheInsertEvent :: class , $event );
$this -> eventDispatcher -> dispatchTyped ( $event );
2020-11-13 11:04:36 -05:00
} else {
2020-11-26 09:39:46 -05:00
$event = new CacheEntryUpdatedEvent ( $this -> storage , $targetPath , $sourceId , $this -> getNumericStorageId ());
$this -> eventDispatcher -> dispatch ( CacheUpdateEvent :: class , $event );
$this -> eventDispatcher -> dispatchTyped ( $event );
2020-11-13 11:04:36 -05:00
}
2015-10-26 11:31:26 -04:00
} else {
2016-01-27 09:45:19 -05:00
$this -> moveFromCacheFallback ( $sourceCache , $sourcePath , $targetPath );
2015-04-01 09:12:59 -04:00
}
}
2024-02-12 09:52:07 -05:00
private function getChildIds ( int $storageId , string $path ) : array {
$query = $this -> connection -> getQueryBuilder ();
$query -> select ( 'fileid' )
-> from ( 'filecache' )
-> where ( $query -> expr () -> eq ( 'storage' , $query -> createNamedParameter ( $storageId , IQueryBuilder :: PARAM_INT )))
-> andWhere ( $query -> expr () -> like ( 'path' , $query -> createNamedParameter ( $this -> connection -> escapeLikeParameter ( $path ) . '/%' )));
return $query -> executeQuery () -> fetchAll ( \PDO :: FETCH_COLUMN );
}
2012-09-16 10:52:32 -04:00
/**
2012-09-26 11:52:02 -04:00
* remove all entries for files that are stored on the storage from the cache
2012-09-16 10:52:32 -04:00
*/
2012-09-26 11:52:02 -04:00
public function clear () {
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> delete ( 'filecache' )
2021-05-04 13:06:02 -04:00
-> whereStorageId ( $this -> getNumericStorageId ());
2019-10-25 10:55:53 -04:00
$query -> execute ();
2012-12-15 17:28:07 -05:00
2019-10-25 10:55:53 -04:00
$query = $this -> connection -> getQueryBuilder ();
$query -> delete ( 'storages' )
-> where ( $query -> expr () -> eq ( 'id' , $query -> createNamedParameter ( $this -> storageId )));
$query -> execute ();
2012-09-16 10:52:32 -04:00
}
2012-10-08 08:58:21 -04:00
/**
2015-05-05 10:06:28 -04: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 08:58:21 -04:00
* @ param string $file
*
2015-01-16 13:31:15 -05:00
* @ return int Cache :: NOT_FOUND , Cache :: PARTIAL , Cache :: SHALLOW or Cache :: COMPLETE
2012-10-08 08:58:21 -04:00
*/
public function getStatus ( $file ) {
2013-05-25 08:56:00 -04:00
// normalize file
$file = $this -> normalize ( $file );
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> select ( 'size' )
-> from ( 'filecache' )
2021-05-04 13:06:02 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> wherePath ( $file );
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
2021-01-03 09:28:31 -05:00
$size = $result -> fetchOne ();
2020-11-05 04:50:53 -05:00
$result -> closeCursor ();
2019-10-25 10:55:53 -04:00
if ( $size !== false ) {
if (( int ) $size === - 1 ) {
2012-10-08 08:58:21 -04:00
return self :: SHALLOW ;
} else {
return self :: COMPLETE ;
}
} else {
if ( isset ( $this -> partial [ $file ])) {
return self :: PARTIAL ;
} else {
return self :: NOT_FOUND ;
}
}
}
2012-10-26 07:23:15 -04:00
/**
* search for files matching $pattern
*
2015-05-05 10:06:28 -04:00
* @ param string $pattern the search pattern using SQL search syntax ( e . g . '%searchstring%' )
2015-12-02 08:59:13 -05:00
* @ return ICacheEntry [] an array of cache entries where the name matches the search pattern
2012-10-26 07:23:15 -04:00
*/
public function search ( $pattern ) {
2021-05-04 13:06:02 -04:00
$operator = new SearchComparison ( ISearchComparison :: COMPARE_LIKE , 'name' , $pattern );
return $this -> searchQuery ( new SearchQuery ( $operator , 0 , 0 , [], null ));
2012-10-26 07:23:15 -04:00
}
2012-10-27 04:01:20 -04:00
2012-10-27 04:34:25 -04:00
/**
* search for files by mimetype
*
2015-05-05 10:06:28 -04:00
* @ param string $mimetype either a full mimetype to search ( 'text/plain' ) or only the first part of a mimetype ( 'image' )
2015-11-05 10:25:02 -05:00
* where it will search for all mimetypes in the group ( 'image/*' )
2015-12-02 08:59:13 -05:00
* @ return ICacheEntry [] an array of cache entries where the mimetype matches the search
2012-10-27 04:34:25 -04:00
*/
public function searchByMime ( $mimetype ) {
2023-05-15 07:47:19 -04:00
if ( ! str_contains ( $mimetype , '/' )) {
2021-05-04 13:06:02 -04:00
$operator = new SearchComparison ( ISearchComparison :: COMPARE_LIKE , 'mimetype' , $mimetype . '/%' );
2012-10-27 04:34:25 -04:00
} else {
2021-05-04 13:06:02 -04:00
$operator = new SearchComparison ( ISearchComparison :: COMPARE_EQUAL , 'mimetype' , $mimetype );
2012-10-27 04:34:25 -04:00
}
2021-05-04 13:06:02 -04:00
return $this -> searchQuery ( new SearchQuery ( $operator , 0 , 0 , [], null ));
2017-02-02 12:20:08 -05:00
}
2017-02-22 20:03:32 -05:00
2017-02-02 12:20:08 -05:00
public function searchQuery ( ISearchQuery $searchQuery ) {
2021-05-05 13:36:41 -04:00
return current ( $this -> querySearchHelper -> searchInCaches ( $searchQuery , [ $this ]));
2012-10-27 04:34:25 -04:00
}
2012-11-08 12:10:54 -05:00
/**
2015-05-05 10:06:28 -04:00
* Re - calculate the folder size and the size of all parent folders
2012-11-08 12:10:54 -05:00
*
2014-02-06 10:30:58 -05:00
* @ param string | boolean $path
2014-02-28 08:23:07 -05:00
* @ param array $data ( optional ) meta data of the folder
2012-11-08 12:10:54 -05:00
*/
2019-03-01 17:52:58 -05:00
public function correctFolderSize ( $path , $data = null , $isBackgroundScan = false ) {
2014-02-28 08:23:07 -05:00
$this -> calculateFolderSize ( $path , $data );
2012-11-08 12:10:54 -05:00
if ( $path !== '' ) {
$parent = dirname ( $path );
2021-12-03 10:05:19 -05:00
if ( $parent === '.' || $parent === '/' ) {
2012-11-08 12:10:54 -05:00
$parent = '' ;
}
2019-03-01 17:52:58 -05:00
if ( $isBackgroundScan ) {
$parentData = $this -> get ( $parent );
if ( $parentData [ 'size' ] !== - 1 && $this -> getIncompleteChildrenCount ( $parentData [ 'fileid' ]) === 0 ) {
$this -> correctFolderSize ( $parent , $parentData , $isBackgroundScan );
}
} else {
$this -> correctFolderSize ( $parent );
}
}
}
/**
* get the incomplete count that shares parent $folder
*
* @ param int $fileId the file id of the folder
* @ return int
*/
public function getIncompleteChildrenCount ( $fileId ) {
if ( $fileId > - 1 ) {
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> select ( $query -> func () -> count ())
-> from ( 'filecache' )
-> whereParent ( $fileId )
-> andWhere ( $query -> expr () -> lt ( 'size' , $query -> createNamedParameter ( 0 , IQueryBuilder :: PARAM_INT )));
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
2021-01-03 09:28:31 -05:00
$size = ( int ) $result -> fetchOne ();
2020-11-05 04:50:53 -05:00
$result -> closeCursor ();
return $size ;
2012-11-08 12:10:54 -05:00
}
2019-03-01 17:52:58 -05:00
return - 1 ;
2012-11-08 12:10:54 -05:00
}
2012-10-27 11:02:05 -04:00
/**
2015-05-05 10:06:28 -04:00
* calculate the size of a folder and set it in the cache
2012-10-27 11:02:05 -04:00
*
* @ param string $path
2023-03-22 14:35:05 -04:00
* @ param array | null | ICacheEntry $entry ( optional ) meta data of the folder
2023-05-15 06:24:42 -04:00
* @ return int | float
2012-10-27 11:02:05 -04:00
*/
2014-02-28 08:23:07 -05:00
public function calculateFolderSize ( $path , $entry = null ) {
2023-03-09 09:25:46 -05:00
return $this -> calculateFolderSizeInner ( $path , $entry );
}
/**
* inner function because we can ' t add new params to the public function without breaking any child classes
*
* @ param string $path
2023-03-22 14:35:05 -04:00
* @ param array | null | ICacheEntry $entry ( optional ) meta data of the folder
2023-03-09 09:25:46 -05:00
* @ param bool $ignoreUnknown don 't mark the folder size as unknown if any of it' s children are unknown
2023-05-11 06:46:16 -04:00
* @ return int | float
2023-03-09 09:25:46 -05:00
*/
2023-03-22 14:35:05 -04:00
protected function calculateFolderSizeInner ( string $path , $entry = null , bool $ignoreUnknown = false ) {
2012-10-27 11:02:05 -04:00
$totalSize = 0 ;
2021-12-03 10:05:19 -05:00
if ( is_null ( $entry ) || ! isset ( $entry [ 'fileid' ])) {
2014-02-28 08:23:07 -05:00
$entry = $this -> get ( $path );
}
2019-10-25 10:55:53 -04:00
if ( isset ( $entry [ 'mimetype' ]) && $entry [ 'mimetype' ] === FileInfo :: MIMETYPE_FOLDER ) {
2013-07-28 16:14:49 -04:00
$id = $entry [ 'fileid' ];
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
2022-04-13 10:05:45 -04:00
$query -> select ( 'size' , 'unencrypted_size' )
2019-10-25 10:55:53 -04:00
-> from ( 'filecache' )
2024-07-04 13:21:03 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> whereParent ( $id );
2023-03-09 09:25:46 -05:00
if ( $ignoreUnknown ) {
$query -> andWhere ( $query -> expr () -> gte ( 'size' , $query -> createNamedParameter ( 0 )));
}
2019-10-25 10:55:53 -04:00
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
2022-04-13 10:05:45 -04:00
$rows = $result -> fetchAll ();
2020-11-05 04:50:53 -05:00
$result -> closeCursor ();
2022-04-13 10:05:45 -04:00
if ( $rows ) {
$sizes = array_map ( function ( array $row ) {
2023-05-11 06:46:16 -04:00
return Util :: numericToNumber ( $row [ 'size' ]);
2022-04-13 10:05:45 -04:00
}, $rows );
$unencryptedOnlySizes = array_map ( function ( array $row ) {
2023-05-11 06:46:16 -04:00
return Util :: numericToNumber ( $row [ 'unencrypted_size' ]);
2022-04-13 10:05:45 -04:00
}, $rows );
$unencryptedSizes = array_map ( function ( array $row ) {
2023-05-11 06:46:16 -04:00
return Util :: numericToNumber (( $row [ 'unencrypted_size' ] > 0 ) ? $row [ 'unencrypted_size' ] : $row [ 'size' ]);
2022-04-13 10:05:45 -04:00
}, $rows );
$sum = array_sum ( $sizes );
$min = min ( $sizes );
$unencryptedSum = array_sum ( $unencryptedSizes );
$unencryptedMin = min ( $unencryptedSizes );
$unencryptedMax = max ( $unencryptedOnlySizes );
2014-02-15 18:50:03 -05:00
$sum = 0 + $sum ;
$min = 0 + $min ;
2013-07-29 10:22:44 -04:00
if ( $min === - 1 ) {
$totalSize = $min ;
2013-07-28 16:14:49 -04:00
} else {
2013-07-29 10:22:44 -04:00
$totalSize = $sum ;
2013-07-28 16:14:49 -04:00
}
2022-04-13 10:05:45 -04:00
if ( $unencryptedMin === - 1 || $min === - 1 ) {
$unencryptedTotal = $unencryptedMin ;
} else {
$unencryptedTotal = $unencryptedSum ;
}
2022-08-16 05:16:14 -04:00
} else {
$totalSize = 0 ;
$unencryptedTotal = 0 ;
$unencryptedMax = 0 ;
}
2023-02-24 11:38:25 -05:00
// only set unencrypted size for a folder if any child entries have it set, or the folder is empty
2023-03-09 09:25:46 -05:00
$shouldWriteUnEncryptedSize = $unencryptedMax > 0 || $totalSize === 0 || $entry [ 'unencrypted_size' ] > 0 ;
2023-02-24 11:38:25 -05:00
if ( $entry [ 'size' ] !== $totalSize || ( $entry [ 'unencrypted_size' ] !== $unencryptedTotal && $shouldWriteUnEncryptedSize )) {
if ( $shouldWriteUnEncryptedSize ) {
2023-03-09 09:25:46 -05:00
// if all children have an unencrypted size of 0, just set the folder unencrypted size to 0 instead of summing the sizes
if ( $unencryptedMax === 0 ) {
$unencryptedTotal = 0 ;
}
2022-08-16 05:16:14 -04:00
$this -> update ( $id , [
'size' => $totalSize ,
'unencrypted_size' => $unencryptedTotal ,
]);
} else {
$this -> update ( $id , [
'size' => $totalSize ,
]);
2014-01-09 11:27:55 -05:00
}
2012-10-27 11:02:05 -04:00
}
}
return $totalSize ;
}
2012-10-27 04:01:20 -04:00
/**
* get all file ids on the files on the storage
*
* @ return int []
*/
public function getAll () {
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> select ( 'fileid' )
-> from ( 'filecache' )
2021-05-04 13:06:02 -04:00
-> whereStorageId ( $this -> getNumericStorageId ());
2019-10-25 10:55:53 -04:00
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
$files = $result -> fetchAll ( \PDO :: FETCH_COLUMN );
$result -> closeCursor ();
2019-10-25 10:55:53 -04:00
return array_map ( function ( $id ) {
return ( int ) $id ;
2020-11-05 04:50:53 -05:00
}, $files );
2012-10-27 04:01:20 -04:00
}
2012-11-21 17:02:43 -05:00
/**
* find a folder in the cache which has not been fully scanned
*
2014-11-10 10:00:08 -05:00
* If multiple incomplete folders are in the cache , the one with the highest id will be returned ,
2012-11-21 17:02:43 -05: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
*
2022-08-01 09:07:53 -04:00
* @ return string | false the path of the folder or false when no folder matched
2012-11-21 17:02:43 -05:00
*/
2012-11-22 18:17:18 -05:00
public function getIncomplete () {
2023-06-08 11:14:18 -04:00
// we select the fileid here first instead of directly selecting the path since this helps mariadb/mysql
// to use the correct index.
// The overhead of this should be minimal since the cost of selecting the path by id should be much lower
// than the cost of finding an item with size < 0
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
2023-06-08 11:14:18 -04:00
$query -> select ( 'fileid' )
2019-10-25 10:55:53 -04:00
-> from ( 'filecache' )
2021-05-04 13:06:02 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> andWhere ( $query -> expr () -> lt ( 'size' , $query -> createNamedParameter ( 0 , IQueryBuilder :: PARAM_INT )))
2020-12-15 15:42:26 -05:00
-> orderBy ( 'fileid' , 'DESC' )
-> setMaxResults ( 1 );
2019-10-25 10:55:53 -04:00
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
2023-06-08 11:14:18 -04:00
$id = $result -> fetchOne ();
2020-11-05 04:50:53 -05:00
$result -> closeCursor ();
2023-06-08 11:14:18 -04:00
if ( $id === false ) {
2023-02-02 05:32:57 -05:00
return false ;
}
2023-06-08 11:14:18 -04:00
$path = $this -> getPathById ( $id );
return $path ? ? false ;
2012-11-21 17:02:43 -05:00
}
2013-01-26 17:59:29 -05:00
2014-03-27 11:43:34 -04:00
/**
2015-05-05 10:06:28 -04:00
* get the path of a file on this storage by it ' s file id
2014-03-27 11:43:34 -04:00
*
2015-05-05 10:06:28 -04: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 11:43:34 -04:00
*/
public function getPathById ( $id ) {
2019-10-25 10:55:53 -04:00
$query = $this -> getQueryBuilder ();
$query -> select ( 'path' )
-> from ( 'filecache' )
2021-05-04 13:06:02 -04:00
-> whereStorageId ( $this -> getNumericStorageId ())
2019-10-25 10:55:53 -04:00
-> whereFileId ( $id );
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
2021-01-03 09:28:31 -05:00
$path = $result -> fetchOne ();
2020-11-05 04:50:53 -05:00
$result -> closeCursor ();
2020-11-09 10:26:09 -05:00
if ( $path === false ) {
return null ;
}
2021-03-18 12:16:28 -04:00
return ( string ) $path ;
2014-03-27 11:43:34 -04:00
}
2013-01-26 17:59:29 -05:00
/**
* get the storage id of the storage for a file and the internal path of the file
2014-03-31 08: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-26 17:59:29 -05:00
*
2013-04-25 18:00:18 -04:00
* @ param int $id
2015-01-16 13:31:15 -05:00
* @ return array first element holding the storage id , second the path
2019-10-25 10:55:53 -04:00
* @ deprecated use getPathById () instead
2013-01-26 17:59:29 -05:00
*/
2020-04-10 10:51:06 -04:00
public static function getById ( $id ) {
2019-10-25 10:55:53 -04:00
$query = \OC :: $server -> getDatabaseConnection () -> getQueryBuilder ();
$query -> select ( 'path' , 'storage' )
-> from ( 'filecache' )
-> where ( $query -> expr () -> eq ( 'fileid' , $query -> createNamedParameter ( $id , IQueryBuilder :: PARAM_INT )));
2020-11-05 04:50:53 -05:00
$result = $query -> execute ();
$row = $result -> fetch ();
$result -> closeCursor ();
if ( $row ) {
2013-01-26 17:59:29 -05:00
$numericId = $row [ 'storage' ];
$path = $row [ 'path' ];
} else {
return null ;
}
2013-04-25 18:00:18 -04:00
if ( $id = Storage :: getStorageId ( $numericId )) {
2019-10-25 10:55:53 -04:00
return [ $id , $path ];
2013-01-26 17:59:29 -05:00
} else {
return null ;
}
}
2013-05-23 14:29:46 -04:00
/**
* normalize the given path
2015-01-15 11:26:12 -05:00
*
2014-05-11 16:51:30 -04:00
* @ param string $path
2013-05-23 14:29:46 -04:00
* @ return string
*/
public function normalize ( $path ) {
2015-01-08 13:43:02 -05:00
return trim ( \OC_Util :: normalizeUnicode ( $path ), '/' );
2013-05-23 14:29:46 -04:00
}
2021-03-08 12:49:08 -05:00
/**
* Copy a file or folder in the cache
*
* @ param ICache $sourceCache
* @ param ICacheEntry $sourceEntry
* @ param string $targetPath
2021-10-07 05:50:33 -04:00
* @ return int fileId of copied entry
2021-03-08 12:49:08 -05:00
*/
public function copyFromCache ( ICache $sourceCache , ICacheEntry $sourceEntry , string $targetPath ) : int {
2021-03-12 11:23:02 -05:00
if ( $sourceEntry -> getId () < 0 ) {
throw new \RuntimeException ( " Invalid source cache entry on copyFromCache " );
}
2021-03-08 12:49:08 -05:00
$data = $this -> cacheEntryToArray ( $sourceEntry );
2022-12-28 11:17:45 -05:00
// when moving from an encrypted storage to a non-encrypted storage remove the `encrypted` mark
if ( $sourceCache instanceof Cache && $sourceCache -> hasEncryptionWrapper () && ! $this -> hasEncryptionWrapper ()) {
$data [ 'encrypted' ] = 0 ;
}
2021-03-08 12:49:08 -05:00
$fileId = $this -> put ( $targetPath , $data );
if ( $fileId <= 0 ) {
throw new \RuntimeException ( " Failed to copy to " . $targetPath . " from cache with source data " . json_encode ( $data ) . " " );
}
if ( $sourceEntry -> getMimeType () === ICacheEntry :: DIRECTORY_MIMETYPE ) {
$folderContent = $sourceCache -> getFolderContentsById ( $sourceEntry -> getId ());
foreach ( $folderContent as $subEntry ) {
$subTargetPath = $targetPath . '/' . $subEntry -> getName ();
$this -> copyFromCache ( $sourceCache , $subEntry , $subTargetPath );
}
}
return $fileId ;
}
private function cacheEntryToArray ( ICacheEntry $entry ) : array {
return [
'size' => $entry -> getSize (),
'mtime' => $entry -> getMTime (),
'storage_mtime' => $entry -> getStorageMTime (),
'mimetype' => $entry -> getMimeType (),
'mimepart' => $entry -> getMimePart (),
'etag' => $entry -> getEtag (),
'permissions' => $entry -> getPermissions (),
'encrypted' => $entry -> isEncrypted (),
'creation_time' => $entry -> getCreationTime (),
'upload_time' => $entry -> getUploadTime (),
'metadata_etag' => $entry -> getMetadataEtag (),
];
}
2021-05-04 13:06:02 -04:00
2021-05-05 12:09:53 -04:00
public function getQueryFilterForStorage () : ISearchOperator {
return new SearchComparison ( ISearchComparison :: COMPARE_EQUAL , 'storage' , $this -> getNumericStorageId ());
2021-05-04 13:06:02 -04:00
}
public function getCacheEntryFromSearchResult ( ICacheEntry $rawEntry ) : ? ICacheEntry {
if ( $rawEntry -> getStorageId () === $this -> getNumericStorageId ()) {
return $rawEntry ;
} else {
return null ;
}
}
2019-05-07 08:53:02 -04:00
}