versions.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Frank Karlitschek <frank@owncloud.org>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. /**
  9. * Versions
  10. *
  11. * A class to handle the versioning of files.
  12. */
  13. namespace OCA_Versions;
  14. class Storage {
  15. // config.php configuration:
  16. // - files_versions
  17. // - files_versionsfolder
  18. // - files_versionsblacklist
  19. // - files_versionsmaxfilesize
  20. // - files_versionsinterval
  21. // - files_versionmaxversions
  22. //
  23. // todo:
  24. // - port to oc_filesystem to enable network transparency
  25. // - check if it works well together with encryption
  26. // - implement expire all function. And find a place to call it ;-)
  27. // - add transparent compression. first test if it´s worth it.
  28. const DEFAULTENABLED=true;
  29. const DEFAULTFOLDER='versions';
  30. const DEFAULTBLACKLIST='avi mp3 mpg mp4 ctmp';
  31. const DEFAULTMAXFILESIZE=1048576; // 10MB
  32. const DEFAULTMININTERVAL=120; // 2 min
  33. const DEFAULTMAXVERSIONS=50;
  34. /**
  35. * init the versioning and create the versions folder.
  36. */
  37. public static function init() {
  38. if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
  39. // create versions folder
  40. $foldername=\OCP\Config::getSystemValue('datadirectory').'/'. \OCP\USER::getUser() .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
  41. if(!is_dir($foldername)){
  42. mkdir($foldername);
  43. }
  44. }
  45. }
  46. /**
  47. * listen to write event.
  48. */
  49. public static function write_hook($params) {
  50. if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
  51. $path = $params[\OC_Filesystem::signal_param_path];
  52. if($path<>'') Storage::store($path);
  53. }
  54. }
  55. /**
  56. * store a new version of a file.
  57. */
  58. public static function store($filename) {
  59. if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
  60. if (\OCP\App::isEnabled('files_sharing') && $source = \OC_Share::getSource('/'.\OCP\User::getUser().'/files'.$filename)) {
  61. $pos = strpos($source, '/files', 1);
  62. $uid = substr($source, 1, $pos - 1);
  63. $filename = substr($source, $pos + 6);
  64. } else {
  65. $uid = \OCP\User::getUser();
  66. }
  67. $versionsfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
  68. $filesfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/files';
  69. Storage::init();
  70. // check if filename is a directory
  71. if(is_dir($filesfoldername.'/'.$filename)){
  72. return false;
  73. }
  74. // check filetype blacklist
  75. $blacklist=explode(' ',\OCP\Config::getSystemValue('files_versionsblacklist', Storage::DEFAULTBLACKLIST));
  76. foreach($blacklist as $bl) {
  77. $parts=explode('.', $filename);
  78. $ext=end($parts);
  79. if(strtolower($ext)==$bl) {
  80. return false;
  81. }
  82. }
  83. // check filesize
  84. if(filesize($filesfoldername.'/'.$filename)>\OCP\Config::getSystemValue('files_versionsmaxfilesize', Storage::DEFAULTMAXFILESIZE)){
  85. return false;
  86. }
  87. // check mininterval if the file is being modified by the owner (all shared files should be versioned despite mininterval)
  88. if ($uid == \OCP\User::getUser()) {
  89. $matches=glob($versionsfoldername.'/'.$filename.'.v*');
  90. sort($matches);
  91. $parts=explode('.v',end($matches));
  92. if((end($parts)+Storage::DEFAULTMININTERVAL)>time()){
  93. return false;
  94. }
  95. }
  96. // create all parent folders
  97. $info=pathinfo($filename);
  98. @mkdir($versionsfoldername.'/'.$info['dirname'],0700,true);
  99. // store a new version of a file
  100. copy($filesfoldername.'/'.$filename,$versionsfoldername.'/'.$filename.'.v'.time());
  101. // expire old revisions
  102. Storage::expire($filename);
  103. }
  104. }
  105. /**
  106. * rollback to an old version of a file.
  107. */
  108. public static function rollback($filename,$revision) {
  109. if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
  110. if (\OCP\App::isEnabled('files_sharing') && $source = \OC_Share::getSource('/'.\OCP\User::getUser().'/files'.$filename)) {
  111. $pos = strpos($source, '/files', 1);
  112. $uid = substr($source, 1, $pos - 1);
  113. $filename = substr($source, $pos + 6);
  114. } else {
  115. $uid = \OCP\User::getUser();
  116. }
  117. $versionsfoldername=\OCP\Config::getSystemValue('datadirectory').'/'.$uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
  118. $filesfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/files';
  119. // rollback
  120. if ( @copy($versionsfoldername.'/'.$filename.'.v'.$revision,$filesfoldername.'/'.$filename) ) {
  121. return true;
  122. }else{
  123. return false;
  124. }
  125. }
  126. }
  127. /**
  128. * check if old versions of a file exist.
  129. */
  130. public static function isversioned($filename) {
  131. if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
  132. if (\OCP\App::isEnabled('files_sharing') && $source = \OC_Share::getSource('/'.\OCP\User::getUser().'/files'.$filename)) {
  133. $pos = strpos($source, '/files', 1);
  134. $uid = substr($source, 1, $pos - 1);
  135. $filename = substr($source, $pos + 6);
  136. } else {
  137. $uid = \OCP\User::getUser();
  138. }
  139. $versionsfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
  140. // check for old versions
  141. $matches=glob($versionsfoldername.'/'.$filename.'.v*');
  142. if(count($matches)>1){
  143. return true;
  144. }else{
  145. return false;
  146. }
  147. }else{
  148. return(false);
  149. }
  150. }
  151. /**
  152. * get a list of old versions of a file.
  153. */
  154. public static function getversions($filename,$count=0) {
  155. if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
  156. if (\OCP\App::isEnabled('files_sharing') && $source = \OC_Share::getSource('/'.\OCP\User::getUser().'/files'.$filename)) {
  157. $pos = strpos($source, '/files', 1);
  158. $uid = substr($source, 1, $pos - 1);
  159. $filename = substr($source, $pos + 6);
  160. } else {
  161. $uid = \OCP\User::getUser();
  162. }
  163. $versionsfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
  164. $versions=array();
  165. // fetch for old versions
  166. $matches=glob($versionsfoldername.'/'.$filename.'.v*');
  167. sort($matches);
  168. foreach($matches as $ma) {
  169. $parts=explode('.v',$ma);
  170. $versions[]=(end($parts));
  171. }
  172. // only show the newest commits
  173. if($count<>0 and (count($versions)>$count)) {
  174. $versions=array_slice($versions,count($versions)-$count);
  175. }
  176. return($versions);
  177. }else{
  178. return(array());
  179. }
  180. }
  181. /**
  182. * expire old versions of a file.
  183. */
  184. public static function expire($filename) {
  185. if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
  186. if (\OCP\App::isEnabled('files_sharing') && $source = \OC_Share::getSource('/'.\OCP\User::getUser().'/files'.$filename)) {
  187. $pos = strpos($source, '/files', 1);
  188. $uid = substr($source, 1, $pos - 1);
  189. $filename = substr($source, $pos + 6);
  190. } else {
  191. $uid = \OCP\User::getUser();
  192. }
  193. $versionsfoldername=\OCP\Config::getSystemValue('datadirectory').'/'. $uid .'/'.\OCP\Config::getSystemValue('files_versionsfolder', Storage::DEFAULTFOLDER);
  194. // check for old versions
  195. $matches=glob($versionsfoldername.'/'.$filename.'.v*');
  196. if(count($matches)>\OCP\Config::getSystemValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS)){
  197. $numbertodelete=count($matches-\OCP\Config::getSystemValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS));
  198. // delete old versions of a file
  199. $deleteitems=array_slice($matches,0,$numbertodelete);
  200. foreach($deleteitems as $de){
  201. unlink($versionsfoldername.'/'.$filename.'.v'.$de);
  202. }
  203. }
  204. }
  205. }
  206. /**
  207. * expire all old versions.
  208. */
  209. public static function expireall($filename) {
  210. // todo this should go through all the versions directories and delete all the not needed files and not needed directories.
  211. // useful to be included in a cleanup cronjob.
  212. }
  213. }