2012-09-07 16:09:41 +04:00
< ? php
/**
* Copyright ( c ) 2012 Frank Karlitschek < frank @ owncloud . org >
2013-01-11 22:33:54 +04:00
* 2013 Bjoern Schiessle < schiessle @ owncloud . com >
2012-09-07 16:09:41 +04:00
* This file is licensed under the Affero General Public License version 3 or
* later .
* See the COPYING - README file .
*/
/**
* Versions
*
* A class to handle the versioning of files .
*/
2012-09-19 22:59:57 +04:00
namespace OCA\Files_Versions ;
2012-09-07 16:09:41 +04:00
class Storage {
const DEFAULTENABLED = true ;
2012-12-18 15:57:28 +04:00
const DEFAULTMAXSIZE = 50 ; // unit: percentage; 50% of available disk space/quota
2013-10-10 22:09:38 +04:00
const VERSIONS_ROOT = 'files_versions/' ;
2013-02-22 20:21:57 +04:00
2013-11-28 16:17:19 +04:00
// files for which we can remove the versions after the delete operation was successful
private static $deletedFiles = array ();
2013-01-11 17:23:28 +04:00
private static $max_versions_per_interval = array (
2013-02-22 03:21:06 +04:00
//first 10sec, one version every 2sec
1 => array ( 'intervalEndsAfter' => 10 , 'step' => 2 ),
//next minute, one version every 10sec
2 => array ( 'intervalEndsAfter' => 60 , 'step' => 10 ),
//next hour, one version every minute
3 => array ( 'intervalEndsAfter' => 3600 , 'step' => 60 ),
//next 24h, one version every hour
4 => array ( 'intervalEndsAfter' => 86400 , 'step' => 3600 ),
//next 30days, one version per day
5 => array ( 'intervalEndsAfter' => 2592000 , 'step' => 86400 ),
//until the end one version per week
6 => array ( 'intervalEndsAfter' => - 1 , 'step' => 604800 ),
);
2012-09-07 16:09:41 +04:00
2013-02-21 17:40:16 +04:00
public static function getUidAndFilename ( $filename ) {
2013-02-18 14:19:40 +04:00
$uid = \OC\Files\Filesystem :: getOwner ( $filename );
2013-02-22 16:15:47 +04:00
\OC\Files\Filesystem :: initMountPoints ( $uid );
2013-02-18 14:19:40 +04:00
if ( $uid != \OCP\User :: getUser () ) {
$info = \OC\Files\Filesystem :: getFileInfo ( $filename );
$ownerView = new \OC\Files\View ( '/' . $uid . '/files' );
$filename = $ownerView -> getPath ( $info [ 'fileid' ]);
2012-09-07 16:09:41 +04:00
}
2012-09-19 22:54:03 +04:00
return array ( $uid , $filename );
2012-09-07 16:09:41 +04:00
}
2013-02-22 20:21:57 +04:00
2013-02-21 15:20:29 +04:00
/**
* get current size of all versions from a given user
2013-08-17 13:57:50 +04:00
*
2013-02-21 15:20:29 +04:00
* @ param $user user who owns the versions
* @ return mixed versions size or false if no versions size is stored
*/
private static function getVersionsSize ( $user ) {
2013-03-22 15:47:43 +04:00
$query = \OC_DB :: prepare ( 'SELECT `size` FROM `*PREFIX*files_versions` WHERE `user`=?' );
2013-02-21 15:20:29 +04:00
$result = $query -> execute ( array ( $user )) -> fetchAll ();
2013-08-17 13:57:50 +04:00
2013-02-21 15:20:29 +04:00
if ( $result ) {
return $result [ 0 ][ 'size' ];
}
return false ;
}
2013-03-08 13:51:28 +04:00
2013-02-21 15:20:29 +04:00
/**
* write to the database how much space is in use for versions
2013-08-17 13:57:50 +04:00
*
2013-02-21 15:20:29 +04:00
* @ param $user owner of the versions
* @ param $size size of the versions
*/
private static function setVersionsSize ( $user , $size ) {
if ( self :: getVersionsSize ( $user ) === false ) {
2013-03-22 15:47:43 +04:00
$query = \OC_DB :: prepare ( 'INSERT INTO `*PREFIX*files_versions` (`size`, `user`) VALUES (?, ?)' );
2013-02-21 15:20:29 +04:00
} else {
2013-03-22 15:47:43 +04:00
$query = \OC_DB :: prepare ( 'UPDATE `*PREFIX*files_versions` SET `size`=? WHERE `user`=?' );
2013-02-21 15:20:29 +04:00
}
$query -> execute ( array ( $size , $user ));
}
2013-08-17 13:57:50 +04:00
2012-09-07 16:09:41 +04:00
/**
* store a new version of a file .
*/
2013-02-14 17:26:49 +04:00
public static function store ( $filename ) {
2012-09-07 16:09:41 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2013-08-17 13:57:50 +04:00
2013-03-08 14:27:25 +04:00
// if the file gets streamed we need to remove the .part extension
// to get the right target
$ext = pathinfo ( $filename , PATHINFO_EXTENSION );
if ( $ext === 'part' ) {
$filename = substr ( $filename , 0 , strlen ( $filename ) - 5 );
}
2013-08-17 13:57:50 +04:00
2012-09-19 22:54:03 +04:00
list ( $uid , $filename ) = self :: getUidAndFilename ( $filename );
2013-02-22 20:21:57 +04:00
2013-02-14 14:56:41 +04:00
$files_view = new \OC\Files\View ( '/' . $uid . '/files' );
$users_view = new \OC\Files\View ( '/' . $uid );
2012-09-07 16:09:41 +04:00
// check if filename is a directory
2012-09-07 17:22:01 +04:00
if ( $files_view -> is_dir ( $filename )) {
2012-09-07 16:09:41 +04:00
return false ;
}
2013-07-30 18:01:27 +04:00
// we should have a source file to work with, and the file shouldn't
// be empty
$fileExists = $files_view -> file_exists ( $filename );
2013-08-17 13:57:50 +04:00
if ( ! ( $fileExists && $files_view -> filesize ( $filename ) > 0 )) {
2012-09-17 19:29:34 +04:00
return false ;
}
2012-09-07 16:09:41 +04:00
// create all parent folders
2013-08-17 15:28:35 +04:00
self :: createMissingDirectories ( $filename , $users_view );
2012-09-07 16:09:41 +04:00
2013-06-25 11:39:01 +04:00
$versionsSize = self :: getVersionsSize ( $uid );
if ( $versionsSize === false || $versionsSize < 0 ) {
$versionsSize = self :: calculateSize ( $uid );
}
2013-06-27 12:49:13 +04:00
// assumption: we need filesize($filename) for the new version +
2013-06-25 11:39:01 +04:00
// some more free space for the modified file which might be
// 1.5 times as large as the current version -> 2.5
$neededSpace = $files_view -> filesize ( $filename ) * 2.5 ;
$versionsSize = self :: expire ( $filename , $versionsSize , $neededSpace );
2013-05-31 00:05:52 +04:00
// disable proxy to prevent multiple fopen calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2012-09-07 16:09:41 +04:00
// store a new version of a file
2014-01-15 14:37:47 +04:00
$mtime = $users_view -> filemtime ( 'files' . $filename );
$users_view -> copy ( 'files' . $filename , 'files_versions' . $filename . '.v' . $mtime );
// call getFileInfo to enforce a file cache entry for the new version
$users_view -> getFileInfo ( 'files_versions' . $filename . '.v' . $mtime );
2013-05-31 00:05:52 +04:00
// reset proxy state
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-01-10 21:04:30 +04:00
$versionsSize += $users_view -> filesize ( 'files' . $filename );
2013-02-21 15:20:29 +04:00
2013-06-26 18:24:46 +04:00
self :: setVersionsSize ( $uid , $versionsSize );
2012-09-07 16:09:41 +04:00
}
}
2013-01-10 21:04:30 +04:00
/**
2013-11-28 16:17:19 +04:00
* @ brief mark file as deleted so that we can remove the versions if the file is gone
* @ param string $path
2013-01-10 21:04:30 +04:00
*/
2013-11-28 16:17:19 +04:00
public static function markDeletedFile ( $path ) {
list ( $uid , $filename ) = self :: getUidAndFilename ( $path );
self :: $deletedFiles [ $path ] = array (
'uid' => $uid ,
'filename' => $filename );
}
2013-02-22 20:21:57 +04:00
2013-11-28 16:17:19 +04:00
/**
* Delete versions of a file
*/
public static function delete ( $path ) {
$deletedFile = self :: $deletedFiles [ $path ];
$uid = $deletedFile [ 'uid' ];
$filename = $deletedFile [ 'filename' ];
if ( ! \OC\Files\Filesystem :: file_exists ( $path )) {
$versions_fileview = new \OC\Files\View ( '/' . $uid . '/files_versions' );
$abs_path = $versions_fileview -> getLocalFile ( $filename . '.v' );
2013-11-28 22:31:35 +04:00
$versions = self :: getVersions ( $uid , $filename );
if ( ! empty ( $versions )) {
2013-11-28 16:17:19 +04:00
$versionsSize = self :: getVersionsSize ( $uid );
if ( $versionsSize === false || $versionsSize < 0 ) {
$versionsSize = self :: calculateSize ( $uid );
}
foreach ( $versions as $v ) {
unlink ( $abs_path . $v [ 'version' ]);
\OC_Hook :: emit ( '\OCP\Versions' , 'delete' , array ( 'path' => $abs_path . $v [ 'version' ]));
$versionsSize -= $v [ 'size' ];
}
self :: setVersionsSize ( $uid , $versionsSize );
2013-01-15 17:57:23 +04:00
}
2013-01-10 21:04:30 +04:00
}
2013-11-28 16:17:19 +04:00
unset ( self :: $deletedFiles [ $path ]);
2013-01-10 21:04:30 +04:00
}
2013-02-22 20:21:57 +04:00
2013-01-15 17:57:23 +04:00
/**
* rename versions of a file
*/
2013-03-14 19:47:59 +04:00
public static function rename ( $old_path , $new_path ) {
list ( $uid , $oldpath ) = self :: getUidAndFilename ( $old_path );
list ( $uidn , $newpath ) = self :: getUidAndFilename ( $new_path );
2013-02-18 14:19:40 +04:00
$versions_view = new \OC\Files\View ( '/' . $uid . '/files_versions' );
$files_view = new \OC\Files\View ( '/' . $uid . '/files' );
2013-08-17 13:57:50 +04:00
2013-03-14 19:47:59 +04:00
// if the file already exists than it was a upload of a existing file
// over the web interface -> store() is the right function we need here
2013-03-14 20:09:48 +04:00
if ( $files_view -> file_exists ( $newpath )) {
return self :: store ( $new_path );
2013-03-14 19:47:59 +04:00
}
2013-06-25 11:39:01 +04:00
2013-06-26 18:28:23 +04:00
self :: expire ( $newpath );
2013-01-11 17:23:28 +04:00
if ( $files_view -> is_dir ( $oldpath ) && $versions_view -> is_dir ( $oldpath ) ) {
$versions_view -> rename ( $oldpath , $newpath );
2013-06-25 11:39:01 +04:00
} else if ( ( $versions = Storage :: getVersions ( $uid , $oldpath )) ) {
2013-08-14 22:51:36 +04:00
// create missing dirs if necessary
2013-08-17 15:28:35 +04:00
self :: createMissingDirectories ( $newpath , new \OC\Files\View ( '/' . $uidn ));
2013-08-17 15:46:33 +04:00
2013-01-11 17:23:28 +04:00
foreach ( $versions as $v ) {
2013-01-15 17:57:23 +04:00
$versions_view -> rename ( $oldpath . '.v' . $v [ 'version' ], $newpath . '.v' . $v [ 'version' ]);
}
}
2013-01-11 17:23:28 +04:00
}
2013-02-22 20:21:57 +04:00
2012-09-07 16:09:41 +04:00
/**
* rollback to an old version of a file .
*/
2013-05-08 17:05:03 +04:00
public static function rollback ( $file , $revision ) {
2012-09-07 16:09:41 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2013-05-08 17:05:03 +04:00
list ( $uid , $filename ) = self :: getUidAndFilename ( $file );
2013-01-16 22:04:50 +04:00
$users_view = new \OC\Files\View ( '/' . $uid );
2013-05-08 17:05:03 +04:00
$files_view = new \OC\Files\View ( '/' . \OCP\User :: getUser () . '/files' );
2013-01-16 13:18:40 +04:00
$versionCreated = false ;
2013-02-22 20:21:57 +04:00
2012-12-17 21:00:11 +04:00
//first create a new version
2013-01-11 17:23:28 +04:00
$version = 'files_versions' . $filename . '.v' . $users_view -> filemtime ( 'files' . $filename );
if ( ! $users_view -> file_exists ( $version )) {
2013-05-31 00:05:52 +04:00
// disable proxy to prevent multiple fopen calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2012-12-17 21:00:11 +04:00
$users_view -> copy ( 'files' . $filename , 'files_versions' . $filename . '.v' . $users_view -> filemtime ( 'files' . $filename ));
2013-05-31 00:05:52 +04:00
// reset proxy state
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-01-11 17:23:28 +04:00
$versionCreated = true ;
2012-12-17 21:00:11 +04:00
}
2013-02-22 20:21:57 +04:00
2012-09-07 16:09:41 +04:00
// rollback
2013-05-08 18:32:29 +04:00
if ( @ $users_view -> rename ( 'files_versions' . $filename . '.v' . $revision , 'files' . $filename ) ) {
2013-05-08 17:05:03 +04:00
$files_view -> touch ( $file , $revision );
Storage :: expire ( $file );
2012-09-07 16:09:41 +04:00
return true ;
2013-01-11 17:23:28 +04:00
} else if ( $versionCreated ) {
$users_view -> unlink ( $version );
2012-09-07 16:09:41 +04:00
}
}
2013-01-11 17:23:28 +04:00
return false ;
2012-09-07 16:09:41 +04:00
}
/**
* @ brief get a list of all available versions of a file in descending chronological order
2014-01-21 16:50:56 +04:00
* @ param string $uid user id from the owner of the file
* @ param string $filename file to find versions of , relative to the user files dir
* @ param string $userFullPath
2014-01-22 14:13:15 +04:00
* @ returns array versions newest version first
2012-09-07 16:09:41 +04:00
*/
2014-01-21 16:50:56 +04:00
public static function getVersions ( $uid , $filename , $userFullPath = '' ) {
2013-10-10 16:43:40 +04:00
$versions = array ();
// fetch for old versions
2013-10-10 22:09:38 +04:00
$view = new \OC\Files\View ( '/' . $uid . '/' . self :: VERSIONS_ROOT );
2013-10-10 16:43:40 +04:00
2013-10-10 22:06:42 +04:00
$pathinfo = pathinfo ( $filename );
$files = $view -> getDirectoryContent ( $pathinfo [ 'dirname' ]);
$versionedFile = $pathinfo [ 'basename' ];
2013-10-10 16:43:40 +04:00
foreach ( $files as $file ) {
if ( $file [ 'type' ] === 'file' ) {
$pos = strrpos ( $file [ 'path' ], '.v' );
2013-10-10 18:58:11 +04:00
$currentFile = substr ( $file [ 'name' ], 0 , strrpos ( $file [ 'name' ], '.v' ));
if ( $currentFile === $versionedFile ) {
2013-10-10 16:43:40 +04:00
$version = substr ( $file [ 'path' ], $pos + 2 );
$key = $version . '#' . $filename ;
$versions [ $key ][ 'cur' ] = 0 ;
$versions [ $key ][ 'version' ] = $version ;
$versions [ $key ][ 'humanReadableTimestamp' ] = self :: getHumanReadableTimestamp ( $version );
2014-01-21 16:50:56 +04:00
if ( empty ( $userFullPath )) {
$versions [ $key ][ 'preview' ] = '' ;
} else {
$versions [ $key ][ 'preview' ] = \OCP\Util :: linkToRoute ( 'core_ajax_versions_preview' , array ( 'file' => $userFullPath , 'version' => $version ));
}
2013-10-10 16:43:40 +04:00
$versions [ $key ][ 'path' ] = $filename ;
2013-11-25 15:51:32 +04:00
$versions [ $key ][ 'name' ] = $versionedFile ;
2013-10-10 16:43:40 +04:00
$versions [ $key ][ 'size' ] = $file [ 'size' ];
2012-09-07 16:09:41 +04:00
}
}
}
2013-10-10 22:06:42 +04:00
// sort with newest version first
krsort ( $versions );
2013-10-10 16:43:40 +04:00
2013-10-10 22:06:42 +04:00
return $versions ;
2012-09-07 16:09:41 +04:00
}
2013-07-25 12:35:19 +04:00
/**
* @ brief translate a timestamp into a string like " 5 days ago "
* @ param int $timestamp
* @ return string for example " 5 days ago "
*/
private static function getHumanReadableTimestamp ( $timestamp ) {
$diff = time () - $timestamp ;
if ( $diff < 60 ) { // first minute
return $diff . " seconds ago " ;
} elseif ( $diff < 3600 ) { //first hour
return round ( $diff / 60 ) . " minutes ago " ;
} elseif ( $diff < 86400 ) { // first day
return round ( $diff / 3600 ) . " hours ago " ;
} elseif ( $diff < 604800 ) { //first week
return round ( $diff / 86400 ) . " days ago " ;
} elseif ( $diff < 2419200 ) { //first month
return round ( $diff / 604800 ) . " weeks ago " ;
} elseif ( $diff < 29030400 ) { // first year
return round ( $diff / 2419200 ) . " months ago " ;
} else {
return round ( $diff / 29030400 ) . " years ago " ;
}
}
2013-04-11 14:36:08 +04:00
/**
* @ brief deletes used space for files versions in db if user was deleted
*
* @ param type $uid id of deleted user
2014-02-19 12:31:54 +04:00
* @ return \OC_DB_StatementWrapper of db delete operation
2013-04-11 14:36:08 +04:00
*/
public static function deleteUser ( $uid ) {
$query = \OC_DB :: prepare ( 'DELETE FROM `*PREFIX*files_versions` WHERE `user`=?' );
return $query -> execute ( array ( $uid ));
}
2012-09-07 16:09:41 +04:00
/**
2013-01-15 17:57:23 +04:00
* @ brief get the size of all stored versions from a given user
* @ param $uid id from the user
* @ return size of vesions
2012-09-07 16:09:41 +04:00
*/
2013-01-15 17:57:23 +04:00
private static function calculateSize ( $uid ) {
2013-11-19 15:23:14 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
$view = new \OC\Files\View ( '/' . $uid . '/files_versions' );
2013-02-22 20:21:57 +04:00
2013-01-10 21:04:30 +04:00
$size = 0 ;
2013-02-22 20:21:57 +04:00
2013-11-19 15:23:14 +04:00
$dirContent = $view -> getDirectoryContent ( '/' );
while ( ! empty ( $dirContent )) {
$path = reset ( $dirContent );
if ( $path [ 'type' ] === 'dir' ) {
$dirContent = array_merge ( $dirContent , $view -> getDirectoryContent ( substr ( $path [ 'path' ], strlen ( 'files_versions' ))));
} else {
$size += $view -> filesize ( substr ( $path [ 'path' ], strlen ( 'files_versions' )));
2012-09-07 16:09:41 +04:00
}
2013-11-19 15:23:14 +04:00
unset ( $dirContent [ key ( $dirContent )]);
2012-09-07 16:09:41 +04:00
}
2013-02-22 20:21:57 +04:00
2013-01-15 17:57:23 +04:00
return $size ;
}
2013-01-10 21:04:30 +04:00
}
2013-02-22 20:21:57 +04:00
2013-01-09 20:11:46 +04:00
/**
* @ brief returns all stored file versions from a given user
2014-02-06 19:30:58 +04:00
* @ param string $uid id of the user
2013-01-09 20:11:46 +04:00
* @ return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
*/
private static function getAllVersions ( $uid ) {
2013-10-10 16:43:40 +04:00
$view = new \OC\Files\View ( '/' . $uid . '/' );
2013-10-10 22:09:38 +04:00
$dirs = array ( self :: VERSIONS_ROOT );
2013-10-10 16:43:40 +04:00
while ( ! empty ( $dirs )) {
$dir = array_pop ( $dirs );
$files = $view -> getDirectoryContent ( $dir );
foreach ( $files as $file ) {
if ( $file [ 'type' ] === 'dir' ) {
array_push ( $dirs , $file [ 'path' ]);
} else {
$versionsBegin = strrpos ( $file [ 'path' ], '.v' );
2013-10-11 12:34:34 +04:00
$relPathStart = strlen ( self :: VERSIONS_ROOT );
2013-10-10 16:43:40 +04:00
$version = substr ( $file [ 'path' ], $versionsBegin + 2 );
$relpath = substr ( $file [ 'path' ], $relPathStart , $versionsBegin - $relPathStart );
$key = $version . '#' . $relpath ;
$versions [ $key ] = array ( 'path' => $relpath , 'timestamp' => $version );
2013-01-09 20:11:46 +04:00
}
2013-01-15 17:57:23 +04:00
}
2013-10-10 16:43:40 +04:00
}
2013-02-22 20:21:57 +04:00
2014-01-22 14:13:15 +04:00
// newest version first
krsort ( $versions );
2013-02-22 20:21:57 +04:00
2013-10-10 16:43:40 +04:00
$result = array ();
2013-02-22 20:21:57 +04:00
2013-10-10 16:43:40 +04:00
foreach ( $versions as $key => $value ) {
2014-01-22 14:10:23 +04:00
$size = $view -> filesize ( self :: VERSIONS_ROOT . '/' . $value [ 'path' ] . '.v' . $value [ 'timestamp' ]);
2013-10-10 16:43:40 +04:00
$filename = $value [ 'path' ];
2013-02-22 20:21:57 +04:00
2013-10-10 16:43:40 +04:00
$result [ 'all' ][ $key ][ 'version' ] = $value [ 'timestamp' ];
$result [ 'all' ][ $key ][ 'path' ] = $filename ;
$result [ 'all' ][ $key ][ 'size' ] = $size ;
2013-02-22 20:21:57 +04:00
2013-10-10 16:43:40 +04:00
$result [ 'by_file' ][ $filename ][ $key ][ 'version' ] = $value [ 'timestamp' ];
$result [ 'by_file' ][ $filename ][ $key ][ 'path' ] = $filename ;
$result [ 'by_file' ][ $filename ][ $key ][ 'size' ] = $size ;
2012-09-07 16:09:41 +04:00
}
2013-10-10 16:43:40 +04:00
return $result ;
2012-09-07 16:09:41 +04:00
}
2014-01-20 19:03:26 +04:00
/**
* @ brief get list of files we want to expire
* @ param array $versions list of versions
2014-02-19 12:31:54 +04:00
* @ param integer $time
2014-01-20 19:03:26 +04:00
* @ return array containing the list of to deleted versions and the size of them
*/
protected static function getExpireList ( $time , $versions ) {
$size = 0 ;
$toDelete = array (); // versions we want to delete
$interval = 1 ;
$step = Storage :: $max_versions_per_interval [ $interval ][ 'step' ];
if ( Storage :: $max_versions_per_interval [ $interval ][ 'intervalEndsAfter' ] == - 1 ) {
$nextInterval = - 1 ;
} else {
$nextInterval = $time - Storage :: $max_versions_per_interval [ $interval ][ 'intervalEndsAfter' ];
}
$firstVersion = reset ( $versions );
$firstKey = key ( $versions );
$prevTimestamp = $firstVersion [ 'version' ];
$nextVersion = $firstVersion [ 'version' ] - $step ;
unset ( $versions [ $firstKey ]);
foreach ( $versions as $key => $version ) {
$newInterval = true ;
while ( $newInterval ) {
if ( $nextInterval == - 1 || $prevTimestamp > $nextInterval ) {
if ( $version [ 'version' ] > $nextVersion ) {
//distance between two version too small, mark to delete
$toDelete [ $key ] = $version [ 'path' ] . '.v' . $version [ 'version' ];
$size += $version [ 'size' ];
\OCP\Util :: writeLog ( 'files_versions' , 'Mark to expire ' . $version [ 'path' ] . ' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . " ; step: " . $step , \OCP\Util :: DEBUG );
} else {
$nextVersion = $version [ 'version' ] - $step ;
$prevTimestamp = $version [ 'version' ];
}
$newInterval = false ; // version checked so we can move to the next one
} else { // time to move on to the next interval
$interval ++ ;
$step = Storage :: $max_versions_per_interval [ $interval ][ 'step' ];
$nextVersion = $prevTimestamp - $step ;
if ( Storage :: $max_versions_per_interval [ $interval ][ 'intervalEndsAfter' ] == - 1 ) {
$nextInterval = - 1 ;
} else {
$nextInterval = $time - Storage :: $max_versions_per_interval [ $interval ][ 'intervalEndsAfter' ];
}
$newInterval = true ; // we changed the interval -> check same version with new interval
}
}
}
return array ( $toDelete , $size );
}
2012-09-07 16:09:41 +04:00
/**
2012-11-04 21:42:18 +04:00
* @ brief Erase a file ' s versions which exceed the set quota
2012-09-07 16:09:41 +04:00
*/
2013-06-25 11:39:01 +04:00
private static function expire ( $filename , $versionsSize = null , $offset = 0 ) {
2012-11-04 21:42:18 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2013-02-22 20:21:57 +04:00
list ( $uid , $filename ) = self :: getUidAndFilename ( $filename );
2013-06-28 22:31:33 +04:00
$versionsFileview = new \OC\Files\View ( '/' . $uid . '/files_versions' );
2013-02-22 20:21:57 +04:00
2012-12-17 16:28:40 +04:00
// get available disk space for user
2013-04-16 15:52:46 +04:00
$softQuota = true ;
2013-03-04 15:59:48 +04:00
$quota = \OC_Preferences :: getValue ( $uid , 'files' , 'quota' );
2013-03-04 15:17:57 +04:00
if ( $quota === null || $quota === 'default' ) {
2014-02-13 19:28:49 +04:00
$quota = \OC :: $server -> getAppConfig () -> getValue ( 'files' , 'default_quota' );
2012-12-17 16:28:40 +04:00
}
2013-03-04 15:33:16 +04:00
if ( $quota === null || $quota === 'none' ) {
2013-04-16 15:52:46 +04:00
$quota = \OC\Files\Filesystem :: free_space ( '/' );
$softQuota = false ;
2013-02-25 19:12:44 +04:00
} else {
$quota = \OCP\Util :: computerFileSize ( $quota );
2012-12-13 19:34:54 +04:00
}
2013-08-17 13:57:50 +04:00
2013-01-11 22:33:54 +04:00
// make sure that we have the current size of the version history
2013-01-10 21:04:30 +04:00
if ( $versionsSize === null ) {
2013-02-21 15:20:29 +04:00
$versionsSize = self :: getVersionsSize ( $uid );
if ( $versionsSize === false || $versionsSize < 0 ) {
2013-01-10 21:04:30 +04:00
$versionsSize = self :: calculateSize ( $uid );
}
}
2013-01-11 22:33:54 +04:00
2013-01-15 17:57:23 +04:00
// calculate available space for version history
2013-04-16 15:52:46 +04:00
// subtract size of files and current versions size from quota
if ( $softQuota ) {
$files_view = new \OC\Files\View ( '/' . $uid . '/files' );
2014-01-20 20:10:09 +04:00
$rootInfo = $files_view -> getFileInfo ( '/' , false );
2013-04-16 15:52:46 +04:00
$free = $quota - $rootInfo [ 'size' ]; // remaining free space for user
if ( $free > 0 ) {
2013-06-25 11:39:01 +04:00
$availableSpace = ( $free * self :: DEFAULTMAXSIZE / 100 ) - ( $versionsSize + $offset ); // how much space can be used for versions
2013-04-16 15:52:46 +04:00
} else {
2013-06-25 11:39:01 +04:00
$availableSpace = $free - $versionsSize - $offset ;
2013-04-16 15:52:46 +04:00
}
2013-01-10 21:04:30 +04:00
} else {
2013-06-25 11:39:01 +04:00
$availableSpace = $quota - $offset ;
2013-02-22 20:21:57 +04:00
}
2013-01-09 20:11:46 +04:00
2013-06-28 22:31:33 +04:00
$allVersions = Storage :: getVersions ( $uid , $filename );
2013-02-22 20:21:57 +04:00
2014-01-20 19:03:26 +04:00
$time = time ();
list ( $toDelete , $sizeOfDeletedVersions ) = self :: getExpireList ( $time , $allVersions );
2013-06-25 11:39:01 +04:00
$availableSpace = $availableSpace + $sizeOfDeletedVersions ;
$versionsSize = $versionsSize - $sizeOfDeletedVersions ;
2013-02-22 20:21:57 +04:00
2013-06-25 11:39:01 +04:00
// if still not enough free space we rearrange the versions from all files
2014-01-20 19:03:26 +04:00
if ( $availableSpace <= 0 ) {
2013-06-25 11:39:01 +04:00
$result = Storage :: getAllVersions ( $uid );
2013-06-28 22:31:33 +04:00
$allVersions = $result [ 'all' ];
2013-02-22 20:21:57 +04:00
2014-01-20 19:03:26 +04:00
foreach ( $result [ 'by_file' ] as $versions ) {
list ( $toDeleteNew , $size ) = self :: getExpireList ( $time , $versions );
$toDelete = array_merge ( $toDelete , $toDeleteNew );
$sizeOfDeletedVersions += $size ;
}
2013-06-25 11:39:01 +04:00
$availableSpace = $availableSpace + $sizeOfDeletedVersions ;
$versionsSize = $versionsSize - $sizeOfDeletedVersions ;
2012-11-04 21:42:18 +04:00
}
2013-02-22 20:21:57 +04:00
2014-01-20 19:03:26 +04:00
foreach ( $toDelete as $key => $path ) {
\OC_Hook :: emit ( '\OCP\Versions' , 'delete' , array ( 'path' => $path ));
$versionsFileview -> unlink ( $path );
unset ( $allVersions [ $key ]); // update array with the versions we keep
\OCP\Util :: writeLog ( 'files_versions' , " Expire: " . $path , \OCP\Util :: DEBUG );
}
2013-03-04 20:20:14 +04:00
// Check if enough space is available after versions are rearranged.
// If not we delete the oldest versions until we meet the size limit for versions,
// but always keep the two latest versions
2013-06-28 22:31:33 +04:00
$numOfVersions = count ( $allVersions ) - 2 ;
2013-02-22 20:21:57 +04:00
$i = 0 ;
2013-03-04 20:20:14 +04:00
while ( $availableSpace < 0 && $i < $numOfVersions ) {
2013-06-28 22:31:33 +04:00
$version = current ( $allVersions );
$versionsFileview -> unlink ( $version [ 'path' ] . '.v' . $version [ 'version' ]);
2013-11-12 17:06:32 +04:00
\OC_Hook :: emit ( '\OCP\Versions' , 'delete' , array ( 'path' => $version [ 'path' ] . '.v' . $version [ 'version' ]));
2014-01-20 19:03:26 +04:00
\OCP\Util :: writeLog ( 'files_versions' , 'running out of space! Delete oldest version: ' . $version [ 'path' ] . '.v' . $version [ 'version' ] , \OCP\Util :: DEBUG );
2013-06-28 22:31:33 +04:00
$versionsSize -= $version [ 'size' ];
$availableSpace += $version [ 'size' ];
next ( $allVersions );
2012-12-17 19:32:09 +04:00
$i ++ ;
}
2013-02-22 20:21:57 +04:00
2013-01-11 22:33:54 +04:00
return $versionsSize ; // finally return the new size of the version history
2012-11-04 21:42:18 +04:00
}
2013-02-22 20:21:57 +04:00
2013-01-11 14:12:32 +04:00
return false ;
2012-09-07 16:09:41 +04:00
}
2013-06-25 11:39:01 +04:00
2013-08-17 15:28:35 +04:00
/**
* @ brief create recursively missing directories
* @ param string $filename $path to a file
2013-08-17 15:46:33 +04:00
* @ param \OC\Files\View $view view on data / user /
2013-08-17 15:28:35 +04:00
*/
private static function createMissingDirectories ( $filename , $view ) {
2013-08-17 15:49:42 +04:00
$dirname = \OC_Filesystem :: normalizePath ( dirname ( $filename ));
2013-08-17 15:28:35 +04:00
$dirParts = explode ( '/' , $dirname );
$dir = " /files_versions " ;
foreach ( $dirParts as $part ) {
$dir = $dir . '/' . $part ;
if ( ! $view -> file_exists ( $dir )) {
$view -> mkdir ( $dir );
}
}
}
2012-09-07 16:09:41 +04:00
}