2013-01-18 16:11:29 +04:00
< ? php
2013-01-31 21:04:00 +04:00
/**
2016-07-21 17:49:16 +03:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2015-03-26 13:44:34 +03:00
* @ author Bart Visscher < bartv @ thisnet . nl >
* @ author Bastien Ho < bastienho @ urbancube . fr >
2017-11-06 17:56:42 +03:00
* @ author Bjoern Schiessle < bjoern @ schiessle . org >
2016-05-26 20:56:05 +03:00
* @ author Björn Schießle < bjoern @ schiessle . org >
2019-12-03 21:57:53 +03:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2020-09-07 15:37:44 +03:00
* @ author Daniel Kesselberg < mail @ danielkesselberg . de >
2015-03-26 13:44:34 +03:00
* @ author Florin Peter < github @ florin - peter . de >
2017-11-06 22:15:27 +03:00
* @ author Georg Ehrke < oc . list @ georgehrke . com >
2017-11-06 17:56:42 +03:00
* @ author Joas Schilling < coding @ schilljs . com >
2015-03-26 13:44:34 +03:00
* @ author Jörn Friedrich Dreyer < jfd @ butonic . de >
2019-12-03 21:57:53 +03:00
* @ author Juan Pablo Villafáñez < jvillafanez @ solidgear . es >
* @ author Lars Knickrehm < mail @ lars - sh . de >
2016-05-26 20:56:05 +03:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
2015-03-26 13:44:34 +03:00
* @ author Morris Jobke < hey @ morrisjobke . de >
* @ author Qingping Hou < dave2008713 @ gmail . com >
2016-07-21 19:13:36 +03:00
* @ author Robin Appelman < robin @ icewind . nl >
2016-01-12 17:02:16 +03:00
* @ author Robin McCorkell < robin @ mccorkell . me . uk >
2016-07-21 17:49:16 +03:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2015-03-26 13:44:34 +03:00
* @ author Sjors van der Pluijm < sjors @ desjors . nl >
2017-11-06 17:56:42 +03:00
* @ author Steven Bühner < buehner @ me . com >
2015-03-26 13:44:34 +03:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
* @ author Victor Dubiniuk < dubiniuk @ owncloud . com >
* @ author Vincent Petry < pvince81 @ owncloud . com >
2013-01-31 21:04:00 +04:00
*
2015-03-26 13:44:34 +03:00
* @ license AGPL - 3.0
2013-01-31 21:04:00 +04:00
*
2015-03-26 13:44:34 +03:00
* 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 .
2013-01-31 21:04:00 +04:00
*
2015-03-26 13:44:34 +03:00
* This program is distributed in the hope that it will be useful ,
2013-01-31 21:04:00 +04:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2015-03-26 13:44:34 +03:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
2013-01-31 21:04:00 +04:00
*
2015-03-26 13:44:34 +03:00
* You should have received a copy of the GNU Affero General Public License , version 3 ,
2019-12-03 21:57:53 +03:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2013-01-31 21:04:00 +04:00
*
*/
2015-02-26 13:37:37 +03:00
2013-02-08 03:11:54 +04:00
namespace OCA\Files_Trashbin ;
2013-01-18 16:11:29 +04:00
2020-09-29 18:02:53 +03:00
use OC\Files\Cache\Cache ;
use OC\Files\Cache\CacheEntry ;
use OC\Files\Cache\CacheQueryBuilder ;
2014-08-08 17:00:47 +04:00
use OC\Files\Filesystem ;
2020-07-30 17:31:56 +03:00
use OC\Files\ObjectStore\ObjectStoreStorage ;
2015-05-19 15:22:09 +03:00
use OC\Files\View ;
2015-07-30 22:31:18 +03:00
use OCA\Files_Trashbin\AppInfo\Application ;
2015-03-02 17:25:50 +03:00
use OCA\Files_Trashbin\Command\Expire ;
2020-05-06 16:32:44 +03:00
use OCP\AppFramework\Utility\ITimeFactory ;
2017-07-24 12:14:12 +03:00
use OCP\Files\File ;
use OCP\Files\Folder ;
2015-06-15 15:10:10 +03:00
use OCP\Files\NotFoundException ;
2019-03-14 18:18:43 +03:00
use OCP\Files\NotPermittedException ;
2020-05-06 16:32:44 +03:00
use OCP\Lock\ILockingProvider ;
use OCP\Lock\LockedException ;
2016-02-16 19:16:47 +03:00
use OCP\User ;
2014-08-08 17:00:47 +04:00
2013-01-18 16:11:29 +04:00
class Trashbin {
2013-02-15 01:46:28 +04:00
// unit: percentage; 50% of available disk space/quota
2020-04-10 17:54:27 +03:00
public const DEFAULTMAXSIZE = 50 ;
2013-02-22 20:21:57 +04:00
2015-03-02 14:48:08 +03:00
/**
* Whether versions have already be rescanned during this PHP request
*
* @ var bool
*/
private static $scannedVersions = false ;
2015-06-09 18:01:31 +03:00
/**
2016-08-25 14:49:32 +03:00
* Ensure we don ' t need to scan the file during the move to trash
2015-06-15 16:43:19 +03:00
* by triggering the scan in the pre - hook
2015-06-09 18:01:31 +03:00
*
* @ param array $params
*/
public static function ensureFileScannedHook ( $params ) {
2015-12-14 15:35:37 +03:00
try {
self :: getUidAndFilename ( $params [ 'path' ]);
} catch ( NotFoundException $e ) {
// nothing to scan for non existing files
}
2015-06-09 18:01:31 +03:00
}
2015-06-15 15:10:10 +03:00
/**
2016-02-16 19:16:47 +03:00
* get the UID of the owner of the file and the path to the file relative to
* owners files folder
*
2015-06-15 15:10:10 +03:00
* @ param string $filename
* @ return array
* @ throws \OC\User\NoUserException
*/
2013-05-03 15:00:04 +04:00
public static function getUidAndFilename ( $filename ) {
2016-02-16 19:16:47 +03:00
$uid = Filesystem :: getOwner ( $filename );
$userManager = \OC :: $server -> getUserManager ();
// if the user with the UID doesn't exists, e.g. because the UID points
// to a remote user with a federated cloud ID we use the current logged-in
// user. We need a valid local user to move the file to the right trash bin
if ( ! $userManager -> userExists ( $uid )) {
$uid = User :: getUser ();
}
2016-11-09 14:36:35 +03:00
if ( ! $uid ) {
// no owner, usually because of share link from ext storage
return [ null , null ];
}
2016-02-16 19:16:47 +03:00
Filesystem :: initMountPoints ( $uid );
2017-05-10 15:18:15 +03:00
if ( $uid !== User :: getUser ()) {
2016-02-16 19:16:47 +03:00
$info = Filesystem :: getFileInfo ( $filename );
$ownerView = new View ( '/' . $uid . '/files' );
try {
$filename = $ownerView -> getPath ( $info [ 'fileid' ]);
} catch ( NotFoundException $e ) {
$filename = null ;
}
}
return [ $uid , $filename ];
2013-05-03 15:00:04 +04:00
}
2014-08-27 13:28:31 +04:00
/**
* get original location of files for user
*
* @ param string $user
* @ return array ( filename => array ( timestamp => original location ))
*/
public static function getLocations ( $user ) {
$query = \OC_DB :: prepare ( 'SELECT `id`, `timestamp`, `location`'
. ' FROM `*PREFIX*files_trash` WHERE `user`=?' );
2020-03-26 11:30:18 +03:00
$result = $query -> execute ([ $user ]);
$array = [];
2014-08-27 13:28:31 +04:00
while ( $row = $result -> fetchRow ()) {
if ( isset ( $array [ $row [ 'id' ]])) {
$array [ $row [ 'id' ]][ $row [ 'timestamp' ]] = $row [ 'location' ];
} else {
2020-03-26 11:30:18 +03:00
$array [ $row [ 'id' ]] = [ $row [ 'timestamp' ] => $row [ 'location' ]];
2014-08-27 13:28:31 +04:00
}
}
return $array ;
}
/**
* get original location of file
*
* @ param string $user
* @ param string $filename
* @ param string $timestamp
* @ return string original location
*/
public static function getLocation ( $user , $filename , $timestamp ) {
$query = \OC_DB :: prepare ( 'SELECT `location` FROM `*PREFIX*files_trash`'
. ' WHERE `user`=? AND `id`=? AND `timestamp`=?' );
2020-03-26 11:30:18 +03:00
$result = $query -> execute ([ $user , $filename , $timestamp ]) -> fetchAll ();
2014-08-27 13:28:31 +04:00
if ( isset ( $result [ 0 ][ 'location' ])) {
return $result [ 0 ][ 'location' ];
} else {
return false ;
}
}
2013-11-25 15:51:32 +04:00
private static function setUpTrash ( $user ) {
2016-02-16 19:16:47 +03:00
$view = new View ( '/' . $user );
2013-01-31 21:04:00 +04:00
if ( ! $view -> is_dir ( 'files_trashbin' )) {
$view -> mkdir ( 'files_trashbin' );
2013-05-10 13:12:42 +04:00
}
if ( ! $view -> is_dir ( 'files_trashbin/files' )) {
2013-04-20 01:21:06 +04:00
$view -> mkdir ( 'files_trashbin/files' );
2013-05-10 13:12:42 +04:00
}
if ( ! $view -> is_dir ( 'files_trashbin/versions' )) {
2013-04-20 01:21:06 +04:00
$view -> mkdir ( 'files_trashbin/versions' );
2013-05-10 13:12:42 +04:00
}
2014-11-10 14:40:24 +03:00
if ( ! $view -> is_dir ( 'files_trashbin/keys' )) {
$view -> mkdir ( 'files_trashbin/keys' );
2013-05-10 13:12:42 +04:00
}
2013-11-25 15:51:32 +04:00
}
2014-02-06 19:30:58 +04:00
/**
2014-05-19 19:50:53 +04:00
* copy file to owners trash
2015-05-19 15:22:09 +03:00
*
2014-02-25 23:46:41 +04:00
* @ param string $sourcePath
2014-02-06 19:30:58 +04:00
* @ param string $owner
2015-12-07 23:08:20 +03:00
* @ param string $targetPath
2015-09-30 15:35:11 +03:00
* @ param $user
2014-02-06 19:30:58 +04:00
* @ param integer $timestamp
*/
2015-09-30 15:35:11 +03:00
private static function copyFilesToUser ( $sourcePath , $owner , $targetPath , $user , $timestamp ) {
2013-11-25 15:51:32 +04:00
self :: setUpTrash ( $owner );
2015-09-30 15:35:11 +03:00
$targetFilename = basename ( $targetPath );
$targetLocation = dirname ( $targetPath );
2013-11-25 15:51:32 +04:00
$sourceFilename = basename ( $sourcePath );
2016-02-16 19:16:47 +03:00
$view = new View ( '/' );
2013-11-25 15:51:32 +04:00
2015-09-30 15:35:11 +03:00
$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp ;
$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp ;
2019-07-25 04:25:12 +03:00
$free = $view -> free_space ( $target );
2019-09-04 14:06:07 +03:00
$isUnknownOrUnlimitedFreeSpace = $free < 0 ;
$isEnoughFreeSpaceLeft = $view -> filesize ( $source ) < $free ;
if ( $isUnknownOrUnlimitedFreeSpace || $isEnoughFreeSpaceLeft ) {
2019-07-25 04:25:12 +03:00
self :: copy_recursive ( $source , $target , $view );
}
2013-11-25 15:51:32 +04:00
if ( $view -> file_exists ( $target )) {
2014-02-25 23:46:41 +04:00
$query = \OC_DB :: prepare ( " INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?) " );
2020-03-26 11:30:18 +03:00
$result = $query -> execute ([ $targetFilename , $timestamp , $targetLocation , $user ]);
2014-02-25 23:46:41 +04:00
if ( ! $result ) {
2018-04-20 15:35:37 +03:00
\OC :: $server -> getLogger () -> error ( 'trash bin database couldn\'t be updated for the files owner' , [ 'app' => 'files_trashbin' ]);
2013-11-25 15:51:32 +04:00
}
}
}
/**
* move file to the trash bin
*
2014-05-13 15:29:25 +04:00
* @ param string $file_path path to the deleted file / directory relative to the files root directory
2016-12-21 17:42:57 +03:00
* @ param bool $ownerOnly delete for owner only ( if file gets moved out of a shared folder )
*
2015-09-30 15:35:11 +03:00
* @ return bool
2013-11-25 15:51:32 +04:00
*/
2016-12-21 17:42:57 +03:00
public static function move2trash ( $file_path , $ownerOnly = false ) {
2014-08-08 17:00:47 +04:00
// get the user for which the filesystem is setup
$root = Filesystem :: getRoot ();
2020-05-06 16:32:44 +03:00
[, $user ] = explode ( '/' , $root );
[ $owner , $ownerPath ] = self :: getUidAndFilename ( $file_path );
2014-09-19 21:01:02 +04:00
2016-11-09 14:36:35 +03:00
// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
if ( is_null ( $owner )) {
$owner = $user ;
$ownerPath = $file_path ;
}
2016-02-16 19:16:47 +03:00
$ownerView = new View ( '/' . $owner );
2014-09-19 21:01:02 +04:00
// file has been deleted in between
2016-03-04 17:27:31 +03:00
if ( is_null ( $ownerPath ) || $ownerPath === '' || ! $ownerView -> file_exists ( '/files/' . $ownerPath )) {
2015-01-14 23:06:26 +03:00
return true ;
2014-09-19 21:01:02 +04:00
}
2013-11-25 15:51:32 +04:00
self :: setUpTrash ( $user );
2015-06-02 17:44:25 +03:00
if ( $owner !== $user ) {
// also setup for owner
self :: setUpTrash ( $owner );
}
2013-11-25 15:51:32 +04:00
2015-09-30 15:35:11 +03:00
$path_parts = pathinfo ( $ownerPath );
2013-01-31 21:04:00 +04:00
2013-04-19 12:31:42 +04:00
$filename = $path_parts [ 'basename' ];
2013-01-31 21:04:00 +04:00
$location = $path_parts [ 'dirname' ];
2020-05-06 16:32:44 +03:00
/** @var ITimeFactory $timeFactory */
$timeFactory = \OC :: $server -> query ( ITimeFactory :: class );
$timestamp = $timeFactory -> getTime ();
$lockingProvider = \OC :: $server -> getLockingProvider ();
2013-06-14 17:14:23 +04:00
2013-05-03 17:18:05 +04:00
// disable proxy to prevent recursive calls
2013-11-25 15:51:32 +04:00
$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp ;
2020-05-06 16:32:44 +03:00
$gotLock = false ;
while ( ! $gotLock ) {
try {
/** @var \OC\Files\Storage\Storage $trashStorage */
[ $trashStorage , $trashInternalPath ] = $ownerView -> resolvePath ( $trashPath );
$trashStorage -> acquireLock ( $trashInternalPath , ILockingProvider :: LOCK_EXCLUSIVE , $lockingProvider );
$gotLock = true ;
} catch ( LockedException $e ) {
// a file with the same name is being deleted concurrently
// nudge the timestamp a bit to resolve the conflict
$timestamp = $timestamp + 1 ;
$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp ;
}
}
2015-05-13 15:09:07 +03:00
/** @var \OC\Files\Storage\Storage $sourceStorage */
2020-05-06 16:32:44 +03:00
[ $sourceStorage , $sourceInternalPath ] = $ownerView -> resolvePath ( '/files/' . $ownerPath );
2020-07-30 17:31:56 +03:00
if ( $trashStorage -> file_exists ( $trashInternalPath )) {
$trashStorage -> unlink ( $trashInternalPath );
}
2020-09-23 17:38:24 +03:00
$config = \OC :: $server -> getConfig ();
$systemTrashbinSize = ( int ) $config -> getAppValue ( 'files_trashbin' , 'trashbin_size' , '-1' );
$userTrashbinSize = ( int ) $config -> getUserValue ( $owner , 'files_trashbin' , 'trashbin_size' , '-1' );
$configuredTrashbinSize = ( $userTrashbinSize < 0 ) ? $systemTrashbinSize : $userTrashbinSize ;
if ( $configuredTrashbinSize >= 0 && $sourceStorage -> filesize ( $sourceInternalPath ) >= $configuredTrashbinSize ) {
return false ;
}
2020-06-29 19:14:47 +03:00
$trashStorage -> getUpdater () -> renameFromStorage ( $sourceStorage , $sourceInternalPath , $trashInternalPath );
2014-07-14 19:03:36 +04:00
try {
2015-11-27 17:37:55 +03:00
$moveSuccessful = true ;
2020-07-30 17:31:56 +03:00
// when moving within the same object store, the cache update done above is enough to move the file
if ( ! ( $trashStorage -> instanceOfStorage ( ObjectStoreStorage :: class ) && $trashStorage -> getId () === $sourceStorage -> getId ())) {
$trashStorage -> moveFromStorage ( $sourceStorage , $sourceInternalPath , $trashInternalPath );
2015-01-28 17:16:55 +03:00
}
2014-07-14 19:03:36 +04:00
} catch ( \OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e ) {
2015-11-27 17:37:55 +03:00
$moveSuccessful = false ;
2015-05-13 15:09:07 +03:00
if ( $trashStorage -> file_exists ( $trashInternalPath )) {
$trashStorage -> unlink ( $trashInternalPath );
2014-07-14 19:03:36 +04:00
}
2018-04-20 15:35:37 +03:00
\OC :: $server -> getLogger () -> error ( 'Couldn\'t move ' . $file_path . ' to the trash bin' , [ 'app' => 'files_trashbin' ]);
2014-07-14 19:03:36 +04:00
}
2013-04-22 05:37:55 +04:00
2015-05-13 15:09:07 +03:00
if ( $sourceStorage -> file_exists ( $sourceInternalPath )) { // failed to delete the original file, abort
2017-01-25 19:21:20 +03:00
if ( $sourceStorage -> is_dir ( $sourceInternalPath )) {
$sourceStorage -> rmdir ( $sourceInternalPath );
} else {
$sourceStorage -> unlink ( $sourceInternalPath );
}
2020-09-30 15:30:54 +03:00
if ( $sourceStorage -> file_exists ( $sourceInternalPath )) {
// undo the cache move
$sourceStorage -> getUpdater () -> renameFromStorage ( $trashStorage , $trashInternalPath , $sourceInternalPath );
} else {
$trashStorage -> getUpdater () -> remove ( $trashInternalPath );
}
2015-01-28 17:35:49 +03:00
return false ;
}
2015-11-27 17:37:55 +03:00
if ( $moveSuccessful ) {
2014-02-25 23:46:41 +04:00
$query = \OC_DB :: prepare ( " INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?) " );
2020-03-26 11:30:18 +03:00
$result = $query -> execute ([ $filename , $timestamp , $location , $owner ]);
2014-02-25 23:46:41 +04:00
if ( ! $result ) {
2018-04-20 15:35:37 +03:00
\OC :: $server -> getLogger () -> error ( 'trash bin database couldn\'t be updated' , [ 'app' => 'files_trashbin' ]);
2013-01-31 21:04:00 +04:00
}
2020-03-26 11:30:18 +03:00
\OCP\Util :: emitHook ( '\OCA\Files_Trashbin\Trashbin' , 'post_moveToTrash' , [ 'filePath' => Filesystem :: normalizePath ( $file_path ),
'trashPath' => Filesystem :: normalizePath ( $filename . '.d' . $timestamp )]);
2013-04-19 12:31:42 +04:00
2015-11-27 16:01:03 +03:00
self :: retainVersions ( $filename , $owner , $ownerPath , $timestamp );
2013-11-25 15:51:32 +04:00
2016-12-21 17:42:57 +03:00
// if owner !== user we need to also add a copy to the users trash
if ( $user !== $owner && $ownerOnly === false ) {
2015-09-30 15:35:11 +03:00
self :: copyFilesToUser ( $ownerPath , $owner , $file_path , $user , $timestamp );
2013-11-25 15:51:32 +04:00
}
2013-01-18 16:11:29 +04:00
}
2013-04-16 15:51:53 +04:00
2020-05-06 16:32:44 +03:00
$trashStorage -> releaseLock ( $trashInternalPath , ILockingProvider :: LOCK_EXCLUSIVE , $lockingProvider );
2015-11-27 16:01:03 +03:00
self :: scheduleExpire ( $user );
2013-02-25 14:14:06 +04:00
2013-11-25 15:51:32 +04:00
// if owner !== user we also need to update the owners trash size
2014-03-17 19:35:08 +04:00
if ( $owner !== $user ) {
2015-11-27 16:01:03 +03:00
self :: scheduleExpire ( $owner );
2013-11-25 15:51:32 +04:00
}
2015-01-14 23:06:26 +03:00
2015-11-27 17:37:55 +03:00
return $moveSuccessful ;
2013-01-18 16:11:29 +04:00
}
2013-02-22 20:21:57 +04:00
2013-06-14 17:14:23 +04:00
/**
* Move file versions to trash so that they can be restored later
*
2014-05-13 15:29:25 +04:00
* @ param string $filename of deleted file
2015-06-02 17:44:25 +03:00
* @ param string $owner owner user id
* @ param string $ownerPath path relative to the owner ' s home storage
2014-02-06 19:30:58 +04:00
* @ param integer $timestamp when the file was deleted
2013-06-14 17:14:23 +04:00
*/
2015-09-30 15:35:11 +03:00
private static function retainVersions ( $filename , $owner , $ownerPath , $timestamp ) {
2015-06-02 17:44:25 +03:00
if ( \OCP\App :: isEnabled ( 'files_versions' ) && ! empty ( $ownerPath )) {
2016-02-16 19:16:47 +03:00
$user = User :: getUser ();
$rootView = new View ( '/' );
2013-05-03 19:14:43 +04:00
2013-06-14 17:14:23 +04:00
if ( $rootView -> is_dir ( $owner . '/files_versions/' . $ownerPath )) {
2013-11-25 15:51:32 +04:00
if ( $owner !== $user ) {
2014-01-15 17:27:23 +04:00
self :: copy_recursive ( $owner . '/files_versions/' . $ownerPath , $owner . '/files_trashbin/versions/' . basename ( $ownerPath ) . '.d' . $timestamp , $rootView );
2013-11-25 15:51:32 +04:00
}
2015-05-19 15:22:09 +03:00
self :: move ( $rootView , $owner . '/files_versions/' . $ownerPath , $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp );
2020-04-10 11:35:09 +03:00
} elseif ( $versions = \OCA\Files_Versions\Storage :: getVersions ( $owner , $ownerPath )) {
2013-06-14 17:14:23 +04:00
foreach ( $versions as $v ) {
2013-11-25 15:51:32 +04:00
if ( $owner !== $user ) {
2015-05-19 15:22:09 +03:00
self :: copy ( $rootView , $owner . '/files_versions' . $v [ 'path' ] . '.v' . $v [ 'version' ], $owner . '/files_trashbin/versions/' . $v [ 'name' ] . '.v' . $v [ 'version' ] . '.d' . $timestamp );
2013-11-25 15:51:32 +04:00
}
2015-05-19 15:22:09 +03:00
self :: move ( $rootView , $owner . '/files_versions' . $v [ 'path' ] . '.v' . $v [ 'version' ], $user . '/files_trashbin/versions/' . $filename . '.v' . $v [ 'version' ] . '.d' . $timestamp );
2013-04-19 12:31:42 +04:00
}
}
}
}
2015-05-19 15:22:09 +03:00
/**
* Move a file or folder on storage level
*
* @ param View $view
* @ param string $source
* @ param string $target
* @ return bool
*/
private static function move ( View $view , $source , $target ) {
/** @var \OC\Files\Storage\Storage $sourceStorage */
2020-05-06 16:32:44 +03:00
[ $sourceStorage , $sourceInternalPath ] = $view -> resolvePath ( $source );
2015-05-19 15:22:09 +03:00
/** @var \OC\Files\Storage\Storage $targetStorage */
2020-05-06 16:32:44 +03:00
[ $targetStorage , $targetInternalPath ] = $view -> resolvePath ( $target );
2015-05-19 15:22:09 +03:00
/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
$result = $targetStorage -> moveFromStorage ( $sourceStorage , $sourceInternalPath , $targetInternalPath );
if ( $result ) {
2015-11-25 15:53:31 +03:00
$targetStorage -> getUpdater () -> renameFromStorage ( $sourceStorage , $sourceInternalPath , $targetInternalPath );
2015-05-19 15:22:09 +03:00
}
return $result ;
}
/**
* Copy a file or folder on storage level
*
* @ param View $view
* @ param string $source
* @ param string $target
* @ return bool
*/
private static function copy ( View $view , $source , $target ) {
/** @var \OC\Files\Storage\Storage $sourceStorage */
2020-05-06 16:32:44 +03:00
[ $sourceStorage , $sourceInternalPath ] = $view -> resolvePath ( $source );
2015-05-19 15:22:09 +03:00
/** @var \OC\Files\Storage\Storage $targetStorage */
2020-05-06 16:32:44 +03:00
[ $targetStorage , $targetInternalPath ] = $view -> resolvePath ( $target );
2015-05-19 15:22:09 +03:00
/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
$result = $targetStorage -> copyFromStorage ( $sourceStorage , $sourceInternalPath , $targetInternalPath );
if ( $result ) {
2015-11-25 15:53:31 +03:00
$targetStorage -> getUpdater () -> update ( $targetInternalPath );
2015-05-19 15:22:09 +03:00
}
return $result ;
}
2013-01-18 16:11:29 +04:00
/**
2015-05-12 14:14:57 +03:00
* Restore a file or folder from trash bin
2014-03-17 19:35:08 +04:00
*
2015-05-12 14:14:57 +03:00
* @ param string $file path to the deleted file / folder relative to " files_trashbin/files/ " ,
* including the timestamp suffix " .d12345678 "
* @ param string $filename name of the file / folder
* @ param int $timestamp time when the file / folder was deleted
2013-06-14 17:14:23 +04:00
*
2015-05-12 14:14:57 +03:00
* @ return bool true on success , false otherwise
2013-06-14 17:14:23 +04:00
*/
2013-01-22 15:00:04 +04:00
public static function restore ( $file , $filename , $timestamp ) {
2016-02-16 19:16:47 +03:00
$user = User :: getUser ();
$view = new View ( '/' . $user );
2013-06-14 17:14:23 +04:00
2014-02-25 23:46:41 +04:00
$location = '' ;
2013-06-14 17:14:23 +04:00
if ( $timestamp ) {
2014-08-27 13:28:31 +04:00
$location = self :: getLocation ( $user , $filename , $timestamp );
if ( $location === false ) {
2018-04-20 15:35:37 +03:00
\OC :: $server -> getLogger () -> error ( 'trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')' , [ 'app' => 'files_trashbin' ]);
2014-02-25 23:46:41 +04:00
} else {
// if location no longer exists, restore file in the root directory
if ( $location !== '/' &&
2015-01-14 23:06:26 +03:00
( ! $view -> is_dir ( 'files/' . $location ) ||
2015-05-19 15:22:09 +03:00
! $view -> isCreatable ( 'files/' . $location ))
2014-03-17 19:35:08 +04:00
) {
2014-02-25 23:46:41 +04:00
$location = '' ;
}
2013-01-22 15:00:04 +04:00
}
2013-01-18 16:11:29 +04:00
}
2013-06-14 17:14:23 +04:00
2013-01-18 17:09:22 +04:00
// we need a extension in case a file/dir with the same name already exists
2013-07-25 18:20:06 +04:00
$uniqueFilename = self :: getUniqueFilename ( $location , $filename , $view );
2016-02-16 19:16:47 +03:00
$source = Filesystem :: normalizePath ( 'files_trashbin/files/' . $file );
$target = Filesystem :: normalizePath ( 'files/' . $location . '/' . $uniqueFilename );
2015-05-12 14:14:57 +03:00
if ( ! $view -> file_exists ( $source )) {
return false ;
}
2013-01-31 13:50:02 +04:00
$mtime = $view -> filemtime ( $source );
2013-04-20 01:21:06 +04:00
2013-06-14 17:14:23 +04:00
// restore file
2019-03-14 18:18:43 +03:00
if ( ! $view -> isCreatable ( dirname ( $target ))) {
throw new NotPermittedException ( " Can't restore trash item because the target folder is not writable " );
}
2013-07-25 18:20:06 +04:00
$restoreResult = $view -> rename ( $source , $target );
2013-04-20 01:21:06 +04:00
2013-06-14 17:14:23 +04:00
// handle the restore result
if ( $restoreResult ) {
2013-05-10 14:05:11 +04:00
$fakeRoot = $view -> getRoot ();
2013-06-14 17:14:23 +04:00
$view -> chroot ( '/' . $user . '/files' );
2013-07-25 18:20:06 +04:00
$view -> touch ( '/' . $location . '/' . $uniqueFilename , $mtime );
2013-05-10 14:05:11 +04:00
$view -> chroot ( $fakeRoot );
2020-03-26 11:30:18 +03:00
\OCP\Util :: emitHook ( '\OCA\Files_Trashbin\Trashbin' , 'post_restore' , [ 'filePath' => Filesystem :: normalizePath ( '/' . $location . '/' . $uniqueFilename ),
'trashPath' => Filesystem :: normalizePath ( $file )]);
2013-04-19 12:35:32 +04:00
2014-02-26 00:35:54 +04:00
self :: restoreVersions ( $view , $file , $filename , $uniqueFilename , $location , $timestamp );
2013-04-19 12:35:32 +04:00
2013-06-14 17:14:23 +04:00
if ( $timestamp ) {
2013-03-22 15:47:43 +04:00
$query = \OC_DB :: prepare ( 'DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?' );
2020-03-26 11:30:18 +03:00
$query -> execute ([ $user , $filename , $timestamp ]);
2013-01-22 15:00:04 +04:00
}
2013-01-18 17:09:22 +04:00
return true ;
2013-01-18 16:11:29 +04:00
}
2013-01-18 17:09:22 +04:00
return false ;
2013-01-18 16:11:29 +04:00
}
2013-02-22 20:21:57 +04:00
2013-06-14 17:14:23 +04:00
/**
2014-05-19 19:50:53 +04:00
* restore versions from trash bin
2013-04-19 12:35:32 +04:00
*
2016-02-16 19:16:47 +03:00
* @ param View $view file view
2014-05-13 15:29:25 +04:00
* @ param string $file complete path to file
* @ param string $filename name of file once it was deleted
2014-02-06 19:30:58 +04:00
* @ param string $uniqueFilename new file name to restore the file without overwriting existing files
2014-05-13 15:29:25 +04:00
* @ param string $location location if file
2014-10-24 16:13:40 +04:00
* @ param int $timestamp deletion time
2015-12-07 23:08:20 +03:00
* @ return false | null
2013-04-19 12:35:32 +04:00
*/
2016-02-16 19:16:47 +03:00
private static function restoreVersions ( View $view , $file , $filename , $uniqueFilename , $location , $timestamp ) {
2013-04-19 12:35:32 +04:00
if ( \OCP\App :: isEnabled ( 'files_versions' )) {
2016-02-16 19:16:47 +03:00
$user = User :: getUser ();
$rootView = new View ( '/' );
2013-05-03 19:14:43 +04:00
2016-02-16 19:16:47 +03:00
$target = Filesystem :: normalizePath ( '/' . $location . '/' . $uniqueFilename );
2013-05-03 19:14:43 +04:00
2020-05-06 16:32:44 +03:00
[ $owner , $ownerPath ] = self :: getUidAndFilename ( $target );
2013-05-03 19:14:43 +04:00
2014-09-19 21:01:02 +04:00
// file has been deleted in between
if ( empty ( $ownerPath )) {
return false ;
}
2013-04-19 12:35:32 +04:00
if ( $timestamp ) {
$versionedFile = $filename ;
} else {
$versionedFile = $file ;
}
2013-04-28 01:51:26 +04:00
2013-06-14 17:14:23 +04:00
if ( $view -> is_dir ( '/files_trashbin/versions/' . $file )) {
2016-02-16 19:16:47 +03:00
$rootView -> rename ( Filesystem :: normalizePath ( $user . '/files_trashbin/versions/' . $file ), Filesystem :: normalizePath ( $owner . '/files_versions/' . $ownerPath ));
2020-04-10 11:35:09 +03:00
} elseif ( $versions = self :: getVersionsFromTrash ( $versionedFile , $timestamp , $user )) {
2013-06-14 17:14:23 +04:00
foreach ( $versions as $v ) {
if ( $timestamp ) {
$rootView -> rename ( $user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp , $owner . '/files_versions/' . $ownerPath . '.v' . $v );
2013-04-19 12:35:32 +04:00
} else {
2013-06-14 17:14:23 +04:00
$rootView -> rename ( $user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v , $owner . '/files_versions/' . $ownerPath . '.v' . $v );
2013-04-19 12:35:32 +04:00
}
}
}
}
}
2013-12-02 14:39:53 +04:00
/**
2014-05-19 19:50:53 +04:00
* delete all files from the trash
2013-12-02 14:39:53 +04:00
*/
public static function deleteAll () {
2016-02-16 19:16:47 +03:00
$user = User :: getUser ();
2017-07-24 12:14:12 +03:00
$userRoot = \OC :: $server -> getUserFolder ( $user ) -> getParent ();
2016-02-16 19:16:47 +03:00
$view = new View ( '/' . $user );
2016-08-30 10:04:01 +03:00
$fileInfos = $view -> getDirectoryContent ( 'files_trashbin/files' );
2016-08-25 14:47:51 +03:00
2017-07-24 12:14:12 +03:00
try {
$trash = $userRoot -> get ( 'files_trashbin' );
} catch ( NotFoundException $e ) {
return false ;
}
2016-09-05 15:24:04 +03:00
// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
2020-03-26 11:30:18 +03:00
$filePaths = [];
2020-04-10 15:19:56 +03:00
foreach ( $fileInfos as $fileInfo ) {
2016-09-05 15:36:44 +03:00
$filePaths [] = $view -> getRelativePath ( $fileInfo -> getPath ());
}
unset ( $fileInfos ); // save memory
// Bulk PreDelete-Hook
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( '\OCP\Trashbin' , 'preDeleteAll' , [ 'paths' => $filePaths ]);
2016-09-05 15:24:04 +03:00
2016-09-05 15:36:44 +03:00
// Single-File Hooks
2020-04-10 15:19:56 +03:00
foreach ( $filePaths as $path ) {
2016-08-30 10:04:01 +03:00
self :: emitTrashbinPreDelete ( $path );
}
2016-08-25 14:47:51 +03:00
2016-09-05 15:36:44 +03:00
// actual file deletion
2017-07-24 12:14:12 +03:00
$trash -> delete ();
2013-12-02 14:39:53 +04:00
$query = \OC_DB :: prepare ( 'DELETE FROM `*PREFIX*files_trash` WHERE `user`=?' );
2020-03-26 11:30:18 +03:00
$query -> execute ([ $user ]);
2016-08-25 14:47:51 +03:00
2016-09-05 15:36:44 +03:00
// Bulk PostDelete-Hook
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( '\OCP\Trashbin' , 'deleteAll' , [ 'paths' => $filePaths ]);
2016-09-05 15:36:44 +03:00
// Single-File Hooks
2020-04-10 15:19:56 +03:00
foreach ( $filePaths as $path ) {
2016-08-30 10:04:01 +03:00
self :: emitTrashbinPostDelete ( $path );
}
2016-08-25 14:47:51 +03:00
2017-07-24 12:14:12 +03:00
$trash = $userRoot -> newFolder ( 'files_trashbin' );
$trash -> newFolder ( 'files' );
2013-12-02 14:39:53 +04:00
return true ;
}
2016-08-30 10:04:01 +03:00
/**
* wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
2020-07-02 18:28:27 +03:00
*
2016-08-30 10:04:01 +03:00
* @ param string $path
*/
2020-04-09 14:53:40 +03:00
protected static function emitTrashbinPreDelete ( $path ) {
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( '\OCP\Trashbin' , 'preDelete' , [ 'path' => $path ]);
2016-08-30 10:04:01 +03:00
}
2016-08-25 14:47:51 +03:00
2016-08-30 10:04:01 +03:00
/**
* wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
2020-07-02 18:28:27 +03:00
*
2016-08-30 10:04:01 +03:00
* @ param string $path
*/
2020-04-09 14:53:40 +03:00
protected static function emitTrashbinPostDelete ( $path ) {
2020-03-26 11:30:18 +03:00
\OC_Hook :: emit ( '\OCP\Trashbin' , 'delete' , [ 'path' => $path ]);
2016-08-30 10:04:01 +03:00
}
2016-08-25 14:47:51 +03:00
2013-02-08 03:11:54 +04:00
/**
2014-05-19 19:50:53 +04:00
* delete file from trash bin permanently
2013-06-14 17:14:23 +04:00
*
2014-05-13 15:29:25 +04:00
* @ param string $filename path to the file
2014-06-17 22:08:40 +04:00
* @ param string $user
2014-05-13 15:29:25 +04:00
* @ param int $timestamp of deletion time
2013-06-14 17:14:23 +04:00
*
2014-05-13 15:29:25 +04:00
* @ return int size of deleted files
2013-02-08 03:11:54 +04:00
*/
2014-06-17 22:08:40 +04:00
public static function delete ( $filename , $user , $timestamp = null ) {
2017-07-24 12:14:12 +03:00
$userRoot = \OC :: $server -> getUserFolder ( $user ) -> getParent ();
2016-02-16 19:16:47 +03:00
$view = new View ( '/' . $user );
2013-02-25 17:29:31 +04:00
$size = 0 ;
2013-06-14 17:14:23 +04:00
if ( $timestamp ) {
2013-03-22 15:47:43 +04:00
$query = \OC_DB :: prepare ( 'DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?' );
2020-03-26 11:30:18 +03:00
$query -> execute ([ $user , $filename , $timestamp ]);
2013-06-14 17:14:23 +04:00
$file = $filename . '.d' . $timestamp ;
2013-02-06 19:23:22 +04:00
} else {
$file = $filename ;
}
2013-02-22 20:21:57 +04:00
2015-03-10 13:47:52 +03:00
$size += self :: deleteVersions ( $view , $file , $filename , $timestamp , $user );
2013-06-14 17:14:23 +04:00
2017-07-24 12:14:12 +03:00
try {
$node = $userRoot -> get ( '/files_trashbin/files/' . $file );
} catch ( NotFoundException $e ) {
return $size ;
}
if ( $node instanceof Folder ) {
2016-02-16 19:16:47 +03:00
$size += self :: calculateSize ( new View ( '/' . $user . '/files_trashbin/files/' . $file ));
2020-04-10 11:35:09 +03:00
} elseif ( $node instanceof File ) {
2013-06-14 17:14:23 +04:00
$size += $view -> filesize ( '/files_trashbin/files/' . $file );
2013-05-03 18:33:18 +04:00
}
2017-07-24 12:14:12 +03:00
2016-08-30 10:04:01 +03:00
self :: emitTrashbinPreDelete ( '/files_trashbin/files/' . $file );
2017-07-24 12:14:12 +03:00
$node -> delete ();
2016-08-25 14:47:51 +03:00
self :: emitTrashbinPostDelete ( '/files_trashbin/files/' . $file );
2013-06-14 17:14:23 +04:00
2013-05-03 18:33:18 +04:00
return $size ;
}
2014-02-06 19:30:58 +04:00
/**
2016-02-16 19:16:47 +03:00
* @ param View $view
2015-12-07 23:08:20 +03:00
* @ param string $file
* @ param string $filename
* @ param integer | null $timestamp
* @ param string $user
2014-10-24 16:13:40 +04:00
* @ return int
2014-02-06 19:30:58 +04:00
*/
2016-02-16 19:16:47 +03:00
private static function deleteVersions ( View $view , $file , $filename , $timestamp , $user ) {
2013-05-03 18:33:18 +04:00
$size = 0 ;
2013-06-14 17:14:23 +04:00
if ( \OCP\App :: isEnabled ( 'files_versions' )) {
if ( $view -> is_dir ( 'files_trashbin/versions/' . $file )) {
2016-02-16 19:16:47 +03:00
$size += self :: calculateSize ( new View ( '/' . $user . '/files_trashbin/versions/' . $file ));
2013-06-14 17:14:23 +04:00
$view -> unlink ( 'files_trashbin/versions/' . $file );
2020-04-10 11:35:09 +03:00
} elseif ( $versions = self :: getVersionsFromTrash ( $filename , $timestamp , $user )) {
2013-02-06 19:23:22 +04:00
foreach ( $versions as $v ) {
2013-06-14 17:14:23 +04:00
if ( $timestamp ) {
$size += $view -> filesize ( '/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp );
$view -> unlink ( '/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp );
2013-02-06 19:23:22 +04:00
} else {
2013-06-14 17:14:23 +04:00
$size += $view -> filesize ( '/files_trashbin/versions/' . $filename . '.v' . $v );
$view -> unlink ( '/files_trashbin/versions/' . $filename . '.v' . $v );
2013-02-06 19:23:22 +04:00
}
}
}
2013-02-08 03:11:54 +04:00
}
2013-05-03 18:33:18 +04:00
return $size ;
}
2013-02-17 02:42:06 +04:00
/**
* check to see whether a file exists in trashbin
2014-03-17 19:35:08 +04:00
*
2014-05-13 15:29:25 +04:00
* @ param string $filename path to the file
* @ param int $timestamp of deletion time
* @ return bool true if file exists , otherwise false
2013-02-17 02:42:06 +04:00
*/
2013-06-14 17:14:23 +04:00
public static function file_exists ( $filename , $timestamp = null ) {
2016-02-16 19:16:47 +03:00
$user = User :: getUser ();
$view = new View ( '/' . $user );
2013-02-18 18:49:50 +04:00
if ( $timestamp ) {
2013-06-14 17:14:23 +04:00
$filename = $filename . '.d' . $timestamp ;
2013-02-18 18:49:50 +04:00
}
2016-02-16 19:16:47 +03:00
$target = Filesystem :: normalizePath ( 'files_trashbin/files/' . $filename );
2013-02-17 02:42:06 +04:00
return $view -> file_exists ( $target );
}
2013-04-11 14:37:52 +04:00
/**
2014-05-19 19:50:53 +04:00
* deletes used space for trash bin in db if user was deleted
2013-04-11 14:37:52 +04:00
*
2014-06-05 12:44:32 +04:00
* @ param string $uid id of deleted user
2014-05-13 15:29:25 +04:00
* @ return bool result of db delete operation
2013-04-11 14:37:52 +04:00
*/
public static function deleteUser ( $uid ) {
$query = \OC_DB :: prepare ( 'DELETE FROM `*PREFIX*files_trash` WHERE `user`=?' );
2020-03-26 11:30:18 +03:00
return $query -> execute ([ $uid ]);
2013-04-11 14:37:52 +04:00
}
2013-04-16 15:51:53 +04:00
/**
* calculate remaining free space for trash bin
*
2014-02-06 19:30:58 +04:00
* @ param integer $trashbinSize current size of the trash bin
2014-06-17 22:08:40 +04:00
* @ param string $user
2014-05-13 15:29:25 +04:00
* @ return int available free space for trash bin
2013-04-16 15:51:53 +04:00
*/
2014-06-17 22:08:40 +04:00
private static function calculateFreeSpace ( $trashbinSize , $user ) {
2020-07-02 18:28:27 +03:00
$config = \OC :: $server -> getConfig ();
$userTrashbinSize = ( int ) $config -> getUserValue ( $user , 'files_trashbin' , 'trashbin_size' , '-1' );
2020-09-01 00:10:04 +03:00
if ( $userTrashbinSize > - 1 ) {
return $userTrashbinSize - $trashbinSize ;
}
$systemTrashbinSize = ( int ) $config -> getAppValue ( 'files_trashbin' , 'trashbin_size' , '-1' );
if ( $systemTrashbinSize > - 1 ) {
return $systemTrashbinSize - $trashbinSize ;
2020-07-02 18:28:27 +03:00
}
2013-04-16 15:51:53 +04:00
$softQuota = true ;
2016-02-16 20:45:25 +03:00
$userObject = \OC :: $server -> getUserManager () -> get ( $user );
2020-04-10 15:19:56 +03:00
if ( is_null ( $userObject )) {
2016-02-16 20:45:25 +03:00
return 0 ;
}
$quota = $userObject -> getQuota ();
2013-06-14 17:14:23 +04:00
if ( $quota === null || $quota === 'none' ) {
2016-02-16 19:16:47 +03:00
$quota = Filesystem :: free_space ( '/' );
2013-04-16 15:51:53 +04:00
$softQuota = false ;
2015-10-15 16:46:26 +03:00
// inf or unknown free space
if ( $quota < 0 ) {
$quota = PHP_INT_MAX ;
2014-10-10 20:26:43 +04:00
}
2013-04-16 15:51:53 +04:00
} else {
$quota = \OCP\Util :: computerFileSize ( $quota );
}
// calculate available space for trash bin
// subtract size of files and current trash bin size from quota
if ( $softQuota ) {
2019-03-01 14:03:28 +03:00
$userFolder = \OC :: $server -> getUserFolder ( $user );
2020-04-10 15:19:56 +03:00
if ( is_null ( $userFolder )) {
2019-03-01 14:03:28 +03:00
return 0 ;
}
2019-03-01 14:05:18 +03:00
$free = $quota - $userFolder -> getSize ( false ); // remaining free space for user
2013-06-14 17:14:23 +04:00
if ( $free > 0 ) {
2013-04-16 15:51:53 +04:00
$availableSpace = ( $free * self :: DEFAULTMAXSIZE / 100 ) - $trashbinSize ; // how much space can be used for versions
} else {
2013-06-14 17:14:23 +04:00
$availableSpace = $free - $trashbinSize ;
2013-04-16 15:51:53 +04:00
}
} else {
$availableSpace = $quota ;
}
return $availableSpace ;
}
2013-07-26 13:45:38 +04:00
/**
2017-04-12 07:16:27 +03:00
* resize trash bin if necessary after a new file was added to Nextcloud
2015-05-19 15:22:09 +03:00
*
2013-07-26 13:45:38 +04:00
* @ param string $user user id
*/
public static function resizeTrash ( $user ) {
$size = self :: getTrashbinSize ( $user );
2014-06-17 22:08:40 +04:00
$freeSpace = self :: calculateFreeSpace ( $size , $user );
2013-07-26 13:45:38 +04:00
if ( $freeSpace < 0 ) {
2015-11-27 16:01:03 +03:00
self :: scheduleExpire ( $user );
2013-07-26 13:45:38 +04:00
}
}
2013-08-27 16:39:43 +04:00
2013-01-31 21:04:00 +04:00
/**
* clean up the trash bin
2014-03-17 19:35:08 +04:00
*
2013-11-25 15:51:32 +04:00
* @ param string $user
2013-01-31 21:04:00 +04:00
*/
2015-11-27 16:01:03 +03:00
public static function expire ( $user ) {
$trashBinSize = self :: getTrashbinSize ( $user );
2015-03-02 17:25:50 +03:00
$availableSpace = self :: calculateFreeSpace ( $trashBinSize , $user );
2013-02-22 20:21:57 +04:00
2014-06-17 22:08:40 +04:00
$dirContent = Helper :: getTrashFiles ( '/' , $user , 'mtime' );
2014-06-17 15:51:49 +04:00
// delete all files older then $retention_obligation
2020-05-06 16:32:44 +03:00
[ $delSize , $count ] = self :: deleteExpiredFiles ( $dirContent , $user );
2014-06-17 15:51:49 +04:00
2015-11-27 17:37:55 +03:00
$availableSpace += $delSize ;
2014-06-17 15:51:49 +04:00
// delete files from trash until we meet the trash bin size limit again
2015-11-27 17:37:55 +03:00
self :: deleteFiles ( array_slice ( $dirContent , $count ), $user , $availableSpace );
2015-03-02 17:25:50 +03:00
}
2014-06-17 15:51:49 +04:00
2015-11-27 16:01:03 +03:00
/**
2015-03-02 17:25:50 +03:00
* @ param string $user
*/
2015-11-27 16:01:03 +03:00
private static function scheduleExpire ( $user ) {
2015-03-02 17:25:50 +03:00
// let the admin disable auto expire
2019-10-11 09:33:09 +03:00
/** @var Application $application */
$application = \OC :: $server -> query ( Application :: class );
2015-07-30 22:31:18 +03:00
$expiration = $application -> getContainer () -> query ( 'Expiration' );
if ( $expiration -> isEnabled ()) {
2015-11-27 16:01:03 +03:00
\OC :: $server -> getCommandBus () -> push ( new Expire ( $user ));
2015-03-02 17:25:50 +03:00
}
2014-06-17 15:51:49 +04:00
}
/**
* if the size limit for the trash bin is reached , we delete the oldest
* files in the trash bin until we meet the limit again
2015-05-19 15:22:09 +03:00
*
2014-06-17 15:51:49 +04:00
* @ param array $files
2014-06-17 22:08:40 +04:00
* @ param string $user
* @ param int $availableSpace available disc space
2014-06-17 15:51:49 +04:00
* @ return int size of deleted files
*/
2014-06-18 00:30:11 +04:00
protected static function deleteFiles ( $files , $user , $availableSpace ) {
2019-10-11 09:33:09 +03:00
/** @var Application $application */
$application = \OC :: $server -> query ( Application :: class );
2015-07-30 22:31:18 +03:00
$expiration = $application -> getContainer () -> query ( 'Expiration' );
2014-06-17 15:51:49 +04:00
$size = 0 ;
2014-06-03 18:45:11 +04:00
2014-06-17 15:51:49 +04:00
if ( $availableSpace < 0 ) {
foreach ( $files as $file ) {
2015-07-30 22:31:18 +03:00
if ( $availableSpace < 0 && $expiration -> isExpired ( $file [ 'mtime' ], true )) {
2014-06-17 22:08:40 +04:00
$tmp = self :: delete ( $file [ 'name' ], $user , $file [ 'mtime' ]);
2018-04-20 15:35:37 +03:00
\OC :: $server -> getLogger () -> info ( 'remove "' . $file [ 'name' ] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)' , [ 'app' => 'files_trashbin' ]);
2014-06-17 15:51:49 +04:00
$availableSpace += $tmp ;
$size += $tmp ;
} else {
break ;
}
}
}
return $size ;
}
/**
* delete files older then max storage time
*
* @ param array $files list of files sorted by mtime
2014-06-17 22:08:40 +04:00
* @ param string $user
2015-12-07 23:08:20 +03:00
* @ return integer [] size of deleted files and number of deleted files
2014-06-17 15:51:49 +04:00
*/
2015-08-11 22:21:32 +03:00
public static function deleteExpiredFiles ( $files , $user ) {
2019-11-04 18:54:22 +03:00
/** @var Expiration $expiration */
$expiration = \OC :: $server -> query ( Expiration :: class );
2014-06-17 15:51:49 +04:00
$size = 0 ;
$count = 0 ;
foreach ( $files as $file ) {
2014-06-03 18:45:11 +04:00
$timestamp = $file [ 'mtime' ];
2014-06-17 15:51:49 +04:00
$filename = $file [ 'name' ];
2015-07-30 22:31:18 +03:00
if ( $expiration -> isExpired ( $timestamp )) {
2018-05-28 01:59:20 +03:00
try {
$size += self :: delete ( $filename , $user , $timestamp );
$count ++ ;
} catch ( \OCP\Files\NotPermittedException $e ) {
2018-06-24 14:41:16 +03:00
\OC :: $server -> getLogger () -> logException ( $e , [ 'app' => 'files_trashbin' , 'level' => \OCP\ILogger :: WARN , 'message' => 'Removing "' . $filename . '" from trashbin failed.' ]);
2018-05-28 01:59:20 +03:00
}
2015-09-16 22:06:57 +03:00
\OC :: $server -> getLogger () -> info (
'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.' ,
[ 'app' => 'files_trashbin' ]
);
2014-06-17 15:51:49 +04:00
} else {
break ;
2013-02-07 20:37:46 +04:00
}
}
2013-02-22 20:21:57 +04:00
2020-03-26 11:30:18 +03:00
return [ $size , $count ];
2013-01-18 16:11:29 +04:00
}
2013-02-22 20:21:57 +04:00
2013-01-18 16:11:29 +04:00
/**
* recursive copy to copy a whole directory
2013-02-22 20:21:57 +04:00
*
2014-02-06 19:30:58 +04:00
* @ param string $source source path , relative to the users files directory
* @ param string $destination destination path relative to the users root directoy
2016-02-16 19:16:47 +03:00
* @ param View $view file view for the users root directory
2014-10-24 16:13:40 +04:00
* @ return int
* @ throws Exceptions\CopyRecursiveException
2013-01-18 16:11:29 +04:00
*/
2016-02-16 19:16:47 +03:00
private static function copy_recursive ( $source , $destination , View $view ) {
2013-02-07 18:16:29 +04:00
$size = 0 ;
2013-11-25 15:51:32 +04:00
if ( $view -> is_dir ( $source )) {
2013-06-14 17:14:23 +04:00
$view -> mkdir ( $destination );
2013-11-25 15:51:32 +04:00
$view -> touch ( $destination , $view -> filemtime ( $source ));
foreach ( $view -> getDirectoryContent ( $source ) as $i ) {
2013-06-14 17:14:23 +04:00
$pathDir = $source . '/' . $i [ 'name' ];
2013-11-25 15:51:32 +04:00
if ( $view -> is_dir ( $pathDir )) {
2013-06-14 17:14:23 +04:00
$size += self :: copy_recursive ( $pathDir , $destination . '/' . $i [ 'name' ], $view );
2013-01-18 16:11:29 +04:00
} else {
2013-11-25 15:51:32 +04:00
$size += $view -> filesize ( $pathDir );
2014-07-14 19:03:36 +04:00
$result = $view -> copy ( $pathDir , $destination . '/' . $i [ 'name' ]);
if ( ! $result ) {
throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException ();
}
2013-11-25 15:51:32 +04:00
$view -> touch ( $destination . '/' . $i [ 'name' ], $view -> filemtime ( $pathDir ));
2013-01-18 16:11:29 +04:00
}
}
} else {
2013-11-25 15:51:32 +04:00
$size += $view -> filesize ( $source );
2014-07-14 19:03:36 +04:00
$result = $view -> copy ( $source , $destination );
if ( ! $result ) {
throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException ();
}
2013-11-25 15:51:32 +04:00
$view -> touch ( $destination , $view -> filemtime ( $source ));
2013-01-18 16:11:29 +04:00
}
2013-02-07 18:16:29 +04:00
return $size ;
2013-01-18 16:11:29 +04:00
}
2013-02-22 20:21:57 +04:00
2013-01-18 16:11:29 +04:00
/**
* find all versions which belong to the file we want to restore
2014-03-17 19:35:08 +04:00
*
2014-05-13 15:29:25 +04:00
* @ param string $filename name of the file which should be restored
* @ param int $timestamp timestamp when the file was deleted
2014-10-10 20:26:43 +04:00
* @ return array
2013-01-18 16:11:29 +04:00
*/
2015-03-10 13:47:52 +03:00
private static function getVersionsFromTrash ( $filename , $timestamp , $user ) {
2016-02-16 19:16:47 +03:00
$view = new View ( '/' . $user . '/files_trashbin/versions' );
2020-03-26 11:30:18 +03:00
$versions = [];
2014-10-10 20:26:43 +04:00
2020-09-29 18:02:53 +03:00
/** @var \OC\Files\Storage\Storage $storage */
[ $storage ,] = $view -> resolvePath ( '/' );
2014-10-10 20:26:43 +04:00
//force rescan of versions, local storage may not have updated the cache
2015-03-02 14:48:08 +03:00
if ( ! self :: $scannedVersions ) {
$storage -> getScanner () -> scan ( 'files_trashbin/versions' );
self :: $scannedVersions = true ;
}
2014-10-10 20:26:43 +04:00
2020-09-29 18:02:53 +03:00
$pattern = \OC :: $server -> getDatabaseConnection () -> escapeLikeParameter ( basename ( $filename ));
2013-06-14 17:14:23 +04:00
if ( $timestamp ) {
2013-02-09 20:27:57 +04:00
// fetch for old versions
2020-09-29 18:02:53 +03:00
$escapedTimestamp = \OC :: $server -> getDatabaseConnection () -> escapeLikeParameter ( $timestamp );
$pattern .= '.v%.d' . $escapedTimestamp ;
$offset = - strlen ( $escapedTimestamp ) - 2 ;
2013-01-22 15:00:04 +04:00
} else {
2020-09-29 18:02:53 +03:00
$pattern .= '.v%' ;
}
// Manually fetch all versions from the file cache to be able to filter them by their parent
$cache = $storage -> getCache ( '' );
$query = new CacheQueryBuilder (
\OC :: $server -> getDatabaseConnection (),
\OC :: $server -> getSystemConfig (),
\OC :: $server -> getLogger (),
$cache
);
$normalizedParentPath = ltrim ( Filesystem :: normalizePath ( dirname ( 'files_trashbin/versions/' . $filename )), '/' );
$parentId = $cache -> getId ( $normalizedParentPath );
if ( $parentId === - 1 ) {
return [];
}
$query -> selectFileCache ()
-> whereStorageId ()
-> andWhere ( $query -> expr () -> eq ( 'parent' , $query -> createNamedParameter ( $parentId )))
-> andWhere ( $query -> expr () -> iLike ( 'name' , $query -> createNamedParameter ( $pattern )));
/** @var CacheEntry[] $matches */
$matches = array_map ( function ( array $data ) {
return Cache :: cacheEntryFromData ( $data , \OC :: $server -> getMimeTypeLoader ());
}, $query -> execute () -> fetchAll ());
foreach ( $matches as $ma ) {
if ( $timestamp ) {
$parts = explode ( '.v' , substr ( $ma [ 'path' ], 0 , $offset ));
$versions [] = end ( $parts );
} else {
$parts = explode ( '.v' , $ma [ 'path' ]);
$versions [] = end ( $parts );
2013-01-22 15:00:04 +04:00
}
2013-01-18 16:11:29 +04:00
}
2020-09-29 18:02:53 +03:00
2013-01-18 16:11:29 +04:00
return $versions ;
}
2013-02-22 20:21:57 +04:00
2013-01-18 16:11:29 +04:00
/**
* find unique extension for restored file if a file with the same name already exists
2014-03-17 19:35:08 +04:00
*
2014-05-13 15:29:25 +04:00
* @ param string $location where the file should be restored
* @ param string $filename name of the file
2016-02-16 19:16:47 +03:00
* @ param View $view filesystem view relative to users root directory
2013-01-18 16:11:29 +04:00
* @ return string with unique extension
*/
2016-02-16 19:16:47 +03:00
private static function getUniqueFilename ( $location , $filename , View $view ) {
2013-07-25 18:20:06 +04:00
$ext = pathinfo ( $filename , PATHINFO_EXTENSION );
$name = pathinfo ( $filename , PATHINFO_FILENAME );
2014-08-31 12:05:59 +04:00
$l = \OC :: $server -> getL10N ( 'files_trashbin' );
2013-07-25 18:20:06 +04:00
2015-05-12 14:14:57 +03:00
$location = '/' . trim ( $location , '/' );
2013-07-25 18:20:06 +04:00
// if extension is not empty we set a dot in front of it
if ( $ext !== '' ) {
$ext = '.' . $ext ;
}
2013-06-14 17:14:23 +04:00
if ( $view -> file_exists ( 'files' . $location . '/' . $filename )) {
2013-07-25 18:20:06 +04:00
$i = 2 ;
2014-03-17 19:35:08 +04:00
$uniqueName = $name . " ( " . $l -> t ( " restored " ) . " ) " . $ext ;
2013-07-25 18:20:06 +04:00
while ( $view -> file_exists ( 'files' . $location . '/' . $uniqueName )) {
2014-03-17 19:35:08 +04:00
$uniqueName = $name . " ( " . $l -> t ( " restored " ) . " " . $i . " ) " . $ext ;
2013-01-31 21:04:00 +04:00
$i ++ ;
}
2013-07-25 18:20:06 +04:00
return $uniqueName ;
2013-01-18 16:11:29 +04:00
}
2013-07-25 18:20:06 +04:00
return $filename ;
2013-01-18 16:11:29 +04:00
}
2013-02-22 20:21:57 +04:00
/**
2014-05-19 19:50:53 +04:00
* get the size from a given root folder
2015-05-19 15:22:09 +03:00
*
2016-02-16 19:16:47 +03:00
* @ param View $view file view on the root folder
2014-02-06 19:30:58 +04:00
* @ return integer size of the folder
2013-02-22 20:21:57 +04:00
*/
private static function calculateSize ( $view ) {
2017-01-20 04:49:41 +03:00
$root = \OC :: $server -> getConfig () -> getSystemValue ( 'datadirectory' , \OC :: $SERVERROOT . '/data' ) . $view -> getAbsolutePath ( '' );
2013-02-19 00:48:08 +04:00
if ( ! file_exists ( $root )) {
return 0 ;
}
2013-06-14 17:14:23 +04:00
$iterator = new \RecursiveIteratorIterator ( new \RecursiveDirectoryIterator ( $root ), \RecursiveIteratorIterator :: CHILD_FIRST );
2013-02-22 20:21:57 +04:00
$size = 0 ;
2015-05-19 15:22:09 +03:00
/**
2014-04-28 21:32:25 +04:00
* RecursiveDirectoryIterator on an NFS path isn ' t iterable with foreach
* This bug is fixed in PHP 5.5 . 9 or before
* See #8376
*/
$iterator -> rewind ();
while ( $iterator -> valid ()) {
$path = $iterator -> current ();
2013-06-14 17:14:23 +04:00
$relpath = substr ( $path , strlen ( $root ) - 1 );
if ( ! $view -> is_dir ( $relpath )) {
2013-02-07 18:16:29 +04:00
$size += $view -> filesize ( $relpath );
2013-02-22 20:21:57 +04:00
}
2014-04-28 21:32:25 +04:00
$iterator -> next ();
2013-02-22 20:21:57 +04:00
}
return $size ;
2013-02-07 18:16:29 +04:00
}
2013-02-21 15:37:13 +04:00
2013-02-25 17:29:31 +04:00
/**
* get current size of trash bin from a given user
*
2014-05-13 15:29:25 +04:00
* @ param string $user user who owns the trash bin
2014-05-06 15:56:22 +04:00
* @ return integer trash bin size
2013-02-25 17:29:31 +04:00
*/
private static function getTrashbinSize ( $user ) {
2016-02-16 19:16:47 +03:00
$view = new View ( '/' . $user );
2014-02-26 00:35:54 +04:00
$fileInfo = $view -> getFileInfo ( '/files_trashbin' );
2014-05-06 15:56:22 +04:00
return isset ( $fileInfo [ 'size' ]) ? $fileInfo [ 'size' ] : 0 ;
2013-02-21 15:37:13 +04:00
}
2013-05-23 01:50:45 +04:00
2013-07-26 13:13:43 +04:00
/**
2014-05-19 19:50:53 +04:00
* check if trash bin is empty for a given user
2015-05-19 15:22:09 +03:00
*
2013-07-26 13:13:43 +04:00
* @ param string $user
2014-10-24 16:13:40 +04:00
* @ return bool
2013-07-26 13:13:43 +04:00
*/
public static function isEmpty ( $user ) {
2016-02-16 19:16:47 +03:00
$view = new View ( '/' . $user . '/files_trashbin' );
2014-04-13 16:47:08 +04:00
if ( $view -> is_dir ( '/files' ) && $dh = $view -> opendir ( '/files' )) {
while ( $file = readdir ( $dh )) {
2016-02-16 19:16:47 +03:00
if ( ! Filesystem :: isIgnoredDir ( $file )) {
2014-04-13 16:47:08 +04:00
return false ;
}
2014-03-17 20:03:32 +04:00
}
}
return true ;
2013-05-23 01:50:45 +04:00
}
2013-07-08 12:53:53 +04:00
2014-10-24 16:13:40 +04:00
/**
* @ param $path
* @ return string
*/
2013-07-08 12:53:53 +04:00
public static function preview_icon ( $path ) {
2020-03-26 11:30:18 +03:00
return \OC :: $server -> getURLGenerator () -> linkToRoute ( 'core_ajax_trashbin_preview' , [ 'x' => 32 , 'y' => 32 , 'file' => $path ]);
2013-07-08 12:53:53 +04:00
}
2013-01-31 21:04:00 +04:00
}