wrote a new versioning app over the weekend. Basic functionality to far but it works (TM) and has all the needed features (TM) for ownCloud 4. Userinterface integration and small improvements still missing.

This commit is contained in:
Frank Karlitschek 2012-04-23 23:54:49 +02:00
parent f5c9fe9ece
commit 4ea927a798
10 changed files with 347 additions and 0 deletions

View File

@ -0,0 +1,19 @@
<?php
require_once('apps/files_versions/versions.php');
// Add an entry in the app list
OC_App::register( array(
'order' => 10,
'id' => 'files_versions',
'name' => 'Versioning' ));
OC_APP::registerAdmin('files_versions', 'settings');
// Listen to write signals
OC_Hook::connect(OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, "OCA_Versions\Storage", "write_hook");
?>

View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<info>
<id>files_versions</id>
<name>Versions</name>
<licence>AGPL</licence>
<author>Frank Karlitschek</author>
<require>3</require>
<description>Versioning of files</description>
<types>
<filesystem/>
</types>
</info>

View File

@ -0,0 +1 @@
1.0.0

View File

@ -0,0 +1,2 @@

View File

@ -0,0 +1,60 @@
<?php
/**
* ownCloud - History page of the Versions App
*
* @author Frank Karlitschek
* @copyright 2011 Frank Karlitschek karlitschek@kde.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
require_once('../../lib/base.php');
OC_Util::checkLoggedIn();
if (isset($_GET['path'])) {
$path = $_GET['path'];
$path = strip_tags($path);
// roll back to old version if button clicked
if(isset($_GET['revert'])) {
\OCA_Versions\Storage::rollback($path,$_GET['revert']);
}
// show the history only if there is something to show
if(OCA_Versions\Storage::isversioned($path)) {
$count=5; //show the newest revisions
$versions=OCA_Versions\Storage::getversions($path,$count);
$tmpl = new OC_Template('files_versions', 'history', 'user');
$tmpl->assign('path', $path);
$tmpl->assign('versions', array_reverse($versions));
$tmpl->printPage();
}else{
$tmpl = new OC_Template('files_versions', 'history', 'user');
$tmpl->assign('path', $path);
$tmpl->assign('message', 'No old versions available');
$tmpl->printPage();
}
}else{
$tmpl = new OC_Template('files_versions', 'history', 'user');
$tmpl->assign('message', 'No path specified');
$tmpl->printPage();
}
?>

View File

@ -0,0 +1,2 @@

View File

@ -0,0 +1,10 @@
<?php
OC_Util::checkAdminUser();
OC_Util::addScript( 'files_versions', 'versions' );
$tmpl = new OC_Template( 'files_versions', 'settings');
return $tmpl->fetchPage();
?>

View File

@ -0,0 +1,18 @@
<?php
if(isset($_['message'])){
if(isset($_['path'])) echo('<strong>File: '.$_['path']).'</strong><br>';
echo('<strong>'.$_['message']).'</strong><br>';
}else{
echo('<strong>Versions of '.$_['path']).'</strong><br>';
echo('<p><em>You can click on the revert button to revert to the specific verson.</em></p><br />');
foreach ($_['versions'] as $v){
echo(' '.OC_Util::formatDate($v).' <a href="history.php?path='.urlencode($_['path']).'&revert='.$v.'" class="button">revert</a><br /><br />');
}
}
?>

View File

@ -0,0 +1,7 @@
<form id="external">
<fieldset class="personalblock">
<strong>Versions</strong><br />
Configuration goes here...
</fieldset>
</form>

View File

@ -0,0 +1,216 @@
<?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
// - check if it works well together with encryption
// - do configuration web interface
// - 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';
const DEFAULTMAXFILESIZE=1048576; // 10MB
const DEFAULTMININTERVAL=300; // 5 min
const DEFAULTMAXVERSIONS=50;
/**
* init the versioning and create the versions folder.
*/
public static function init() {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
// create versions folder
$foldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
if(!is_dir($foldername)){
mkdir($foldername);
}
}
}
/**
* listen to write event.
*/
public static function write_hook($params) {
if(\OC_Config::getValue('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(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
$filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files';
Storage::init();
// check if filename is a directory
if(is_dir($filesfoldername.$filename)){
return false;
}
// check filetype blacklist
$blacklist=explode(' ',\OC_Config::getValue('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)>\OC_Config::getValue('files_versionsmaxfilesize', Storage::DEFAULTMAXFILESIZE)){
return false;
}
// check mininterval
$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);
@mkdir($versionsfoldername.$info['dirname'],0700,true);
// store a new version of a file
copy($filesfoldername.$filename,$versionsfoldername.$filename.'.v'.time());
// expire old revisions
Storage::expire($filename);
}
}
/**
* rollback to an old version of a file.
*/
public static function rollback($filename,$revision) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
$filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files';
// rollback
@copy($versionsfoldername.$filename.'.v'.$revision,$filesfoldername.$filename);
}
}
/**
* check if old versions of a file exist.
*/
public static function isversioned($filename) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('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);
}
}
/**
* get a list of old versions of a file.
*/
public static function getversions($filename,$count=0) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
$versions=array();
// fetch for old versions
$matches=glob($versionsfoldername.$filename.'.v*');
sort($matches);
foreach($matches as $ma) {
$parts=explode('.v',$ma);
$versions[]=(end($parts));
}
// only show the newest commits
if($count<>0 and (count($versions)>$count)) {
$versions=array_slice($versions,count($versions)-$count);
}
return($versions);
}else{
return(array());
}
}
/**
* expire old versions of a file.
*/
public static function expire($filename) {
if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') {
$versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER);
// check for old versions
$matches=glob($versionsfoldername.$filename.'.v*');
if(count($matches)>\OC_Config::getValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS)){
$numbertodelete=count($matches-\OC_Config::getValue('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);
}
}
}
}
/**
* expire all old versions.
*/
public static function expireall($filename) {
// todo this should go through all the versions directories and delete all the not needed files and not needed directories.
// useful to be included in a cleanup cronjob.
}
}