server/apps/files_external/lib/swift.php
Robin Appelman d96146a017 Give storage backends the option to define having no known free space
When this is the case only the configured max upload size is taking into account for uploading
2013-02-16 03:27:50 +01:00

582 lines
13 KiB
PHP

<?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.
*/
namespace OC\Files\Storage;
require_once 'php-cloudfiles/cloudfiles.php';
class SWIFT extends \OC\Files\Storage\Common{
private $id;
private $host;
private $root;
private $user;
private $token;
private $secure;
private $ready = false;
/**
* @var \CF_Authentication auth
*/
private $auth;
/**
* @var \CF_Connection conn
*/
private $conn;
/**
* @var \CF_Container rootContainer
*/
private $rootContainer;
private static $tempFiles=array();
private $objects=array();
private $containers=array();
const SUBCONTAINER_FILE='.subcontainers';
/**
* translate directory path to container name
* @param string $path
* @return string
*/
private function getContainerName($path) {
$path=trim(trim($this->root, '/') . "/".$path, '/.');
return str_replace('/', '\\', $path);
}
/**
* get container by path
* @param string $path
* @return \CF_Container
*/
private function getContainer($path) {
if ($path=='' or $path=='/') {
return $this->rootContainer;
}
if (isset($this->containers[$path])) {
return $this->containers[$path];
}
try {
$container=$this->conn->get_container($this->getContainerName($path));
$this->containers[$path]=$container;
return $container;
} catch(\NoSuchContainerException $e) {
return null;
}
}
/**
* create container
* @param string $path
* @return \CF_Container
*/
private function createContainer($path) {
if ($path=='' or $path=='/' or $path=='.') {
return $this->conn->create_container($this->getContainerName($path));
}
$parent=dirname($path);
if ($parent=='' or $parent=='/' or $parent=='.') {
$parentContainer=$this->rootContainer;
} else {
if ( ! $this->containerExists($parent)) {
$parentContainer=$this->createContainer($parent);
} else {
$parentContainer=$this->getContainer($parent);
}
}
$this->addSubContainer($parentContainer, basename($path));
return $this->conn->create_container($this->getContainerName($path));
}
/**
* get object by path
* @param string $path
* @return \CF_Object
*/
private function getObject($path) {
if (isset($this->objects[$path])) {
return $this->objects[$path];
}
$container=$this->getContainer(dirname($path));
if (is_null($container)) {
return null;
} else {
if ($path=="/" or $path=='') {
return null;
}
try {
$obj=$container->get_object(basename($path));
$this->objects[$path]=$obj;
return $obj;
} catch(\NoSuchObjectException $e) {
return null;
}
}
}
/**
* get the names of all objects in a container
* @param CF_Container
* @return array
*/
private function getObjects($container) {
if (is_null($container)) {
return array();
} else {
$files=$container->get_objects();
foreach ($files as &$file) {
$file=$file->name;
}
return $files;
}
}
/**
* create object
* @param string $path
* @return \CF_Object
*/
private function createObject($path) {
$container=$this->getContainer(dirname($path));
if ( ! is_null($container)) {
$container=$this->createContainer(dirname($path));
}
return $container->create_object(basename($path));
}
/**
* check if an object exists
* @param string
* @return bool
*/
private function objectExists($path) {
return !is_null($this->getObject($path));
}
/**
* check if container for path exists
* @param string $path
* @return bool
*/
private function containerExists($path) {
return !is_null($this->getContainer($path));
}
/**
* get the list of emulated sub containers
* @param \CF_Container $container
* @return array
*/
private function getSubContainers($container) {
$tmpFile=\OCP\Files::tmpFile();
$obj=$this->getSubContainerFile($container);
try {
$obj->save_to_filename($tmpFile);
} catch(\Exception $e) {
return array();
}
$obj->save_to_filename($tmpFile);
$containers=file($tmpFile);
unlink($tmpFile);
foreach ($containers as &$sub) {
$sub=trim($sub);
}
return $containers;
}
/**
* add an emulated sub container
* @param \CF_Container $container
* @param string $name
* @return bool
*/
private function addSubContainer($container, $name) {
if ( ! $name) {
return false;
}
$tmpFile=\OCP\Files::tmpFile();
$obj=$this->getSubContainerFile($container);
try {
$obj->save_to_filename($tmpFile);
$containers=file($tmpFile);
foreach ($containers as &$sub) {
$sub=trim($sub);
}
if(array_search($name, $containers) !== false) {
unlink($tmpFile);
return false;
} else {
$fh=fopen($tmpFile, 'a');
fwrite($fh, $name . "\n");
}
} catch(\Exception $e) {
file_put_contents($tmpFile, $name . "\n");
}
$obj->load_from_filename($tmpFile);
unlink($tmpFile);
return true;
}
/**
* remove an emulated sub container
* @param \CF_Container $container
* @param string $name
* @return bool
*/
private function removeSubContainer($container, $name) {
if ( ! $name) {
return false;
}
$tmpFile=\OCP\Files::tmpFile();
$obj=$this->getSubContainerFile($container);
try {
$obj->save_to_filename($tmpFile);
$containers=file($tmpFile);
} catch (\Exception $e) {
return false;
}
foreach ($containers as &$sub) {
$sub=trim($sub);
}
$i=array_search($name, $containers);
if ($i===false) {
unlink($tmpFile);
return false;
} else {
unset($containers[$i]);
file_put_contents($tmpFile, implode("\n", $containers)."\n");
}
$obj->load_from_filename($tmpFile);
unlink($tmpFile);
return true;
}
/**
* ensure a subcontainer file exists and return it's object
* @param \CF_Container $container
* @return \CF_Object
*/
private function getSubContainerFile($container) {
try {
return $container->get_object(self::SUBCONTAINER_FILE);
} catch(NoSuchObjectException $e) {
return $container->create_object(self::SUBCONTAINER_FILE);
}
}
public function __construct($params) {
$this->token=$params['token'];
$this->host=$params['host'];
$this->user=$params['user'];
$this->root=isset($params['root'])?$params['root']:'/';
if (isset($params['secure'])) {
if (is_string($params['secure'])) {
$this->secure = ($params['secure'] === 'true');
} else {
$this->secure = (bool)$params['secure'];
}
} else {
$this->secure = false;
}
if ( ! $this->root || $this->root[0]!='/') {
$this->root='/'.$this->root;
}
}
private function init(){
if($this->ready) {
return;
}
$this->ready = true;
$this->auth = new \CF_Authentication($this->user, $this->token, null, $this->host);
$this->auth->authenticate();
$this->conn = new \CF_Connection($this->auth);
if ( ! $this->containerExists('/')) {
$this->rootContainer=$this->createContainer('/');
} else {
$this->rootContainer=$this->getContainer('/');
}
}
public function getId(){
return $this->id;
}
public function mkdir($path) {
$this->init();
if ($this->containerExists($path)) {
return false;
} else {
$this->createContainer($path);
return true;
}
}
public function rmdir($path) {
$this->init();
if (!$this->containerExists($path)) {
return false;
} else {
$this->emptyContainer($path);
if ($path!='' and $path!='/') {
$parentContainer=$this->getContainer(dirname($path));
$this->removeSubContainer($parentContainer, basename($path));
}
$this->conn->delete_container($this->getContainerName($path));
unset($this->containers[$path]);
return true;
}
}
private function emptyContainer($path) {
$container=$this->getContainer($path);
if (is_null($container)) {
return;
}
$subContainers=$this->getSubContainers($container);
foreach ($subContainers as $sub) {
if ($sub) {
$this->emptyContainer($path.'/'.$sub);
$this->conn->delete_container($this->getContainerName($path.'/'.$sub));
unset($this->containers[$path.'/'.$sub]);
}
}
$objects=$this->getObjects($container);
foreach ($objects as $object) {
$container->delete_object($object);
unset($this->objects[$path.'/'.$object]);
}
}
public function opendir($path) {
$this->init();
$container=$this->getContainer($path);
$files=$this->getObjects($container);
$i=array_search(self::SUBCONTAINER_FILE, $files);
if ($i!==false) {
unset($files[$i]);
}
$subContainers=$this->getSubContainers($container);
$files=array_merge($files, $subContainers);
$id=$this->getContainerName($path);
\OC\Files\Stream\Dir::register($id, $files);
return opendir('fakedir://'.$id);
}
public function filetype($path) {
$this->init();
if ($this->containerExists($path)) {
return 'dir';
} else {
return 'file';
}
}
public function isReadable($path) {
return true;
}
public function isUpdatable($path) {
return true;
}
public function file_exists($path) {
$this->init();
if ($this->is_dir($path)) {
return true;
} else {
return $this->objectExists($path);
}
}
public function file_get_contents($path) {
$this->init();
$obj=$this->getObject($path);
if (is_null($obj)) {
return false;
}
return $obj->read();
}
public function file_put_contents($path, $content) {
$this->init();
$obj=$this->getObject($path);
if (is_null($obj)) {
$container=$this->getContainer(dirname($path));
if (is_null($container)) {
return false;
}
$obj=$container->create_object(basename($path));
}
$this->resetMTime($obj);
return $obj->write($content);
}
public function unlink($path) {
$this->init();
if ($this->containerExists($path)) {
return $this->rmdir($path);
}
if ($this->objectExists($path)) {
$container=$this->getContainer(dirname($path));
$container->delete_object(basename($path));
unset($this->objects[$path]);
} else {
return false;
}
}
public function fopen($path, $mode) {
$this->init();
switch($mode) {
case 'r':
case 'rb':
$obj=$this->getObject($path);
if (is_null($obj)) {
return false;
}
$fp = fopen('php://temp', 'r+');
$obj->stream($fp);
rewind($fp);
return $fp;
case 'w':
case 'wb':
case 'a':
case 'ab':
case 'r+':
case 'w+':
case 'wb+':
case 'a+':
case 'x':
case 'x+':
case 'c':
case 'c+':
$tmpFile=$this->getTmpFile($path);
\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
self::$tempFiles[$tmpFile]=$path;
return fopen('close://'.$tmpFile, $mode);
}
}
public function writeBack($tmpFile) {
if (isset(self::$tempFiles[$tmpFile])) {
$this->fromTmpFile($tmpFile, self::$tempFiles[$tmpFile]);
unlink($tmpFile);
}
}
public function touch($path, $mtime=null) {
$this->init();
$obj=$this->getObject($path);
if (is_null($obj)) {
return false;
}
if (is_null($mtime)) {
$mtime=time();
}
//emulate setting mtime with metadata
$obj->metadata['Mtime']=$mtime;
$obj->sync_metadata();
}
public function rename($path1, $path2) {
$this->init();
$sourceContainer=$this->getContainer(dirname($path1));
$targetContainer=$this->getContainer(dirname($path2));
$result=$sourceContainer->move_object_to(basename($path1), $targetContainer, basename($path2));
unset($this->objects[$path1]);
if ($result) {
$targetObj=$this->getObject($path2);
$this->resetMTime($targetObj);
}
return $result;
}
public function copy($path1, $path2) {
$this->init();
$sourceContainer=$this->getContainer(dirname($path1));
$targetContainer=$this->getContainer(dirname($path2));
$result=$sourceContainer->copy_object_to(basename($path1), $targetContainer, basename($path2));
if ($result) {
$targetObj=$this->getObject($path2);
$this->resetMTime($targetObj);
}
return $result;
}
public function stat($path) {
$this->init();
$container=$this->getContainer($path);
if ( ! is_null($container)) {
return array(
'mtime'=>-1,
'size'=>$container->bytes_used,
'ctime'=>-1
);
}
$obj=$this->getObject($path);
if (is_null($obj)) {
return false;
}
if (isset($obj->metadata['Mtime']) and $obj->metadata['Mtime']>-1) {
$mtime=$obj->metadata['Mtime'];
} else {
$mtime=strtotime($obj->last_modified);
}
return array(
'mtime'=>$mtime,
'size'=>$obj->content_length,
'ctime'=>-1,
);
}
private function getTmpFile($path) {
$this->init();
$obj=$this->getObject($path);
if ( ! is_null($obj)) {
$tmpFile=\OCP\Files::tmpFile();
$obj->save_to_filename($tmpFile);
return $tmpFile;
} else {
return \OCP\Files::tmpFile();
}
}
private function fromTmpFile($tmpFile, $path) {
$this->init();
$obj=$this->getObject($path);
if (is_null($obj)) {
$obj=$this->createObject($path);
}
$obj->load_from_filename($tmpFile);
$this->resetMTime($obj);
}
/**
* remove custom mtime metadata
* @param \CF_Object $obj
*/
private function resetMTime($obj) {
if (isset($obj->metadata['Mtime'])) {
$obj->metadata['Mtime']=-1;
$obj->sync_metadata();
}
}
}