2012-09-07 08:09:41 -04:00
< ? php
/**
* Copyright ( c ) 2012 Frank Karlitschek < frank @ owncloud . org >
2013-01-11 13:33:54 -05:00
* 2013 Bjoern Schiessle < schiessle @ owncloud . com >
2012-09-07 08: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 14:59:57 -04:00
namespace OCA\Files_Versions ;
2012-09-07 08:09:41 -04:00
class Storage {
const DEFAULTENABLED = true ;
2012-12-18 06:57:28 -05:00
const DEFAULTMAXSIZE = 50 ; // unit: percentage; 50% of available disk space/quota
2013-10-10 14:09:38 -04:00
const VERSIONS_ROOT = 'files_versions/' ;
2013-02-22 11:21:57 -05:00
2013-11-28 07:17:19 -05:00
// files for which we can remove the versions after the delete operation was successful
private static $deletedFiles = array ();
2013-01-11 08:23:28 -05:00
private static $max_versions_per_interval = array (
2013-02-21 18:21:06 -05: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 08:09:41 -04:00
2013-02-21 08:40:16 -05:00
public static function getUidAndFilename ( $filename ) {
2013-02-18 05:19:40 -05:00
$uid = \OC\Files\Filesystem :: getOwner ( $filename );
2013-02-22 07:15:47 -05:00
\OC\Files\Filesystem :: initMountPoints ( $uid );
2013-02-18 05:19:40 -05: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 08:09:41 -04:00
}
2012-09-19 14:54:03 -04:00
return array ( $uid , $filename );
2012-09-07 08:09:41 -04:00
}
2013-02-22 11:21:57 -05:00
2013-02-21 06:20:29 -05:00
/**
* get current size of all versions from a given user
2013-08-17 05:57:50 -04:00
*
2013-02-21 06:20:29 -05: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 07:47:43 -04:00
$query = \OC_DB :: prepare ( 'SELECT `size` FROM `*PREFIX*files_versions` WHERE `user`=?' );
2013-02-21 06:20:29 -05:00
$result = $query -> execute ( array ( $user )) -> fetchAll ();
2013-08-17 05:57:50 -04:00
2013-02-21 06:20:29 -05:00
if ( $result ) {
return $result [ 0 ][ 'size' ];
}
return false ;
}
2013-03-08 04:51:28 -05:00
2013-02-21 06:20:29 -05:00
/**
* write to the database how much space is in use for versions
2013-08-17 05:57:50 -04:00
*
2013-02-21 06:20:29 -05: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 07:47:43 -04:00
$query = \OC_DB :: prepare ( 'INSERT INTO `*PREFIX*files_versions` (`size`, `user`) VALUES (?, ?)' );
2013-02-21 06:20:29 -05:00
} else {
2013-03-22 07:47:43 -04:00
$query = \OC_DB :: prepare ( 'UPDATE `*PREFIX*files_versions` SET `size`=? WHERE `user`=?' );
2013-02-21 06:20:29 -05:00
}
$query -> execute ( array ( $size , $user ));
}
2013-08-17 05:57:50 -04:00
2012-09-07 08:09:41 -04:00
/**
* store a new version of a file .
*/
2013-02-14 08:26:49 -05:00
public static function store ( $filename ) {
2012-09-07 08:09:41 -04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2013-08-17 05:57:50 -04:00
2013-03-08 05:27:25 -05: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 05:57:50 -04:00
2012-09-19 14:54:03 -04:00
list ( $uid , $filename ) = self :: getUidAndFilename ( $filename );
2013-02-22 11:21:57 -05:00
2013-02-14 05:56:41 -05:00
$files_view = new \OC\Files\View ( '/' . $uid . '/files' );
$users_view = new \OC\Files\View ( '/' . $uid );
2012-09-07 08:09:41 -04:00
// check if filename is a directory
2012-09-07 09:22:01 -04:00
if ( $files_view -> is_dir ( $filename )) {
2012-09-07 08:09:41 -04:00
return false ;
}
2013-07-30 10: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 05:57:50 -04:00
if ( ! ( $fileExists && $files_view -> filesize ( $filename ) > 0 )) {
2012-09-17 11:29:34 -04:00
return false ;
}
2012-09-07 08:09:41 -04:00
// create all parent folders
2013-08-17 07:28:35 -04:00
self :: createMissingDirectories ( $filename , $users_view );
2012-09-07 08:09:41 -04:00
2013-06-25 03:39:01 -04:00
$versionsSize = self :: getVersionsSize ( $uid );
if ( $versionsSize === false || $versionsSize < 0 ) {
$versionsSize = self :: calculateSize ( $uid );
}
2013-06-27 04:49:13 -04:00
// assumption: we need filesize($filename) for the new version +
2013-06-25 03: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-30 16:05:52 -04:00
// disable proxy to prevent multiple fopen calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2012-09-07 08:09:41 -04:00
// store a new version of a file
2014-01-15 05:37:47 -05: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-30 16:05:52 -04:00
// reset proxy state
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-01-10 12:04:30 -05:00
$versionsSize += $users_view -> filesize ( 'files' . $filename );
2013-02-21 06:20:29 -05:00
2013-06-26 10:24:46 -04:00
self :: setVersionsSize ( $uid , $versionsSize );
2012-09-07 08:09:41 -04:00
}
}
2013-01-10 12:04:30 -05:00
/**
2013-11-28 07:17:19 -05:00
* @ brief mark file as deleted so that we can remove the versions if the file is gone
* @ param string $path
2013-01-10 12:04:30 -05:00
*/
2013-11-28 07:17:19 -05:00
public static function markDeletedFile ( $path ) {
list ( $uid , $filename ) = self :: getUidAndFilename ( $path );
self :: $deletedFiles [ $path ] = array (
'uid' => $uid ,
'filename' => $filename );
}
2013-02-22 11:21:57 -05:00
2013-11-28 07:17:19 -05: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 13:31:35 -05:00
$versions = self :: getVersions ( $uid , $filename );
if ( ! empty ( $versions )) {
2013-11-28 07:17:19 -05: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 08:57:23 -05:00
}
2013-01-10 12:04:30 -05:00
}
2013-11-28 07:17:19 -05:00
unset ( self :: $deletedFiles [ $path ]);
2013-01-10 12:04:30 -05:00
}
2013-02-22 11:21:57 -05:00
2013-01-15 08:57:23 -05:00
/**
* rename versions of a file
*/
2013-03-14 11: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 05:19:40 -05:00
$versions_view = new \OC\Files\View ( '/' . $uid . '/files_versions' );
$files_view = new \OC\Files\View ( '/' . $uid . '/files' );
2013-08-17 05:57:50 -04:00
2013-03-14 11: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 12:09:48 -04:00
if ( $files_view -> file_exists ( $newpath )) {
return self :: store ( $new_path );
2013-03-14 11:47:59 -04:00
}
2013-06-25 03:39:01 -04:00
2013-06-26 10:28:23 -04:00
self :: expire ( $newpath );
2013-01-11 08:23:28 -05:00
if ( $files_view -> is_dir ( $oldpath ) && $versions_view -> is_dir ( $oldpath ) ) {
$versions_view -> rename ( $oldpath , $newpath );
2013-06-25 03:39:01 -04:00
} else if ( ( $versions = Storage :: getVersions ( $uid , $oldpath )) ) {
2013-08-14 14:51:36 -04:00
// create missing dirs if necessary
2013-08-17 07:28:35 -04:00
self :: createMissingDirectories ( $newpath , new \OC\Files\View ( '/' . $uidn ));
2013-08-17 07:46:33 -04:00
2013-01-11 08:23:28 -05:00
foreach ( $versions as $v ) {
2013-01-15 08:57:23 -05:00
$versions_view -> rename ( $oldpath . '.v' . $v [ 'version' ], $newpath . '.v' . $v [ 'version' ]);
}
}
2013-01-11 08:23:28 -05:00
}
2013-02-22 11:21:57 -05:00
2012-09-07 08:09:41 -04:00
/**
* rollback to an old version of a file .
*/
2013-05-08 09:05:03 -04:00
public static function rollback ( $file , $revision ) {
2012-09-07 08:09:41 -04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2013-05-08 09:05:03 -04:00
list ( $uid , $filename ) = self :: getUidAndFilename ( $file );
2013-01-16 13:04:50 -05:00
$users_view = new \OC\Files\View ( '/' . $uid );
2013-05-08 09:05:03 -04:00
$files_view = new \OC\Files\View ( '/' . \OCP\User :: getUser () . '/files' );
2013-01-16 04:18:40 -05:00
$versionCreated = false ;
2013-02-22 11:21:57 -05:00
2012-12-17 12:00:11 -05:00
//first create a new version
2013-01-11 08:23:28 -05:00
$version = 'files_versions' . $filename . '.v' . $users_view -> filemtime ( 'files' . $filename );
if ( ! $users_view -> file_exists ( $version )) {
2013-05-30 16:05:52 -04:00
// disable proxy to prevent multiple fopen calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2012-12-17 12:00:11 -05:00
$users_view -> copy ( 'files' . $filename , 'files_versions' . $filename . '.v' . $users_view -> filemtime ( 'files' . $filename ));
2013-05-30 16:05:52 -04:00
// reset proxy state
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-01-11 08:23:28 -05:00
$versionCreated = true ;
2012-12-17 12:00:11 -05:00
}
2013-02-22 11:21:57 -05:00
2012-09-07 08:09:41 -04:00
// rollback
2013-05-08 10:32:29 -04:00
if ( @ $users_view -> rename ( 'files_versions' . $filename . '.v' . $revision , 'files' . $filename ) ) {
2013-05-08 09:05:03 -04:00
$files_view -> touch ( $file , $revision );
Storage :: expire ( $file );
2012-09-07 08:09:41 -04:00
return true ;
2013-01-11 08:23:28 -05:00
} else if ( $versionCreated ) {
$users_view -> unlink ( $version );
2012-09-07 08:09:41 -04:00
}
}
2013-01-11 08:23:28 -05:00
return false ;
2012-09-07 08:09:41 -04:00
}
/**
* @ brief get a list of all available versions of a file in descending chronological order
2014-01-21 07:50:56 -05: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 05:13:15 -05:00
* @ returns array versions newest version first
2012-09-07 08:09:41 -04:00
*/
2014-01-21 07:50:56 -05:00
public static function getVersions ( $uid , $filename , $userFullPath = '' ) {
2013-10-10 08:43:40 -04:00
$versions = array ();
// fetch for old versions
2013-10-10 14:09:38 -04:00
$view = new \OC\Files\View ( '/' . $uid . '/' . self :: VERSIONS_ROOT );
2013-10-10 08:43:40 -04:00
2013-10-10 14:06:42 -04:00
$pathinfo = pathinfo ( $filename );
$files = $view -> getDirectoryContent ( $pathinfo [ 'dirname' ]);
$versionedFile = $pathinfo [ 'basename' ];
2013-10-10 08:43:40 -04:00
foreach ( $files as $file ) {
if ( $file [ 'type' ] === 'file' ) {
$pos = strrpos ( $file [ 'path' ], '.v' );
2013-10-10 10:58:11 -04:00
$currentFile = substr ( $file [ 'name' ], 0 , strrpos ( $file [ 'name' ], '.v' ));
if ( $currentFile === $versionedFile ) {
2013-10-10 08: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 07:50:56 -05: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 08:43:40 -04:00
$versions [ $key ][ 'path' ] = $filename ;
2013-11-25 06:51:32 -05:00
$versions [ $key ][ 'name' ] = $versionedFile ;
2013-10-10 08:43:40 -04:00
$versions [ $key ][ 'size' ] = $file [ 'size' ];
2012-09-07 08:09:41 -04:00
}
}
}
2013-10-10 14:06:42 -04:00
// sort with newest version first
krsort ( $versions );
2013-10-10 08:43:40 -04:00
2013-10-10 14:06:42 -04:00
return $versions ;
2012-09-07 08:09:41 -04:00
}
2013-07-25 04: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 06: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 03:31:54 -05:00
* @ return \OC_DB_StatementWrapper of db delete operation
2013-04-11 06: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 08:09:41 -04:00
/**
2013-01-15 08:57:23 -05: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 08:09:41 -04:00
*/
2013-01-15 08:57:23 -05:00
private static function calculateSize ( $uid ) {
2013-11-19 06:23:14 -05:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
$view = new \OC\Files\View ( '/' . $uid . '/files_versions' );
2013-02-22 11:21:57 -05:00
2013-01-10 12:04:30 -05:00
$size = 0 ;
2013-02-22 11:21:57 -05:00
2013-11-19 06:23:14 -05: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 08:09:41 -04:00
}
2013-11-19 06:23:14 -05:00
unset ( $dirContent [ key ( $dirContent )]);
2012-09-07 08:09:41 -04:00
}
2013-02-22 11:21:57 -05:00
2013-01-15 08:57:23 -05:00
return $size ;
}
2013-01-10 12:04:30 -05:00
}
2013-02-22 11:21:57 -05:00
2013-01-09 11:11:46 -05:00
/**
* @ brief returns all stored file versions from a given user
2014-02-06 10:30:58 -05:00
* @ param string $uid id of the user
2013-01-09 11:11:46 -05: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 08:43:40 -04:00
$view = new \OC\Files\View ( '/' . $uid . '/' );
2013-10-10 14:09:38 -04:00
$dirs = array ( self :: VERSIONS_ROOT );
2013-10-10 08: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 04:34:34 -04:00
$relPathStart = strlen ( self :: VERSIONS_ROOT );
2013-10-10 08: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 11:11:46 -05:00
}
2013-01-15 08:57:23 -05:00
}
2013-10-10 08:43:40 -04:00
}
2013-02-22 11:21:57 -05:00
2014-01-22 05:13:15 -05:00
// newest version first
krsort ( $versions );
2013-02-22 11:21:57 -05:00
2013-10-10 08:43:40 -04:00
$result = array ();
2013-02-22 11:21:57 -05:00
2013-10-10 08:43:40 -04:00
foreach ( $versions as $key => $value ) {
2014-01-22 05:10:23 -05:00
$size = $view -> filesize ( self :: VERSIONS_ROOT . '/' . $value [ 'path' ] . '.v' . $value [ 'timestamp' ]);
2013-10-10 08:43:40 -04:00
$filename = $value [ 'path' ];
2013-02-22 11:21:57 -05:00
2013-10-10 08:43:40 -04:00
$result [ 'all' ][ $key ][ 'version' ] = $value [ 'timestamp' ];
$result [ 'all' ][ $key ][ 'path' ] = $filename ;
$result [ 'all' ][ $key ][ 'size' ] = $size ;
2013-02-22 11:21:57 -05:00
2013-10-10 08: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 08:09:41 -04:00
}
2013-10-10 08:43:40 -04:00
return $result ;
2012-09-07 08:09:41 -04:00
}
2014-01-20 10:03:26 -05:00
/**
* @ brief get list of files we want to expire
* @ param array $versions list of versions
2014-02-19 03:31:54 -05:00
* @ param integer $time
2014-01-20 10:03:26 -05: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 08:09:41 -04:00
/**
2012-11-04 12:42:18 -05:00
* @ brief Erase a file ' s versions which exceed the set quota
2012-09-07 08:09:41 -04:00
*/
2013-06-25 03:39:01 -04:00
private static function expire ( $filename , $versionsSize = null , $offset = 0 ) {
2012-11-04 12:42:18 -05:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2013-02-22 11:21:57 -05:00
list ( $uid , $filename ) = self :: getUidAndFilename ( $filename );
2013-06-28 14:31:33 -04:00
$versionsFileview = new \OC\Files\View ( '/' . $uid . '/files_versions' );
2013-02-22 11:21:57 -05:00
2012-12-17 07:28:40 -05:00
// get available disk space for user
2013-04-16 07:52:46 -04:00
$softQuota = true ;
2013-03-04 06:59:48 -05:00
$quota = \OC_Preferences :: getValue ( $uid , 'files' , 'quota' );
2013-03-04 06:17:57 -05:00
if ( $quota === null || $quota === 'default' ) {
2014-02-13 10:28:49 -05:00
$quota = \OC :: $server -> getAppConfig () -> getValue ( 'files' , 'default_quota' );
2012-12-17 07:28:40 -05:00
}
2013-03-04 06:33:16 -05:00
if ( $quota === null || $quota === 'none' ) {
2013-04-16 07:52:46 -04:00
$quota = \OC\Files\Filesystem :: free_space ( '/' );
$softQuota = false ;
2013-02-25 10:12:44 -05:00
} else {
$quota = \OCP\Util :: computerFileSize ( $quota );
2012-12-13 10:34:54 -05:00
}
2013-08-17 05:57:50 -04:00
2013-01-11 13:33:54 -05:00
// make sure that we have the current size of the version history
2013-01-10 12:04:30 -05:00
if ( $versionsSize === null ) {
2013-02-21 06:20:29 -05:00
$versionsSize = self :: getVersionsSize ( $uid );
if ( $versionsSize === false || $versionsSize < 0 ) {
2013-01-10 12:04:30 -05:00
$versionsSize = self :: calculateSize ( $uid );
}
}
2013-01-11 13:33:54 -05:00
2013-01-15 08:57:23 -05:00
// calculate available space for version history
2013-04-16 07: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 11:10:09 -05:00
$rootInfo = $files_view -> getFileInfo ( '/' , false );
2013-04-16 07:52:46 -04:00
$free = $quota - $rootInfo [ 'size' ]; // remaining free space for user
if ( $free > 0 ) {
2013-06-25 03:39:01 -04:00
$availableSpace = ( $free * self :: DEFAULTMAXSIZE / 100 ) - ( $versionsSize + $offset ); // how much space can be used for versions
2013-04-16 07:52:46 -04:00
} else {
2013-06-25 03:39:01 -04:00
$availableSpace = $free - $versionsSize - $offset ;
2013-04-16 07:52:46 -04:00
}
2013-01-10 12:04:30 -05:00
} else {
2013-06-25 03:39:01 -04:00
$availableSpace = $quota - $offset ;
2013-02-22 11:21:57 -05:00
}
2013-01-09 11:11:46 -05:00
2013-06-28 14:31:33 -04:00
$allVersions = Storage :: getVersions ( $uid , $filename );
2013-02-22 11:21:57 -05:00
2014-01-20 10:03:26 -05:00
$time = time ();
list ( $toDelete , $sizeOfDeletedVersions ) = self :: getExpireList ( $time , $allVersions );
2013-06-25 03:39:01 -04:00
$availableSpace = $availableSpace + $sizeOfDeletedVersions ;
$versionsSize = $versionsSize - $sizeOfDeletedVersions ;
2013-02-22 11:21:57 -05:00
2013-06-25 03:39:01 -04:00
// if still not enough free space we rearrange the versions from all files
2014-01-20 10:03:26 -05:00
if ( $availableSpace <= 0 ) {
2013-06-25 03:39:01 -04:00
$result = Storage :: getAllVersions ( $uid );
2013-06-28 14:31:33 -04:00
$allVersions = $result [ 'all' ];
2013-02-22 11:21:57 -05:00
2014-01-20 10:03:26 -05:00
foreach ( $result [ 'by_file' ] as $versions ) {
list ( $toDeleteNew , $size ) = self :: getExpireList ( $time , $versions );
$toDelete = array_merge ( $toDelete , $toDeleteNew );
$sizeOfDeletedVersions += $size ;
}
2013-06-25 03:39:01 -04:00
$availableSpace = $availableSpace + $sizeOfDeletedVersions ;
$versionsSize = $versionsSize - $sizeOfDeletedVersions ;
2012-11-04 12:42:18 -05:00
}
2013-02-22 11:21:57 -05:00
2014-01-20 10:03:26 -05: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 11:20:14 -05: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 14:31:33 -04:00
$numOfVersions = count ( $allVersions ) - 2 ;
2013-02-22 11:21:57 -05:00
$i = 0 ;
2013-03-04 11:20:14 -05:00
while ( $availableSpace < 0 && $i < $numOfVersions ) {
2013-06-28 14:31:33 -04:00
$version = current ( $allVersions );
$versionsFileview -> unlink ( $version [ 'path' ] . '.v' . $version [ 'version' ]);
2013-11-12 08:06:32 -05:00
\OC_Hook :: emit ( '\OCP\Versions' , 'delete' , array ( 'path' => $version [ 'path' ] . '.v' . $version [ 'version' ]));
2014-01-20 10:03:26 -05:00
\OCP\Util :: writeLog ( 'files_versions' , 'running out of space! Delete oldest version: ' . $version [ 'path' ] . '.v' . $version [ 'version' ] , \OCP\Util :: DEBUG );
2013-06-28 14:31:33 -04:00
$versionsSize -= $version [ 'size' ];
$availableSpace += $version [ 'size' ];
next ( $allVersions );
2012-12-17 10:32:09 -05:00
$i ++ ;
}
2013-02-22 11:21:57 -05:00
2013-01-11 13:33:54 -05:00
return $versionsSize ; // finally return the new size of the version history
2012-11-04 12:42:18 -05:00
}
2013-02-22 11:21:57 -05:00
2013-01-11 05:12:32 -05:00
return false ;
2012-09-07 08:09:41 -04:00
}
2013-06-25 03:39:01 -04:00
2013-08-17 07:28:35 -04:00
/**
* @ brief create recursively missing directories
* @ param string $filename $path to a file
2013-08-17 07:46:33 -04:00
* @ param \OC\Files\View $view view on data / user /
2013-08-17 07:28:35 -04:00
*/
private static function createMissingDirectories ( $filename , $view ) {
2013-08-17 07:49:42 -04:00
$dirname = \OC_Filesystem :: normalizePath ( dirname ( $filename ));
2013-08-17 07: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 08:09:41 -04:00
}