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()" + + > + + + + + + + + + + + + + + + + + + + + + + + + + +