Implement trashbin support
This commit is contained in:
parent
6e51f11e9d
commit
18bb00057b
|
@ -35,6 +35,7 @@ If you are looking for an even lighter WebDAV server, try also our other server,
|
|||
* Passes most of the [Litmus compliance tests](https://github.com/tolsen/litmus) (see below)
|
||||
* Supports WOPI, for editing and viewing of documents using OnlyOffice, Collabora Online or MS Office.
|
||||
* Support for LDAP
|
||||
* Trashbin: files are moved to a `.trash` folder before being deleted completely
|
||||
* Good performance!
|
||||
|
||||
### NextCloud/ownCloud features
|
||||
|
@ -45,6 +46,7 @@ The following ownCloud/NextCloud specific features are supported:
|
|||
|
||||
* [Direct download](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#direct-download)
|
||||
* [Chunk upload](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/chunking.html)
|
||||
* [Trashbin](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/trashbin.html)
|
||||
* `X-OC-MTime` [header](https://gitlab.gnome.org/GNOME/gvfs/-/issues/637) to set file modification time
|
||||
* `OC-Checksum` [header](https://github.com/owncloud-archive/documentation/issues/2964) to verify file upload integrity
|
||||
* Login via app-specific passwords (necessary for NextCloud desktop and Android clients)
|
||||
|
@ -106,8 +108,7 @@ Here is a list of clients tested with KaraDAV:
|
|||
|
||||
This might get supported in future (maybe):
|
||||
|
||||
* Probably: [NextCloud Trashbin](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/trashbin.html)
|
||||
* Maybe: [NextCloud sharing](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html)
|
||||
* Probably: [NextCloud sharing](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html)
|
||||
* Maybe: NextCloud files versioning
|
||||
* [NextCloud API](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/versions.html)
|
||||
* [NextCloud versioning pattern](https://docs.nextcloud.com/server/latest/user_manual/en/files/version_control.html)
|
||||
|
|
|
@ -16,6 +16,13 @@ namespace KaraDAV;
|
|||
*/
|
||||
const DEFAULT_QUOTA = 200;
|
||||
|
||||
/**
|
||||
* Default delay after which files should be deleted from the trashbin
|
||||
* (in seconds)
|
||||
* Set to zero (0) to disable the trashbin (files will be deleted directly)
|
||||
*/
|
||||
const DEFAULT_TRASHBIN_DELAY = 60*60*24*30; // 15 days
|
||||
|
||||
/**
|
||||
* Users file storage path
|
||||
* %s is replaced by the login name of the user
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
## 0.3.13 - September 10, 2023
|
||||
## 0.4.0 - September 11, 2023
|
||||
|
||||
* Implement trashbin support, compatible with NextCloud API
|
||||
|
||||
## 0.3.13
|
||||
|
||||
* Use files inodes as file ID, so that we keep the same id when the file is moved or renamed.
|
||||
* Fix a bug in the NextCloud Android app, where the "Plus" button was disabled, because the NextCloud app doesn't respect the NextCloud spec.
|
||||
|
|
|
@ -52,6 +52,10 @@ abstract class NextCloud
|
|||
// in Android app
|
||||
const PROP_NC_RICH_WORKSPACE = self::NC_NAMESPACE . ':rich-workspace';
|
||||
|
||||
const PROP_NC_TRASHBIN_FILENAME = self::NC_NAMESPACE . ':trashbin-filename';
|
||||
const PROP_NC_TRASHBIN_ORIGINAL_LOCATION = self::NC_NAMESPACE . ':trashbin-original-location';
|
||||
const PROP_NC_TRASHBIN_DELETION_TIME = self::NC_NAMESPACE . ':trashbin-deletion-time';
|
||||
|
||||
// Useless?
|
||||
const PROP_OC_SHARETYPES = self::OC_NAMESPACE . ':share-types';
|
||||
const PROP_NC_NOTE = self::NC_NAMESPACE . ':note';
|
||||
|
@ -238,6 +242,10 @@ abstract class NextCloud
|
|||
// There's just 3 or 4 different endpoints for avatars, this is ridiculous
|
||||
'remote.php/dav/avatars/' => 'avatar',
|
||||
|
||||
// Trasbin API
|
||||
// https://docs.nextcloud.com/server/19/developer_manual/client_apis/WebDAV/trashbin.html
|
||||
'remote.php/dav/trashbin/' => 'trashbin',
|
||||
|
||||
// Main routes
|
||||
'remote.php/webdav/' => 'webdav', // desktop client
|
||||
'remote.php/dav' => 'webdav', // android client
|
||||
|
@ -526,7 +534,7 @@ abstract class NextCloud
|
|||
// https://github.com/owncloud/client/blob/24ca9615f6e8ea765f6c25fb4e009b1acc262a2d/src/libsync/capabilities.cpp#L166
|
||||
'bigfilechunking' => true,
|
||||
'comments' => false,
|
||||
'undelete' => false,
|
||||
'undelete' => in_array(TrashInterface::class, class_implements($this->storage)),
|
||||
'versioning' => false,
|
||||
],
|
||||
'files_sharing' => [
|
||||
|
@ -786,6 +794,10 @@ abstract class NextCloud
|
|||
$dir = $match[2] ?? null;
|
||||
$part = $match[3] ?? null;
|
||||
|
||||
if ($login !== $user) {
|
||||
throw new Exception('Invalid username in URL, does not match logged user', 403);
|
||||
}
|
||||
|
||||
if ($method == 'MKCOL') {
|
||||
http_response_code(201);
|
||||
}
|
||||
|
@ -858,4 +870,68 @@ abstract class NextCloud
|
|||
throw new Exception('Invalid method for chunked upload', 400);
|
||||
}
|
||||
}
|
||||
|
||||
protected function nc_trashbin(string $uri): ?string
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$r = '!^remote\.php/dav/trashbin/([^/]+)/trash/([^/]*)$!';
|
||||
|
||||
if (!preg_match($r, $uri, $match)) {
|
||||
throw new Exception('Invalid URL for trashbin API', 400);
|
||||
}
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? null;
|
||||
$login = $match[1] ?? null;
|
||||
$path = $match[2] ?? null;
|
||||
|
||||
if ($method === 'DELETE' && empty($path)) {
|
||||
$this->storage->emptyTrash();
|
||||
http_response_code(204);
|
||||
}
|
||||
elseif ($method === 'DELETE') {
|
||||
$this->storage->deleteFromTrash($path);
|
||||
http_response_code(204);
|
||||
}
|
||||
elseif ($method === 'MOVE' && !empty($path)) {
|
||||
$this->storage->restoreFromTrash($path);
|
||||
http_response_code(201);
|
||||
}
|
||||
elseif ($method === 'PROPFIND') {
|
||||
header('HTTP/1.1 207 Multi-Status', true);
|
||||
header('Content-Type: text/xml; charset=utf-8', true);
|
||||
|
||||
$out = '<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL;
|
||||
$out .= '<d:multistatus xmlns:d="DAV:" xmlns:nc="http://nextcloud.org/ns" xmlns:oc="http://owncloud.org/ns">' . PHP_EOL;
|
||||
|
||||
$out .= sprintf('<d:response><d:href>%s</d:href><d:propstat><d:prop><d:resourcetype><d:collection/></d:resourcetype></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat><d:propstat><d:prop><nc:trashbin-deletion-time/><d:getcontenttype/><oc:size/><oc:id/><d:getcontentlength/><nc:trashbin-filename/><nc:trashbin-original-location/></d:prop><d:status>HTTP/1.1 404 Not Found</d:status></d:propstat></d:response>', $match[0]);
|
||||
|
||||
foreach ($this->storage->listTrashFiles() as $file => $props) {
|
||||
$out .= '<d:response>' . PHP_EOL;
|
||||
$path = '/' . trim($uri, '/') . '/' . rawurlencode($file);
|
||||
$out .= sprintf('<d:href>%s</d:href>', htmlspecialchars($path, ENT_XML1)) . PHP_EOL;
|
||||
$out .= '<d:propstat><d:prop>';
|
||||
|
||||
foreach ($props as $key => $value) {
|
||||
$pos = strrpos($key, ':');
|
||||
$ns = substr($key, 0, $pos);
|
||||
$tag = substr($key, $pos + 1);
|
||||
$out .= sprintf('<%s xmlns="%s">%s</%1$s>', $tag, $ns, htmlspecialchars($value, ENT_XML1));
|
||||
}
|
||||
|
||||
$out .= '</d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat>' . PHP_EOL;
|
||||
$out .= '</d:response>' . PHP_EOL;
|
||||
}
|
||||
|
||||
$out .= '</d:multistatus>';
|
||||
|
||||
echo $out;
|
||||
|
||||
$this->server->log("=> Body:\n%s", $out); }
|
||||
else {
|
||||
throw new Exception('Invalid method for trashbin', 400);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ class Server
|
|||
|
||||
public function setBaseURI(string $uri): void
|
||||
{
|
||||
$this->base_uri = '/' . ltrim($uri, '/');
|
||||
$this->base_uri = ltrim($uri, '/');
|
||||
$this->base_uri = rtrim($this->base_uri, '/') . '/';
|
||||
}
|
||||
|
||||
|
|
50
lib/KD2/WebDAV/TrashInterface.php
Normal file
50
lib/KD2/WebDAV/TrashInterface.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace KD2\WebDAV;
|
||||
|
||||
interface TrashInterface
|
||||
{
|
||||
/**
|
||||
* Restore an URI (relative to the root of the trashbin) to its original location
|
||||
*/
|
||||
public function restoreFromTrash(string $uri): void;
|
||||
|
||||
/**
|
||||
* Move an URI to the trashbin
|
||||
*/
|
||||
public function moveToTrash(string $uri): void;
|
||||
|
||||
/**
|
||||
* Delete everything from trashbin
|
||||
*/
|
||||
public function emptyTrash(): void;
|
||||
|
||||
/**
|
||||
* Delete an URI from the trashbin
|
||||
* @param string $uri Path, relative to the trashbin directory
|
||||
*/
|
||||
public function deleteFromTrash(string $uri): void;
|
||||
|
||||
/**
|
||||
* Delete old files from trashbin
|
||||
* @param int $delete_before_timestamp UNIX timestamp representing the date before which all trashed files must be deleted
|
||||
* @return int number of deleted files
|
||||
*/
|
||||
public function pruneTrash(int $delete_before_timestamp): int;
|
||||
|
||||
/**
|
||||
* List all files in trash
|
||||
*
|
||||
* @return iterable Each item having the file name (URI) as the key
|
||||
* and an array of these PROPFIND properties:
|
||||
* - {http://nextcloud.org/ns}trashbin-deletion-time
|
||||
* - {http://nextcloud.org/ns}trashbin-original-location
|
||||
* - {http://nextcloud.org/ns}trashbin-filename
|
||||
* - {dav:}getcontenttype
|
||||
* - {dav:}getcontentlength
|
||||
* - {dav:}resourcetype
|
||||
* - {http://owncloud.org/ns}size
|
||||
* - {http://owncloud.org/ns}id
|
||||
*/
|
||||
public function listTrashFiles(): iterable;
|
||||
}
|
|
@ -3,10 +3,11 @@
|
|||
namespace KaraDAV;
|
||||
|
||||
use KD2\WebDAV\AbstractStorage;
|
||||
use KD2\WebDAV\TrashInterface;
|
||||
use KD2\WebDAV\WOPI;
|
||||
use KD2\WebDAV\Exception as WebDAV_Exception;
|
||||
|
||||
class Storage extends AbstractStorage
|
||||
class Storage extends AbstractStorage implements TrashInterface
|
||||
{
|
||||
protected Users $users;
|
||||
protected NextCloud $nextcloud;
|
||||
|
@ -24,6 +25,15 @@ class Storage extends AbstractStorage
|
|||
$this->nextcloud = $nextcloud;
|
||||
}
|
||||
|
||||
protected function ensureDirectoryExists(string $path): void
|
||||
{
|
||||
$path = $this->users->current()->path . $path;
|
||||
|
||||
if (!file_exists($path)) {
|
||||
@mkdir($path, @fileperms($this->users->current()->path) ?: 0770, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function getLock(string $uri, ?string $token = null): ?string
|
||||
{
|
||||
// It is important to check also for a lock on parent directory as we support depth=1
|
||||
|
@ -183,6 +193,24 @@ class Storage extends AbstractStorage
|
|||
}
|
||||
|
||||
return implode('', $permissions);
|
||||
case NextCloud::PROP_NC_TRASHBIN_FILENAME:
|
||||
if (0 !== strpos($uri, '.trash/')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return basename($uri);
|
||||
case NextCloud::PROP_NC_TRASHBIN_DELETION_TIME:
|
||||
if (0 !== strpos($uri, '.trash/')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getTrashInfo(basename($uri))['DeletionDate'] ?? null;
|
||||
case NextCloud::PROP_NC_TRASHBIN_ORIGINAL_LOCATION:
|
||||
if (0 !== strpos($uri, '.trash/')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getTrashInfo(basename($uri))['Path'] ?? null;
|
||||
case 'DAV::quota-available-bytes':
|
||||
return null;
|
||||
case 'DAV::quota-used-bytes':
|
||||
|
@ -240,7 +268,7 @@ class Storage extends AbstractStorage
|
|||
return $out;
|
||||
}
|
||||
|
||||
public function put(string $uri, $pointer, ?string $hash_algo, ?string $hash, ?int $mtime): bool
|
||||
public function put(string $uri, $pointer, ?string $hash_algo = null, ?string $hash = null, ?int $mtime = null): bool
|
||||
{
|
||||
if (preg_match(self::PUT_IGNORE_PATTERN, basename($uri))) {
|
||||
return false;
|
||||
|
@ -253,9 +281,7 @@ class Storage extends AbstractStorage
|
|||
throw new WebDAV_Exception('Target is a directory', 409);
|
||||
}
|
||||
|
||||
if (!file_exists($parent)) {
|
||||
mkdir($parent, 0770, true);
|
||||
}
|
||||
$this->ensureDirectoryExists($uri);
|
||||
|
||||
$new = !file_exists($target);
|
||||
|
||||
|
@ -326,6 +352,12 @@ class Storage extends AbstractStorage
|
|||
throw new WebDAV_Exception('Target does not exist', 404);
|
||||
}
|
||||
|
||||
// Move to trash
|
||||
if (DEFAULT_TRASHBIN_DELAY > 0 && 0 !== strpos($uri, '.trash')) {
|
||||
$this->moveToTrash($uri);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir($target)) {
|
||||
self::deleteDirectory($target);
|
||||
}
|
||||
|
@ -367,7 +399,7 @@ class Storage extends AbstractStorage
|
|||
$method = $move ? 'rename' : 'copy';
|
||||
|
||||
if ($method == 'copy' && is_dir($source)) {
|
||||
@mkdir($target, 0770, true);
|
||||
$this->ensureDirectoryExists($destination);
|
||||
|
||||
if (!is_dir($target)) {
|
||||
throw new WebDAV_Exception('Target directory could not be created', 409);
|
||||
|
@ -422,7 +454,7 @@ class Storage extends AbstractStorage
|
|||
throw new WebDAV_Exception('You don\'t have the right to create a directory here', 403);
|
||||
}
|
||||
|
||||
mkdir($target, 0770);
|
||||
$this->ensureDirectoryExists($uri);
|
||||
}
|
||||
|
||||
public function getResourceProperties(string $uri): Properties
|
||||
|
@ -578,4 +610,135 @@ class Storage extends AbstractStorage
|
|||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://specifications.freedesktop.org/trash-spec/trashspec-latest.html
|
||||
*/
|
||||
public function moveToTrash(string $uri): void
|
||||
{
|
||||
$this->ensureDirectoryExists('.trash/info');
|
||||
$this->ensureDirectoryExists('.trash/files');
|
||||
|
||||
$name = basename($uri);
|
||||
|
||||
$target = $this->users->current()->path . '.trash/info/' . $name . '.trashinfo';
|
||||
$info = sprintf("[Trash Info]\nPath=%s\nDeletionDate=%s\n",
|
||||
str_replace('%2F', '/', rawurlencode($uri)),
|
||||
date(DATE_RFC3339)
|
||||
);
|
||||
|
||||
file_put_contents($target, $info);
|
||||
|
||||
$this->move($uri, '.trash/files/' . $name);
|
||||
}
|
||||
|
||||
public function restoreFromTrash(string $uri): void
|
||||
{
|
||||
$src = $this->users->current()->path . '.trash/files/' . $uri;
|
||||
|
||||
if (!file_exists($src)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$info = $this->getTrashInfo($uri);
|
||||
$dest = $info['Path'] ?? $uri;
|
||||
|
||||
if ($info) {
|
||||
$this->delete('.trash/info/' . $uri . '.trashinfo');
|
||||
}
|
||||
|
||||
$this->move('.trash/files/' . $uri, $dest);
|
||||
}
|
||||
|
||||
public function emptyTrash(): void
|
||||
{
|
||||
$this->delete('.trash');
|
||||
$this->ensureDirectoryExists('.trash/info');
|
||||
$this->ensureDirectoryExists('.trash/files');
|
||||
}
|
||||
|
||||
public function deleteFromTrash(string $uri): void
|
||||
{
|
||||
$this->delete('.trash/files/' . $uri);
|
||||
$this->delete('.trash/info/' . $uri . '.trashinfo');
|
||||
}
|
||||
|
||||
protected function getTrashInfo(string $uri): ?array
|
||||
{
|
||||
$info_file = $this->users->current()->path . '.trash/info/' . $uri . '.trashinfo';
|
||||
$info = @parse_ini_file($info_file, false, INI_SCANNER_RAW);
|
||||
|
||||
if (!isset($info['Path'], $info['DeletionDate'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info['Path'] = rawurldecode($info['Path']);
|
||||
$info['DeletionDate'] = strtotime($info['DeletionDate']);
|
||||
$info['InfoFilePath'] = $info_file;
|
||||
return $info;
|
||||
}
|
||||
|
||||
public function pruneTrash(int $delete_before_timestamp): int
|
||||
{
|
||||
$this->ensureDirectoryExists('.trash/info');
|
||||
$this->ensureDirectoryExists('.trash/files');
|
||||
|
||||
$info_dir = $this->users->current()->path . '.trash/info';
|
||||
$count = 0;
|
||||
|
||||
foreach (glob($info_dir . '/*.trashinfo') as $file) {
|
||||
$name = basename($file);
|
||||
$name = str_replace('.trashinfo', '', $name);
|
||||
$info = $this->getTrashInfo($name);
|
||||
|
||||
if (!$info) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($info['DeletionDate'] < $delete_before_timestamp) {
|
||||
$this->delete('.trash/files/' . $name);
|
||||
$this->delete('.trash/info/' . $name . '.trashinfo');
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function listTrashFiles(): iterable
|
||||
{
|
||||
$this->pruneTrash(time() - DEFAULT_TRASHBIN_DELAY);
|
||||
|
||||
$this->ensureDirectoryExists('.trash/info');
|
||||
$this->ensureDirectoryExists('.trash/files');
|
||||
$info_dir = $this->users->current()->path . '.trash/info';
|
||||
$files_dir = $this->users->current()->path . '.trash/files';
|
||||
|
||||
foreach (glob($info_dir . '/*.trashinfo') as $file) {
|
||||
$name = basename($file);
|
||||
$name = str_replace('.trashinfo', '', $name);
|
||||
$target = $files_dir . '/' . $name;
|
||||
|
||||
if (!file_exists($target)) {
|
||||
@unlink($file);
|
||||
continue;
|
||||
}
|
||||
|
||||
$info = $this->getTrashInfo($name);
|
||||
|
||||
$is_dir = is_dir($target);
|
||||
$size = $is_dir ? self::getDirectorySize($target) : filesize($target);
|
||||
|
||||
yield $name => [
|
||||
NextCloud::PROP_NC_TRASHBIN_FILENAME => $name,
|
||||
NextCloud::PROP_NC_TRASHBIN_ORIGINAL_LOCATION => $info['Path'],
|
||||
NextCloud::PROP_NC_TRASHBIN_DELETION_TIME => $info['DeletionDate'],
|
||||
NextCloud::PROP_OC_SIZE => $size,
|
||||
NextCloud::PROP_OC_ID => fileinode($file),
|
||||
'DAV::getcontentlength' => $size,
|
||||
'DAV::getcontenttype' => $is_dir ? null : @mime_content_type($target),
|
||||
'DAV::resourcetype' => $is_dir ? 'collection' : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -309,10 +309,11 @@ class Users
|
|||
return $this->makeUserObjectGreatAgain($user);
|
||||
}
|
||||
|
||||
public function quota(?stdClass $user = null): stdClass
|
||||
public function quota(?stdClass $user = null, bool $with_trash = false): stdClass
|
||||
{
|
||||
$user ??= $this->current();
|
||||
$used = $total = $free = 0;
|
||||
$trash = null;
|
||||
|
||||
if ($user) {
|
||||
if ($user->quota == -1) {
|
||||
|
@ -330,9 +331,11 @@ class Users
|
|||
$total = $user->quota;
|
||||
$free = max(0, $total - $used);
|
||||
}
|
||||
|
||||
$trash = $with_trash ? Storage::getDirectorySize($user->path . '/.trash') : null;
|
||||
}
|
||||
|
||||
return (object) compact('free', 'total', 'used');
|
||||
return (object) compact('free', 'total', 'used', 'trash');
|
||||
}
|
||||
|
||||
public function delete(?stdClass $user)
|
||||
|
|
|
@ -20,6 +20,7 @@ if (file_exists($cfg_file)) {
|
|||
// Default configuration constants
|
||||
$defaults = [
|
||||
'DEFAULT_QUOTA' => 200,
|
||||
'DEFAULT_TRASHBIN_DELAY' => 60*60*24*30,
|
||||
'STORAGE_PATH' => __DIR__ . '/../data/%s',
|
||||
'DB_FILE' => __DIR__ . '/../data/db.sqlite',
|
||||
'WOPI_DISCOVERY_URL' => null,
|
||||
|
@ -28,8 +29,8 @@ $defaults = [
|
|||
'ENABLE_XSENDFILE' => false,
|
||||
'DISABLE_SLOW_OPERATIONS' => false,
|
||||
'ERRORS_SHOW' => true,
|
||||
'ERRORS_EMAIL' => true,
|
||||
'ERRORS_LOG' => null,
|
||||
'ERRORS_EMAIL' => null,
|
||||
'ERRORS_LOG' => __DIR__ . '/../data/error.log',
|
||||
'ERRORS_REPORT_URL' => null,
|
||||
'AUTH_CALLBACK' => null,
|
||||
'LDAP_HOST' => null,
|
||||
|
@ -71,7 +72,7 @@ if (ERRORS_REPORT_URL) {
|
|||
|
||||
// Create random secret key
|
||||
if (!defined('KaraDAV\SECRET_KEY')) {
|
||||
$cfg = file_exists($cfg_file) ? file_get_contents($cfg_file) : "<?php\n";
|
||||
$cfg = file_exists($cfg_file) ? file_get_contents($cfg_file) : "<?php\nnamespace KaraDAV;\n\n";
|
||||
|
||||
if (false == strpos($cfg, 'SECRET_KEY')) {
|
||||
$secret = base64_encode(random_bytes(16));
|
||||
|
|
|
@ -22,11 +22,11 @@ if (!$user) {
|
|||
exit;
|
||||
}
|
||||
|
||||
$quota = $users->quota($user);
|
||||
$server = new Server;
|
||||
$quota = $users->quota($user, true);
|
||||
$free = format_bytes($quota->free);
|
||||
$used = format_bytes($quota->used);
|
||||
$total = format_bytes($quota->total);
|
||||
$trash = format_bytes($quota->trash ?? 0);
|
||||
$percent = $quota->total ? floor(($quota->used / $quota->total)*100) . '%' : '100%';
|
||||
$www_url = WWW_URL;
|
||||
$username = htmlspecialchars($user->login);
|
||||
|
@ -40,6 +40,7 @@ echo <<<EOF
|
|||
<dd><h3>{$percent} used, {$free} free</h3></dd>
|
||||
<dd><progress max="{$quota->total}" value="{$quota->used}"></progress>
|
||||
<dd>Used {$used} out of a total of {$total}.</dd>
|
||||
<dd>Trash: {$trash}.</dd>
|
||||
<dt>WebDAV URL</dt>
|
||||
<dd><h3><a href="{$user->dav_url}"><tt>{$user->dav_url}</tt></a></h3>
|
||||
<dt>NextCloud URL</dt>
|
||||
|
|
Loading…
Reference in a new issue