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