2014-06-11 07:57:24 -04:00
< ? php
2024-05-23 03:26:56 -04:00
2014-06-11 07:57:24 -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
2014-06-11 07:57:24 -04:00
*/
namespace OC\Files\ObjectStore ;
2021-05-06 12:26:42 -04:00
use Aws\S3\Exception\S3Exception ;
use Aws\S3\Exception\S3MultipartUploadException ;
2017-01-03 11:26:44 -05:00
use Icewind\Streams\CallbackWrapper ;
2019-02-15 07:24:58 -05:00
use Icewind\Streams\CountWrapper ;
2015-03-10 11:30:13 -04:00
use Icewind\Streams\IteratorDirectory ;
2021-03-08 12:50:33 -05:00
use OC\Files\Cache\Cache ;
2015-12-02 09:38:17 -05:00
use OC\Files\Cache\CacheEntry ;
2020-11-05 10:30:05 -05:00
use OC\Files\Storage\PolyFill\CopyDirectory ;
2023-05-26 08:51:05 -04:00
use OCP\Files\Cache\ICache ;
2020-11-05 10:30:05 -05:00
use OCP\Files\Cache\ICacheEntry ;
use OCP\Files\FileInfo ;
2021-05-06 12:26:42 -04:00
use OCP\Files\GenericFileException ;
2018-11-16 14:21:21 -05:00
use OCP\Files\NotFoundException ;
2014-06-17 16:06:56 -04:00
use OCP\Files\ObjectStore\IObjectStore ;
2021-05-06 12:26:42 -04:00
use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload ;
use OCP\Files\Storage\IChunkedFileWrite ;
2021-02-19 09:52:58 -05:00
use OCP\Files\Storage\IStorage ;
2024-02-08 09:47:39 -05:00
use Psr\Log\LoggerInterface ;
2014-06-11 07:57:24 -04:00
2021-05-06 12:26:42 -04:00
class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite {
2020-11-05 10:30:05 -05:00
use CopyDirectory ;
2023-10-20 03:09:50 -04:00
protected IObjectStore $objectStore ;
protected string $id ;
private string $objectPrefix = 'urn:oid:' ;
2016-10-27 08:15:59 -04:00
2024-02-08 09:47:39 -05:00
private LoggerInterface $logger ;
2017-06-19 07:52:51 -04:00
2023-11-17 02:43:22 -05:00
private bool $handleCopiesAsOwned ;
2023-10-20 03:08:08 -04:00
protected bool $validateWrites = true ;
2023-11-17 02:43:22 -05:00
2023-10-20 03:08:08 -04:00
/**
* @ param array $params
* @ throws \Exception
*/
2014-06-13 09:05:54 -04:00
public function __construct ( $params ) {
2014-06-17 16:06:56 -04:00
if ( isset ( $params [ 'objectstore' ]) && $params [ 'objectstore' ] instanceof IObjectStore ) {
$this -> objectStore = $params [ 'objectstore' ];
} else {
throw new \Exception ( 'missing IObjectStore instance' );
}
2014-06-18 09:20:26 -04:00
if ( isset ( $params [ 'storageid' ])) {
2014-06-20 06:27:47 -04:00
$this -> id = 'object::store:' . $params [ 'storageid' ];
2014-06-18 09:20:26 -04:00
} else {
2014-06-20 06:27:47 -04:00
$this -> id = 'object::store:' . $this -> objectStore -> getStorageId ();
2014-06-18 09:20:26 -04:00
}
2016-10-27 08:15:59 -04:00
if ( isset ( $params [ 'objectPrefix' ])) {
$this -> objectPrefix = $params [ 'objectPrefix' ];
}
2022-08-04 06:32:20 -04:00
if ( isset ( $params [ 'validateWrites' ])) {
$this -> validateWrites = ( bool ) $params [ 'validateWrites' ];
}
2023-11-17 02:43:22 -05:00
$this -> handleCopiesAsOwned = ( bool )( $params [ 'handleCopiesAsOwned' ] ? ? false );
2017-06-19 07:52:51 -04:00
2024-02-08 09:47:39 -05:00
$this -> logger = \OCP\Server :: get ( LoggerInterface :: class );
2014-06-13 09:05:54 -04:00
}
2023-05-23 08:28:37 -04:00
public function mkdir ( $path , bool $force = false ) {
2014-06-11 07:57:24 -04:00
$path = $this -> normalizePath ( $path );
2023-05-23 08:28:37 -04:00
if ( ! $force && $this -> file_exists ( $path )) {
2022-05-09 10:56:40 -04:00
$this -> logger -> warning ( " Tried to create an object store folder that already exists: $path " );
2014-06-11 07:57:24 -04:00
return false ;
}
2014-06-12 06:18:19 -04:00
$mTime = time ();
2015-10-02 03:59:58 -04:00
$data = [
2014-06-12 06:18:19 -04:00
'mimetype' => 'httpd/unix-directory' ,
'size' => 0 ,
'mtime' => $mTime ,
'storage_mtime' => $mTime ,
2014-11-25 10:28:41 -05:00
'permissions' => \OCP\Constants :: PERMISSION_ALL ,
2015-10-02 03:59:58 -04:00
];
if ( $path === '' ) {
2014-06-11 07:57:24 -04:00
//create root on the fly
2014-06-27 09:00:29 -04:00
$data [ 'etag' ] = $this -> getETag ( '' );
2014-06-11 07:57:24 -04:00
$this -> getCache () -> put ( '' , $data );
2015-10-02 03:59:58 -04:00
return true ;
} else {
// if parent does not exist, create it
$parent = $this -> normalizePath ( dirname ( $path ));
$parentType = $this -> filetype ( $parent );
if ( $parentType === false ) {
if ( ! $this -> mkdir ( $parent )) {
// something went wrong
2022-05-09 10:56:40 -04:00
$this -> logger -> warning ( " Parent folder ( $parent ) doesn't exist and couldn't be created " );
2015-10-02 03:59:58 -04:00
return false ;
}
2020-04-10 04:35:09 -04:00
} elseif ( $parentType === 'file' ) {
2015-10-02 03:59:58 -04:00
// parent is a file
2022-05-09 10:56:40 -04:00
$this -> logger -> warning ( " Parent ( $parent ) is a file " );
2015-10-02 03:59:58 -04:00
return false ;
2014-06-27 09:00:29 -04:00
}
2015-10-02 03:59:58 -04:00
// finally create the new dir
$mTime = time (); // update mtime
$data [ 'mtime' ] = $mTime ;
$data [ 'storage_mtime' ] = $mTime ;
2014-06-12 06:18:19 -04:00
$data [ 'etag' ] = $this -> getETag ( $path );
2014-06-11 07:57:24 -04:00
$this -> getCache () -> put ( $path , $data );
return true ;
}
}
2014-06-20 06:27:47 -04:00
/**
* @ param string $path
* @ return string
*/
private function normalizePath ( $path ) {
$path = trim ( $path , '/' );
//FIXME why do we sometimes get a path like 'files//username'?
$path = str_replace ( '//' , '/' , $path );
2014-06-27 09:00:29 -04:00
// dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
if ( ! $path || $path === '.' ) {
$path = '' ;
2014-06-20 06:27:47 -04:00
}
return $path ;
}
/**
* Object Stores use a NoopScanner because metadata is directly stored in
* the file cache and cannot really scan the filesystem . The storage passed in is not used anywhere .
*/
public function getScanner ( $path = '' , $storage = null ) {
if ( ! $storage ) {
$storage = $this ;
}
if ( ! isset ( $this -> scanner )) {
2023-04-12 12:08:14 -04:00
$this -> scanner = new ObjectStoreScanner ( $storage );
2014-06-20 06:27:47 -04:00
}
2024-09-15 11:14:37 -04:00
/** @var \OC\Files\ObjectStore\ObjectStoreScanner */
2014-06-20 06:27:47 -04:00
return $this -> scanner ;
}
public function getId () {
return $this -> id ;
}
public function rmdir ( $path ) {
2014-06-11 07:57:24 -04:00
$path = $this -> normalizePath ( $path );
2023-03-27 11:44:33 -04:00
$entry = $this -> getCache () -> get ( $path );
2014-06-20 06:27:47 -04:00
2023-03-27 11:44:33 -04:00
if ( ! $entry || $entry -> getMimeType () !== ICacheEntry :: DIRECTORY_MIMETYPE ) {
2019-09-25 12:07:32 -04:00
return false ;
}
2014-06-20 06:27:47 -04:00
2023-03-27 11:44:33 -04:00
return $this -> rmObjects ( $entry );
2014-06-11 07:57:24 -04:00
}
2023-03-27 11:44:33 -04:00
private function rmObjects ( ICacheEntry $entry ) : bool {
$children = $this -> getCache () -> getFolderContentsById ( $entry -> getId ());
2014-06-11 16:15:42 -04:00
foreach ( $children as $child ) {
2023-03-27 11:44:33 -04:00
if ( $child -> getMimeType () === ICacheEntry :: DIRECTORY_MIMETYPE ) {
if ( ! $this -> rmObjects ( $child )) {
2019-09-25 12:07:32 -04:00
return false ;
}
2014-06-11 16:15:42 -04:00
} else {
2023-03-27 11:44:33 -04:00
if ( ! $this -> rmObject ( $child )) {
2019-09-25 12:07:32 -04:00
return false ;
}
2014-06-11 16:15:42 -04:00
}
}
2019-09-25 12:07:32 -04:00
2023-03-27 11:44:33 -04:00
$this -> getCache () -> remove ( $entry -> getPath ());
2019-09-25 12:07:32 -04:00
return true ;
2014-06-11 16:15:42 -04:00
}
2014-06-11 07:57:24 -04:00
2014-06-20 06:27:47 -04:00
public function unlink ( $path ) {
2014-06-11 07:57:24 -04:00
$path = $this -> normalizePath ( $path );
2023-03-27 11:44:33 -04:00
$entry = $this -> getCache () -> get ( $path );
2014-06-11 07:57:24 -04:00
2023-03-27 11:44:33 -04:00
if ( $entry instanceof ICacheEntry ) {
if ( $entry -> getMimeType () === ICacheEntry :: DIRECTORY_MIMETYPE ) {
return $this -> rmObjects ( $entry );
} else {
return $this -> rmObject ( $entry );
2014-06-20 06:27:47 -04:00
}
2014-06-11 07:57:24 -04:00
}
2014-06-20 06:27:47 -04:00
return false ;
}
2014-06-11 07:57:24 -04:00
2023-03-27 11:44:33 -04:00
public function rmObject ( ICacheEntry $entry ) : bool {
try {
$this -> objectStore -> deleteObject ( $this -> getURN ( $entry -> getId ()));
} catch ( \Exception $ex ) {
if ( $ex -> getCode () !== 404 ) {
2024-02-08 09:47:39 -05:00
$this -> logger -> error (
'Could not delete object ' . $this -> getURN ( $entry -> getId ()) . ' for ' . $entry -> getPath (),
[
'app' => 'objectstore' ,
'exception' => $ex ,
]
);
2023-03-27 11:44:33 -04:00
return false ;
}
//removing from cache is ok as it does not exist in the objectstore anyway
}
$this -> getCache () -> remove ( $entry -> getPath ());
return true ;
}
2014-06-20 06:27:47 -04:00
public function stat ( $path ) {
2014-06-23 10:29:01 -04:00
$path = $this -> normalizePath ( $path );
2015-12-02 09:38:17 -05:00
$cacheEntry = $this -> getCache () -> get ( $path );
if ( $cacheEntry instanceof CacheEntry ) {
return $cacheEntry -> getData ();
} else {
2023-05-23 08:28:37 -04:00
if ( $path === '' ) {
$this -> mkdir ( '' , true );
$cacheEntry = $this -> getCache () -> get ( $path );
if ( $cacheEntry instanceof CacheEntry ) {
return $cacheEntry -> getData ();
}
}
2015-12-02 09:38:17 -05:00
return false ;
}
2014-06-20 06:27:47 -04:00
}
2014-06-11 07:57:24 -04:00
2020-09-08 08:38:36 -04:00
public function getPermissions ( $path ) {
$stat = $this -> stat ( $path );
if ( is_array ( $stat ) && isset ( $stat [ 'permissions' ])) {
return $stat [ 'permissions' ];
}
return parent :: getPermissions ( $path );
}
2014-06-20 06:27:47 -04:00
/**
* Override this method if you need a different unique resource identifier for your object storage implementation .
* The default implementations just appends the fileId to 'urn:oid:' . Make sure the URN is unique over all users .
* You may need a mapping table to store your URN if it cannot be generated from the fileid .
*
* @ param int $fileId the fileid
* @ return null | string the unified resource name used to identify the object
*/
2019-05-22 06:17:33 -04:00
public function getURN ( $fileId ) {
2014-06-20 06:27:47 -04:00
if ( is_numeric ( $fileId )) {
2016-10-27 08:15:59 -04:00
return $this -> objectPrefix . $fileId ;
2014-06-20 06:27:47 -04:00
}
return null ;
2014-06-11 07:57:24 -04:00
}
public function opendir ( $path ) {
$path = $this -> normalizePath ( $path );
2014-06-20 06:27:47 -04:00
2014-06-11 07:57:24 -04:00
try {
2020-03-26 04:30:18 -04:00
$files = [];
2014-06-11 07:57:24 -04:00
$folderContents = $this -> getCache () -> getFolderContents ( $path );
foreach ( $folderContents as $file ) {
$files [] = $file [ 'name' ];
}
2014-06-20 06:27:47 -04:00
2015-03-10 11:30:13 -04:00
return IteratorDirectory :: wrap ( $files );
2015-05-19 09:27:50 -04:00
} catch ( \Exception $e ) {
2024-02-08 09:47:39 -05:00
$this -> logger -> error ( $e -> getMessage (), [ 'exception' => $e ]);
2014-06-11 07:57:24 -04:00
return false ;
}
}
public function filetype ( $path ) {
$path = $this -> normalizePath ( $path );
$stat = $this -> stat ( $path );
if ( $stat ) {
if ( $stat [ 'mimetype' ] === 'httpd/unix-directory' ) {
return 'dir' ;
}
return 'file' ;
} else {
return false ;
}
}
public function fopen ( $path , $mode ) {
$path = $this -> normalizePath ( $path );
2018-03-07 06:05:57 -05:00
if ( strrpos ( $path , '.' ) !== false ) {
$ext = substr ( $path , strrpos ( $path , '.' ));
} else {
$ext = '' ;
}
2014-06-11 07:57:24 -04:00
switch ( $mode ) {
case 'r' :
case 'rb' :
$stat = $this -> stat ( $path );
if ( is_array ( $stat )) {
2022-12-01 11:42:07 -05:00
$filesize = $stat [ 'size' ] ? ? 0 ;
2020-09-07 14:53:56 -04:00
// Reading 0 sized files is a waste of time
2022-12-01 11:42:07 -05:00
if ( $filesize === 0 ) {
2020-09-07 14:53:56 -04:00
return fopen ( 'php://memory' , $mode );
}
2014-06-11 16:15:42 -04:00
try {
2022-12-01 11:42:07 -05:00
$handle = $this -> objectStore -> readObject ( $this -> getURN ( $stat [ 'fileid' ]));
if ( $handle === false ) {
return false ; // keep backward compatibility
}
$streamStat = fstat ( $handle );
$actualSize = $streamStat [ 'size' ] ? ? - 1 ;
if ( $actualSize > - 1 && $actualSize !== $filesize ) {
$this -> getCache () -> update (( int ) $stat [ 'fileid' ], [ 'size' => $actualSize ]);
}
return $handle ;
2018-11-16 14:21:21 -05:00
} catch ( NotFoundException $e ) {
2024-02-08 09:47:39 -05:00
$this -> logger -> error (
'Could not get object ' . $this -> getURN ( $stat [ 'fileid' ]) . ' for file ' . $path ,
[
'app' => 'objectstore' ,
'exception' => $e ,
]
);
2018-11-16 14:21:21 -05:00
throw $e ;
2024-02-08 09:47:39 -05:00
} catch ( \Exception $e ) {
$this -> logger -> error (
'Could not get object ' . $this -> getURN ( $stat [ 'fileid' ]) . ' for file ' . $path ,
[
'app' => 'objectstore' ,
'exception' => $e ,
]
);
2014-06-11 16:15:42 -04:00
return false ;
}
2014-06-11 07:57:24 -04:00
} else {
return false ;
}
2023-01-20 05:45:08 -05:00
// no break
2014-06-11 07:57:24 -04:00
case 'w' :
case 'wb' :
2018-03-07 06:05:57 -05:00
case 'w+' :
case 'wb+' :
2023-05-23 08:28:37 -04:00
$dirName = dirname ( $path );
$parentExists = $this -> is_dir ( $dirName );
if ( ! $parentExists ) {
return false ;
}
2018-03-07 06:05:57 -05:00
$tmpFile = \OC :: $server -> getTempManager () -> getTemporaryFile ( $ext );
$handle = fopen ( $tmpFile , $mode );
return CallbackWrapper :: wrap ( $handle , null , null , function () use ( $path , $tmpFile ) {
$this -> writeBack ( $tmpFile , $path );
2022-05-30 11:53:07 -04:00
unlink ( $tmpFile );
2018-03-07 06:05:57 -05:00
});
2014-06-11 07:57:24 -04:00
case 'a' :
case 'ab' :
case 'r+' :
case 'a+' :
case 'x' :
case 'x+' :
case 'c' :
case 'c+' :
2015-12-18 05:25:33 -05:00
$tmpFile = \OC :: $server -> getTempManager () -> getTemporaryFile ( $ext );
2014-06-11 07:57:24 -04:00
if ( $this -> file_exists ( $path )) {
$source = $this -> fopen ( $path , 'r' );
file_put_contents ( $tmpFile , $source );
}
2017-01-03 11:26:44 -05:00
$handle = fopen ( $tmpFile , $mode );
return CallbackWrapper :: wrap ( $handle , null , null , function () use ( $path , $tmpFile ) {
$this -> writeBack ( $tmpFile , $path );
2022-05-30 11:53:07 -04:00
unlink ( $tmpFile );
2017-01-03 11:26:44 -05:00
});
2014-06-11 07:57:24 -04:00
}
return false ;
}
2014-06-20 06:27:47 -04:00
public function file_exists ( $path ) {
$path = $this -> normalizePath ( $path );
return ( bool ) $this -> stat ( $path );
}
2014-06-23 10:29:01 -04:00
public function rename ( $source , $target ) {
$source = $this -> normalizePath ( $source );
$target = $this -> normalizePath ( $target );
2014-09-05 07:25:59 -04:00
$this -> remove ( $target );
$this -> getCache () -> move ( $source , $target );
$this -> touch ( dirname ( $target ));
return true ;
2014-06-11 07:57:24 -04:00
}
2014-06-20 06:27:47 -04:00
2014-06-11 07:57:24 -04:00
public function getMimeType ( $path ) {
$path = $this -> normalizePath ( $path );
2020-03-11 06:14:12 -04:00
return parent :: getMimeType ( $path );
2014-06-11 07:57:24 -04:00
}
2014-06-20 06:27:47 -04:00
2014-06-11 07:57:24 -04:00
public function touch ( $path , $mtime = null ) {
if ( is_null ( $mtime )) {
$mtime = time ();
}
$path = $this -> normalizePath ( $path );
2014-06-11 16:15:42 -04:00
$dirName = dirname ( $path );
$parentExists = $this -> is_dir ( $dirName );
2014-06-11 07:57:24 -04:00
if ( ! $parentExists ) {
return false ;
}
$stat = $this -> stat ( $path );
if ( is_array ( $stat )) {
// update existing mtime in db
$stat [ 'mtime' ] = $mtime ;
$this -> getCache () -> update ( $stat [ 'fileid' ], $stat );
} else {
2014-06-11 16:15:42 -04:00
try {
2017-11-17 05:59:25 -05:00
//create a empty file, need to have at least on char to make it
// work with all object storage implementations
2017-11-08 11:51:02 -05:00
$this -> file_put_contents ( $path , ' ' );
2017-11-17 05:59:25 -05:00
$mimeType = \OC :: $server -> getMimeTypeDetector () -> detectPath ( $path );
2020-03-26 04:30:18 -04:00
$stat = [
2017-11-17 05:59:25 -05:00
'etag' => $this -> getETag ( $path ),
'mimetype' => $mimeType ,
'size' => 0 ,
'mtime' => $mtime ,
'storage_mtime' => $mtime ,
'permissions' => \OCP\Constants :: PERMISSION_ALL - \OCP\Constants :: PERMISSION_CREATE ,
2020-03-26 04:30:18 -04:00
];
2017-11-17 05:59:25 -05:00
$this -> getCache () -> put ( $path , $stat );
2014-06-11 16:15:42 -04:00
} catch ( \Exception $ex ) {
2024-02-08 09:47:39 -05:00
$this -> logger -> error (
'Could not create object for ' . $path ,
[
'app' => 'objectstore' ,
'exception' => $ex ,
]
);
2018-02-07 16:06:07 -05:00
throw $ex ;
2014-06-11 16:15:42 -04:00
}
2014-06-11 07:57:24 -04:00
}
return true ;
}
2017-01-03 11:26:44 -05:00
public function writeBack ( $tmpFile , $path ) {
2018-10-26 13:15:23 -04:00
$size = filesize ( $tmpFile );
$this -> writeStream ( $path , fopen ( $tmpFile , 'r' ), $size );
}
/**
* external changes are not supported , exclusive access to the object storage is assumed
*
* @ param string $path
* @ param int $time
* @ return false
*/
public function hasUpdated ( $path , $time ) {
return false ;
}
public function needsPartFile () {
return false ;
}
public function file_put_contents ( $path , $data ) {
2024-09-18 05:14:20 -04:00
$fh = fopen ( 'php://temp' , 'w+' );
fwrite ( $fh , $data );
rewind ( $fh );
return $this -> writeStream ( $path , $fh , strlen ( $data ));
2018-10-26 13:15:23 -04:00
}
2024-03-28 11:13:19 -04:00
public function writeStream ( string $path , $stream , ? int $size = null ) : int {
2014-06-11 07:57:24 -04:00
$stat = $this -> stat ( $path );
2014-06-12 06:18:19 -04:00
if ( empty ( $stat )) {
2014-06-11 07:57:24 -04:00
// create new file
2018-10-26 13:15:23 -04:00
$stat = [
2015-10-13 06:25:59 -04:00
'permissions' => \OCP\Constants :: PERMISSION_ALL - \OCP\Constants :: PERMISSION_CREATE ,
2018-10-26 13:15:23 -04:00
];
2014-06-11 07:57:24 -04:00
}
2014-06-12 06:18:19 -04:00
// update stat with new data
$mTime = time ();
2018-10-26 13:15:23 -04:00
$stat [ 'size' ] = ( int ) $size ;
2014-06-12 06:18:19 -04:00
$stat [ 'mtime' ] = $mTime ;
$stat [ 'storage_mtime' ] = $mTime ;
2017-10-25 11:57:21 -04:00
2017-11-08 11:51:02 -05:00
$mimetypeDetector = \OC :: $server -> getMimeTypeDetector ();
2017-10-25 11:57:21 -04:00
$mimetype = $mimetypeDetector -> detectPath ( $path );
$stat [ 'mimetype' ] = $mimetype ;
2014-06-27 12:49:06 -04:00
$stat [ 'etag' ] = $this -> getETag ( $path );
2021-06-21 06:04:49 -04:00
$stat [ 'checksum' ] = '' ;
2014-06-12 06:18:19 -04:00
2018-12-12 09:24:40 -05:00
$exists = $this -> getCache () -> inCache ( $path );
$uploadPath = $exists ? $path : $path . '.part' ;
2020-08-31 06:28:04 -04:00
if ( $exists ) {
$fileId = $stat [ 'fileid' ];
} else {
2024-09-18 07:54:21 -04:00
$parent = $this -> normalizePath ( dirname ( $path ));
if ( ! $this -> is_dir ( $parent )) {
throw new \InvalidArgumentException ( " trying to upload a file ( $path ) inside a non-directory ( $parent ) " );
}
2020-08-31 06:28:04 -04:00
$fileId = $this -> getCache () -> put ( $uploadPath , $stat );
}
2018-12-12 09:24:40 -05:00
$urn = $this -> getURN ( $fileId );
2014-06-11 16:15:42 -04:00
try {
2014-06-12 06:18:19 -04:00
//upload to object storage
2018-10-26 13:15:23 -04:00
if ( $size === null ) {
2019-02-15 07:24:58 -05:00
$countStream = CountWrapper :: wrap ( $stream , function ( $writtenSize ) use ( $fileId , & $size ) {
2018-10-26 13:15:23 -04:00
$this -> getCache () -> update ( $fileId , [
2020-11-05 10:30:05 -05:00
'size' => $writtenSize ,
2018-10-26 13:15:23 -04:00
]);
$size = $writtenSize ;
});
2021-04-15 11:14:57 -04:00
$this -> objectStore -> writeObject ( $urn , $countStream , $mimetype );
2018-10-26 13:15:23 -04:00
if ( is_resource ( $countStream )) {
fclose ( $countStream );
}
2020-08-31 06:28:04 -04:00
$stat [ 'size' ] = $size ;
2018-10-26 13:15:23 -04:00
} else {
2021-04-15 11:14:57 -04:00
$this -> objectStore -> writeObject ( $urn , $stream , $mimetype );
2021-04-29 11:01:19 -04:00
if ( is_resource ( $stream )) {
fclose ( $stream );
}
2018-10-26 13:15:23 -04:00
}
2014-06-11 16:15:42 -04:00
} catch ( \Exception $ex ) {
2020-08-31 06:25:20 -04:00
if ( ! $exists ) {
/*
* Only remove the entry if we are dealing with a new file .
* Else people lose access to existing files
*/
$this -> getCache () -> remove ( $uploadPath );
2024-02-08 09:47:39 -05:00
$this -> logger -> error (
'Could not create object ' . $urn . ' for ' . $path ,
[
'app' => 'objectstore' ,
'exception' => $ex ,
]
);
2020-08-31 06:25:20 -04:00
} else {
2024-02-08 09:47:39 -05:00
$this -> logger -> error (
'Could not update object ' . $urn . ' for ' . $path ,
[
'app' => 'objectstore' ,
'exception' => $ex ,
]
);
2020-08-31 06:25:20 -04:00
}
2014-10-08 12:02:42 -04:00
throw $ex ; // make this bubble up
2014-06-11 16:15:42 -04:00
}
2014-06-11 07:57:24 -04:00
2020-08-31 06:28:04 -04:00
if ( $exists ) {
2023-06-14 08:38:33 -04:00
// Always update the unencrypted size, for encryption the Encryption wrapper will update this afterwards anyways
$stat [ 'unencrypted_size' ] = $stat [ 'size' ];
2020-08-31 06:28:04 -04:00
$this -> getCache () -> update ( $fileId , $stat );
} else {
2022-08-04 06:32:20 -04:00
if ( ! $this -> validateWrites || $this -> objectStore -> objectExists ( $urn )) {
2018-12-12 09:24:40 -05:00
$this -> getCache () -> move ( $uploadPath , $path );
} else {
$this -> getCache () -> remove ( $uploadPath );
throw new \Exception ( " Object not found after writing (urn: $urn , path: $path ) " , 404 );
}
}
2018-10-26 13:15:23 -04:00
return $size ;
2018-03-07 07:33:35 -05:00
}
2019-05-21 11:18:00 -04:00
public function getObjectStore () : IObjectStore {
return $this -> objectStore ;
}
2020-11-05 10:30:05 -05:00
2023-03-27 11:44:33 -04:00
public function copyFromStorage (
IStorage $sourceStorage ,
$sourceInternalPath ,
$targetInternalPath ,
2024-09-19 05:10:31 -04:00
$preserveMtime = false ,
2023-03-27 11:44:33 -04:00
) {
2021-02-19 09:52:58 -05:00
if ( $sourceStorage -> instanceOfStorage ( ObjectStoreStorage :: class )) {
/** @var ObjectStoreStorage $sourceStorage */
if ( $sourceStorage -> getObjectStore () -> getStorageId () === $this -> getObjectStore () -> getStorageId ()) {
2021-10-07 05:50:33 -04:00
/** @var CacheEntry $sourceEntry */
2021-02-19 09:52:58 -05:00
$sourceEntry = $sourceStorage -> getCache () -> get ( $sourceInternalPath );
2021-10-07 05:50:33 -04:00
$sourceEntryData = $sourceEntry -> getData ();
// $sourceEntry['permissions'] here is the permissions from the jailed storage for the current
// user. Instead we use $sourceEntryData['scan_permissions'] that are the permissions from the
// unjailed storage.
if ( is_array ( $sourceEntryData ) && array_key_exists ( 'scan_permissions' , $sourceEntryData )) {
$sourceEntry [ 'permissions' ] = $sourceEntryData [ 'scan_permissions' ];
}
2023-05-26 08:51:05 -04:00
$this -> copyInner ( $sourceStorage -> getCache (), $sourceEntry , $targetInternalPath );
2021-02-19 09:52:58 -05:00
return true ;
}
}
return parent :: copyFromStorage ( $sourceStorage , $sourceInternalPath , $targetInternalPath );
}
2024-06-20 09:57:59 -04:00
public function moveFromStorage ( IStorage $sourceStorage , $sourceInternalPath , $targetInternalPath , ? ICacheEntry $sourceCacheEntry = null ) : bool {
$sourceCache = $sourceStorage -> getCache ();
2024-09-14 17:05:12 -04:00
if ( $sourceStorage -> instanceOfStorage ( ObjectStoreStorage :: class ) && $sourceStorage -> getObjectStore () -> getStorageId () === $this -> getObjectStore () -> getStorageId ()) {
$this -> getCache () -> moveFromCache ( $sourceCache , $sourceInternalPath , $targetInternalPath );
// Do not import any data when source and target bucket are identical.
return true ;
}
2024-06-20 09:57:59 -04:00
if ( ! $sourceCacheEntry ) {
$sourceCacheEntry = $sourceCache -> get ( $sourceInternalPath );
}
if ( $sourceCacheEntry -> getMimeType () === FileInfo :: MIMETYPE_FOLDER ) {
2024-09-18 05:13:51 -04:00
$this -> mkdir ( $targetInternalPath );
2024-06-20 09:57:59 -04:00
foreach ( $sourceCache -> getFolderContents ( $sourceInternalPath ) as $child ) {
$this -> moveFromStorage ( $sourceStorage , $child -> getPath (), $targetInternalPath . '/' . $child -> getName ());
}
$sourceStorage -> rmdir ( $sourceInternalPath );
} else {
2024-09-18 05:13:20 -04:00
$sourceStream = $sourceStorage -> fopen ( $sourceInternalPath , 'r' );
if ( ! $sourceStream ) {
return false ;
}
2024-06-20 09:57:59 -04:00
// move the cache entry before the contents so that we have the correct fileid/urn for the target
$this -> getCache () -> moveFromCache ( $sourceCache , $sourceInternalPath , $targetInternalPath );
try {
2024-09-18 05:13:20 -04:00
$this -> writeStream ( $targetInternalPath , $sourceStream , $sourceCacheEntry -> getSize ());
2024-06-20 09:57:59 -04:00
} catch ( \Exception $e ) {
// restore the cache entry
$sourceCache -> moveFromCache ( $this -> getCache (), $targetInternalPath , $sourceInternalPath );
throw $e ;
}
$sourceStorage -> unlink ( $sourceInternalPath );
}
return true ;
}
2022-10-18 06:49:34 -04:00
public function copy ( $source , $target ) {
$source = $this -> normalizePath ( $source );
$target = $this -> normalizePath ( $target );
2020-11-05 10:30:05 -05:00
$cache = $this -> getCache ();
2022-10-18 06:49:34 -04:00
$sourceEntry = $cache -> get ( $source );
2020-11-05 10:30:05 -05:00
if ( ! $sourceEntry ) {
throw new NotFoundException ( 'Source object not found' );
}
2023-05-26 08:51:05 -04:00
$this -> copyInner ( $cache , $sourceEntry , $target );
2020-11-05 10:30:05 -05:00
return true ;
}
2023-05-26 08:51:05 -04:00
private function copyInner ( ICache $sourceCache , ICacheEntry $sourceEntry , string $to ) {
2020-11-05 10:30:05 -05:00
$cache = $this -> getCache ();
if ( $sourceEntry -> getMimeType () === FileInfo :: MIMETYPE_FOLDER ) {
if ( $cache -> inCache ( $to )) {
$cache -> remove ( $to );
}
$this -> mkdir ( $to );
2023-05-26 08:51:05 -04:00
foreach ( $sourceCache -> getFolderContentsById ( $sourceEntry -> getId ()) as $child ) {
$this -> copyInner ( $sourceCache , $child , $to . '/' . $child -> getName ());
2020-11-05 10:30:05 -05:00
}
} else {
$this -> copyFile ( $sourceEntry , $to );
}
}
private function copyFile ( ICacheEntry $sourceEntry , string $to ) {
$cache = $this -> getCache ();
$sourceUrn = $this -> getURN ( $sourceEntry -> getId ());
2021-03-08 12:50:33 -05:00
if ( ! $cache instanceof Cache ) {
2024-08-23 09:10:27 -04:00
throw new \Exception ( 'Invalid source cache for object store copy' );
2020-11-05 10:30:05 -05:00
}
2021-03-08 12:50:33 -05:00
$targetId = $cache -> copyFromCache ( $cache , $sourceEntry , $to );
$targetUrn = $this -> getURN ( $targetId );
2020-11-05 10:30:05 -05:00
try {
$this -> objectStore -> copyObject ( $sourceUrn , $targetUrn );
2023-11-17 02:43:22 -05:00
if ( $this -> handleCopiesAsOwned ) {
// Copied the file thus we gain all permissions as we are the owner now ! warning while this aligns with local storage it should not be used and instead fix local storage !
$cache -> update ( $targetId , [ 'permissions' => \OCP\Constants :: PERMISSION_ALL ]);
}
2020-11-05 10:30:05 -05:00
} catch ( \Exception $e ) {
$cache -> remove ( $to );
throw $e ;
}
}
2021-05-06 12:26:42 -04:00
public function startChunkedWrite ( string $targetPath ) : string {
if ( ! $this -> objectStore instanceof IObjectStoreMultiPartUpload ) {
throw new GenericFileException ( 'Object store does not support multipart upload' );
}
$cacheEntry = $this -> getCache () -> get ( $targetPath );
$urn = $this -> getURN ( $cacheEntry -> getId ());
return $this -> objectStore -> initiateMultipartUpload ( $urn );
}
/**
*
* @ throws GenericFileException
*/
2023-03-27 11:44:33 -04:00
public function putChunkedWritePart (
string $targetPath ,
string $writeToken ,
string $chunkId ,
$data ,
2024-09-19 05:10:31 -04:00
$size = null ,
2023-03-27 11:44:33 -04:00
) : ? array {
2021-05-06 12:26:42 -04:00
if ( ! $this -> objectStore instanceof IObjectStoreMultiPartUpload ) {
throw new GenericFileException ( 'Object store does not support multipart upload' );
}
$cacheEntry = $this -> getCache () -> get ( $targetPath );
$urn = $this -> getURN ( $cacheEntry -> getId ());
$result = $this -> objectStore -> uploadMultipartPart ( $urn , $writeToken , ( int ) $chunkId , $data , $size );
$parts [ $chunkId ] = [
'PartNumber' => $chunkId ,
2023-03-27 11:44:33 -04:00
'ETag' => trim ( $result -> get ( 'ETag' ), '"' ),
2021-05-06 12:26:42 -04:00
];
return $parts [ $chunkId ];
}
public function completeChunkedWrite ( string $targetPath , string $writeToken ) : int {
if ( ! $this -> objectStore instanceof IObjectStoreMultiPartUpload ) {
throw new GenericFileException ( 'Object store does not support multipart upload' );
}
$cacheEntry = $this -> getCache () -> get ( $targetPath );
$urn = $this -> getURN ( $cacheEntry -> getId ());
$parts = $this -> objectStore -> getMultipartUploads ( $urn , $writeToken );
$sortedParts = array_values ( $parts );
sort ( $sortedParts );
try {
$size = $this -> objectStore -> completeMultipartUpload ( $urn , $writeToken , $sortedParts );
$stat = $this -> stat ( $targetPath );
$mtime = time ();
if ( is_array ( $stat )) {
$stat [ 'size' ] = $size ;
$stat [ 'mtime' ] = $mtime ;
$stat [ 'mimetype' ] = $this -> getMimeType ( $targetPath );
$this -> getCache () -> update ( $stat [ 'fileid' ], $stat );
}
2023-03-27 11:44:33 -04:00
} catch ( S3MultipartUploadException | S3Exception $e ) {
2021-05-06 12:26:42 -04:00
$this -> objectStore -> abortMultipartUpload ( $urn , $writeToken );
2024-02-08 09:47:39 -05:00
$this -> logger -> error (
'Could not compete multipart upload ' . $urn . ' with uploadId ' . $writeToken ,
[
'app' => 'objectstore' ,
'exception' => $e ,
]
);
2021-05-06 12:26:42 -04:00
throw new GenericFileException ( 'Could not write chunked file' );
}
return $size ;
}
public function cancelChunkedWrite ( string $targetPath , string $writeToken ) : void {
if ( ! $this -> objectStore instanceof IObjectStoreMultiPartUpload ) {
throw new GenericFileException ( 'Object store does not support multipart upload' );
}
$cacheEntry = $this -> getCache () -> get ( $targetPath );
$urn = $this -> getURN ( $cacheEntry -> getId ());
$this -> objectStore -> abortMultipartUpload ( $urn , $writeToken );
}
2014-06-24 08:48:59 -04:00
}