mirror of
https://github.com/nextcloud/server.git
synced 2026-04-24 07:39:23 -04:00
Merge pull request #4203 from owncloud/trashbin_improvements
Trashbin improvements
This commit is contained in:
commit
7f9795cc3d
9 changed files with 135 additions and 50 deletions
|
|
@ -24,7 +24,7 @@
|
|||
#new>ul>li>p { cursor:pointer; }
|
||||
#new>ul>li>form>input { padding:0.3em; margin:-0.3em; }
|
||||
|
||||
#trash { height:17px; margin: 0 1em; z-index:1010; float: right; }
|
||||
#trash { margin: 0 1em; z-index:1010; float: right; }
|
||||
|
||||
#upload {
|
||||
height:27px; padding:0; margin-left:0.2em; overflow:hidden;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,12 @@ if ($needUpgrade) {
|
|||
$publicUploadEnabled = 'no';
|
||||
}
|
||||
|
||||
$trashEnabled = \OCP\App::isEnabled('files_trashbin');
|
||||
$trashEmpty = true;
|
||||
if ($trashEnabled) {
|
||||
$trashEmpty = \OCA\Files_Trashbin\Trashbin::isEmpty($user);
|
||||
}
|
||||
|
||||
OCP\Util::addscript('files', 'fileactions');
|
||||
OCP\Util::addscript('files', 'files');
|
||||
OCP\Util::addscript('files', 'keyboardshortcuts');
|
||||
|
|
@ -136,7 +142,8 @@ if ($needUpgrade) {
|
|||
$tmpl->assign('isCreatable', \OC\Files\Filesystem::isCreatable($dir . '/'));
|
||||
$tmpl->assign('permissions', $permissions);
|
||||
$tmpl->assign('files', $files);
|
||||
$tmpl->assign('trash', \OCP\App::isEnabled('files_trashbin'));
|
||||
$tmpl->assign('trash', $trashEnabled);
|
||||
$tmpl->assign('trashEmpty', $trashEmpty);
|
||||
$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
|
||||
$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize));
|
||||
$tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true)));
|
||||
|
|
|
|||
|
|
@ -392,6 +392,7 @@ var FileList={
|
|||
files.removeClass('selected');
|
||||
});
|
||||
procesSelection();
|
||||
checkTrashStatus();
|
||||
} else {
|
||||
$.each(files,function(index,file) {
|
||||
var deleteAction = $('tr').filterAttr('data-file',file).children("td.date").children(".move2trash");
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ $(document).ready(function() {
|
|||
});
|
||||
|
||||
// Show trash bin
|
||||
$('#trash a').live('click', function() {
|
||||
$('#trash').on('click', function() {
|
||||
window.location=OC.filePath('files_trashbin', '', 'index.php');
|
||||
});
|
||||
|
||||
|
|
@ -845,3 +845,11 @@ function getUniqueName(name){
|
|||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function checkTrashStatus() {
|
||||
$.post(OC.filePath('files_trashbin', 'ajax', 'isEmpty.php'), function(result){
|
||||
if (result.data.isEmpty === false) {
|
||||
$("input[type=button][id=trash]").removeAttr("disabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -38,9 +38,7 @@
|
|||
</form>
|
||||
</div>
|
||||
<?php if ($_['trash'] ): ?>
|
||||
<div id="trash" class="button">
|
||||
<a><?php p($l->t('Deleted files'));?></a>
|
||||
</div>
|
||||
<input id="trash" type="button" value="<?php p($l->t('Deleted files'));?>" class="button" <?php $_['trashEmpty'] ? p('disabled') : '' ?>></input>
|
||||
<?php endif; ?>
|
||||
<div id="uploadprogresswrapper">
|
||||
<div id="uploadprogressbar"></div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ $(document).ready(function() {
|
|||
var undeleteAction = $('tr').filterAttr('data-file',filename).children("td.date");
|
||||
var files = tr.attr('data-file');
|
||||
undeleteAction[0].innerHTML = undeleteAction[0].innerHTML+spinner;
|
||||
disableActions();
|
||||
$.post(OC.filePath('files_trashbin','ajax','undelete.php'),
|
||||
{files:JSON.stringify([files]), dirlisting:tr.attr('data-dirlisting') },
|
||||
function(result){
|
||||
|
|
@ -18,6 +19,7 @@ $(document).ready(function() {
|
|||
if (result.status != 'success') {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Error'));
|
||||
}
|
||||
enableActions();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -34,7 +36,7 @@ $(document).ready(function() {
|
|||
var newHTML = '<img class="move2trash" data-action="Delete" title="'+t('files', 'delete file permanently')+'" src="'+ OC.imagePath('core', 'loading.gif') +'"></a>';
|
||||
var files = tr.attr('data-file');
|
||||
deleteAction[0].outerHTML = newHTML;
|
||||
|
||||
disableActions();
|
||||
$.post(OC.filePath('files_trashbin','ajax','delete.php'),
|
||||
{files:JSON.stringify([files]), dirlisting:tr.attr('data-dirlisting') },
|
||||
function(result){
|
||||
|
|
@ -45,6 +47,7 @@ $(document).ready(function() {
|
|||
if (result.status != 'success') {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Error'));
|
||||
}
|
||||
enableActions();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -98,7 +101,7 @@ $(document).ready(function() {
|
|||
var files=getSelectedFiles('file');
|
||||
var fileslist = JSON.stringify(files);
|
||||
var dirlisting=getSelectedFiles('dirlisting')[0];
|
||||
|
||||
disableActions();
|
||||
for (var i=0; i<files.length; i++) {
|
||||
var undeleteAction = $('tr').filterAttr('data-file',files[i]).children("td.date");
|
||||
undeleteAction[0].innerHTML = undeleteAction[0].innerHTML+spinner;
|
||||
|
|
@ -114,6 +117,7 @@ $(document).ready(function() {
|
|||
if (result.status != 'success') {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Error'));
|
||||
}
|
||||
enableActions();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -125,6 +129,7 @@ $(document).ready(function() {
|
|||
var fileslist = JSON.stringify(files);
|
||||
var dirlisting=getSelectedFiles('dirlisting')[0];
|
||||
|
||||
disableActions();
|
||||
for (var i=0; i<files.length; i++) {
|
||||
var deleteAction = $('tr').filterAttr('data-file',files[i]).children("td.date");
|
||||
deleteAction[0].innerHTML = deleteAction[0].innerHTML+spinner;
|
||||
|
|
@ -140,6 +145,7 @@ $(document).ready(function() {
|
|||
if (result.status != 'success') {
|
||||
OC.dialogs.alert(result.data.message, t('core', 'Error'));
|
||||
}
|
||||
enableActions();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -236,3 +242,13 @@ function getSelectedFiles(property){
|
|||
function fileDownloadPath(dir, file) {
|
||||
return OC.filePath('files_trashbin', '', 'download.php') + '?file='+encodeURIComponent(file);
|
||||
}
|
||||
|
||||
function enableActions() {
|
||||
$(".action").css("display", "inline");
|
||||
$(":input:checkbox").css("display", "inline");
|
||||
}
|
||||
|
||||
function disableActions() {
|
||||
$(".action").css("display", "none");
|
||||
$(":input:checkbox").css("display", "none");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,4 +56,8 @@ class Hooks {
|
|||
Trashbin::deleteUser($uid);
|
||||
}
|
||||
}
|
||||
|
||||
public static function post_write_hook($params) {
|
||||
Trashbin::resizeTrash(\OCP\User::getUser());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace OCA\Files_Trashbin;
|
|||
class Trashbin {
|
||||
// how long do we keep files in the trash bin if no other value is defined in the config file (unit: days)
|
||||
|
||||
const DEFAULT_RETENTION_OBLIGATION = 180;
|
||||
const DEFAULT_RETENTION_OBLIGATION = 30;
|
||||
|
||||
// unit: percentage; 50% of available disk space/quota
|
||||
const DEFAULTMAXSIZE = 50;
|
||||
|
|
@ -72,6 +72,11 @@ class Trashbin {
|
|||
$mime = $view->getMimeType('files' . $file_path);
|
||||
|
||||
if ($view->is_dir('files' . $file_path)) {
|
||||
$dirContent = $view->getDirectoryContent('files' . $file_path);
|
||||
// no need to move empty folders to the trash bin
|
||||
if (empty($dirContent)) {
|
||||
return true;
|
||||
}
|
||||
$type = 'dir';
|
||||
} else {
|
||||
$type = 'file';
|
||||
|
|
@ -100,8 +105,8 @@ class Trashbin {
|
|||
\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', array('filePath' => \OC\Files\Filesystem::normalizePath($file_path),
|
||||
'trashPath' => \OC\Files\Filesystem::normalizePath($filename . '.d' . $timestamp)));
|
||||
|
||||
$trashbinSize += self::retainVersions($view, $file_path, $filename, $timestamp);
|
||||
$trashbinSize += self::retainEncryptionKeys($view, $file_path, $filename, $timestamp);
|
||||
$trashbinSize += self::retainVersions($file_path, $filename, $timestamp);
|
||||
$trashbinSize += self::retainEncryptionKeys($file_path, $filename, $timestamp);
|
||||
} else {
|
||||
\OC_Log::write('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OC_log::ERROR);
|
||||
}
|
||||
|
|
@ -114,14 +119,13 @@ class Trashbin {
|
|||
/**
|
||||
* Move file versions to trash so that they can be restored later
|
||||
*
|
||||
* @param \OC\Files\View $view
|
||||
* @param $file_path path to original file
|
||||
* @param $filename of deleted file
|
||||
* @param $timestamp when the file was deleted
|
||||
*
|
||||
* @return size of stored versions
|
||||
*/
|
||||
private static function retainVersions($view, $file_path, $filename, $timestamp) {
|
||||
private static function retainVersions($file_path, $filename, $timestamp) {
|
||||
$size = 0;
|
||||
if (\OCP\App::isEnabled('files_versions')) {
|
||||
|
||||
|
|
@ -154,14 +158,13 @@ class Trashbin {
|
|||
/**
|
||||
* Move encryption keys to trash so that they can be restored later
|
||||
*
|
||||
* @param \OC\Files\View $view
|
||||
* @param $file_path path to original file
|
||||
* @param $filename of deleted file
|
||||
* @param $timestamp when the file was deleted
|
||||
*
|
||||
* @return size of encryption keys
|
||||
*/
|
||||
private static function retainEncryptionKeys($view, $file_path, $filename, $timestamp) {
|
||||
private static function retainEncryptionKeys($file_path, $filename, $timestamp) {
|
||||
$size = 0;
|
||||
|
||||
if (\OCP\App::isEnabled('files_encryption')) {
|
||||
|
|
@ -283,11 +286,11 @@ class Trashbin {
|
|||
$location = '';
|
||||
}
|
||||
|
||||
$source = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $file);
|
||||
$target = \OC\Files\Filesystem::normalizePath('files/' . $location . '/' . $filename);
|
||||
|
||||
// we need a extension in case a file/dir with the same name already exists
|
||||
$ext = self::getUniqueExtension($location, $filename, $view);
|
||||
$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
|
||||
|
||||
$source = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $file);
|
||||
$target = \OC\Files\Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
|
||||
$mtime = $view->filemtime($source);
|
||||
|
||||
// disable proxy to prevent recursive calls
|
||||
|
|
@ -295,24 +298,24 @@ class Trashbin {
|
|||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// restore file
|
||||
$restoreResult = $view->rename($source, $target . $ext);
|
||||
$restoreResult = $view->rename($source, $target);
|
||||
|
||||
// handle the restore result
|
||||
if ($restoreResult) {
|
||||
$fakeRoot = $view->getRoot();
|
||||
$view->chroot('/' . $user . '/files');
|
||||
$view->touch('/' . $location . '/' . $filename . $ext, $mtime);
|
||||
$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
|
||||
$view->chroot($fakeRoot);
|
||||
\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $filename . $ext),
|
||||
\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
|
||||
'trashPath' => \OC\Files\Filesystem::normalizePath($file)));
|
||||
if ($view->is_dir($target . $ext)) {
|
||||
$trashbinSize -= self::calculateSize(new \OC\Files\View('/' . $user . '/' . $target . $ext));
|
||||
if ($view->is_dir($target)) {
|
||||
$trashbinSize -= self::calculateSize(new \OC\Files\View('/' . $user . '/' . $target));
|
||||
} else {
|
||||
$trashbinSize -= $view->filesize($target . $ext);
|
||||
$trashbinSize -= $view->filesize($target);
|
||||
}
|
||||
|
||||
$trashbinSize -= self::restoreVersions($view, $file, $filename, $ext, $location, $timestamp);
|
||||
$trashbinSize -= self::restoreEncryptionKeys($view, $file, $filename, $ext, $location, $timestamp);
|
||||
$trashbinSize -= self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
|
||||
$trashbinSize -= self::restoreEncryptionKeys($view, $file, $filename, $uniqueFilename, $location, $timestamp);
|
||||
|
||||
if ($timestamp) {
|
||||
$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
|
||||
|
|
@ -338,15 +341,16 @@ class Trashbin {
|
|||
*
|
||||
* @param \OC\Files\View $view file view
|
||||
* @param $file complete path to file
|
||||
* @param $filename name of file
|
||||
* @param $ext file extension in case a file with the same $filename already exists
|
||||
* @param $filename name of file once it was deleted
|
||||
* @param $uniqueFilename new file name to restore the file without overwriting existing files
|
||||
* @param $location location if file
|
||||
* @param $timestamp deleteion time
|
||||
*
|
||||
* @return size of restored versions
|
||||
*/
|
||||
private static function restoreVersions($view, $file, $filename, $ext, $location, $timestamp) {
|
||||
private static function restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp) {
|
||||
$size = 0;
|
||||
|
||||
if (\OCP\App::isEnabled('files_versions')) {
|
||||
// disable proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
|
|
@ -355,7 +359,7 @@ class Trashbin {
|
|||
$user = \OCP\User::getUser();
|
||||
$rootView = new \OC\Files\View('/');
|
||||
|
||||
$target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $filename . $ext);
|
||||
$target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
|
||||
|
||||
list($owner, $ownerPath) = self::getUidAndFilename($target);
|
||||
|
||||
|
|
@ -392,20 +396,20 @@ class Trashbin {
|
|||
* @param \OC\Files\View $view
|
||||
* @param $file complete path to file
|
||||
* @param $filename name of file
|
||||
* @param $ext file extension in case a file with the same $filename already exists
|
||||
* @param $uniqueFilename new file name to restore the file without overwriting existing files
|
||||
* @param $location location of file
|
||||
* @param $timestamp deleteion time
|
||||
*
|
||||
* @return size of restored encrypted file
|
||||
*/
|
||||
private static function restoreEncryptionKeys($view, $file, $filename, $ext, $location, $timestamp) {
|
||||
private static function restoreEncryptionKeys($view, $file, $filename, $uniqueFilename, $location, $timestamp) {
|
||||
// Take care of encryption keys TODO! Get '.key' in file between file name and delete date (also for permanent delete!)
|
||||
$size = 0;
|
||||
if (\OCP\App::isEnabled('files_encryption')) {
|
||||
$user = \OCP\User::getUser();
|
||||
$rootView = new \OC\Files\View('/');
|
||||
|
||||
$target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $filename . $ext);
|
||||
$target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
|
||||
|
||||
list($owner, $ownerPath) = self::getUidAndFilename($target);
|
||||
|
||||
|
|
@ -482,14 +486,11 @@ class Trashbin {
|
|||
// get current sharing state
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// get the final filename
|
||||
$target = \OC\Files\Filesystem::normalizePath($location . '/' . $filename);
|
||||
|
||||
// get users sharing this file
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $target . $ext, $user);
|
||||
$usersSharing = $util->getSharingUsersArray($sharingEnabled, $target, $user);
|
||||
|
||||
// Attempt to set shareKey
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $target . $ext);
|
||||
$util->setSharedFileKeyfiles($session, $usersSharing, $target);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -667,9 +668,32 @@ class Trashbin {
|
|||
return $availableSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief resize trash bin if necessary after a new file was added to ownCloud
|
||||
* @param string $user user id
|
||||
*/
|
||||
public static function resizeTrash($user) {
|
||||
|
||||
$size = self::getTrashbinSize($user);
|
||||
|
||||
if ($size === false || $size < 0) {
|
||||
$size = self::calculateSize(new \OC\Files\View('/' . $user . '/files_trashbin'));
|
||||
}
|
||||
|
||||
$freeSpace = self::calculateFreeSpace($size);
|
||||
|
||||
if ($freeSpace < 0) {
|
||||
$newSize = $size - self::expire($size);
|
||||
if ($newSize !== $size) {
|
||||
self::setTrashbinSize($user, $newSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clean up the trash bin
|
||||
* @param current size of the trash bin
|
||||
* @return size of expired files
|
||||
*/
|
||||
private static function expire($trashbinSize) {
|
||||
|
||||
|
|
@ -780,18 +804,28 @@ class Trashbin {
|
|||
* @param $view filesystem view relative to users root directory
|
||||
* @return string with unique extension
|
||||
*/
|
||||
private static function getUniqueExtension($location, $filename, $view) {
|
||||
$ext = '';
|
||||
private static function getUniqueFilename($location, $filename, $view) {
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$name = pathinfo($filename, PATHINFO_FILENAME);
|
||||
$l = \OC_L10N::get('files_trashbin');
|
||||
|
||||
// if extension is not empty we set a dot in front of it
|
||||
if ($ext !== '') {
|
||||
$ext = '.' . $ext;
|
||||
}
|
||||
|
||||
if ($view->file_exists('files' . $location . '/' . $filename)) {
|
||||
$tmpext = '.restored';
|
||||
$ext = $tmpext;
|
||||
$i = 1;
|
||||
while ($view->file_exists('files' . $location . '/' . $filename . $ext)) {
|
||||
$ext = $tmpext . $i;
|
||||
$i = 2;
|
||||
$uniqueName = $name . " (".$l->t("restored").")". $ext;
|
||||
while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
|
||||
$uniqueName = $name . " (".$l->t("restored") . " " . $i . ")" . $ext;
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $uniqueName;
|
||||
}
|
||||
return $ext;
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -827,7 +861,7 @@ class Trashbin {
|
|||
$result = $query->execute(array($user))->fetchAll();
|
||||
|
||||
if ($result) {
|
||||
return $result[0]['size'];
|
||||
return (int)$result[0]['size'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -855,6 +889,23 @@ class Trashbin {
|
|||
\OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA\Files_Trashbin\Hooks", "remove_hook");
|
||||
//Listen to delete user signal
|
||||
\OCP\Util::connectHook('OC_User', 'pre_deleteUser', "OCA\Files_Trashbin\Hooks", "deleteUser_hook");
|
||||
//Listen to post write hook
|
||||
\OCP\Util::connectHook('OC_Filesystem', 'post_write', "OCA\Files_Trashbin\Hooks", "post_write_hook");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief check if trash bin is empty for a given user
|
||||
* @param string $user
|
||||
*/
|
||||
public static function isEmpty($user) {
|
||||
|
||||
$trashSize = self::getTrashbinSize($user);
|
||||
|
||||
if ($trashSize !== false && $trashSize > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,8 +114,8 @@ $CONFIG = array(
|
|||
/* Password to use for sendmail mail, depends on mail_smtpauth if this is used */
|
||||
"mail_smtppassword" => "",
|
||||
|
||||
/* How long should ownCloud keep deleted files in the trash bin, default value: 180 days */
|
||||
'trashbin_retention_obligation' => 180,
|
||||
/* How long should ownCloud keep deleted files in the trash bin, default value: 30 days */
|
||||
'trashbin_retention_obligation' => 30,
|
||||
|
||||
/* allow user to change his display name, if it is supported by the back-end */
|
||||
'allow_user_to_change_display_name' => true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue