add archive library and a storage provider on top of the archive library
only zip backend is implemented atm
This commit is contained in:
parent
c609b30841
commit
042878a5a9
8 changed files with 543 additions and 0 deletions
14
apps/files_archive/appinfo/app.php
Normal file
14
apps/files_archive/appinfo/app.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
OC::$CLASSPATH['OC_Archive'] = 'apps/files_archive/lib/archive.php';
|
||||
foreach(array('ZIP') as $type){
|
||||
OC::$CLASSPATH['OC_Archive_'.$type] = 'apps/files_archive/lib/'.strtolower($type).'.php';
|
||||
}
|
||||
|
||||
OC::$CLASSPATH['OC_Filestorage_Archive']='apps/files_archive/lib/storage.php';
|
10
apps/files_archive/appinfo/info.xml
Normal file
10
apps/files_archive/appinfo/info.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0"?>
|
||||
<info>
|
||||
<id>files_archive</id>
|
||||
<name>Archive support</name>
|
||||
<description>Transparent opening of archives</description>
|
||||
<version>0.1</version>
|
||||
<licence>AGPL</licence>
|
||||
<author>Robin Appelman</author>
|
||||
<require>3</require>
|
||||
</info>
|
99
apps/files_archive/lib/archive.php
Normal file
99
apps/files_archive/lib/archive.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
abstract class OC_Archive{
|
||||
/**
|
||||
* open any of the supporeted archive types
|
||||
* @param string path
|
||||
* @return OC_Archive
|
||||
*/
|
||||
public static function open($path){
|
||||
$ext=substr($path,strrpos($path,'.'));
|
||||
switch($ext){
|
||||
case '.zip':
|
||||
return new OC_Archive_ZIP($path);
|
||||
}
|
||||
}
|
||||
|
||||
abstract function __construct($source);
|
||||
/**
|
||||
* add an empty folder to the archive
|
||||
* @param string path
|
||||
* @return bool
|
||||
*/
|
||||
abstract function addFolder($path);
|
||||
/**
|
||||
* add a file to the archive
|
||||
* @param string path
|
||||
* @param string source either a local file or string data
|
||||
* @return bool
|
||||
*/
|
||||
abstract function addFile($path,$source='');
|
||||
/**
|
||||
* rename a file or folder in the archive
|
||||
* @param string source
|
||||
* @param string dest
|
||||
* @return bool
|
||||
*/
|
||||
abstract function rename($source,$dest);
|
||||
/**
|
||||
* get the uncompressed size of a file in the archive
|
||||
* @param string path
|
||||
* @return int
|
||||
*/
|
||||
abstract function filesize($path);
|
||||
/**
|
||||
* get the last modified time of a file in the archive
|
||||
* @param string path
|
||||
* @return int
|
||||
*/
|
||||
abstract function mtime($path);
|
||||
/**
|
||||
* get the files in a folder
|
||||
* @param path
|
||||
* @return array
|
||||
*/
|
||||
abstract function getFolder($path);
|
||||
/**
|
||||
*get all files in the archive
|
||||
* @return array
|
||||
*/
|
||||
abstract function getFiles();
|
||||
/**
|
||||
* get the content of a file
|
||||
* @param string path
|
||||
* @return string
|
||||
*/
|
||||
abstract function getFile($path);
|
||||
/**
|
||||
* extract a single file from the archive
|
||||
* @param string path
|
||||
* @param string dest
|
||||
* @return bool
|
||||
*/
|
||||
abstract function extractFile($path,$dest);
|
||||
/**
|
||||
* check if a file or folder exists in the archive
|
||||
* @param string path
|
||||
* @return bool
|
||||
*/
|
||||
abstract function fileExists($path);
|
||||
/**
|
||||
* remove a file or folder from the archive
|
||||
* @param string path
|
||||
* @return bool
|
||||
*/
|
||||
abstract function remove($path);
|
||||
/**
|
||||
* get a file handler
|
||||
* @param string path
|
||||
* @param string mode
|
||||
* @return resource
|
||||
*/
|
||||
abstract function getStream($path,$mode);
|
||||
}
|
102
apps/files_archive/lib/storage.php
Normal file
102
apps/files_archive/lib/storage.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
class OC_Filestorage_Archive extends OC_Filestorage_Common{
|
||||
/**
|
||||
* underlying local storage used for missing functions
|
||||
* @var OC_Archive
|
||||
*/
|
||||
private $archive;
|
||||
private $path;
|
||||
|
||||
private function stripPath($path){//files should never start with /
|
||||
if(substr($path,0,1)=='/'){
|
||||
return substr($path,1);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function __construct($params){
|
||||
$this->archive=OC_Archive::open($params['archive']);
|
||||
$this->path=$params['archive'];
|
||||
}
|
||||
|
||||
public function mkdir($path){
|
||||
$path=$this->stripPath($path);
|
||||
return $this->archive->addFolder($path);
|
||||
}
|
||||
public function rmdir($path){
|
||||
$path=$this->stripPath($path);
|
||||
return $this->archive->remove($path.'/');
|
||||
}
|
||||
public function opendir($path){
|
||||
$path=$this->stripPath($path);
|
||||
$content=$this->archive->getFolder($path);
|
||||
foreach($content as &$file){
|
||||
if(substr($file,-1)=='/'){
|
||||
$file=substr($file,0,-1);
|
||||
}
|
||||
}
|
||||
$id=md5($this->path.$path);
|
||||
OC_FakeDirStream::$dirs[$id]=$content;
|
||||
return opendir('fakedir://'.$id);
|
||||
}
|
||||
public function stat($path){
|
||||
$ctime=filectime($this->path);
|
||||
$path=$this->stripPath($path);
|
||||
if($path==''){
|
||||
$stat=stat($this->path);
|
||||
}else{
|
||||
$stat=array();
|
||||
$stat['mtime']=$this->archive->mtime($path);
|
||||
$stat['size']=$this->archive->filesize($path);
|
||||
}
|
||||
$stat['ctime']=$ctime;
|
||||
return $stat;
|
||||
}
|
||||
public function filetype($path){
|
||||
$path=$this->stripPath($path);
|
||||
if($path==''){
|
||||
return 'dir';
|
||||
}
|
||||
return $this->archive->fileExists($path.'/')?'dir':'file';
|
||||
}
|
||||
public function is_readable($path){
|
||||
return is_readable($this->path);
|
||||
}
|
||||
public function is_writable($path){
|
||||
return is_writable($this->path);
|
||||
}
|
||||
public function file_exists($path){
|
||||
$path=$this->stripPath($path);
|
||||
if($path==''){
|
||||
return file_exists($this->path);
|
||||
}
|
||||
return $this->archive->fileExists($path) or $this->archive->fileExists($path.'/');
|
||||
}
|
||||
public function unlink($path){
|
||||
$path=$this->stripPath($path);
|
||||
return $this->archive->remove($path);
|
||||
}
|
||||
public function fopen($path,$mode){
|
||||
$path=$this->stripPath($path);
|
||||
return $this->archive->getStream($path,$mode);
|
||||
}
|
||||
public function free_space($path){
|
||||
return 0;
|
||||
}
|
||||
public function touch($path, $mtime=null){
|
||||
if(is_null($mtime)){
|
||||
$tmpFile=OC_Helper::tmpFile();
|
||||
$this->archive->extractFile($path,$tmpFile);
|
||||
$this->archive->addfile($path,$tmpFile);
|
||||
}else{
|
||||
return false;//not supported
|
||||
}
|
||||
}
|
||||
}
|
182
apps/files_archive/lib/zip.php
Normal file
182
apps/files_archive/lib/zip.php
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
class OC_Archive_ZIP extends OC_Archive{
|
||||
/**
|
||||
* @var ZipArchive zip
|
||||
*/
|
||||
private $zip=null;
|
||||
private $contents=array();
|
||||
private $success=false;
|
||||
private $path;
|
||||
|
||||
function __construct($source){
|
||||
$this->path=$source;
|
||||
$this->zip=new ZipArchive();
|
||||
if($this->zip->open($source,ZipArchive::CREATE)){
|
||||
}else{
|
||||
OC_LOG::write('files_archive','Error while opening archive '.$source,OC_Log::WARN);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* add an empty folder to the archive
|
||||
* @param string path
|
||||
* @return bool
|
||||
*/
|
||||
function addFolder($path){
|
||||
return $this->zip->addEmptyDir($path);
|
||||
}
|
||||
/**
|
||||
* add a file to the archive
|
||||
* @param string path
|
||||
* @param string source either a local file or string data
|
||||
* @return bool
|
||||
*/
|
||||
function addFile($path,$source=''){
|
||||
if(file_exists($source)){
|
||||
$result=$this->zip->addFile($source,$path);
|
||||
}else{
|
||||
$result=$this->zip->addFromString($path,$source);
|
||||
}
|
||||
if($result){
|
||||
$this->zip->close();//close and reopen to save the zip
|
||||
$this->zip->open($this->path);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* rename a file or folder in the archive
|
||||
* @param string source
|
||||
* @param string dest
|
||||
* @return bool
|
||||
*/
|
||||
function rename($source,$dest){
|
||||
return $this->zip->renameName($source,$dest);
|
||||
}
|
||||
/**
|
||||
* get the uncompressed size of a file in the archive
|
||||
* @param string path
|
||||
* @return int
|
||||
*/
|
||||
function filesize($path){
|
||||
$stat=$this->zip->statName($path);
|
||||
return $stat['size'];
|
||||
}
|
||||
/**
|
||||
* get the last modified time of a file in the archive
|
||||
* @param string path
|
||||
* @return int
|
||||
*/
|
||||
function mtime($path){
|
||||
$stat=$this->zip->statName($path);
|
||||
return $stat['mtime'];
|
||||
}
|
||||
/**
|
||||
* get the files in a folder
|
||||
* @param path
|
||||
* @return array
|
||||
*/
|
||||
function getFolder($path){
|
||||
$files=$this->getFiles();
|
||||
$folderContent=array();
|
||||
$pathLength=strlen($path);
|
||||
foreach($files as $file){
|
||||
if(substr($file,0,$pathLength)==$path and $file!=$path){
|
||||
if(strrpos(substr($file,0,-1),'/')<=$pathLength){
|
||||
$folderContent[]=substr($file,$pathLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $folderContent;
|
||||
}
|
||||
/**
|
||||
*get all files in the archive
|
||||
* @return array
|
||||
*/
|
||||
function getFiles(){
|
||||
if(count($this->contents)){
|
||||
return $this->contents;
|
||||
}
|
||||
$fileCount=$this->zip->numFiles;
|
||||
$files=array();
|
||||
for($i=0;$i<$fileCount;$i++){
|
||||
$files[]=$this->zip->getNameIndex($i);
|
||||
}
|
||||
$this->contents=$files;
|
||||
return $files;
|
||||
}
|
||||
/**
|
||||
* get the content of a file
|
||||
* @param string path
|
||||
* @return string
|
||||
*/
|
||||
function getFile($path){
|
||||
return $this->zip->getFromName($path);
|
||||
}
|
||||
/**
|
||||
* extract a single file from the archive
|
||||
* @param string path
|
||||
* @param string dest
|
||||
* @return bool
|
||||
*/
|
||||
function extractFile($path,$dest){
|
||||
$fp = $this->zip->getStream($path);
|
||||
file_put_contents($dest,$fp);
|
||||
}
|
||||
/**
|
||||
* check if a file or folder exists in the archive
|
||||
* @param string path
|
||||
* @return bool
|
||||
*/
|
||||
function fileExists($path){
|
||||
return $this->zip->locateName($path)!==false;
|
||||
}
|
||||
/**
|
||||
* remove a file or folder from the archive
|
||||
* @param string path
|
||||
* @return bool
|
||||
*/
|
||||
function remove($path){
|
||||
return $this->zip->deleteName($path);
|
||||
}
|
||||
/**
|
||||
* get a file handler
|
||||
* @param string path
|
||||
* @param string mode
|
||||
* @return resource
|
||||
*/
|
||||
function getStream($path,$mode){
|
||||
if($mode=='r' or $mode=='rb'){
|
||||
return $this->zip->getStream($path);
|
||||
}else{//since we cant directly get a writable stream, make a temp copy of the file and put it back in the archive when the stream is closed
|
||||
if(strrpos($path,'.')!==false){
|
||||
$ext=substr($path,strrpos($path,'.'));
|
||||
}else{
|
||||
$ext='';
|
||||
}
|
||||
$tmpFile=OC_Helper::tmpFile($ext);
|
||||
OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack');
|
||||
if($this->fileExists($path)){
|
||||
$this->extractFile($path,$tmpFile);
|
||||
}
|
||||
self::$tempFiles[$tmpFile]=$path;
|
||||
return fopen('close://'.$tmpFile,$mode);
|
||||
}
|
||||
}
|
||||
|
||||
private static $tempFiles=array();
|
||||
/**
|
||||
* write back temporary files
|
||||
*/
|
||||
function writeBack($tmpFile){
|
||||
if(isset(self::$tempFiles[$tmpFile])){
|
||||
$this->addFile(self::$tempFiles[$tmpFile],$tmpFile);
|
||||
unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
}
|
97
apps/files_archive/tests/archive.php
Normal file
97
apps/files_archive/tests/archive.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
abstract class Test_Archive extends UnitTestCase {
|
||||
/**
|
||||
* @var OC_Archive
|
||||
*/
|
||||
protected $instance;
|
||||
|
||||
/**
|
||||
* get the existing test archive
|
||||
* @return OC_Archive
|
||||
*/
|
||||
abstract protected function getExisting();
|
||||
/**
|
||||
* get a new archive for write testing
|
||||
* @return OC_Archive
|
||||
*/
|
||||
abstract protected function getNew();
|
||||
|
||||
public function testGetFiles(){
|
||||
$this->instance=$this->getExisting();
|
||||
$allFiles=$this->instance->getFiles();
|
||||
$expected=array('lorem.txt','logo-wide.png','dir/','dir/lorem.txt');
|
||||
$this->assertEqual(4,count($allFiles));
|
||||
foreach($expected as $file){
|
||||
$this->assertNotIdentical(false,array_search($file,$allFiles),'cant find '.$file.' in archive');
|
||||
$this->assertTrue($this->instance->fileExists($file));
|
||||
}
|
||||
$this->assertFalse($this->instance->fileExists('non/existing/file'));
|
||||
|
||||
$rootContent=$this->instance->getFolder('');
|
||||
$expected=array('lorem.txt','logo-wide.png','dir/');
|
||||
$this->assertEqual(3,count($rootContent));
|
||||
foreach($expected as $file){
|
||||
$this->assertNotIdentical(false,array_search($file,$rootContent),'cant find '.$file.' in archive');
|
||||
}
|
||||
|
||||
$dirContent=$this->instance->getFolder('dir/');
|
||||
$expected=array('lorem.txt');
|
||||
$this->assertEqual(1,count($dirContent));
|
||||
foreach($expected as $file){
|
||||
$this->assertNotIdentical(false,array_search($file,$dirContent),'cant find '.$file.' in archive');
|
||||
}
|
||||
}
|
||||
|
||||
public function testContent(){
|
||||
$this->instance=$this->getExisting();
|
||||
$dir=OC::$SERVERROOT.'/apps/files_archive/tests/data';
|
||||
$textFile=$dir.'/lorem.txt';
|
||||
$this->assertEqual(file_get_contents($textFile),$this->instance->getFile('lorem.txt'));
|
||||
|
||||
$tmpFile=OC_Helper::tmpFile('.txt');
|
||||
$this->instance->extractFile('lorem.txt',$tmpFile);
|
||||
$this->assertEqual(file_get_contents($textFile),file_get_contents($tmpFile));
|
||||
}
|
||||
|
||||
public function testWrite(){
|
||||
$dir=OC::$SERVERROOT.'/apps/files_archive/tests/data';
|
||||
$textFile=$dir.'/lorem.txt';
|
||||
$this->instance=$this->getNew();
|
||||
$this->assertEqual(0,count($this->instance->getFiles()));
|
||||
$this->instance->addFile('lorem.txt',$textFile);
|
||||
$this->assertEqual(1,count($this->instance->getFiles()));
|
||||
$this->assertTrue($this->instance->fileExists('lorem.txt'));
|
||||
|
||||
$this->assertEqual(file_get_contents($textFile),$this->instance->getFile('lorem.txt'));
|
||||
$this->instance->addFile('lorem.txt','foobar');
|
||||
$this->assertEqual('foobar',$this->instance->getFile('lorem.txt'));
|
||||
}
|
||||
|
||||
public function testReadStream(){
|
||||
$dir=OC::$SERVERROOT.'/apps/files_archive/tests/data';
|
||||
$this->instance=$this->getExisting();
|
||||
$fh=$this->instance->getStream('lorem.txt','r');
|
||||
$this->assertTrue($fh);
|
||||
$content=fread($fh,$this->instance->filesize('lorem.txt'));
|
||||
fclose($fh);
|
||||
$this->assertEqual(file_get_contents($dir.'/lorem.txt'),$content);
|
||||
}
|
||||
public function testWriteStream(){
|
||||
$dir=OC::$SERVERROOT.'/apps/files_archive/tests/data';
|
||||
$this->instance=$this->getNew();
|
||||
$fh=$this->instance->getStream('lorem.txt','w');
|
||||
$source=fopen($dir.'/lorem.txt','r');
|
||||
OC_Helper::streamCopy($source,$fh);
|
||||
fclose($source);
|
||||
fclose($fh);
|
||||
$this->assertTrue($this->instance->fileExists('lorem.txt'));
|
||||
$this->assertEqual(file_get_contents($dir.'/lorem.txt'),$this->instance->getFile('lorem.txt'));
|
||||
}
|
||||
}
|
19
apps/files_archive/tests/storage.php
Normal file
19
apps/files_archive/tests/storage.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
class Test_Filestorage_Archive_Zip extends Test_FileStorage {
|
||||
/**
|
||||
* @var string tmpDir
|
||||
*/
|
||||
public function setUp(){
|
||||
$tmpFile=OC_Helper::tmpFile('.zip');
|
||||
$this->instance=new OC_Filestorage_Archive(array('archive'=>$tmpFile));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
20
apps/files_archive/tests/zip.php
Normal file
20
apps/files_archive/tests/zip.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
require_once('archive.php');
|
||||
|
||||
class Test_Archive_ZIP extends Test_Archive{
|
||||
protected function getExisting(){
|
||||
$dir=OC::$SERVERROOT.'/apps/files_archive/tests/data';
|
||||
return new OC_Archive_ZIP($dir.'/data.zip');
|
||||
}
|
||||
|
||||
protected function getNew(){
|
||||
return new OC_Archive_ZIP(OC_Helper::tmpFile('.zip'));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue