From 73d45e79a7066b56581072c732f38ca375a4fc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Tue, 15 Jan 2013 20:35:15 +0100 Subject: [PATCH 01/15] add trash button to web interface --- apps/files/css/files.css | 2 ++ apps/files/index.php | 1 + apps/files/js/files.js | 5 +++++ apps/files/templates/index.php | 6 +++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 36a1e5c954..6355a8cde1 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -23,6 +23,8 @@ #new>ul>li>p { cursor:pointer; } #new>ul>li>form>input { padding:0.3em; margin:-0.3em; } +#trash { height:17px; margin:0 0 0 1em; z-index:1010; position:absolute; right:13.5em; } + #upload { height:27px; padding:0; margin-left:0.2em; overflow:hidden; } diff --git a/apps/files/index.php b/apps/files/index.php index b64bde44cc..24f58bbd07 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -105,6 +105,7 @@ $tmpl->assign('dir', OC_Filesystem::normalizePath($dir)); $tmpl->assign('isCreatable', OC_Filesystem::isCreatable($dir . '/')); $tmpl->assign('permissions', $permissions); $tmpl->assign('files', $files); +$tmpl->assign('trash', \OCP\App::isEnabled('files_trashbin')); $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); $tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize)); $tmpl->assign('allowZipDownload', intval(OCP\Config::getSystemValue('allowZipDownload', true))); diff --git a/apps/files/js/files.js b/apps/files/js/files.js index bb298431e8..c13d7a5961 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -82,6 +82,11 @@ $(document).ready(function() { $(this).parent().children('#file_upload_start').trigger('click'); return false; }); + + // Show Trash bin + $('#trash a').live('click', function() { + console.log("hello"); + }); var lastChecked; diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 2e0772443f..f6b4c29d5a 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -35,6 +35,11 @@ + +
+ t('Trash');?> +
+
-
From c494eb79ab049d71113c786375b1ee11338e5edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Wed, 16 Jan 2013 16:01:11 +0100 Subject: [PATCH 02/15] listen to post delete event to allow the trash bin to create a copy of the version first --- apps/files_versions/appinfo/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_versions/appinfo/app.php b/apps/files_versions/appinfo/app.php index afc0a67edb..edd0a2f702 100644 --- a/apps/files_versions/appinfo/app.php +++ b/apps/files_versions/appinfo/app.php @@ -12,5 +12,5 @@ OCP\Util::addscript('files_versions', 'versions'); // Listen to write signals OCP\Util::connectHook('OC_Filesystem', 'write', "OCA_Versions\Hooks", "write_hook"); // Listen to delete and rename signals -OCP\Util::connectHook('OC_Filesystem', 'delete', "OCA_Versions\Hooks", "remove_hook"); +OCP\Util::connectHook('OC_Filesystem', 'post-delete', "OCA_Versions\Hooks", "remove_hook"); OCP\Util::connectHook('OC_Filesystem', 'rename', "OCA_Versions\Hooks", "rename_hook"); \ No newline at end of file From d5ee4352539f2165eebde5dc983113849b6a1a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Thu, 17 Jan 2013 13:17:48 +0100 Subject: [PATCH 03/15] rename "publicListView" option to "disableSharing", this is more meaningful also because it is not only useful for the public list view --- apps/files/templates/part.list.php | 6 +++--- apps/files_sharing/js/share.js | 2 +- apps/files_sharing/public.php | 2 +- apps/files_sharing/templates/public.php | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/files/templates/part.list.php b/apps/files/templates/part.list.php index dfac43d1b1..1970a80e1b 100644 --- a/apps/files/templates/part.list.php +++ b/apps/files/templates/part.list.php @@ -1,8 +1,8 @@ diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 8a546d6216..59faf82e26 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -1,6 +1,6 @@ $(document).ready(function() { - if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined' && !publicListView) { + if (typeof OC.Share !== 'undefined' && typeof FileActions !== 'undefined' && !disableSharing) { FileActions.register('all', 'Share', OC.PERMISSION_READ, OC.imagePath('core', 'actions/share'), function(filename) { if ($('#dir').val() == '/') { diff --git a/apps/files_sharing/public.php b/apps/files_sharing/public.php index 487b9e7996..7e1959cd95 100644 --- a/apps/files_sharing/public.php +++ b/apps/files_sharing/public.php @@ -257,7 +257,7 @@ if ($linkItem) { $list = new OCP\Template('files', 'part.list', ''); $list->assign('files', $files, false); - $list->assign('publicListView', true); + $list->assign('disableSharing', true); $list->assign('baseURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&path=', false); $list->assign('downloadURL', OCP\Util::linkToPublic('files').$urlLinkIdentifiers.'&download&path=', false); $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', '' ); diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 647e1e08a3..bfcc521e10 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -1,8 +1,8 @@ From 82d4da0d3d0dc09c22d2cd2220d418372206099e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 10:23:31 +0100 Subject: [PATCH 04/15] call the trash bin view --- apps/files/js/files.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files/js/files.js b/apps/files/js/files.js index c13d7a5961..359b92a6fd 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -83,9 +83,9 @@ $(document).ready(function() { return false; }); - // Show Trash bin + // Show trash bin $('#trash a').live('click', function() { - console.log("hello"); + window.location=OC.filePath('files_trashbin', '', 'index.php'); }); var lastChecked; From 1318450791146e7a075c01d8a39df6d333b619f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 10:51:13 +0100 Subject: [PATCH 05/15] introduce option to disable download action --- apps/files/js/fileactions.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 80b9c01f83..093b6204c3 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -147,15 +147,19 @@ $(document).ready(function () { } else { var downloadScope = 'file'; } - FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { - return OC.imagePath('core', 'actions/download'); - }, function (filename) { - window.location = OC.filePath('files', 'ajax', 'download.php') + '?files=' + encodeURIComponent(filename) + '&dir=' + encodeURIComponent($('#dir').val()); - }); - + + if (typeof disableDownloadActions == 'undefined' || !disableDownloadActions) { + FileActions.register(downloadScope, 'Download', OC.PERMISSION_READ, function () { + return OC.imagePath('core', 'actions/download'); + }, function (filename) { + window.location = OC.filePath('files', 'ajax', 'download.php') + '?files=' + encodeURIComponent(filename) + '&dir=' + encodeURIComponent($('#dir').val()); + }); + } + $('#fileList tr').each(function(){ FileActions.display($(this).children('td.filename')); }); + }); FileActions.register('all', 'Delete', OC.PERMISSION_DELETE, function () { From d5151fa61c561544ee64472a2c778cbe4fe086f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 13:11:29 +0100 Subject: [PATCH 06/15] first version of the trash bin app --- apps/files_trashbin/ajax/undelete.php | 13 ++ apps/files_trashbin/appinfo/app.php | 7 + apps/files_trashbin/appinfo/database.xml | 92 ++++++++++ apps/files_trashbin/appinfo/info.xml | 14 ++ apps/files_trashbin/appinfo/version | 1 + apps/files_trashbin/css/trash.css | 78 ++++++++ apps/files_trashbin/download.php | 52 ++++++ apps/files_trashbin/index.php | 55 ++++++ apps/files_trashbin/js/trash.js | 37 ++++ apps/files_trashbin/lib/hooks.php | 45 +++++ apps/files_trashbin/lib/trash.php | 190 ++++++++++++++++++++ apps/files_trashbin/templates/index.php | 36 ++++ apps/files_trashbin/templates/part.list.php | 70 ++++++++ 13 files changed, 690 insertions(+) create mode 100644 apps/files_trashbin/ajax/undelete.php create mode 100644 apps/files_trashbin/appinfo/app.php create mode 100644 apps/files_trashbin/appinfo/database.xml create mode 100644 apps/files_trashbin/appinfo/info.xml create mode 100644 apps/files_trashbin/appinfo/version create mode 100644 apps/files_trashbin/css/trash.css create mode 100644 apps/files_trashbin/download.php create mode 100644 apps/files_trashbin/index.php create mode 100644 apps/files_trashbin/js/trash.js create mode 100644 apps/files_trashbin/lib/hooks.php create mode 100644 apps/files_trashbin/lib/trash.php create mode 100644 apps/files_trashbin/templates/index.php create mode 100644 apps/files_trashbin/templates/part.list.php diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php new file mode 100644 index 0000000000..c548034828 --- /dev/null +++ b/apps/files_trashbin/ajax/undelete.php @@ -0,0 +1,13 @@ + array('content'=>'foo', 'id' => 'bar'))); \ No newline at end of file diff --git a/apps/files_trashbin/appinfo/app.php b/apps/files_trashbin/appinfo/app.php new file mode 100644 index 0000000000..3741d42c78 --- /dev/null +++ b/apps/files_trashbin/appinfo/app.php @@ -0,0 +1,7 @@ + + + + *dbname* + true + false + + utf8 + + + + *dbprefix*files_trash + + + + + id + text + + true + 50 + + + + user + text + + true + 50 + + + + timestamp + text + + true + 12 + + + + location + text + + true + 200 + + + + type + text + + true + 4 + + + + mime + text + + true + 30 + + + + id_index + + id + ascending + + + + + timestamp_index + + timestamp + ascending + + + + + user_index + + user + ascending + + + + + +
+ +
diff --git a/apps/files_trashbin/appinfo/info.xml b/apps/files_trashbin/appinfo/info.xml new file mode 100644 index 0000000000..9b48612636 --- /dev/null +++ b/apps/files_trashbin/appinfo/info.xml @@ -0,0 +1,14 @@ + + + files_trashbin + Trash + Trash bin + AGPL + Bjoern Schiessle + true + 4.9 + + + + + diff --git a/apps/files_trashbin/appinfo/version b/apps/files_trashbin/appinfo/version new file mode 100644 index 0000000000..49d59571fb --- /dev/null +++ b/apps/files_trashbin/appinfo/version @@ -0,0 +1 @@ +0.1 diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css new file mode 100644 index 0000000000..bd6341c6fb --- /dev/null +++ b/apps/files_trashbin/css/trash.css @@ -0,0 +1,78 @@ +body { + background:#ddd; +} + +#header { + background:#1d2d44; + box-shadow:0 0 10px rgba(0,0,0,.5), inset 0 -2px 10px #222; + height:2.5em; + left:0; + line-height:2.5em; + position:fixed; + right:0; + top:0; + z-index:100; + padding:.5em; +} + +#details { + color:#fff; +} + +#header #download { + font-weight:700; + margin-left:2em; +} + +#header #download img { + padding-left:.1em; + padding-right:.3em; + vertical-align:text-bottom; +} + +#preview { + background:#eee; + border-bottom:1px solid #f8f8f8; + min-height:30em; + padding-top:2em; + text-align:center; + margin:50px auto; +} + +#noPreview { + display:none; + padding-top:5em; +} + +p.info { + color:#777; + text-align:center; + text-shadow:#fff 0 1px 0; + width:22em; + margin:2em auto; +} + +p.info a { + color:#777; + font-weight:700; +} + +#imgframe { + height:75%; + padding-bottom:2em; + width:80%; + margin:0 auto; +} + +#imgframe img { + max-height:100%; + max-width:100%; +} + +table td.filename .name { + display:block; + height:1.5em; + vertical-align:middle; + margin-left:3em; + /*font-weight:bold;*/ +} \ No newline at end of file diff --git a/apps/files_trashbin/download.php b/apps/files_trashbin/download.php new file mode 100644 index 0000000000..a987dd4fd1 --- /dev/null +++ b/apps/files_trashbin/download.php @@ -0,0 +1,52 @@ +. +* +*/ + +// Check if we are a user +OCP\User::checkLoggedIn(); + +$filename = $_GET["file"]; + +$view = new OC_FilesystemView('/'.\OCP\User::getUser().'/files_trashbin'); + +if(!$view->file_exists($filename)) { + error_log("file does not exist... " . $view->getInternalPath($filename)); + header("HTTP/1.0 404 Not Found"); + $tmpl = new OCP\Template( '', '404', 'guest' ); + $tmpl->assign('file', $filename); + $tmpl->printPage(); + exit; +} + +$ftype=$view->getMimeType( $filename ); + +header('Content-Type:'.$ftype);if ( preg_match( "/MSIE/", $_SERVER["HTTP_USER_AGENT"] ) ) { + header( 'Content-Disposition: attachment; filename="' . rawurlencode( basename($filename) ) . '"' ); +} else { + header( 'Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode( basename($filename) ) + . '; filename="' . rawurlencode( basename($filename) ) . '"' ); +} +OCP\Response::disableCaching(); +header('Content-Length: '. $view->filesize($filename)); + +OC_Util::obEnd(); +$view->readfile( $filename ); diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php new file mode 100644 index 0000000000..28414cc1ce --- /dev/null +++ b/apps/files_trashbin/index.php @@ -0,0 +1,55 @@ +execute(array($user))->fetchAll(); + +$files = array(); +foreach ($result as $r) { + $i = array(); + $i['name'] = $r['id']; + $i['date'] = OCP\Util::formatDate($r['timestamp']); + $i['timestamp'] = $r['timestamp']; + $i['mimetype'] = $r['mime']; + $i['type'] = $r['type']; + if ($i['type'] == 'file') { + $fileinfo = pathinfo($r['id']); + $i['basename'] = $fileinfo['filename']; + $i['extension'] = isset($fileinfo['extension']) ? ('.'.$fileinfo['extension']) : ''; + } + $i['directory'] = $r['location']; + if ($i['directory'] == '/') { + $i['directory'] = ''; + } + $i['permissions'] = OCP\PERMISSION_READ; + $files[] = $i; +} + +$breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); +$breadcrumbNav->assign('breadcrumb', array(array('dir' => '', 'name' => 'Trash')), false); +$breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir=', false); + +$list = new OCP\Template('files_trashbin', 'part.list', ''); +$list->assign('files', $files, false); +$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir=', false); +$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file=', false); +$list->assign('disableSharing', true); +$list->assign('disableDownloadActions', true); +$tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); +$tmpl->assign('fileList', $list->fetchPage(), false); +$tmpl->assign('dir', OC_Filesystem::normalizePath($view->getAbsolutePath())); + +$tmpl->printPage(); diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js new file mode 100644 index 0000000000..37e9a4bf10 --- /dev/null +++ b/apps/files_trashbin/js/trash.js @@ -0,0 +1,37 @@ +// Override download path to files_sharing/public.php +function fileDownloadPath(dir, file) { + var url = $('#downloadURL').val(); + if (url.indexOf('&path=') != -1) { + url += '/'+file; + } + return url; +} + +$(document).ready(function() { + + if (typeof FileActions !== 'undefined') { + FileActions.register('all', 'Undelete', OC.PERMISSION_READ, '', function(filename) { + var tr=$('tr').filterAttr('data-file', filename); + console.log("tr: " + tr.attr('data-timestamp') + " name: " + name); + $.post(OC.filePath('files_trashbin','ajax','undelete.php'), + {timestamp:tr.attr('data-timestamp'),filename:tr.attr('data-filename')}, + function(result){ + if (result.status == 'success') { + return; + var date=new Date(); + FileList.addFile(name,0,date,false,hidden); + var tr=$('tr').filterAttr('data-file',name); + tr.data('mime','text/plain').data('id',result.data.id); + tr.attr('data-id', result.data.id); + getMimeIcon('text/plain',function(path){ + tr.find('td.filename').attr('style','background-image:url('+path+')'); + }); + } else { + OC.dialogs.alert(result.data.message, 'Error'); + } + }); + + }); + }; + +}); \ No newline at end of file diff --git a/apps/files_trashbin/lib/hooks.php b/apps/files_trashbin/lib/hooks.php new file mode 100644 index 0000000000..d3bee105b5 --- /dev/null +++ b/apps/files_trashbin/lib/hooks.php @@ -0,0 +1,45 @@ +. + * + */ + +/** + * This class contains all hooks. + */ + +namespace OCA_Trash; + +class Hooks { + + /** + * @brief Copy files to trash bin + * @param array + * + * This function is connected to the delete signal of OC_Filesystem + * to copy the file to the trash bin + */ + public static function remove_hook($params) { + + if ( \OCP\App::isEnabled('files_trashbin') ) { + $path = $params['path']; + Trashbin::move2trash($path); + } + } +} diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php new file mode 100644 index 0000000000..384a865ce0 --- /dev/null +++ b/apps/files_trashbin/lib/trash.php @@ -0,0 +1,190 @@ +. + * + */ + +namespace OCA_Trash; + +class Trashbin { + + /** + * move file to the trash bin + * + * @param $file_path path to the deleted file/directory relative to the files root directory + */ + public static function move2trash($file_path) { + $user = \OCP\User::getUser(); + $view = new \OC_FilesystemView('/'. $user); + if (!$view->is_dir('files_trashbin')) { + $view->mkdir('files_trashbin'); + $view->mkdir("versions_trashbin"); + } + + $path_parts = pathinfo($file_path); + + $deleted = $path_parts['basename']; + $location = $path_parts['dirname']; + $timestamp = time(); + $mime = $view->getMimeType('files'.$file_path); + + if ( $view->is_dir('files'.$file_path) ) { + $type = 'dir'; + } else { + $type = 'file'; + } + + self::copy_recursive($file_path, 'files_trashbin/'.$deleted.'.d'.$timestamp, $view); + + $query = \OC_DB::prepare("INSERT INTO *PREFIX*files_trash (id,timestamp,location,type,mime,user) VALUES (?,?,?,?,?,?)"); + $result = $query->execute(array($deleted, $timestamp, $location, $type, $mime, $user)); + + if ( \OCP\App::isEnabled('files_versions') ) { + if ( $view->is_dir('files_versions'.$file_path) ) { + $view->rename('files_versions'.$file_path, 'versions_trashbin/'. $deleted.'.d'.$timestamp); + } else if ( $versions = \OCA_Versions\Storage::getVersions($file_path) ) { + foreach ($versions as $v) { + $view->rename('files_versions'.$v['path'].'.v'.$v['version'], 'versions_trashbin/'. $deleted.'.v'.$v['version'].'.d'.$timestamp); + } + } + } + + self::expire(); + } + + + /** + * restore files from trash bin + * @param $filename name of the file + * @param $timestamp time when the file was deleted + */ + public static function restore($filename, $timestamp) { + + $user = \OCP\User::getUser(); + $view = new \OC_FilesystemView('/'.$user); + + $query = \OC_DB::prepare('SELECT location,type FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $result = $query->execute(array($user,$filename,$timestamp))->fetchAll(); + + if ( count($result) != 1 ) { + \OC_Log::write('files_trashbin', 'trash bin database inconsistent!', OC_Log::ERROR); + return false; + } + + $location = $result[0]['location']; + if ( $result[0]['location'] != '/' && !$view->is_dir('files'.$result[0]['location']) ) { + $location = '/'; + } + + $source = 'files_trashbin/'.$filename.'.d'.$timestamp; + $target = \OC_Filesystem::normalizePath('files/'.$location.'/'.$filename); + + $ext = self::getUniqueExtension($location, $filename, $view); + + $view->rename($source, $target.$ext); + + if ( \OCP\App::isEnabled('files_versions') ) { + if ( $result[0][type] == 'dir' ) { + $view->rename('versions_trashbin/'. $filename.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext); + } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + foreach ($versions as $v) { + $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + } + } + } + + $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $query->execute(array($user,$filename,$timestamp)); + + } + + /** + * clean up the trash bin + */ + private static function expire() { + //TODO: implement expire function + return true; + } + + /** + * recursive copy to copy a whole directory + * + * @param $source source path, relative to the users files directory + * @param $destination destination path relative to the users root directoy + * @param $view file view for the users root directory + * @param $location location of the source files, either "fscache" or "local" + */ + private static function copy_recursive( $source, $destination, $view, $location='fscache' ) { + if ( $view->is_dir( 'files'.$source ) ) { + $view->mkdir( $destination ); + foreach ( \OC_Files::getDirectoryContent($source) as $i ) { + $pathDir = $source.'/'.$i['name']; + if ( $view->is_dir('files'.$pathDir) ) { + self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view); + } else { + $view->copy( 'files'.$pathDir, $destination . '/' . $i['name'] ); + } + } + } else { + $view->copy( 'files'.$source, $destination ); + } + } + + /** + * find all versions which belong to the file we want to restore + * @param $filename name of the file which should be restored + * @param $timestamp timestamp when the file was deleted + */ + private static function getVersionsFromTrash($filename, $timestamp) { + $view = new \OC_FilesystemView('/'.\OCP\User::getUser().'/versions_trashbin'); + $versionsName = \OCP\Config::getSystemValue('datadirectory').$view->getAbsolutePath($filename); + $versions = array(); + + // fetch for old versions + $matches = glob( $versionsName.'.v*.d'.$timestamp ); + + foreach( $matches as $ma ) { + $parts = explode( '.v', substr($ma, 0, -strlen($timestamp)-2) ); + $versions[] = ( end( $parts ) ); + } + return $versions; + } + + /** + * find unique extension for restored file if a file with the same name already exists + * @param $location where the file should be restored + * @param $filename name of the file + * @param $view filesystem view relative to users root directory + * @return string with unique extension + */ + private static function getUniqueExtension($location, $filename, $view) { + $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++; + } + } + return $ext; + } + +} diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php new file mode 100644 index 0000000000..a412379d53 --- /dev/null +++ b/apps/files_trashbin/templates/index.php @@ -0,0 +1,36 @@ + +
+ +
+
+
+ + +
t('Nothing in here. Upload something!')?>
+ + + + + + + + + + + + +
+ + t( 'Name' ); ?> + + + + Download" /> + t('Download')?> + + + + + t( 'Deleted' ); ?> +
diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php new file mode 100644 index 0000000000..d022854330 --- /dev/null +++ b/apps/files_trashbin/templates/part.list.php @@ -0,0 +1,70 @@ + + +200) $relative_date_color = 200; + $name = str_replace('+', '%20', urlencode($file['name'])); + $name = str_replace('%2F', '/', $name); + $directory = str_replace('+', '%20', urlencode($file['directory'])); + $directory = str_replace('%2F', '/', $directory); ?> + ' + data-permissions=''> + + style="background-image:url()" + + style="background-image:url()" + + > + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Fri, 18 Jan 2013 14:09:22 +0100 Subject: [PATCH 07/15] remove item in the trash bin view after successful undelete --- apps/files_trashbin/ajax/undelete.php | 9 +++--- apps/files_trashbin/js/trash.js | 12 ++----- apps/files_trashbin/lib/trash.php | 35 ++++++++++++--------- apps/files_trashbin/templates/part.list.php | 3 +- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index c548034828..f55629d695 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -7,7 +7,8 @@ if(!OC_User::isLoggedIn()) { $timestamp = isset( $_REQUEST['timestamp'] ) ? $_REQUEST['timestamp'] : ''; $filename = isset( $_REQUEST['filename'] ) ? trim($_REQUEST['filename'], '/\\') : ''; -OCA_Trash\Trashbin::restore($filename, $timestamp); - -//TODO: return useful data after succsessful restore operation and remove restored files from the list view -OCP\JSON::success(array("data" => array('content'=>'foo', 'id' => 'bar'))); \ No newline at end of file +if ( OCA_Trash\Trashbin::restore($filename, $timestamp) ) { + OCP\JSON::success(array("data" => array('filename'=>$filename, 'timestamp' => $timestamp))); +} else { + OCP\JSON::error(array("data" => array("message" => "Couldn't restore ".$filename))); +} diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 37e9a4bf10..b9088944fd 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -12,20 +12,12 @@ $(document).ready(function() { if (typeof FileActions !== 'undefined') { FileActions.register('all', 'Undelete', OC.PERMISSION_READ, '', function(filename) { var tr=$('tr').filterAttr('data-file', filename); - console.log("tr: " + tr.attr('data-timestamp') + " name: " + name); $.post(OC.filePath('files_trashbin','ajax','undelete.php'), {timestamp:tr.attr('data-timestamp'),filename:tr.attr('data-filename')}, function(result){ if (result.status == 'success') { - return; - var date=new Date(); - FileList.addFile(name,0,date,false,hidden); - var tr=$('tr').filterAttr('data-file',name); - tr.data('mime','text/plain').data('id',result.data.id); - tr.attr('data-id', result.data.id); - getMimeIcon('text/plain',function(path){ - tr.find('td.filename').attr('style','background-image:url('+path+')'); - }); + var row = document.getElementById(result.data.filename+'.d'+result.data.timestamp); + row.parentNode.removeChild(row); } else { OC.dialogs.alert(result.data.message, 'Error'); } diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 384a865ce0..322f5679b7 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -86,7 +86,8 @@ class Trashbin { \OC_Log::write('files_trashbin', 'trash bin database inconsistent!', OC_Log::ERROR); return false; } - + + // if location no longer exists, restore file in the root directory $location = $result[0]['location']; if ( $result[0]['location'] != '/' && !$view->is_dir('files'.$result[0]['location']) ) { $location = '/'; @@ -95,23 +96,29 @@ class Trashbin { $source = 'files_trashbin/'.$filename.'.d'.$timestamp; $target = \OC_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); - $view->rename($source, $target.$ext); - - if ( \OCP\App::isEnabled('files_versions') ) { - if ( $result[0][type] == 'dir' ) { - $view->rename('versions_trashbin/'. $filename.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext); - } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { - foreach ($versions as $v) { - $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + if( $view->rename($source, $target.$ext) ) { + + // if versioning app is enabled, copy versions from the trash bin back to the original location + if ( $return && \OCP\App::isEnabled('files_versions') ) { + if ( $result[0][type] == 'dir' ) { + $view->rename('versions_trashbin/'. $filename.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext); + } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + foreach ($versions as $v) { + $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + } } - } + } + + $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $query->execute(array($user,$filename,$timestamp)); + + return true; } - - $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); - $query->execute(array($user,$filename,$timestamp)); - + + return false; } /** diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php index d022854330..c9a641a2e2 100644 --- a/apps/files_trashbin/templates/part.list.php +++ b/apps/files_trashbin/templates/part.list.php @@ -24,7 +24,8 @@ $name = str_replace('%2F', '/', $name); $directory = str_replace('+', '%20', urlencode($file['directory'])); $directory = str_replace('%2F', '/', $directory); ?> - Date: Fri, 18 Jan 2013 14:11:55 +0100 Subject: [PATCH 08/15] remove unneeded function --- apps/files_trashbin/js/trash.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index b9088944fd..075dc6c315 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -1,12 +1,3 @@ -// Override download path to files_sharing/public.php -function fileDownloadPath(dir, file) { - var url = $('#downloadURL').val(); - if (url.indexOf('&path=') != -1) { - url += '/'+file; - } - return url; -} - $(document).ready(function() { if (typeof FileActions !== 'undefined') { From 8fdcd72d7f5fa9a0f2f84c63a1bc91211265cb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 15:12:38 +0100 Subject: [PATCH 09/15] expire files in trash bin after 30 days --- apps/files_trashbin/lib/trash.php | 32 ++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 322f5679b7..1b0b9cef4b 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -23,6 +23,8 @@ namespace OCA_Trash; class Trashbin { + + const DELETEAFTER=30; // how long do we keep files in the trash bin (number of days) /** * move file to the trash bin @@ -124,9 +126,33 @@ class Trashbin { /** * clean up the trash bin */ - private static function expire() { - //TODO: implement expire function - return true; + private static function expire() { + + $view = new \OC_FilesystemView('/'.\OCP\User::getUser()); + $user = \OCP\User::getUser(); + + $query = \OC_DB::prepare('SELECT location,type,id,timestamp FROM *PREFIX*files_trash WHERE user=?'); + $result = $query->execute(array($user))->fetchAll(); + + $limit = time() - (self::DELETEAFTER * 86400); + + foreach ( $result as $r ) { + $timestamp = $r['timestamp']; + $filename = $r['id']; + if ( $r['timestamp'] < $limit ) { + $view->unlink('files_trashbin/'.$filename.'.d'.$timestamp); + if ($r['type'] == 'dir') { + $view->unlink('versions_trashbin/'.$filename.'.d'.$timestamp); + } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + foreach ($versions as $v) { + $view->unlink('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp); + } + } + } + } + + $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND timestampexecute(array($user,$limit)); } /** From adbc576439faf19515b84d0fe4bb48ba52c4450a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 18 Jan 2013 17:50:44 +0100 Subject: [PATCH 10/15] handle group restore --- apps/files_trashbin/ajax/undelete.php | 35 ++++-- apps/files_trashbin/css/trash.css | 4 +- apps/files_trashbin/js/trash.js | 135 +++++++++++++++++++++++- apps/files_trashbin/lib/trash.php | 2 +- apps/files_trashbin/templates/index.php | 9 +- 5 files changed, 166 insertions(+), 19 deletions(-) diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index f55629d695..05b5e7a5ee 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -4,11 +4,34 @@ if(!OC_User::isLoggedIn()) { exit; } -$timestamp = isset( $_REQUEST['timestamp'] ) ? $_REQUEST['timestamp'] : ''; -$filename = isset( $_REQUEST['filename'] ) ? trim($_REQUEST['filename'], '/\\') : ''; +$files = $_REQUEST['files']; +$list = explode(';', $files); + +$error = array(); + +$i = 0; +foreach ($list as $file) { + $delimiter = strrpos($file, '.d'); + $filename = substr($file, 0, $delimiter); + $timestamp = substr($file, $delimiter+2); + + if ( !OCA_Trash\Trashbin::restore($filename, $timestamp) ) { + $error[] = $filename; + } else { + $success[$i]['filename'] = $filename; + $success[$i]['timestamp'] = $timestamp; + $i++; + } -if ( OCA_Trash\Trashbin::restore($filename, $timestamp) ) { - OCP\JSON::success(array("data" => array('filename'=>$filename, 'timestamp' => $timestamp))); -} else { - OCP\JSON::error(array("data" => array("message" => "Couldn't restore ".$filename))); } + +if ( $error ) { + $filelist = ''; + foreach ( $error as $e ) { + $filelist .= $e.', '; + } + OCP\JSON::error(array("data" => array("message" => "Couldn't restore ".rtrim($filelist,', '), "success" => $success, "error" => $error))); +} else { + OCP\JSON::success(array("data" => array("success" => $success))); +} + diff --git a/apps/files_trashbin/css/trash.css b/apps/files_trashbin/css/trash.css index bd6341c6fb..e0dd8c6e93 100644 --- a/apps/files_trashbin/css/trash.css +++ b/apps/files_trashbin/css/trash.css @@ -19,12 +19,12 @@ body { color:#fff; } -#header #download { +#header #undelete { font-weight:700; margin-left:2em; } -#header #download img { +#header #undelete img { padding-left:.1em; padding-right:.3em; vertical-align:text-bottom; diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 075dc6c315..8f3786f15e 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -1,20 +1,145 @@ + $(document).ready(function() { if (typeof FileActions !== 'undefined') { FileActions.register('all', 'Undelete', OC.PERMISSION_READ, '', function(filename) { var tr=$('tr').filterAttr('data-file', filename); $.post(OC.filePath('files_trashbin','ajax','undelete.php'), - {timestamp:tr.attr('data-timestamp'),filename:tr.attr('data-filename')}, + {files:tr.attr('data-filename')+'.d'+tr.attr('data-timestamp')}, function(result){ - if (result.status == 'success') { - var row = document.getElementById(result.data.filename+'.d'+result.data.timestamp); + for (var i = 0; i < result.data.success.length; i++) { + var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); row.parentNode.removeChild(row); - } else { + } + if (result.status != 'success') { OC.dialogs.alert(result.data.message, 'Error'); } }); }); }; + + // Sets the select_all checkbox behaviour : + $('#select_all').click(function() { + if($(this).attr('checked')){ + // Check all + $('td.filename input:checkbox').attr('checked', true); + $('td.filename input:checkbox').parent().parent().addClass('selected'); + }else{ + // Uncheck all + $('td.filename input:checkbox').attr('checked', false); + $('td.filename input:checkbox').parent().parent().removeClass('selected'); + } + procesSelection(); + }); -}); \ No newline at end of file + $('td.filename input:checkbox').live('change',function(event) { + if (event.shiftKey) { + var last = $(lastChecked).parent().parent().prevAll().length; + var first = $(this).parent().parent().prevAll().length; + var start = Math.min(first, last); + var end = Math.max(first, last); + var rows = $(this).parent().parent().parent().children('tr'); + for (var i = start; i < end; i++) { + $(rows).each(function(index) { + if (index == i) { + var checkbox = $(this).children().children('input:checkbox'); + $(checkbox).attr('checked', 'checked'); + $(checkbox).parent().parent().addClass('selected'); + } + }); + } + } + var selectedCount=$('td.filename input:checkbox:checked').length; + $(this).parent().parent().toggleClass('selected'); + if(!$(this).attr('checked')){ + $('#select_all').attr('checked',false); + }else{ + if(selectedCount==$('td.filename input:checkbox').length){ + $('#select_all').attr('checked',true); + } + } + procesSelection(); + }); + + $('.undelete').click('click',function(event) { + var fileslist=getSelectedFiles('file').join(';'); + $.post(OC.filePath('files_trashbin','ajax','undelete.php'), + {files:fileslist}, + function(result){ + for (var i = 0; i < result.data.success.length; i++) { + var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); + row.parentNode.removeChild(row); + } + if (result.status != 'success') { + OC.dialogs.alert(result.data.message, 'Error'); + } + }); + }); + + +}); + +function procesSelection(){ + var selected=getSelectedFiles(); + var selectedFiles=selected.filter(function(el){return el.type=='file'}); + var selectedFolders=selected.filter(function(el){return el.type=='dir'}); + if(selectedFiles.length==0 && selectedFolders.length==0) { + $('#headerName>span.name').text(t('files','Name')); + $('#modified').text(t('files','Deleted')); + $('table').removeClass('multiselect'); + $('.selectedActions').hide(); + } + else { + $('.selectedActions').show(); + var selection=''; + if(selectedFolders.length>0){ + if(selectedFolders.length==1){ + selection+=t('files','1 folder'); + }else{ + selection+=t('files','{count} folders',{count: selectedFolders.length}); + } + if(selectedFiles.length>0){ + selection+=' & '; + } + } + if(selectedFiles.length>0){ + if(selectedFiles.length==1){ + selection+=t('files','1 file'); + }else{ + selection+=t('files','{count} files',{count: selectedFiles.length}); + } + } + $('#headerName>span.name').text(selection); + $('#modified').text(''); + $('table').addClass('multiselect'); + } +} + +/** + * @brief get a list of selected files + * @param string property (option) the property of the file requested + * @return array + * + * possible values for property: name, mime, size and type + * if property is set, an array with that property for each file is returnd + * if it's ommited an array of objects with all properties is returned + */ +function getSelectedFiles(property){ + var elements=$('td.filename input:checkbox:checked').parent().parent(); + var files=[]; + elements.each(function(i,element){ + var file={ + name:$(element).attr('data-filename'), + file:$(element).attr('data-file'), + timestamp:$(element).attr('data-timestamp'), + type:$(element).attr('data-type') + }; + if(property){ + files.push(file[property]); + }else{ + files.push(file); + } + }); + return files; +} \ No newline at end of file diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 1b0b9cef4b..abfcf847ac 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -77,7 +77,7 @@ class Trashbin { * @param $timestamp time when the file was deleted */ public static function restore($filename, $timestamp) { - + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user); diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index a412379d53..a9cb216b1f 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -16,13 +16,12 @@ t( 'Name' ); ?> - - - Download + + t('Undelete')?> - From 3fd28632c470e89d7c9334379da3d8fa6faf0de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Mon, 21 Jan 2013 13:07:43 +0100 Subject: [PATCH 11/15] allow to look into deleted directories --- apps/files/js/fileactions.js | 1 + apps/files_trashbin/index.php | 48 +++++++++++++++++++-- apps/files_trashbin/templates/part.list.php | 13 +++++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 093b6204c3..d20f9e835b 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -189,6 +189,7 @@ FileActions.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { FileList.rename(filename); }); + FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { window.location = OC.linkTo('files', 'index.php') + '?dir=' + encodeURIComponent($('#dir').val()).replace(/%2F/g, '/') + '/' + encodeURIComponent(filename); }); diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php index 28414cc1ce..2925223197 100644 --- a/apps/files_trashbin/index.php +++ b/apps/files_trashbin/index.php @@ -14,8 +14,37 @@ $view = new OC_Filesystemview('/'.$user.'/files_trashbin'); OCP\Util::addStyle('files', 'files'); OCP\Util::addScript('files', 'filelist'); -$query = \OC_DB::prepare('SELECT id,location,timestamp,type,mime FROM *PREFIX*files_trash WHERE user=?'); -$result = $query->execute(array($user))->fetchAll(); +$dir = isset($_GET['dir']) ? stripslashes($_GET['dir']) : ''; + +if ($dir) { + $dirlisting = true; + $view = new \OC_FilesystemView('/'.\OCP\User::getUser().'/files_trashbin'); + $fullpath = \OCP\Config::getSystemValue('datadirectory').$view->getAbsolutePath($dir); + $dirContent = opendir($fullpath); + $i = 0; + while($entryName = readdir($dirContent)) { + if ( $entryName != '.' && $entryName != '..' ) { + $pos = strpos($dir.'/', '/', 1); + $tmp = substr($dir, 0, $pos); + $pos = strrpos($tmp, '.d'); + $timestamp = substr($tmp,$pos+2); + error_log("timestamp: $timestamp"); + $result[] = array( + 'id' => $entryName, + 'timestamp' => $timestamp, + 'mime' => $view->getMimeType($dir.'/'.$entryName), + 'type' => $view->is_dir($dir.'/'.$entryName) ? 'dir' : 'file', + 'location' => $dir, + ); + } + } + closedir($fullpath); + +} else { + $dirlisting = false; + $query = \OC_DB::prepare('SELECT id,location,timestamp,type,mime FROM *PREFIX*files_trash WHERE user=?'); + $result = $query->execute(array($user))->fetchAll(); +} $files = array(); foreach ($result as $r) { @@ -38,15 +67,26 @@ foreach ($result as $r) { $files[] = $i; } +// Make breadcrumb +$breadcrumb = array('dir' => '', 'name' => 'Trash'); +$pathtohere = ''; +foreach (explode('/', $dir) as $i) { + if ($i != '') { + $pathtohere .= '/' . $i; + $breadcrumb[] = array('dir' => $pathtohere, 'name' => $i); + } +} + $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); $breadcrumbNav->assign('breadcrumb', array(array('dir' => '', 'name' => 'Trash')), false); $breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir=', false); $list = new OCP\Template('files_trashbin', 'part.list', ''); $list->assign('files', $files, false); -$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir=', false); -$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file=', false); +$list->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php'). '?dir='.$dir, false); +$list->assign('downloadURL', OCP\Util::linkTo('files_trashbin', 'download.php') . '?file='.$dir, false); $list->assign('disableSharing', true); +$list->assign('dirlisting', $dirlisting); $list->assign('disableDownloadActions', true); $tmpl->assign('breadcrumb', $breadcrumbNav->fetchPage(), false); $tmpl->assign('fileList', $list->fetchPage(), false); diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php index c9a641a2e2..72359da299 100644 --- a/apps/files_trashbin/templates/part.list.php +++ b/apps/files_trashbin/templates/part.list.php @@ -40,9 +40,17 @@ > - + + + + + - + + + + + @@ -69,3 +77,4 @@ Date: Tue, 22 Jan 2013 12:00:04 +0100 Subject: [PATCH 12/15] allow to restore single files/folder from a deleted folder --- apps/files_trashbin/ajax/undelete.php | 15 +++-- apps/files_trashbin/index.php | 15 +++-- apps/files_trashbin/js/trash.js | 8 ++- apps/files_trashbin/lib/trash.php | 74 ++++++++++++++------- apps/files_trashbin/templates/part.list.php | 16 +++-- 5 files changed, 87 insertions(+), 41 deletions(-) diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index 05b5e7a5ee..5aa6436ae5 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -5,17 +5,24 @@ if(!OC_User::isLoggedIn()) { } $files = $_REQUEST['files']; +$dirlisting = $_REQUEST['dirlisting']; $list = explode(';', $files); $error = array(); $i = 0; foreach ($list as $file) { - $delimiter = strrpos($file, '.d'); - $filename = substr($file, 0, $delimiter); - $timestamp = substr($file, $delimiter+2); + if ( $dirlisting=='0') { + $delimiter = strrpos($file, '.d'); + $filename = substr($file, 0, $delimiter); + $timestamp = substr($file, $delimiter+2); + } else { + $path_parts = pathinfo($file); + $filename = $path_parts['basename']; + $timestamp = null; + } - if ( !OCA_Trash\Trashbin::restore($filename, $timestamp) ) { + if ( !OCA_Trash\Trashbin::restore($file, $filename, $timestamp) ) { $error[] = $filename; } else { $success[$i]['filename'] = $filename; diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php index 2925223197..97bb64c626 100644 --- a/apps/files_trashbin/index.php +++ b/apps/files_trashbin/index.php @@ -28,7 +28,6 @@ if ($dir) { $tmp = substr($dir, 0, $pos); $pos = strrpos($tmp, '.d'); $timestamp = substr($tmp,$pos+2); - error_log("timestamp: $timestamp"); $result[] = array( 'id' => $entryName, 'timestamp' => $timestamp, @@ -68,17 +67,23 @@ foreach ($result as $r) { } // Make breadcrumb -$breadcrumb = array('dir' => '', 'name' => 'Trash'); +$breadcrumb = array(array('dir' => '', 'name' => 'Trash')); $pathtohere = ''; foreach (explode('/', $dir) as $i) { - if ($i != '') { + if ($i != '') { + if ( preg_match('/^(.+)\.d[0-9]+$/', $i, $match) ) { + error_log("match!"); + $name = $match[1]; + } else { + $name = $i; + } $pathtohere .= '/' . $i; - $breadcrumb[] = array('dir' => $pathtohere, 'name' => $i); + $breadcrumb[] = array('dir' => $pathtohere, 'name' => $name); } } $breadcrumbNav = new OCP\Template('files', 'part.breadcrumb', ''); -$breadcrumbNav->assign('breadcrumb', array(array('dir' => '', 'name' => 'Trash')), false); +$breadcrumbNav->assign('breadcrumb', $breadcrumb, false); $breadcrumbNav->assign('baseURL', OCP\Util::linkTo('files_trashbin', 'index.php') . '?dir=', false); $list = new OCP\Template('files_trashbin', 'part.list', ''); diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 8f3786f15e..355e25d97d 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -5,7 +5,7 @@ $(document).ready(function() { FileActions.register('all', 'Undelete', OC.PERMISSION_READ, '', function(filename) { var tr=$('tr').filterAttr('data-file', filename); $.post(OC.filePath('files_trashbin','ajax','undelete.php'), - {files:tr.attr('data-filename')+'.d'+tr.attr('data-timestamp')}, + {files:tr.attr('data-file'), dirlisting:tr.attr('data-dirlisting') }, function(result){ for (var i = 0; i < result.data.success.length; i++) { var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); @@ -64,8 +64,9 @@ $(document).ready(function() { $('.undelete').click('click',function(event) { var fileslist=getSelectedFiles('file').join(';'); + var dirlisting=getSelectedFiles('dirlisting')[0]; $.post(OC.filePath('files_trashbin','ajax','undelete.php'), - {files:fileslist}, + {files:fileslist, dirlisting:dirlisting}, function(result){ for (var i = 0; i < result.data.success.length; i++) { var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); @@ -133,7 +134,8 @@ function getSelectedFiles(property){ name:$(element).attr('data-filename'), file:$(element).attr('data-file'), timestamp:$(element).attr('data-timestamp'), - type:$(element).attr('data-type') + type:$(element).attr('data-type'), + dirlisting:$(element).attr('data-dirlisting') }; if(property){ files.push(file[property]); diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index abfcf847ac..9b891e773b 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -73,49 +73,63 @@ class Trashbin { /** * restore files from trash bin + * @param $file path to the deleted file * @param $filename name of the file * @param $timestamp time when the file was deleted */ - public static function restore($filename, $timestamp) { + public static function restore($file, $filename, $timestamp) { $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user); - $query = \OC_DB::prepare('SELECT location,type FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); - $result = $query->execute(array($user,$filename,$timestamp))->fetchAll(); - - if ( count($result) != 1 ) { - \OC_Log::write('files_trashbin', 'trash bin database inconsistent!', OC_Log::ERROR); - return false; - } - - // if location no longer exists, restore file in the root directory - $location = $result[0]['location']; - if ( $result[0]['location'] != '/' && !$view->is_dir('files'.$result[0]['location']) ) { - $location = '/'; + if ( $timestamp ) { + $query = \OC_DB::prepare('SELECT location,type FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $result = $query->execute(array($user,$filename,$timestamp))->fetchAll(); + if ( count($result) != 1 ) { + \OC_Log::write('files_trashbin', 'trash bin database inconsistent!', OC_Log::ERROR); + return false; + } + + // if location no longer exists, restore file in the root directory + $location = $result[0]['location']; + if ( $result[0]['location'] != '/' && !$view->is_dir('files'.$result[0]['location']) ) { + $location = ''; + } + } else { + $path_parts = pathinfo($filename); + $result[] = array( + 'location' => $path_parts['dirname'], + 'type' => $view->is_dir('/files_trashbin/'.$file) ? 'dir' : 'files', + ); + $location = ''; } - $source = 'files_trashbin/'.$filename.'.d'.$timestamp; + $source = \OC_Filesystem::normalizePath('files_trashbin/'.$file); $target = \OC_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); if( $view->rename($source, $target.$ext) ) { - // if versioning app is enabled, copy versions from the trash bin back to the original location - if ( $return && \OCP\App::isEnabled('files_versions') ) { - if ( $result[0][type] == 'dir' ) { - $view->rename('versions_trashbin/'. $filename.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext); - } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + if ( \OCP\App::isEnabled('files_versions') ) { + if ( $result[0]['type'] == 'dir' ) { + $view->rename(\OC_Filesystem::normalizePath('versions_trashbin/'. $file), \OC_Filesystem::normalizePath('files_versions/'.$location.'/'.$filename.$ext)); + } else if ( $versions = self::getVersionsFromTrash($file, $timestamp) ) { foreach ($versions as $v) { - $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + if ($timestamp ) { + $view->rename('versions_trashbin/'.$filename.'.v'.$v.'.d'.$timestamp, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + } else { + $view->rename('versions_trashbin/'.$file.'.v'.$v, 'files_versions/'.$location.'/'.$filename.$ext.'.v'.$v); + } } } } - $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); - $query->execute(array($user,$filename,$timestamp)); + if ( $timestamp ) { + $query = \OC_DB::prepare('DELETE FROM *PREFIX*files_trash WHERE user=? AND id=? AND timestamp=?'); + $query->execute(array($user,$filename,$timestamp)); + } return true; } @@ -189,12 +203,22 @@ class Trashbin { $versionsName = \OCP\Config::getSystemValue('datadirectory').$view->getAbsolutePath($filename); $versions = array(); + if ($timestamp ) { // fetch for old versions - $matches = glob( $versionsName.'.v*.d'.$timestamp ); + $matches = glob( $versionsName.'.v*.d'.$timestamp ); + $offset = -strlen($timestamp)-2; + } else { + $matches = glob( $versionsName.'.v*' ); + } foreach( $matches as $ma ) { - $parts = explode( '.v', substr($ma, 0, -strlen($timestamp)-2) ); - $versions[] = ( end( $parts ) ); + if ( $timestamp ) { + $parts = explode( '.v', substr($ma, 0, $offset) ); + $versions[] = ( end( $parts ) ); + } else { + $parts = explode( '.v', $ma ); + $versions[] = ( end( $parts ) ); + } } return $versions; } diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php index 72359da299..6faddcf485 100644 --- a/apps/files_trashbin/templates/part.list.php +++ b/apps/files_trashbin/templates/part.list.php @@ -24,13 +24,21 @@ $name = str_replace('%2F', '/', $name); $directory = str_replace('+', '%20', urlencode($file['directory'])); $directory = str_replace('%2F', '/', $directory); ?> - ' + + id="" + data-file="" + data-timestamp='' + data-dirlisting=1 + + id="" + data-file="" data-timestamp='' - data-permissions=''> + data-dirlisting=0 + > style="background-image:url()" From 76bd0cffcecfdcee5b5101e50db75b6b92314423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Tue, 22 Jan 2013 12:13:50 +0100 Subject: [PATCH 13/15] remove table row in trash bin view after undelete --- apps/files_trashbin/ajax/undelete.php | 2 +- apps/files_trashbin/js/trash.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/files_trashbin/ajax/undelete.php b/apps/files_trashbin/ajax/undelete.php index 5aa6436ae5..178d85ccf3 100644 --- a/apps/files_trashbin/ajax/undelete.php +++ b/apps/files_trashbin/ajax/undelete.php @@ -25,7 +25,7 @@ foreach ($list as $file) { if ( !OCA_Trash\Trashbin::restore($file, $filename, $timestamp) ) { $error[] = $filename; } else { - $success[$i]['filename'] = $filename; + $success[$i]['filename'] = $file; $success[$i]['timestamp'] = $timestamp; $i++; } diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 355e25d97d..92a431d495 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -8,7 +8,7 @@ $(document).ready(function() { {files:tr.attr('data-file'), dirlisting:tr.attr('data-dirlisting') }, function(result){ for (var i = 0; i < result.data.success.length; i++) { - var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); + var row = document.getElementById(result.data.success[i].filename); row.parentNode.removeChild(row); } if (result.status != 'success') { @@ -69,7 +69,7 @@ $(document).ready(function() { {files:fileslist, dirlisting:dirlisting}, function(result){ for (var i = 0; i < result.data.success.length; i++) { - var row = document.getElementById(result.data.success[i].filename+'.d'+result.data.success[i].timestamp); + var row = document.getElementById(result.data.success[i].filename); row.parentNode.removeChild(row); } if (result.status != 'success') { From bdee0e204eed057270f787afc9aa1702147b37d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Tue, 22 Jan 2013 12:30:12 +0100 Subject: [PATCH 14/15] calculate correct relative date for trashbin file list --- apps/files_trashbin/index.php | 1 - apps/files_trashbin/templates/part.list.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/files_trashbin/index.php b/apps/files_trashbin/index.php index 97bb64c626..d415ef263e 100644 --- a/apps/files_trashbin/index.php +++ b/apps/files_trashbin/index.php @@ -72,7 +72,6 @@ $pathtohere = ''; foreach (explode('/', $dir) as $i) { if ($i != '') { if ( preg_match('/^(.+)\.d[0-9]+$/', $i, $match) ) { - error_log("match!"); $name = $match[1]; } else { $name = $i; diff --git a/apps/files_trashbin/templates/part.list.php b/apps/files_trashbin/templates/part.list.php index 6faddcf485..205e11c286 100644 --- a/apps/files_trashbin/templates/part.list.php +++ b/apps/files_trashbin/templates/part.list.php @@ -16,7 +16,7 @@ // the bigger the file, the darker the shade of grey; megabytes*2 $simple_size_color = intval(200-$file['size']/(1024*1024)*2); if($simple_size_color<0) $simple_size_color = 0; - $relative_deleted_date = OCP\relative_modified_date($file['mtime']); + $relative_deleted_date = OCP\relative_modified_date($file['timestamp']); // the older the file, the brighter the shade of grey; days*14 $relative_date_color = round((time()-$file['mtime'])/60/60/24*14); if($relative_date_color>200) $relative_date_color = 200; From 5c4e2ad490014113ad32c33e00e2aab01d7a3205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Tue, 22 Jan 2013 13:19:41 +0100 Subject: [PATCH 15/15] allow admin to change retention obligation for the trash bin, default value is 180 days --- apps/files_trashbin/lib/trash.php | 7 ++++--- config/config.sample.php | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 9b891e773b..0dad059022 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -24,8 +24,7 @@ namespace OCA_Trash; class Trashbin { - const DELETEAFTER=30; // how long do we keep files in the trash bin (number of days) - + const DEFAULT_RETENTION_OBLIGATION=180; // how long do we keep files in the trash bin if no other value is defined in the config file (unit: days) /** * move file to the trash bin * @@ -148,7 +147,9 @@ class Trashbin { $query = \OC_DB::prepare('SELECT location,type,id,timestamp FROM *PREFIX*files_trash WHERE user=?'); $result = $query->execute(array($user))->fetchAll(); - $limit = time() - (self::DELETEAFTER * 86400); + $retention_obligation = \OC_Config::getValue('trashbin_retention_obligation', self::DEFAULT_RETENTION_OBLIGATION); + + $limit = time() - ($retention_obligation * 86400); foreach ( $result as $r ) { $timestamp = $r['timestamp']; diff --git a/config/config.sample.php b/config/config.sample.php index b1655d0283..373466c08d 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -92,6 +92,9 @@ $CONFIG = array( /* Password to use for sendmail mail, depends on mail_smtpauth if this is used */ "mail_smtppassword" => "", +/* how long should ownCloud keep delteted files in the trash bin, default value: 180 days */ +'trashbin_retention_obligation' => 180, + /* Check 3rdparty apps for malicious code fragments */ "appcodechecker" => "",