2012-04-24 01:54:49 +04:00
< ? php
/**
* Copyright ( c ) 2012 Frank Karlitschek < frank @ owncloud . org >
* 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 .
*/
namespace OCA_Versions ;
class Storage {
// config.php configuration:
// - files_versions
// - files_versionsfolder
// - files_versionsblacklist
// - files_versionsmaxfilesize
// - files_versionsinterval
// - files_versionmaxversions
//
// todo:
// - port to oc_filesystem to enable network transparency
// - implement expire all function. And find a place to call it ;-)
// - add transparent compression. first test if it´ s worth it.
const DEFAULTENABLED = true ;
const DEFAULTFOLDER = 'versions' ;
2012-05-14 12:51:41 +04:00
const DEFAULTBLACKLIST = 'avi mp3 mpg mp4 ctmp' ;
2012-04-24 01:54:49 +04:00
const DEFAULTMAXFILESIZE = 1048576 ; // 10MB
2012-05-16 17:56:37 +04:00
const DEFAULTMININTERVAL = 1 ; // 2 min
2012-04-24 01:54:49 +04:00
const DEFAULTMAXVERSIONS = 50 ;
/**
* init the versioning and create the versions folder .
*/
public static function init () {
2012-05-02 15:28:56 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2012-04-24 01:54:49 +04:00
// create versions folder
2012-05-02 15:28:56 +04:00
$foldername = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . \OCP\USER :: getUser () . '/' . \OCP\Config :: getSystemValue ( 'files_versionsfolder' , Storage :: DEFAULTFOLDER );
2012-04-24 01:54:49 +04:00
if ( ! is_dir ( $foldername )){
mkdir ( $foldername );
}
}
}
/**
* listen to write event .
*/
public static function write_hook ( $params ) {
2012-05-02 15:28:56 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2012-04-24 01:54:49 +04:00
$path = $params [ \OC_Filesystem :: signal_param_path ];
if ( $path <> '' ) Storage :: store ( $path );
}
}
/**
* store a new version of a file .
*/
public static function store ( $filename ) {
2012-05-02 15:28:56 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2012-05-19 06:01:47 +04:00
if ( \OCP\App :: isEnabled ( 'files_sharing' ) && $source = \OC_Share :: getSource ( '/' . \OCP\User :: getUser () . '/files' . $filename )) {
$pos = strpos ( $source , '/files' , 1 );
$uid = substr ( $source , 1 , $pos - 1 );
$filename = substr ( $source , $pos + 6 );
} else {
$uid = \OCP\User :: getUser ();
}
2012-06-05 01:02:05 +04:00
$versionsFolderName = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . $uid . '/' . \OCP\Config :: getSystemValue ( 'files_versionsfolder' , Storage :: DEFAULTFOLDER );
2012-05-19 06:01:47 +04:00
$filesfoldername = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . $uid . '/files' ;
2012-04-24 01:54:49 +04:00
Storage :: init ();
// check if filename is a directory
2012-05-31 23:16:36 +04:00
if ( is_dir ( $filesfoldername . '/' . $filename )){
2012-04-24 01:54:49 +04:00
return false ;
}
// check filetype blacklist
2012-05-02 15:28:56 +04:00
$blacklist = explode ( ' ' , \OCP\Config :: getSystemValue ( 'files_versionsblacklist' , Storage :: DEFAULTBLACKLIST ));
2012-04-24 01:54:49 +04:00
foreach ( $blacklist as $bl ) {
$parts = explode ( '.' , $filename );
$ext = end ( $parts );
if ( strtolower ( $ext ) == $bl ) {
return false ;
}
}
// check filesize
2012-05-31 23:10:03 +04:00
if ( filesize ( $filesfoldername . '/' . $filename ) > \OCP\Config :: getSystemValue ( 'files_versionsmaxfilesize' , Storage :: DEFAULTMAXFILESIZE )){
2012-04-24 01:54:49 +04:00
return false ;
}
2012-05-19 06:27:43 +04:00
// check mininterval if the file is being modified by the owner (all shared files should be versioned despite mininterval)
if ( $uid == \OCP\User :: getUser ()) {
2012-06-05 01:02:05 +04:00
$matches = glob ( $versionsFolderName . '/' . $filename . '.v*' );
2012-05-19 06:01:47 +04:00
sort ( $matches );
$parts = explode ( '.v' , end ( $matches ));
if (( end ( $parts ) + Storage :: DEFAULTMININTERVAL ) > time ()){
return false ;
}
2012-04-24 01:54:49 +04:00
}
2012-05-19 06:01:47 +04:00
2012-04-24 01:54:49 +04:00
// create all parent folders
$info = pathinfo ( $filename );
2012-06-05 01:02:05 +04:00
if ( ! file_exists ( $versionsFolderName . '/' . $info [ 'dirname' ])) mkdir ( $versionsFolderName . '/' . $info [ 'dirname' ], 0700 , true );
2012-04-24 01:54:49 +04:00
// store a new version of a file
2012-06-05 01:02:05 +04:00
copy ( $filesfoldername . '/' . $filename , $versionsFolderName . '/' . $filename . '.v' . time ());
2012-04-24 01:54:49 +04:00
2012-05-16 21:30:26 +04:00
// expire old revisions if necessary
2012-04-24 01:54:49 +04:00
Storage :: expire ( $filename );
}
}
/**
* rollback to an old version of a file .
*/
public static function rollback ( $filename , $revision ) {
2012-04-27 16:19:16 +04:00
2012-05-02 15:28:56 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2012-05-19 06:01:47 +04:00
if ( \OCP\App :: isEnabled ( 'files_sharing' ) && $source = \OC_Share :: getSource ( '/' . \OCP\User :: getUser () . '/files' . $filename )) {
$pos = strpos ( $source , '/files' , 1 );
$uid = substr ( $source , 1 , $pos - 1 );
$filename = substr ( $source , $pos + 6 );
} else {
$uid = \OCP\User :: getUser ();
}
2012-06-05 01:02:05 +04:00
$versionsFolderName = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . $uid . '/' . \OCP\Config :: getSystemValue ( 'files_versionsfolder' , Storage :: DEFAULTFOLDER );
2012-04-27 16:19:16 +04:00
2012-05-19 06:01:47 +04:00
$filesfoldername = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . $uid . '/files' ;
2012-04-27 16:19:16 +04:00
2012-04-24 01:54:49 +04:00
// rollback
2012-06-05 01:02:05 +04:00
if ( @ copy ( $versionsFolderName . '/' . $filename . '.v' . $revision , $filesfoldername . '/' . $filename ) ) {
2012-04-27 16:19:16 +04:00
return true ;
} else {
return false ;
}
2012-04-24 01:54:49 +04:00
}
2012-04-27 16:19:16 +04:00
2012-04-24 01:54:49 +04:00
}
/**
* check if old versions of a file exist .
*/
public static function isversioned ( $filename ) {
2012-05-02 15:28:56 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2012-05-19 06:01:47 +04:00
if ( \OCP\App :: isEnabled ( 'files_sharing' ) && $source = \OC_Share :: getSource ( '/' . \OCP\User :: getUser () . '/files' . $filename )) {
$pos = strpos ( $source , '/files' , 1 );
$uid = substr ( $source , 1 , $pos - 1 );
$filename = substr ( $source , $pos + 6 );
} else {
$uid = \OCP\User :: getUser ();
}
2012-06-05 01:02:05 +04:00
$versionsFolderName = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . $uid . '/' . \OCP\Config :: getSystemValue ( 'files_versionsfolder' , Storage :: DEFAULTFOLDER );
2012-04-24 01:54:49 +04:00
// check for old versions
2012-06-05 01:02:05 +04:00
$matches = glob ( $versionsFolderName . '/' . $filename . '.v*' );
2012-04-24 01:54:49 +04:00
if ( count ( $matches ) > 1 ){
return true ;
} else {
return false ;
}
} else {
return ( false );
}
}
/**
2012-05-16 18:17:51 +04:00
* @ brief get a list of all available versions of a file in descending chronological order
* @ param $filename file to find versions of , relative to the user files dir
* @ param $count number of versions to return
* @ returns array
2012-04-24 01:54:49 +04:00
*/
2012-05-16 21:30:26 +04:00
public static function getVersions ( $filename , $count = 0 ) {
2012-05-16 17:56:37 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2012-05-19 06:01:47 +04:00
if ( \OCP\App :: isEnabled ( 'files_sharing' ) && $source = \OC_Share :: getSource ( '/' . \OCP\User :: getUser () . '/files' . $filename )) {
$pos = strpos ( $source , '/files' , 1 );
$uid = substr ( $source , 1 , $pos - 1 );
$filename = substr ( $source , $pos + 6 );
} else {
$uid = \OCP\User :: getUser ();
}
2012-06-05 01:02:05 +04:00
$versionsFolderName = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . $uid . '/' . \OCP\Config :: getSystemValue ( 'files_versionsfolder' , Storage :: DEFAULTFOLDER );
2012-05-16 17:56:37 +04:00
$versions = array ();
// fetch for old versions
2012-06-05 01:02:05 +04:00
$matches = glob ( $versionsFolderName . '/' . $filename . '.v*' );
2012-05-16 17:56:37 +04:00
sort ( $matches );
$i = 0 ;
foreach ( $matches as $ma ) {
$i ++ ;
$versions [ $i ][ 'cur' ] = 0 ;
2012-05-16 21:30:26 +04:00
$parts = explode ( '.v' , $ma );
2012-05-16 17:56:37 +04:00
$versions [ $i ][ 'version' ] = ( end ( $parts ) );
// if file with modified date exists, flag it in array as currently enabled version
$curFile [ 'fileName' ] = basename ( $parts [ 0 ] );
2012-05-16 21:30:26 +04:00
$curFile [ 'filePath' ] = \OCP\Config :: getSystemValue ( 'datadirectory' ) . \OC_Filesystem :: getRoot () . '/' . $curFile [ 'fileName' ];
2012-05-16 17:56:37 +04:00
( \md5_file ( $ma ) == \md5_file ( $curFile [ 'filePath' ] ) ? $versions [ $i ][ 'fileMatch' ] = 1 : $versions [ $i ][ 'fileMatch' ] = 0 );
2012-04-24 01:54:49 +04:00
}
2012-05-16 17:56:37 +04:00
$versions = array_reverse ( $versions );
foreach ( $versions as $key => $value ) {
// flag the first matched file in array (which will have latest modification date) as current version
if ( $versions [ $key ][ 'fileMatch' ] ) {
$versions [ $key ][ 'cur' ] = 1 ;
break ;
}
}
$versions = array_reverse ( $versions );
2012-04-24 01:54:49 +04:00
// only show the newest commits
2012-05-16 17:56:37 +04:00
if ( $count != 0 and ( count ( $versions ) > $count ) ) {
$versions = array_slice ( $versions , count ( $versions ) - $count );
2012-04-24 01:54:49 +04:00
}
2012-05-16 17:56:37 +04:00
return ( $versions );
2012-04-24 01:54:49 +04:00
2012-05-16 17:56:37 +04:00
} else {
// if versioning isn't enabled then return an empty array
return ( array () );
2012-04-24 01:54:49 +04:00
}
2012-05-16 21:30:26 +04:00
}
2012-04-24 01:54:49 +04:00
/**
* expire old versions of a file .
*/
public static function expire ( $filename ) {
2012-05-02 15:28:56 +04:00
if ( \OCP\Config :: getSystemValue ( 'files_versions' , Storage :: DEFAULTENABLED ) == 'true' ) {
2012-04-24 01:54:49 +04:00
2012-05-19 06:01:47 +04:00
if ( \OCP\App :: isEnabled ( 'files_sharing' ) && $source = \OC_Share :: getSource ( '/' . \OCP\User :: getUser () . '/files' . $filename )) {
$pos = strpos ( $source , '/files' , 1 );
$uid = substr ( $source , 1 , $pos - 1 );
$filename = substr ( $source , $pos + 6 );
} else {
$uid = \OCP\User :: getUser ();
}
2012-06-05 01:02:05 +04:00
$versionsFolderName = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . $uid . '/' . \OCP\Config :: getSystemValue ( 'files_versionsfolder' , Storage :: DEFAULTFOLDER );
2012-04-24 01:54:49 +04:00
// check for old versions
2012-06-05 01:02:05 +04:00
$matches = glob ( $versionsFolderName . '/' . $filename . '.v*' );
2012-05-16 21:30:26 +04:00
if ( count ( $matches ) > \OCP\Config :: getSystemValue ( 'files_versionmaxversions' , Storage :: DEFAULTMAXVERSIONS ) ) {
$numberToDelete = count ( $matches - \OCP\Config :: getSystemValue ( 'files_versionmaxversions' , Storage :: DEFAULTMAXVERSIONS ) );
2012-04-24 01:54:49 +04:00
// delete old versions of a file
2012-05-16 21:30:26 +04:00
$deleteItems = array_slice ( $matches , 0 , $numberToDelete );
foreach ( $deleteItems as $de ) {
2012-06-05 01:02:05 +04:00
unlink ( $versionsFolderName . '/' . $filename . '.v' . $de );
2012-05-16 21:30:26 +04:00
2012-04-24 01:54:49 +04:00
}
}
}
}
/**
2012-05-16 21:30:26 +04:00
* @ brief erase all old versions of all user files
* @ return
2012-04-24 01:54:49 +04:00
*/
2012-05-16 21:30:26 +04:00
public static function expireAll () {
2012-06-19 22:42:40 +04:00
function deleteAll ( $directory , $empty = false ) {
2012-05-16 21:30:26 +04:00
2012-06-19 22:42:40 +04:00
// strip leading slash
if ( substr ( $directory , 0 , 1 ) == " / " ) {
$directory = substr ( $directory , 1 );
}
// strip trailing slash
if ( substr ( $directory , - 1 ) == " / " ) {
$directory = substr ( $directory , 0 , - 1 );
2012-05-16 21:30:26 +04:00
}
2012-06-19 22:42:40 +04:00
$view = new \OC_FilesystemView ( '' );
if ( ! $view -> file_exists ( $directory ) || ! $view -> is_dir ( $directory ) ) {
2012-05-16 21:30:26 +04:00
return false ;
2012-06-19 22:42:40 +04:00
} elseif ( ! $view -> is_readable ( $directory ) ) {
2012-05-16 21:30:26 +04:00
return false ;
} else {
2012-06-19 22:42:40 +04:00
$foldername = \OCP\Config :: getSystemValue ( 'datadirectory' ) . '/' . \OCP\USER :: getUser () . '/' . $directory ; // have to set an absolute path for use with PHP's opendir as OC version doesn't work
$directoryHandle = opendir ( $foldername );
2012-05-16 21:30:26 +04:00
2012-06-19 22:42:40 +04:00
while ( $contents = $view -> readdir ( $directoryHandle ) ) {
2012-05-16 21:30:26 +04:00
2012-06-19 22:42:40 +04:00
if ( $contents != '.' && $contents != '..' ) {
2012-05-16 21:30:26 +04:00
$path = $directory . " / " . $contents ;
2012-06-19 22:42:40 +04:00
if ( $view -> is_dir ( $path ) ) {
2012-05-16 21:30:26 +04:00
2012-06-19 22:42:40 +04:00
deleteAll ( $path );
2012-05-16 21:30:26 +04:00
} else {
2012-06-19 22:42:40 +04:00
$view -> unlink ( \OCP\USER :: getUser () . '/' . $path ); // TODO: make unlink use same system path as is_dir
2012-05-16 21:30:26 +04:00
}
}
}
2012-06-19 22:42:40 +04:00
//$view->closedir( $directoryHandle ); // TODO: implement closedir in OC_FSV
2012-05-16 21:30:26 +04:00
2012-06-19 22:42:40 +04:00
if ( $empty == false ) {
2012-05-16 21:30:26 +04:00
2012-06-19 22:42:40 +04:00
if ( ! $view -> rmdir ( $directory ) ) {
2012-05-16 21:30:26 +04:00
return false ;
}
2012-06-19 22:42:40 +04:00
}
2012-05-16 21:30:26 +04:00
return true ;
}
}
2012-06-19 22:42:40 +04:00
$dir = \OCP\Config :: getSystemValue ( 'files_versionsfolder' , Storage :: DEFAULTFOLDER );
deleteAll ( $dir , true );
// if ( deleteAll( $dir, 1 ) ) {
//
// echo "<h1>deleted ok</h1>";
//
// } else {
//
// echo "<h1>not deleted</h1>";
//
// }
2012-06-05 11:28:51 +04:00
2012-04-24 01:54:49 +04:00
}
}