123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- <?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';
- const DEFAULTBLACKLIST='avi mp3 mpg mp4 ctmp';
- const DEFAULTMAXFILESIZE=1048576; // 10MB
- const DEFAULTMININTERVAL=1; // 2 min
- const DEFAULTMAXVERSIONS=50;
- /**
- * init the versioning and create the versions folder.
- */
- public static function init() {
- if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
- // create versions folder
- $foldername=\OCP\Config::getSystemValue('datadirectory').'/'. \OCP\USER::getUser() .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
- if(!is_dir($foldername)){
- mkdir($foldername);
- }
- }
- }
- /**
- * listen to write event.
- */
- public static function write_hook($params) {
- if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
- $path = $params[\OC_Filesystem::signal_param_path];
- if($path<>'') Storage::store($path);
- }
- }
- /**
- * store a new version of a file.
- */
- public static function store($filename) {
- if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
- 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();
- }
- $versionsFolderName=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
- $filesfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/files';
- Storage::init();
- // check if filename is a directory
- if(is_dir($filesfoldername.'/'.$filename)){
- return false;
- }
- // check filetype blacklist
- $blacklist=explode(' ',\OCP\Config::getSystemValue('files_versionsblacklist', Storage::DEFAULTBLACKLIST));
- foreach($blacklist as $bl) {
- $parts=explode('.', $filename);
- $ext=end($parts);
- if(strtolower($ext)==$bl) {
- return false;
- }
- }
-
- // check filesize
- if(filesize($filesfoldername.'/'.$filename)>\OCP\Config::getSystemValue('files_versionsmaxfilesize', Storage::DEFAULTMAXFILESIZE)){
- return false;
- }
- // check mininterval if the file is being modified by the owner (all shared files should be versioned despite mininterval)
- if ($uid == \OCP\User::getUser()) {
- $matches=glob($versionsFolderName.'/'.$filename.'.v*');
- sort($matches);
- $parts=explode('.v',end($matches));
- if((end($parts)+Storage::DEFAULTMININTERVAL)>time()){
- return false;
- }
- }
- // create all parent folders
- $info=pathinfo($filename);
- if(!file_exists($versionsFolderName.'/'.$info['dirname'])) mkdir($versionsFolderName.'/'.$info['dirname'],0700,true);
- // store a new version of a file
- copy($filesfoldername.'/'.$filename,$versionsFolderName.'/'.$filename.'.v'.time());
-
- // expire old revisions if necessary
- Storage::expire($filename);
- }
- }
- /**
- * rollback to an old version of a file.
- */
- public static function rollback($filename,$revision) {
-
- if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
- 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();
- }
- $versionsFolderName=\OCP\Config::getSystemValue('datadirectory').'/'.$uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
-
- $filesfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/files';
-
- // rollback
- if ( @copy($versionsFolderName.'/'.$filename.'.v'.$revision,$filesfoldername.'/'.$filename) ) {
-
- return true;
-
- }else{
-
- return false;
-
- }
-
- }
-
- }
- /**
- * check if old versions of a file exist.
- */
- public static function isversioned($filename) {
- if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
- 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();
- }
- $versionsFolderName=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
- // check for old versions
- $matches=glob($versionsFolderName.'/'.$filename.'.v*');
- if(count($matches)>1){
- return true;
- }else{
- return false;
- }
- }else{
- return(false);
- }
- }
-
- /**
- * @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
- */
- public static function getVersions( $filename, $count = 0 ) {
-
- if( \OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true' ) {
-
- 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();
- }
- $versionsFolderName = \OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
- $versions = array();
-
- // fetch for old versions
- $matches = glob( $versionsFolderName.'/'.$filename.'.v*' );
-
- sort( $matches );
-
- $i = 0;
-
- foreach( $matches as $ma ) {
-
- $i++;
- $versions[$i]['cur'] = 0;
- $parts = explode( '.v', $ma );
- $versions[$i]['version'] = ( end( $parts ) );
-
- // if file with modified date exists, flag it in array as currently enabled version
- $curFile['fileName'] = basename( $parts[0] );
- $curFile['filePath'] = \OCP\Config::getSystemValue('datadirectory').\OC_Filesystem::getRoot().'/'.$curFile['fileName'];
-
- ( \md5_file( $ma ) == \md5_file( $curFile['filePath'] ) ? $versions[$i]['fileMatch'] = 1 : $versions[$i]['fileMatch'] = 0 );
-
- }
-
- $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 );
-
- // only show the newest commits
- if( $count != 0 and ( count( $versions )>$count ) ) {
-
- $versions = array_slice( $versions, count( $versions ) - $count );
-
- }
-
- return( $versions );
- } else {
-
- // if versioning isn't enabled then return an empty array
- return( array() );
-
- }
-
- }
-
- /**
- * expire old versions of a file.
- */
- public static function expire($filename) {
- if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
- 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();
- }
- $versionsFolderName=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
- // check for old versions
- $matches = glob( $versionsFolderName.'/'.$filename.'.v*' );
-
- if( count( $matches ) > \OCP\Config::getSystemValue( 'files_versionmaxversions', Storage::DEFAULTMAXVERSIONS ) ) {
-
- $numberToDelete = count( $matches-\OCP\Config::getSystemValue( 'files_versionmaxversions', Storage::DEFAULTMAXVERSIONS ) );
- // delete old versions of a file
- $deleteItems = array_slice( $matches, 0, $numberToDelete );
-
- foreach( $deleteItems as $de ) {
-
- unlink( $versionsFolderName.'/'.$filename.'.v'.$de );
-
- }
- }
- }
- }
- /**
- * @brief erase all old versions of all user files
- * @return
- */
- public static function expireAll() {
-
- function deleteAll( $directory, $empty = false ) {
-
- // strip leading slash
- if( substr( $directory, 0, 1 ) == "/" ) {
-
- $directory = substr( $directory, 1 );
-
- }
-
- // strip trailing slash
- if( substr( $directory, -1) == "/" ) {
-
- $directory = substr( $directory, 0, -1 );
-
- }
- $view = new \OC_FilesystemView('');
-
- if ( !$view->file_exists( $directory ) || !$view->is_dir( $directory ) ) {
-
- return false;
-
- } elseif( !$view->is_readable( $directory ) ) {
-
- return false;
-
- } else {
-
- $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 = $view->opendir( \OCP\USER::getUser() . '/' . $directory );
-
- while ( $contents = readdir( $directoryHandle ) ) {
-
- if ( $contents != '.' && $contents != '..') {
-
- $path = $directory . "/" . $contents;
-
- if ( $view->is_dir( $path ) ) {
-
- deleteAll( $path );
-
- } else {
-
- $view->unlink( \OCP\USER::getUser() .'/' . $path ); // TODO: make unlink use same system path as is_dir
-
- }
- }
-
- }
-
- //$view->closedir( $directoryHandle ); // TODO: implement closedir in OC_FSV
- if ( $empty == false ) {
-
- if ( !$view->rmdir( $directory ) ) {
-
- return false;
-
- }
-
- }
-
- return true;
- }
-
- }
-
- $dir = \OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
-
- if ( deleteAll( $dir, true ) ) {
-
- return true;
-
- } else {
-
- return false;
-
- }
-
- }
- }
|