Page MenuHomePhorge

No OneTemporary

diff --git a/lib/drivers/seafile/seafile_api.php b/lib/drivers/seafile/seafile_api.php
index ae6d65b..0ffe060 100644
--- a/lib/drivers/seafile/seafile_api.php
+++ b/lib/drivers/seafile/seafile_api.php
@@ -1,891 +1,896 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2014, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program 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 Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Class implementing access via SeaFile Web API v2
*/
class seafile_api
{
const STATUS_OK = 200;
const CREATED = 201;
const ACCEPTED = 202;
const MOVED_PERMANENTLY = 301;
const BAD_REQUEST = 400;
const FORBIDDEN = 403;
const NOT_FOUND = 404;
const CONFLICT = 409;
const TOO_MANY_REQUESTS = 429;
const REPO_PASSWD_REQUIRED = 440;
const REPO_PASSWD_MAGIC_REQUIRED = 441;
const INTERNAL_SERVER_ERROR = 500;
const OPERATION_FAILED = 520;
const CONNECTION_ERROR = 550;
/**
* Specifies how long max. we'll wait and renew throttled request (in seconds)
*/
const WAIT_LIMIT = 30;
/**
* Configuration
*
* @var array
*/
protected $config = array();
/**
* HTTP request handle
*
* @var HTTP_Request
*/
protected $request;
/**
* Web API URI prefix
*
* @var string
*/
protected $url;
/**
* Session token
*
* @var string
*/
protected $token;
/**
* API URL prefix (schema and host[:port])
*
* @var string
*/
protected $url_prefix;
public function __construct($config = array())
{
$this->config = $config;
// set Web API URI
$this->url = rtrim(trim($config['host']), '/') ?: 'localhost';
if (!preg_match('|^https?://|i', $this->url)) {
$this->url = 'https://' . $this->url;
}
if (!preg_match('|/api2$|', $this->url)) {
$this->url .= '/api2/';
}
$this->url_prefix = preg_replace('|^(https?://[^/]+).*$|i', '\\1', $this->url);
}
/**
*
* @param array Configuration for this Request instance, that will be merged
* with default configuration
*
* @return HTTP_Request2 Request object
*/
public static function http_request($config = array())
{
// load HTTP_Request2
require_once 'HTTP/Request2.php';
// remove unknown config, otherwise HTTP_Request will throw an error
$config = array_intersect_key($config, array_flip(array(
'connect_timeout', 'timeout', 'use_brackets', 'protocol_version',
'buffer_size', 'store_body', 'follow_redirects', 'max_redirects',
'strict_redirects', 'ssl_verify_peer', 'ssl_verify_host',
'ssl_cafile', 'ssl_capath', 'ssl_local_cert', 'ssl_passphrase'
)));
// force CURL adapter, this allows to handle correctly
// compressed responses with simple SplObserver registered
$config['adapter'] = 'HTTP_Request2_Adapter_Curl';
try {
$request = new HTTP_Request2();
$request->setConfig($config);
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
return;
}
return $request;
}
/**
* Send HTTP request
*
* @param string $method Request method ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT')
* @param string $url Request API URL
* @param array $get GET parameters
* @param array $post POST parameters
* @param array $upload Uploaded files data
*
* @return string|array Server response
*/
protected function request($method, $url, $get = null, $post = null, $upload = null)
{
if (!preg_match('/^https?:\/\//', $url)) {
$url = $this->url . $url;
// Note: It didn't work for me without the last backslash
$url = rtrim($url, '/') . '/';
}
else {
$url = $this->mod_url($url);
}
if (!$this->request) {
$this->config['store_body'] = true;
// some methods respond with 301 redirect, we'll not follow them
// also because of https://github.com/haiwen/seahub/issues/288
$this->config['follow_redirects'] = false;
$this->request = self::http_request($this->config);
if (!$this->request) {
$this->status = self::CONNECTION_ERROR;
return;
}
}
// cleanup
try {
$this->request->setBody('');
$this->request->setUrl($url);
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
$this->status = self::CONNECTION_ERROR;
return;
}
if ($this->config['debug']) {
$log_line = "SeaFile $method: $url";
$json_opt = PHP_VERSION_ID >= 50400 ? JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0;
if (!empty($get)) {
$log_line .= ", GET: " . @json_encode($get, $json_opt);
}
if (!empty($post)) {
$log_line .= ", POST: " . preg_replace('/("password":)[^\},]+/', '\\1"*"', @json_encode($post, $json_opt));
}
if (!empty($upload)) {
$log_line .= ", Files: " . @json_encode(array_keys($upload), $json_opt);
}
rcube::write_log('console', $log_line);
}
$this->request->setMethod($method ?: HTTP_Request2::METHOD_GET);
if (!empty($get)) {
$_url = $this->request->getUrl();
$_url->setQueryVariables($get);
$this->request->setUrl($_url);
}
if (!empty($post)) {
$this->request->addPostParameter($post);
}
if (!empty($upload)) {
foreach ($upload as $field_name => $file) {
$this->request->addUpload($field_name, $file['data'], $file['name'], $file['type']);
}
}
if ($this->token) {
$this->request->setHeader('Authorization', "Token " . $this->token);
}
// some HTTP server configurations require this header
$this->request->setHeader('Accept', "application/json,text/javascript,*/*");
// proxy User-Agent string
$this->request->setHeader('User-Agent', $_SERVER['HTTP_USER_AGENT']);
// send request to the SeaFile API server
try {
$response = $this->request->send();
$this->status = $response->getStatus();
$body = $response->getBody();
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
$this->status = self::CONNECTION_ERROR;
}
if ($this->config['debug']) {
rcube::write_log('console', "SeaFile Response [$this->status]: " . trim($body));
}
// request throttled, try again
if ($this->status == self::TOO_MANY_REQUESTS) {
if (preg_match('/([0-9]+) second/', $body, $m) && ($seconds = $m[1]) < self::WAIT_LIMIT) {
sleep($seconds);
return $this->request($method, $url, $get, $post, $upload);
}
}
// decode response
return $this->status >= 400 ? false : @json_decode($body, true);
}
/**
* Return error code of last operation
*/
public function is_error()
{
return $this->status >= 400 ? $this->status : false;
}
/**
* Authenticate to SeaFile API and get auth token
*
* @param string $username User name (email)
* @param string $password User password
*
* @return string Authentication token
*/
public function authenticate($username, $password)
{
// sanity checks
if ($username === '' || !is_string($username) || $password === '' || !is_string($password)) {
$this->status = self::BAD_REQUEST;
return false;
}
$result = $this->request('POST', 'auth-token', null, array(
'username' => $username,
'password' => $password,
));
if ($result['token']) {
return $this->token = $result['token'];
}
}
/**
* Get account information
*
* @return array Account info (usage, total, email)
*/
public function account_info()
{
return $this->request('GET', "account/info");
}
/**
* Delete a directory
*
* @param string $repo_id Library identifier
* @param string $dir Directory name (with path)
*
* @return bool True on success, False on failure
*/
public function directory_delete($repo_id, $dir)
{
// sanity checks
if ($dir === '' || $dir === '/' || !is_string($dir)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
$this->request('DELETE', "repos/$repo_id/dir", array('p' => $dir));
return $this->is_error() === false;
}
/**
* Rename a directory
*
* @param string $repo_id Library identifier
* @param string $src_dir Directory name (with path)
* @param string $dest_dir New directory name (with path)
*
* @return bool True on success, False on failure
*/
public function directory_rename($repo_id, $src_dir, $dest_dir)
{
// sanity checks
if ($src_dir === '' || $src_dir === '/' || !is_string($src_dir)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($dest_dir === '' || $dest_dir === '/' || !is_string($dest_dir) || $dest_dir === $src_dir) {
$this->status = self::BAD_REQUEST;
return false;
}
$result = $this->request('POST', "repos/$repo_id/dir", array('p' => $src_dir), array(
'operation' => 'rename',
'newname' => $dest_dir,
));
return $this->is_error() === false;
}
/**
* Rename a directory
*
* @param string $repo_id Library identifier
* @param string $dir Directory name (with path)
*
* @return bool True on success, False on failure
*/
public function directory_create($repo_id, $dir)
{
// sanity checks
if ($dir === '' || $dir === '/' || !is_string($dir)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
$result = $this->request('POST', "repos/$repo_id/dir", array('p' => $dir), array(
'operation' => 'mkdir',
));
return $this->is_error() === false;
}
/**
* List directory entries (files and directories)
*
- * @param string $repo_id Library identifier
- * @param string $dir Directory name (with path)
- * @param string $type Entry type ('dir' or 'file')
+ * @param string $repo_id Library identifier
+ * @param string $dir Directory name (with path)
+ * @param string $type Entry type ('dir' or 'file') (requires Seafile 4.4.1)
+ * @param bool $recursive Enable recursive call for 'dir' listing (requires Seafile 4.4.1)
*
* @return bool|array List of directories/files on success, False on failure
*/
- public function directory_entries($repo_id, $dir, $type = null)
+ public function directory_entries($repo_id, $dir, $type = null, $recursive = false)
{
// sanity checks
if (!is_string($dir)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($dir === '') {
$dir = '/';
}
// args: p=<$name> ('/' is a root, default), oid=?
// sample result
// [{
// "id": "0000000000000000000000000000000000000000",
// "type": "file",
// "name": "test1.c",
// "size": 0
// },{
// "id": "e4fe14c8cda2206bb9606907cf4fca6b30221cf9",
// "type": "dir",
// "name": "test_dir"
// }]
$params = array('p' => $dir);
if ($type) {
$params['t'] = $type == 'dir' ? 'd' : 'f';
}
+ if ($recursive && $type == 'dir') {
+ $params['recursive'] = 1;
+ }
+
return $this->request('GET', "repos/$repo_id/dir", $params);
}
/**
* Update a file
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
* @param array $file File data (data, type, name)
*
* @return bool True on success, False on failure
*/
public function file_update($repo_id, $filename, $file)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
// first get the update link
$result = $this->request('GET', "repos/$repo_id/update-link");
if ($this->is_error() || empty($result)) {
return false;
}
$path = explode('/', $filename);
$fn = array_pop($path);
// then update file
$result = $this->request('POST', $result, null, array(
'filename' => $fn,
'target_file' => $filename,
),
array('file' => $file)
);
return $this->is_error() === false;
}
/**
* Upload a file
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
* @param array $file File data (data, type, name)
*
* @return bool True on success, False on failure
*/
public function file_upload($repo_id, $filename, $file)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
// first get upload link
$result = $this->request('GET', "repos/$repo_id/upload-link");
if ($this->is_error() || empty($result)) {
return false;
}
$path = explode('/', $filename);
$filename = array_pop($path);
$dir = '/' . ltrim(implode('/', $path), '/');
$file['name'] = $filename;
// then update file
$result = $this->request('POST', $result, null, array(
'parent_dir' => $dir
),
array('file' => $file)
);
return $this->is_error() === false;
}
/**
* Delete a file
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
*
* @return bool True on success, False on failure
*/
public function file_delete($repo_id, $filename)
{
// sanity check
if ($filename === '' || $filename === '/' || !is_string($filename)) {
$this->status = self::BAD_REQUEST;
return false;
}
$this->request('DELETE', "repos/$repo_id/file", array('p' => $filename));
return $this->is_error() === false;
}
/**
* Copy file(s) (no rename here)
*
* @param string $repo_id Library identifier
* @param string|array $files List of files (without path)
* @param string $src_dir Source directory
* @param string $dest_dir Destination directory
* @param string $dest_repo Destination library (optional)
*
* @return bool True on success, False on failure
*/
public function file_copy($repo_id, $files, $src_dir, $dest_dir, $dest_repo)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($src_dir === '' || !is_string($src_dir)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($dest_dir === '' || !is_string($dest_dir)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ((!is_array($files) && !strlen($files)) || (is_array($files) && empty($files))) {
$this->status = self::BAD_REQUEST;
return false;
}
if (empty($dest_repo)) {
$dest_repo = $repo_id;
}
$result = $this->request('POST', "repos/$repo_id/fileops/copy", array('p' => $src_dir), array(
'file_names' => implode(':', (array) $files),
'dst_dir' => $dest_dir,
'dst_repo' => $dest_repo,
));
return $this->is_error() === false;
}
/**
* Move a file (no rename here)
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
* @param string $dst_dir Destination directory
* @param string $dst_repo Destination library (optional)
*
* @return bool True on success, False on failure
*/
public function file_move($repo_id, $filename, $dst_dir, $dst_repo = null)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($filename === '' || !is_string($filename)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($dst_dir === '' || !is_string($dst_dir)) {
$this->status = self::BAD_REQUEST;
return false;
}
if (empty($dst_repo)) {
$dst_repo = $repo_id;
}
$result = $this->request('POST', "repos/$repo_id/file", array('p' => $filename), array(
'operation' => 'move',
'dst_dir' => $dst_dir,
'dst_repo' => $dst_repo,
));
return $this->is_error() === false;
}
/**
* Rename a file
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
* @param string $new_name New file name (without path)
*
* @return bool True on success, False on failure
*/
public function file_rename($repo_id, $filename, $new_name)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($filename === '' || !is_string($filename)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($new_name === '' || !is_string($new_name)) {
$this->status = self::BAD_REQUEST;
return false;
}
$result = $this->request('POST', "repos/$repo_id/file", array('p' => $filename), array(
'operation' => 'rename',
'newname' => $new_name,
));
return $this->is_error() === false;
}
/**
* Create an empty file
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
*
* @return bool True on success, False on failure
*/
public function file_create($repo_id, $filename)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($filename === '' || !is_string($filename)) {
$this->status = self::BAD_REQUEST;
return false;
}
$result = $this->request('POST', "repos/$repo_id/file", array('p' => $filename), array(
'operation' => 'create',
));
return $this->is_error() === false;
}
/**
* Get file info
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
*
* @return bool|array File info on success, False on failure
*/
public function file_info($repo_id, $filename)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($filename === '' || !is_string($filename)) {
$this->status = self::BAD_REQUEST;
return false;
}
// sample result:
// "id": "013d3d38fed38b3e8e26b21bb3463eab6831194f",
// "mtime": 1398148877,
// "type": "file",
// "name": "foo.py",
// "size": 22
return $this->request('GET', "repos/$repo_id/file/detail", array('p' => $filename));
}
/**
* Get file content
*
* @param string $repo_id Library identifier
* @param string $filename File name (with path)
*
* @return bool|string File download URI on success, False on failure
*/
public function file_get($repo_id, $filename)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($filename === '' || !is_string($filename)) {
$this->status = self::BAD_REQUEST;
return false;
}
return $this->request('GET', "repos/$repo_id/file", array('p' => $filename));
}
/**
* List libraries (repositories)
*
* @return array|bool List of libraries on success, False on failure
*/
public function library_list()
{
$result = $this->request('GET', "repos");
// sample result
// [{
// "permission": "rw",
// "encrypted": false,
// "mtime": 1400054900,
// "owner": "user@mail.com",
// "id": "f158d1dd-cc19-412c-b143-2ac83f352290",
// "size": 0,
// "name": "foo",
// "type": "repo",
// "virtual": false,
// "desc": "new library",
// "root": "0000000000000000000000000000000000000000"
// }]
return $result;
}
/**
* Get library info
*
* @param string $repo_id Library identifier
*
* @return array|bool Library info on success, False on failure
*/
public function library_info($repo_id)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
return $this->request('GET', "repos/$repo_id");
}
/**
* Create library
*
* @param string $name Library name
* @param string $description Library description
*
* @return bool|array Library info on success, False on failure
*/
public function library_create($name, $description = '')
{
if ($name === '' || !is_string($name)) {
$this->status = self::BAD_REQUEST;
return false;
}
return $this->request('POST', "repos", null, array(
'name' => $name,
'desc' => $description,
));
}
/**
* Rename library
*
* @param string $repo_id Library identifier
* @param string $new_name Library description
*
* @return bool True on success, False on failure
*/
public function library_rename($repo_id, $name, $description = '')
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
if ($name === '' || !is_string($name)) {
$this->status = self::BAD_REQUEST;
return false;
}
// Note: probably by mistake the 'op' is a GET parameter
// maybe changed in future to be consistent with other methods
$this->request('POST', "repos/$repo_id", array('op' => 'rename'), array(
'repo_name' => $name,
'repo_desc' => $description,
));
return $this->is_error() === false;
}
/**
* Delete library
*
* @param string $repo_id Library identifier
*
* @return bool True on success, False on failure
*/
public function library_delete($repo_id)
{
if ($repo_id === '' || !is_string($repo_id)) {
$this->status = self::BAD_REQUEST;
return false;
}
$this->request('DELETE', "repos/$repo_id");
return $this->is_error() === false;
}
/**
* Ping the API server
*
* @param string $token If set, auth token will be used
*
* @param bool True on success, False on failure
*/
public function ping($token = null)
{
// can be used to check if token is still valid
if ($token) {
$this->token = $token;
$result = $this->request('GET', 'auth/ping', null, null);
}
// or if api works
else {
$result = $this->request('GET', 'ping', null, null);
}
return $this->is_error() === false;
}
/**
* Parse and fix API request URLs
*/
public function mod_url($url)
{
// If Seafile is behind a proxy and different port, it will return
// wrong URL for file uploads/downloads. We force the original URL prefix here
if (stripos($url, $this->url_prefix) !== 0) {
$url = $this->url_prefix . preg_replace('|^(https?://[^/]+)|i', '', $url);
}
return $url;
}
}
diff --git a/lib/drivers/seafile/seafile_file_storage.php b/lib/drivers/seafile/seafile_file_storage.php
index 06556a2..bd1e542 100644
--- a/lib/drivers/seafile/seafile_file_storage.php
+++ b/lib/drivers/seafile/seafile_file_storage.php
@@ -1,1339 +1,1356 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2012-2014, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program 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 Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class seafile_file_storage implements file_storage
{
/**
* @var rcube
*/
protected $rc;
/**
* @var array
*/
protected $config = array();
/**
* @var seafile_api
*/
protected $api;
/**
* List of SeaFile libraries
*
* @var array
*/
protected $libraries;
/**
* Instance title (mount point)
*
* @var string
*/
protected $title;
/**
* Class constructor
*/
public function __construct()
{
$this->rc = rcube::get_instance();
}
/**
* Authenticates a user
*
* @param string $username User name
* @param string $password User password
*
* @param bool True on success, False on failure
*/
public function authenticate($username, $password)
{
$this->init(true);
$token = $this->api->authenticate($username, $password);
if ($token) {
$_SESSION[$this->title . 'seafile_user'] = $username;
$_SESSION[$this->title . 'seafile_token'] = $this->rc->encrypt($token);
$_SESSION[$this->title . 'seafile_pass'] = $this->rc->encrypt($password);
return true;
}
$this->api = false;
return false;
}
/**
* Get password and name of authenticated user
*
* @return array Authenticated user data
*/
public function auth_info()
{
return array(
'username' => $_SESSION[$this->title . 'seafile_user'],
'password' => $this->rc->decrypt($_SESSION[$this->title . 'seafile_pass']),
);
}
/**
* Initialize SeaFile Web API connection
*/
protected function init($skip_auth = false)
{
if ($this->api !== null) {
return $this->api !== false;
}
// read configuration
$config = array(
'host' => $this->rc->config->get('fileapi_seafile_host', 'localhost'),
'ssl_verify_peer' => $this->rc->config->get('fileapi_seafile_ssl_verify_peer', true),
'ssl_verify_host' => $this->rc->config->get('fileapi_seafile_ssl_verify_host', true),
'cache' => $this->rc->config->get('fileapi_seafile_cache'),
'cache_ttl' => $this->rc->config->get('fileapi_seafile_cache_ttl', '14d'),
'debug' => $this->rc->config->get('fileapi_seafile_debug', false),
);
$this->config = array_merge($config, $this->config);
// initialize Web API
$this->api = new seafile_api($this->config);
if ($skip_auth) {
return true;
}
// try session token
if ($_SESSION[$this->title . 'seafile_token']
&& ($token = $this->rc->decrypt($_SESSION[$this->title . 'seafile_token']))
) {
$valid = $this->api->ping($token);
}
if (!$valid) {
// already authenticated in session
if ($_SESSION[$this->title . 'seafile_user']) {
$user = $_SESSION[$this->title . 'seafile_user'];
$pass = $this->rc->decrypt($_SESSION[$this->title . 'seafile_pass']);
}
// try user/pass of the main driver
else {
$user = $this->config['username'];
$pass = $this->config['password'];
}
if ($user) {
$valid = $this->authenticate($user, $pass);
}
}
// throw special exception, so we can ask user for the credentials
if (!$valid && empty($_SESSION[$this->title . 'seafile_user'])) {
throw new Exception("User credentials not provided", file_storage::ERROR_NOAUTH);
}
else if (!$valid && $this->api->is_error() == seafile_api::TOO_MANY_REQUESTS) {
throw new Exception("SeaFile storage temporarily unavailable (too many requests)", file_storage::ERROR);
}
return $valid;
}
/**
* Configures environment
*
* @param array $config Configuration
* @param string $title Source identifier
*/
public function configure($config, $title = null)
{
$this->config = array_merge($this->config, $config);
$this->title = $title;
}
/**
* Returns current instance title
*
* @return string Instance title (mount point)
*/
public function title()
{
return $this->title;
}
/**
* Storage driver capabilities
*
* @return array List of capabilities
*/
public function capabilities()
{
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize) {
$max_filesize = $max_postsize;
}
return array(
file_storage::CAPS_MAX_UPLOAD => $max_filesize,
file_storage::CAPS_QUOTA => true,
file_storage::CAPS_LOCKS => true,
);
}
/**
* Save configuration of external driver (mount point)
*
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_create($driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Delete configuration of external driver (mount point)
*
* @param string $title Driver instance name
*
* @throws Exception
*/
public function driver_delete($title)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Return list of registered drivers (mount points)
*
* @return array List of drivers data
* @throws Exception
*/
public function driver_list()
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Update configuration of external driver (mount point)
*
* @param string $title Driver instance name
* @param array $driver Driver data
*
* @throws Exception
*/
public function driver_update($title, $driver)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Returns metadata of the driver
*
* @return array Driver meta data (image, name, form)
*/
public function driver_metadata()
{
$image_content = file_get_contents(__DIR__ . '/seafile.png');
$metadata = array(
'image' => 'data:image/png;base64,' . base64_encode($image_content),
'name' => 'SeaFile',
'ref' => 'http://seafile.com',
'description' => 'Storage implementing SeaFile API access',
'form' => array(
'host' => 'hostname',
'username' => 'username',
'password' => 'password',
),
);
// these are returned when authentication on folders list fails
if ($this->config['username']) {
$metadata['form_values'] = array(
'host' => $this->config['host'],
'username' => $this->config['username'],
);
}
return $metadata;
}
/**
* Validate metadata (config) of the driver
*
* @param array $metadata Driver metadata
*
* @return array Driver meta data to be stored in configuration
* @throws Exception
*/
public function driver_validate($metadata)
{
if (!is_string($metadata['username']) || !strlen($metadata['username'])) {
throw new Exception("Missing user name.", file_storage::ERROR);
}
if (!is_string($metadata['password']) || !strlen($metadata['password'])) {
throw new Exception("Missing user password.", file_storage::ERROR);
}
if (!is_string($metadata['host']) || !strlen($metadata['host'])) {
throw new Exception("Missing host name.", file_storage::ERROR);
}
$this->config['host'] = $metadata['host'];
if (!$this->authenticate($metadata['username'], $metadata['password'])) {
throw new Exception("Unable to authenticate user", file_storage::ERROR_NOAUTH);
}
return array(
'host' => $metadata['host'],
'username' => $metadata['username'],
'password' => $metadata['password'],
);
}
/**
* Create a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_create($file_name, $file)
{
list($fn, $repo_id) = $this->find_library($file_name);
if (empty($repo_id)) {
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
if ($file['path']) {
$file['data'] = $file['path'];
}
else if (is_resource($file['content'])) {
$file['data'] = $file['content'];
}
else {
$fp = fopen('php://temp', 'wb');
fwrite($fp, $file['content'], strlen($file['content']));
$file['data'] = $fp;
unset($file['content']);
}
$created = $this->api->file_upload($repo_id, $fn, $file);
if ($fp) {
fclose($fp);
}
if (!$created) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving file to SeaFile server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Update a file.
*
* @param string $file_name Name of a file (with folder path)
* @param array $file File data (path, type)
*
* @throws Exception
*/
public function file_update($file_name, $file)
{
list($fn, $repo_id) = $this->find_library($file_name);
if (empty($repo_id)) {
throw new Exception("Storage error. Folder not found.", file_storage::ERROR);
}
if ($file['path']) {
$file['data'] = $file['path'];
}
else if (is_resource($file['content'])) {
$file['data'] = $file['content'];
}
else {
$fp = fopen('php://temp', 'wb');
fwrite($fp, $file['content'], strlen($file['content']));
$file['data'] = $fp;
unset($file['content']);
}
$saved = $this->api->file_update($repo_id, $fn, $file);
if ($fp) {
fclose($fp);
}
if (!$saved) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving file to SeaFile server"),
true, false);
throw new Exception("Storage error. Saving file failed.", file_storage::ERROR);
}
}
/**
* Delete a file.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_delete($file_name)
{
list($file_name, $repo_id) = $this->find_library($file_name);
if ($repo_id && $file_name != '/') {
$deleted = $this->api->file_delete($repo_id, $file_name);
}
if (!$deleted) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting object from SeaFile server"),
true, false);
throw new Exception("Storage error. Deleting file failed.", file_storage::ERROR);
}
}
/**
* Return file body.
*
* @param string $file_name Name of a file (with folder path)
* @param array $params Parameters (force-download, force-type, head)
* @param resource $fp Print to file pointer instead (send no headers)
*
* @throws Exception
*/
public function file_get($file_name, $params = array(), $fp = null)
{
list($fn, $repo_id) = $this->find_library($file_name);
$file = $this->api->file_info($repo_id, $fn);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
// get file location on SeaFile server for download
if ($file['size'] && empty($params['head'])) {
$link = $this->api->file_get($repo_id, $fn);
}
// write to file pointer, send no headers
if ($fp) {
if ($file['size']) {
$this->save_file_content($link, $fp);
}
return;
}
if (!empty($params['force-download'])) {
$disposition = 'attachment';
header("Content-Type: application/octet-stream");
// @TODO
// if ($browser->ie)
// header("Content-Type: application/force-download");
}
else {
$mimetype = file_utils::real_mimetype($params['force-type'] ? $params['force-type'] : $file['type']);
$disposition = 'inline';
header("Content-Transfer-Encoding: binary");
header("Content-Type: $mimetype");
}
$filename = addcslashes($file['name'], '"');
// Workaround for nasty IE bug (#1488844)
// If Content-Disposition header contains string "attachment" e.g. in filename
// IE handles data as attachment not inline
/*
@TODO
if ($disposition == 'inline' && $browser->ie && $browser->ver < 9) {
$filename = str_ireplace('attachment', 'attach', $filename);
}
*/
header("Content-Length: " . $file['size']);
header("Content-Disposition: $disposition; filename=\"$filename\"");
// just send redirect to SeaFile server
if ($file['size'] && empty($params['head'])) {
$allow_redirects = $this->rc->config->get('fileapi_seafile_allow_redirects');
// In view-mode we can't redirect to SeaFile server because:
// - it responds with Content-Disposition: attachment, which causes that
// e.g. previewing images is not possible
// - pdf/odf viewers can't follow redirects for some reason (#4590)
if ($allow_redirects && !empty($params['force-download'])) {
header("Location: $link");
}
else if ($fp = fopen('php://output', 'wb')) {
$this->save_file_content($link, $fp);
fclose($fp);
}
}
}
/**
* Returns file metadata.
*
* @param string $file_name Name of a file (with folder path)
*
* @throws Exception
*/
public function file_info($file_name)
{
list($file, $repo_id) = $this->find_library($file_name);
$file = $this->api->file_info($repo_id, $file);
if (empty($file)) {
throw new Exception("Storage error. File not found.", file_storage::ERROR);
}
$file = $this->from_file_object($file);
return array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => file_utils::date_format($file['changed'], $this->config['date_format'], $this->config['timezone']),
'ctime' => file_utils::date_format($file['created'], $this->config['date_format'], $this->config['timezone']),
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
}
/**
* List files in a folder.
*
* @param string $folder_name Name of a folder with full path
* @param array $params List parameters ('sort', 'reverse', 'search', 'prefix')
*
* @return array List of files (file properties array indexed by filename)
* @throws Exception
*/
public function file_list($folder_name, $params = array())
{
list($folder, $repo_id) = $this->find_library($folder_name);
// prepare search filter
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
if ($idx == 'name') {
$params['search'][$idx] = mb_strtoupper($value);
}
else if ($idx == 'class') {
$params['search'][$idx] = file_utils::class2mimetypes($value);
}
}
}
// get directory entries
$entries = $this->api->directory_entries($repo_id, $folder, 'file');
$result = array();
foreach ((array) $entries as $idx => $file) {
if ($file['type'] != 'file') {
continue;
}
$file = $this->from_file_object($file);
// search filter
if (!empty($params['search'])) {
foreach ($params['search'] as $idx => $value) {
if ($idx == 'name') {
if (strpos(mb_strtoupper($file['name']), $value) === false) {
continue 2;
}
}
else if ($idx == 'class') {
foreach ($value as $v) {
if (stripos($file['type'], $v) !== false) {
continue 2;
}
}
continue 2;
}
}
}
$filename = $params['prefix'] . $folder_name . file_storage::SEPARATOR . $file['name'];
$result[$filename] = array(
'name' => $file['name'],
'size' => (int) $file['size'],
'type' => (string) $file['type'],
'mtime' => file_utils::date_format($file['changed'], $this->config['date_format'], $this->config['timezone']),
'ctime' => file_utils::date_format($file['created'], $this->config['date_format'], $this->config['timezone']),
'modified' => $file['changed'] ? $file['changed']->format('U') : 0,
'created' => $file['created'] ? $file['created']->format('U') : 0,
);
unset($entries[$idx]);
}
// @TODO: pagination, search (by filename, mimetype)
// Sorting
$sort = !empty($params['sort']) ? $params['sort'] : 'name';
$index = array();
if ($sort == 'mtime') {
$sort = 'modified';
}
if (in_array($sort, array('name', 'size', 'modified'))) {
foreach ($result as $key => $val) {
$index[$key] = $val[$sort];
}
array_multisort($index, SORT_ASC, SORT_NUMERIC, $result);
}
if ($params['reverse']) {
$result = array_reverse($result, true);
}
return $result;
}
/**
* Copy a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_copy($file_name, $new_name)
{
list($src_name, $repo_id) = $this->find_library($file_name);
list($dst_name, $dst_repo_id) = $this->find_library($new_name);
if ($repo_id && $dst_repo_id) {
$path_src = explode('/', $src_name);
$path_dst = explode('/', $dst_name);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = '/' . ltrim(implode('/', $path_src), '/');
$dst_dir = '/' . ltrim(implode('/', $path_dst), '/');
$success = $this->api->file_copy($repo_id, $f_src, $src_dir, $dst_dir, $dst_repo_id);
// now rename the file if needed
if ($success && $f_src != $f_dst) {
$success = $this->api->file_rename($dst_repo_id, rtrim($dst_dir, '/') . '/' . $f_src, $f_dst);
}
}
if (!$success) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error copying file on SeaFile server"),
true, false);
throw new Exception("Storage error. File copying failed.", file_storage::ERROR);
}
}
/**
* Move (or rename) a file.
*
* @param string $file_name Name of a file (with folder path)
* @param string $new_name New name of a file (with folder path)
*
* @throws Exception
*/
public function file_move($file_name, $new_name)
{
list($src_name, $repo_id) = $this->find_library($file_name);
list($dst_name, $dst_repo_id) = $this->find_library($new_name);
if ($repo_id && $dst_repo_id) {
$path_src = explode('/', $src_name);
$path_dst = explode('/', $dst_name);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = '/' . ltrim(implode('/', $path_src), '/');
$dst_dir = '/' . ltrim(implode('/', $path_dst), '/');
if ($src_dir == $dst_dir && $repo_id == $dst_repo_id) {
$success = true;
}
else {
$success = $this->api->file_move($repo_id, $src_name, $dst_dir, $dst_repo_id);
}
// now rename the file if needed
if ($success && $f_src != $f_dst) {
$success = $this->api->file_rename($dst_repo_id, rtrim($dst_dir, '/') . '/' . $f_src, $f_dst);
}
}
if (!$success) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error moving file on SeaFile server"),
true, false);
throw new Exception("Storage error. File rename failed.", file_storage::ERROR);
}
}
/**
* Create a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_create($folder_name)
{
list($folder, $repo_id) = $this->find_library($folder_name, true);
if (empty($repo_id)) {
$success = $this->api->library_create($folder_name);
}
else if ($folder != '/') {
$success = $this->api->directory_create($repo_id, $folder);
}
if (!$success) {
throw new Exception("Storage error. Unable to create folder", file_storage::ERROR);
}
// clear the cache
if (empty($repo_id)) {
$this->libraries = null;
}
}
/**
* Delete a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception on error
*/
public function folder_delete($folder_name)
{
list($folder, $repo_id) = $this->find_library($folder_name, true);
if ($repo_id && $folder == '/') {
$success = $this->api->library_delete($repo_id);
}
else if ($repo_id) {
$success = $this->api->directory_delete($repo_id, $folder);
}
if (!$success) {
throw new Exception("Storage error. Unable to delete folder.", file_storage::ERROR);
}
}
/**
* Move/Rename a folder.
*
* @param string $folder_name Name of a folder with full path
* @param string $new_name New name of a folder with full path
*
* @throws Exception on error
*/
public function folder_move($folder_name, $new_name)
{
list($folder, $repo_id, $library) = $this->find_library($folder_name, true);
list($dest_folder, $dest_repo_id) = $this->find_library($new_name, true);
// folders rename/move is possible only in the same library and folder
// @TODO: support folder move between libraries and folders
// @TODO: support converting library into a folder and vice-versa
// library rename
if ($repo_id && !$dest_repo_id && $folder == '/' && strpos($new_name, '/') === false) {
$success = $this->api->library_rename($repo_id, $new_name, $library['desc']);
}
// folder rename
else if ($folder != '/' && $dest_folder != '/' && $repo_id && $repo_id == $dest_repo_id) {
$path_src = explode('/', $folder);
$path_dst = explode('/', $dest_folder);
$f_src = array_pop($path_src);
$f_dst = array_pop($path_dst);
$src_dir = implode('/', $path_src);
$dst_dir = implode('/', $path_dst);
if ($src_dir == $dst_dir) {
$success = $this->api->directory_rename($repo_id, $folder, $f_dst);
}
}
if (!$success) {
throw new Exception("Storage error. Unable to rename/move folder", file_storage::ERROR);
}
}
/**
* Subscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_subscribe($folder_name)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Unsubscribe a folder.
*
* @param string $folder_name Name of a folder with full path
*
* @throws Exception
*/
public function folder_unsubscribe($folder_name)
{
throw new Exception("Not implemented", file_storage::ERROR_UNSUPPORTED);
}
/**
* Returns list of folders.
*
* @param array $params List parameters ('type', 'search')
*
* @return array List of folders
* @throws Exception
*/
public function folder_list($params = array())
{
$writable = ($params['type'] & file_storage::FILTER_WRITABLE) ? true : false;
$libraries = $this->libraries();
$folders = array();
if ($this->config['cache']) {
- $cache = $this->rc->get_cache('seafile_' . $this->title,
+ $repos = array();
+ $repo_ids = array();
+ $cache = $this->rc->get_cache('seafile_' . $this->title,
$this->config['cache'], $this->config['cache_ttl'], true);
if ($cache) {
- $cached = $cache->get('folders');
+ $repos = (array) $cache->get('repos');
+ }
+
+ // Mark unmodified repos
+ foreach ($libraries as $idx => $library) {
+ if ($mtime = $repos[$library['id']]) {
+ if ($mtime == $library['mtime']) {
+ $libraries[$idx]['use-cache'] = true;
+ }
+ else {
+ $cache_update = true;
+ }
+ }
+ else {
+ $cache_update = true;
+ }
+
+ $repos[$library['id']] = $library['mtime'];
+ $repo_ids[] = $library['id'];
}
}
foreach ($libraries as $library) {
if ($library['virtual'] || $library['encrypted']) {
continue;
}
if (strpos($library['permission'], 'w') === false) {
$readonly_prefixes[] = $library['name'];
}
$folders[$library['name']] = array(
'mtime' => $library['mtime'],
'permission' => $library['permission'],
);
- if ($folder_tree = $this->folders_tree($library, '', $library, $cached)) {
- $folders = array_merge($folders, $folder_tree);
+ foreach ($this->folders_tree($library) as $folder_name => $folder) {
+ $folders[$library['name'] . '/' . $folder_name] = $folder;
}
}
if (empty($folders)) {
throw new Exception("Storage error. Unable to get folders list.", file_storage::ERROR);
}
- if ($cache && $cached != $folders) {
- $cache->set('folders', $folders);
+ if ($cache && $cache_update) {
+ // Cleanup repos data
+ $repos = array_intersect_key($repos, array_flip($repo_ids));
+
+ $cache->set('repos', $repos);
}
// remove read-only folders when requested
if ($writable) {
foreach ($folders as $folder_name => $folder) {
if (strpos($folder['permission'], 'w') === false) {
unset($folders[$folder_name]);
}
}
}
// In extended format we return array of arrays
if (!empty($params['extended'])) {
foreach ($folders as $folder_name => $folder) {
$item = array('folder' => $folder_name);
// check if folder is readonly
if (!$writable && $params['permissions']) {
if (strpos($folder['permission'], 'w') === false) {
$item['readonly'] = true;
}
}
$folders[$folder_name] = $item;
}
}
else {
$folders = array_keys($folders);
}
// sort folders
usort($folders, array('file_utils', 'sort_folder_comparator'));
return $folders;
}
/**
* Check folder rights.
*
* @param string $folder_name Name of a folder with full path
*
* @return int Folder rights (sum of file_storage::ACL_*)
*/
public function folder_rights($folder_name)
{
// It is not possible (yet) to assign a specified library/folder
// to the mount point. So, it is a "virtual" folder.
if (!strlen($folder_name)) {
return 0;
}
list($folder, $repo_id, $library) = $this->find_library($folder_name);
// @TODO: we should check directory permission not library
// However, there's no API for this, we'd need to get a list
// of directories of a parent folder/library
/*
if (strpos($folder, '/')) {
// @TODO
}
else {
$acl = $library['permission'];
}
*/
$acl = $library['permission'];
$rights = 0;
$map = array(
'r' => file_storage::ACL_READ,
'w' => file_storage::ACL_WRITE,
);
foreach ($map as $key => $value) {
if (strpos($acl, $key) !== false) {
$rights |= $value;
}
}
return $rights;
}
/**
* Returns a list of locks
*
* This method should return all the locks for a particular URI, including
* locks that might be set on a parent URI.
*
* If child_locks is set to true, this method should also look for
* any locks in the subtree of the URI for locks.
*
* @param string $path File/folder path
* @param bool $child_locks Enables subtree checks
*
* @return array List of locks
* @throws Exception
*/
public function lock_list($path, $child_locks = false)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->path2uri($path);
// get locks list
$list = $this->lock_db->lock_list($uri, $child_locks);
// convert back resource string into URIs
foreach ($list as $idx => $lock) {
$list[$idx]['uri'] = $this->uri2path($lock['uri']);
}
return $list;
}
/**
* Locks a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
* - depth: 0/'infinite'
* - scope: 'shared'/'exclusive'
* - owner: string
* - token: string
* - timeout: int
*
* @throws Exception
*/
public function lock($path, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->uri2resource($path);
if (!$this->lock_db->lock($uri, $lock)) {
throw new Exception("Database error. Unable to create a lock.", file_storage::ERROR);
}
}
/**
* Removes a lock from a URI
*
* @param string $path File/folder path
* @param array $lock Lock data
*
* @throws Exception
*/
public function unlock($path, $lock)
{
$this->init_lock_db();
// convert URI to global resource string
$uri = $this->path2uri($path);
if (!$this->lock_db->unlock($uri, $lock)) {
throw new Exception("Database error. Unable to remove a lock.", file_storage::ERROR);
}
}
/**
* Return disk quota information for specified folder.
*
* @param string $folder_name Name of a folder with full path
*
* @return array Quota
* @throws Exception
*/
public function quota($folder)
{
if (!$this->init()) {
throw new Exception("Storage error. Unable to get SeaFile account info.", file_storage::ERROR);
}
$account_info = $this->api->account_info();
if (empty($account_info)) {
throw new Exception("Storage error. Unable to get SeaFile account info.", file_storage::ERROR);
}
$quota = array(
// expected values in kB
'total' => intval($account_info['total'] / 1024),
'used' => intval($account_info['usage'] / 1024),
);
return $quota;
}
/**
- * Recursively builds folders list
+ * Get folders tree in the Seafile library
*/
- protected function folders_tree($library, $path, $folder, $cached)
+ protected function folders_tree($library)
{
- $folders = array();
- $fname = strlen($path) ? $path . $folder['name'] : '/';
- $root = $library['name'] . ($fname != '/' ? $fname : '');
-
- // nothing changed, use cached folders tree of this folder
- if ($cached && is_array($cached[$root]) && $cached[$root]['mtime'] == $folder['mtime']) {
- foreach ($cached as $folder_name => $f) {
- if (strpos($folder_name, $root . '/') === 0) {
- $folders[$folder_name] = array(
- 'mtime' => $f['mtime'],
- 'permission' => $f['permission'],
- );
- }
+ if ($this->config['cache']) {
+ $cache = $this->rc->get_cache('seafile_' . $this->title,
+ $this->config['cache'], $this->config['cache_ttl'], true);
+
+ if ($cache && $library['use-cache']) {
+ $folders = $cache->get('folders.' . $library['id']);
}
}
- // get folder content (files and sub-folders)
- // there's no API method to get only folders
- else if ($content = $this->api->directory_entries($library['id'], $fname, 'dir')) {
- if ($fname != '/') {
- $fname .= '/';
- }
- foreach ($content as $item) {
- if ($item['type'] == 'dir' && strlen($item['name'])) {
- $folders[$root . '/' . $item['name']] = array(
- 'mtime' => $item['mtime'],
- 'permission' => $item['permission'],
- );
-
- // get subfolders recursively
- $folders_tree = $this->folders_tree($library, $fname, $item, $cached);
- if (!empty($folders_tree)) {
- $folders = array_merge($folders, $folders_tree);
+ if (!isset($folders) || !is_array($folders)) {
+ $folders = array();
+
+ // get folders in the repo (requires Seafile 4.4.1)
+ if ($content = $this->api->directory_entries($library['id'], '', 'dir', true)) {
+ foreach ($content as $item) {
+ if ($item['type'] == 'dir' && strlen($item['name'])) {
+ $parent = trim($item['parent_dir'], '/');
+ $name = (strlen($parent) > 0 ? "$parent/" : '') . $item['name'];
+
+ $folders[$name] = array(
+ 'mtime' => $item['mtime'],
+ 'permission' => $item['permission'],
+ );
}
}
}
+
+ if ($cache && is_array($content)) {
+ $cache->set('folders.' . $library['id'], $folders);
+ }
}
return $folders;
}
/**
* Get list of SeaFile libraries
*/
protected function libraries()
{
// get from memory, @TODO: cache in rcube_cache?
if ($this->libraries !== null) {
return $this->libraries;
}
if (!$this->init()) {
throw new Exception("Storage error. Unable to get list of SeaFile libraries.", file_storage::ERROR);
}
if ($list = $this->api->library_list()) {
$this->libraries = $list;
}
else {
$this->libraries = array();
}
return $this->libraries;
}
/**
* Find library ID from folder name
*/
protected function find_library($folder_name, $no_exception = false)
{
$libraries = $this->libraries();
foreach ($libraries as $lib) {
$path = $lib['name'] . '/';
if ($folder_name == $lib['name'] || strpos($folder_name, $path) === 0) {
if (empty($library) || strlen($library['name']) < strlen($lib['name'])) {
$library = $lib;
}
}
}
if (empty($library)) {
if (!$no_exception) {
throw new Exception("Storage error. Library not found.", file_storage::ERROR);
}
}
else {
$folder = substr($folder_name, strlen($library['name']) + 1);
}
return array(
'/' . ($folder ? $folder : ''),
$library['id'],
$library
);
}
/**
* Get file object.
*
* @param string $file_name Name of a file (with folder path)
* @param kolab_storage_folder $folder Reference to folder object
*
* @return array File data
* @throws Exception
*/
protected function get_file_object(&$file_name, &$folder = null)
{
// extract file path and file name
$path = explode(file_storage::SEPARATOR, $file_name);
$file_name = array_pop($path);
$folder_name = implode(file_storage::SEPARATOR, $path);
if ($folder_name === '') {
throw new Exception("Missing folder name", file_storage::ERROR);
}
// get folder object
$folder = $this->get_folder_object($folder_name);
$files = $folder->select(array(
array('type', '=', 'file'),
array('filename', '=', $file_name)
));
return $files[0];
}
/**
* Simplify internal structure of the file object
*/
protected function from_file_object($file)
{
if ($file['type'] != 'file') {
return null;
}
// file modification time
if ($file['mtime']) {
try {
$file['changed'] = new DateTime('@' . $file['mtime']);
}
catch (Exception $e) { }
}
// find file mimetype from extension
$file['type'] = file_utils::ext_to_type($file['name']);
unset($file['id']);
unset($file['mtime']);
return $file;
}
/**
* Save remote file into file pointer
*/
protected function save_file_content($location, $fp)
{
if (!$fp || !$location) {
return false;
}
$config = array_merge($this->config, array('store_bodies' => true));
$request = seafile_api::http_request($config);
if (!$request) {
return false;
}
$observer = new seafile_request_observer();
$observer->set_fp($fp);
try {
$request->setUrl($this->api->mod_url($location));
$request->attach($observer);
$response = $request->send();
$status = $response->getStatus();
$response->getBody(); // returns nothing
$request->detach($observer);
if ($status != 200) {
throw new Exception("Unable to save file. Status $status.");
}
}
catch (Exception $e) {
rcube::raise_error($e, true, false);
return false;
}
return true;
}
/**
* Convert file/folder path into a global URI.
*
* @param string $path File/folder path
*
* @return string URI
* @throws Exception
*/
public function path2uri($path)
{
list($file, $repo_id, $library) = $this->find_library($path);
$host = preg_replace('|https?://|i', '', $this->config['host']);
return 'seafile://' . rawurlencode($library['owner']) . '@' . $host . '/' . file_utils::encode_path($path);
}
/**
* Convert global URI into file/folder path.
*
* @param string $uri URI
*
* @return string File/folder path
* @throws Exception
*/
public function uri2path($uri)
{
if (!preg_match('|^seafile://([^@]+)@([^/]+)/(.*)$|', $uri, $matches)) {
throw new Exception("Internal storage error. Unexpected data format.", file_storage::ERROR);
}
$user = rawurldecode($matches[1]);
$host = $matches[2];
$path = file_utils::decode_path($matches[3]);
$c_host = preg_replace('|https?://|i', '', $this->config['host']);
list($file, $repo_id, $library) = $this->find_library($path, true);
if (empty($library) || $host != $c_host || $user != $library['owner']) {
throw new Exception("Internal storage error. Unresolvable URI.", file_storage::ERROR);
}
return $path;
}
/**
* Initializes file_locks object
*/
protected function init_lock_db()
{
if (!$this->lock_db) {
$this->lock_db = new file_locks;
}
}
}
diff --git a/lib/viewers/media.php b/lib/viewers/media.php
index f240d0e..cfb97d8 100644
--- a/lib/viewers/media.php
+++ b/lib/viewers/media.php
@@ -1,106 +1,101 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab File API |
| |
| Copyright (C) 2011-2013, Kolab Systems AG |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program 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 Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Class integrating HTML5 audio/video player from http://mediaelementjs.com
*/
class file_viewer_media extends file_viewer
{
protected $mimetypes = array(
'video/mp4',
'video/m4v',
'video/ogg',
'video/webm',
// 'video/3gpp',
// 'video/flv',
// 'video/x-flv',
'application/ogg',
'audio/mp3',
'audio/mpeg',
'audio/ogg',
'audio/wav',
'audio/flv',
'audio/x-mpeg',
'audio/x-ogg',
'audio/x-wav',
'audio/x-flv',
);
/**
* Class constructor
*
* @param file_api File API object
*/
public function __construct($api)
{
// @TODO: disable types not supported by some browsers
$this->api = $api;
}
/**
* Return output of file content area
*
* @param string $file File name
* @param string $mimetype File type
*/
public function frame($file, $mimetype = null)
{
$path = $this->api->api_url();
$file_uri = htmlentities($this->api->file_url($file));
$mimetype = htmlentities($mimetype);
$source = "<source src=\"$file_uri\" type=\"$mimetype\"></source>";
if (preg_match('/^audio/', $mimetype)) {
$tag = 'audio';
}
else {
$tag = 'video';
}
return <<<EOT
<link rel="stylesheet" type="text/css" href="$path/viewers/media/mediaelementplayer.css" />
<script type="text/javascript" src="$path/viewers/media/mediaelement-and-player.js"></script>
<$tag id="media-player" controls preload="auto">$source</$tag>
<style>
.mejs-container { text-align: center; }
</style>
<script>
- var content_frame = $('#media-player').parent(),
- height = content_frame.height(),
- width = content_frame.width(),
+ var height = '100%';
+ width = '100%';
player = new MediaElementPlayer('#media-player', {
videoHeight: height, audioHeight: height, videoWidth: width, audioWidth: width
});
player.pause();
player.play();
- // add player resize handler
- $(window).resize(function() {
- player.setPlayerSize(content_frame.width(), content_frame.height());
- });
</script>
EOT;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jun 8, 8:11 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
196706
Default Alt Text
(76 KB)

Event Timeline