summaryrefslogtreecommitdiffstats
path: root/apps/files/lib
diff options
context:
space:
mode:
authorJoas Schilling <nickvergessen@gmx.de>2016-05-12 12:07:06 +0200
committerThomas Müller <DeepDiver1975@users.noreply.github.com>2016-05-12 12:07:06 +0200
commitb34bacd0718fa24c67a8ef0aa6f3b824a9b525bb (patch)
tree904bda1263850905c2c8164f4f1367d8c7bc9d46 /apps/files/lib
parenteea98f1d74daf2a20c6b08b9df743f0478c48103 (diff)
downloadnextcloud-server-b34bacd0718fa24c67a8ef0aa6f3b824a9b525bb.tar.gz
nextcloud-server-b34bacd0718fa24c67a8ef0aa6f3b824a9b525bb.zip
Move Files app to PSR-4 (#24569)
* Move lib/ of Files app to PSR-4 * Move tests to PSR-4
Diffstat (limited to 'apps/files/lib')
-rw-r--r--apps/files/lib/Activity.php (renamed from apps/files/lib/activity.php)0
-rw-r--r--apps/files/lib/ActivityHelper.php (renamed from apps/files/lib/activityhelper.php)0
-rw-r--r--apps/files/lib/App.php (renamed from apps/files/lib/app.php)0
-rw-r--r--apps/files/lib/AppInfo/Application.php94
-rw-r--r--apps/files/lib/BackgroundJob/CleanupFileLocks.php (renamed from apps/files/lib/backgroundjob/cleanupfilelocks.php)0
-rw-r--r--apps/files/lib/BackgroundJob/DeleteOrphanedItems.php (renamed from apps/files/lib/backgroundjob/deleteorphaneditems.php)0
-rw-r--r--apps/files/lib/BackgroundJob/ScanFiles.php (renamed from apps/files/lib/backgroundjob/scanfiles.php)0
-rw-r--r--apps/files/lib/Capabilities.php (renamed from apps/files/lib/capabilities.php)0
-rw-r--r--apps/files/lib/Command/DeleteOrphanedFiles.php84
-rw-r--r--apps/files/lib/Command/Scan.php310
-rw-r--r--apps/files/lib/Command/TransferOwnership.php240
-rw-r--r--apps/files/lib/Controller/ApiController.php239
-rw-r--r--apps/files/lib/Controller/ViewController.php304
-rw-r--r--apps/files/lib/Helper.php (renamed from apps/files/lib/helper.php)0
-rw-r--r--apps/files/lib/Service/TagService.php114
15 files changed, 1385 insertions, 0 deletions
diff --git a/apps/files/lib/activity.php b/apps/files/lib/Activity.php
index 1cbd6c3b973..1cbd6c3b973 100644
--- a/apps/files/lib/activity.php
+++ b/apps/files/lib/Activity.php
diff --git a/apps/files/lib/activityhelper.php b/apps/files/lib/ActivityHelper.php
index 046dd59bc76..046dd59bc76 100644
--- a/apps/files/lib/activityhelper.php
+++ b/apps/files/lib/ActivityHelper.php
diff --git a/apps/files/lib/app.php b/apps/files/lib/App.php
index 981c41ff413..981c41ff413 100644
--- a/apps/files/lib/app.php
+++ b/apps/files/lib/App.php
diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php
new file mode 100644
index 00000000000..b2faa43cc3c
--- /dev/null
+++ b/apps/files/lib/AppInfo/Application.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * @author Roeland Jago Douma <rullzer@owncloud.com>
+ * @author Tobias Kaminsky <tobias@kaminsky.me>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\Files\AppInfo;
+
+use OCA\Files\Controller\ApiController;
+use OCP\AppFramework\App;
+use \OCA\Files\Service\TagService;
+use \OCP\IContainer;
+use OCA\Files\Controller\ViewController;
+
+class Application extends App {
+ public function __construct(array $urlParams=array()) {
+ parent::__construct('files', $urlParams);
+ $container = $this->getContainer();
+ $server = $container->getServer();
+
+ /**
+ * Controllers
+ */
+ $container->registerService('APIController', function (IContainer $c) use ($server) {
+ return new ApiController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $server->getUserSession(),
+ $c->query('TagService'),
+ $server->getPreviewManager(),
+ $server->getShareManager(),
+ $server->getConfig()
+ );
+ });
+
+ $container->registerService('ViewController', function (IContainer $c) use ($server) {
+ return new ViewController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $server->getURLGenerator(),
+ $server->getNavigationManager(),
+ $c->query('L10N'),
+ $server->getConfig(),
+ $server->getEventDispatcher(),
+ $server->getUserSession(),
+ $server->getAppManager(),
+ $server->getRootFolder()
+ );
+ });
+
+ /**
+ * Core
+ */
+ $container->registerService('L10N', function(IContainer $c) {
+ return $c->query('ServerContainer')->getL10N($c->query('AppName'));
+ });
+
+ /**
+ * Services
+ */
+ $container->registerService('Tagger', function(IContainer $c) {
+ return $c->query('ServerContainer')->getTagManager()->load('files');
+ });
+ $container->registerService('TagService', function(IContainer $c) {
+ $homeFolder = $c->query('ServerContainer')->getUserFolder();
+ return new TagService(
+ $c->query('ServerContainer')->getUserSession(),
+ $c->query('Tagger'),
+ $homeFolder
+ );
+ });
+
+ /*
+ * Register capabilities
+ */
+ $container->registerCapability('OCA\Files\Capabilities');
+ }
+}
diff --git a/apps/files/lib/backgroundjob/cleanupfilelocks.php b/apps/files/lib/BackgroundJob/CleanupFileLocks.php
index b5cf8e94551..b5cf8e94551 100644
--- a/apps/files/lib/backgroundjob/cleanupfilelocks.php
+++ b/apps/files/lib/BackgroundJob/CleanupFileLocks.php
diff --git a/apps/files/lib/backgroundjob/deleteorphaneditems.php b/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php
index 1eef9c24e0c..1eef9c24e0c 100644
--- a/apps/files/lib/backgroundjob/deleteorphaneditems.php
+++ b/apps/files/lib/BackgroundJob/DeleteOrphanedItems.php
diff --git a/apps/files/lib/backgroundjob/scanfiles.php b/apps/files/lib/BackgroundJob/ScanFiles.php
index dcc180bcfbe..dcc180bcfbe 100644
--- a/apps/files/lib/backgroundjob/scanfiles.php
+++ b/apps/files/lib/BackgroundJob/ScanFiles.php
diff --git a/apps/files/lib/capabilities.php b/apps/files/lib/Capabilities.php
index dc49ca174b3..dc49ca174b3 100644
--- a/apps/files/lib/capabilities.php
+++ b/apps/files/lib/Capabilities.php
diff --git a/apps/files/lib/Command/DeleteOrphanedFiles.php b/apps/files/lib/Command/DeleteOrphanedFiles.php
new file mode 100644
index 00000000000..91043471ce5
--- /dev/null
+++ b/apps/files/lib/Command/DeleteOrphanedFiles.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Command;
+
+use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
+use Doctrine\DBAL\Platforms\SqlitePlatform;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Delete all file entries that have no matching entries in the storage table.
+ */
+class DeleteOrphanedFiles extends Command {
+
+ const CHUNK_SIZE = 200;
+
+ /**
+ * @var IDBConnection
+ */
+ protected $connection;
+
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('files:cleanup')
+ ->setDescription('cleanup filecache');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output) {
+ $deletedEntries = 0;
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select('fc.fileid')
+ ->from('filecache', 'fc')
+ ->where($query->expr()->isNull('s.numeric_id'))
+ ->leftJoin('fc', 'storages', 's', $query->expr()->eq('fc.storage', 's.numeric_id'))
+ ->setMaxResults(self::CHUNK_SIZE);
+
+ $deleteQuery = $this->connection->getQueryBuilder();
+ $deleteQuery->delete('filecache')
+ ->where($deleteQuery->expr()->eq('fileid', $deleteQuery->createParameter('objectid')));
+
+ $deletedInLastChunk = self::CHUNK_SIZE;
+ while ($deletedInLastChunk === self::CHUNK_SIZE) {
+ $deletedInLastChunk = 0;
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ $deletedInLastChunk++;
+ $deletedEntries += $deleteQuery->setParameter('objectid', (int) $row['fileid'])
+ ->execute();
+ }
+ $result->closeCursor();
+ }
+
+ $output->writeln("$deletedEntries orphaned file cache entries deleted");
+ }
+
+}
diff --git a/apps/files/lib/Command/Scan.php b/apps/files/lib/Command/Scan.php
new file mode 100644
index 00000000000..1ae04c585bb
--- /dev/null
+++ b/apps/files/lib/Command/Scan.php
@@ -0,0 +1,310 @@
+<?php
+/**
+ * @author Bart Visscher <bartv@thisnet.nl>
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author martin.mattel@diemattels.at <martin.mattel@diemattels.at>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Command;
+
+use OC\Core\Command\Base;
+use OC\ForbiddenException;
+use OCP\Files\StorageNotAvailableException;
+use OCP\IUserManager;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Helper\Table;
+
+class Scan extends Base {
+
+ /** @var IUserManager $userManager */
+ private $userManager;
+ /** @var float */
+ protected $execTime = 0;
+ /** @var int */
+ protected $foldersCounter = 0;
+ /** @var int */
+ protected $filesCounter = 0;
+
+ public function __construct(IUserManager $userManager) {
+ $this->userManager = $userManager;
+ parent::__construct();
+ }
+
+ protected function configure() {
+ parent::configure();
+
+ $this
+ ->setName('files:scan')
+ ->setDescription('rescan filesystem')
+ ->addArgument(
+ 'user_id',
+ InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
+ 'will rescan all files of the given user(s)'
+ )
+ ->addOption(
+ 'path',
+ 'p',
+ InputArgument::OPTIONAL,
+ 'limit rescan to this path, eg. --path="/alice/files/Music", the user_id is determined by the path and the user_id parameter and --all are ignored'
+ )
+ ->addOption(
+ 'quiet',
+ 'q',
+ InputOption::VALUE_NONE,
+ 'suppress any output'
+ )
+ ->addOption(
+ 'verbose',
+ '-v|vv|vvv',
+ InputOption::VALUE_NONE,
+ 'verbose the output'
+ )
+ ->addOption(
+ 'all',
+ null,
+ InputOption::VALUE_NONE,
+ 'will rescan all files of all known users'
+ );
+ }
+
+ public function checkScanWarning($fullPath, OutputInterface $output) {
+ $normalizedPath = basename(\OC\Files\Filesystem::normalizePath($fullPath));
+ $path = basename($fullPath);
+
+ if ($normalizedPath !== $path) {
+ $output->writeln("\t<error>Entry \"" . $fullPath . '" will not be accessible due to incompatible encoding</error>');
+ }
+ }
+
+ protected function scanFiles($user, $path, $verbose, OutputInterface $output) {
+ $scanner = new \OC\Files\Utils\Scanner($user, \OC::$server->getDatabaseConnection(), \OC::$server->getLogger());
+ # check on each file/folder if there was a user interrupt (ctrl-c) and throw an exception
+ # printout and count
+ if ($verbose) {
+ $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function ($path) use ($output) {
+ $output->writeln("\tFile <info>$path</info>");
+ $this->filesCounter += 1;
+ if ($this->hasBeenInterrupted()) {
+ throw new \Exception('ctrl-c');
+ }
+ });
+ $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function ($path) use ($output) {
+ $output->writeln("\tFolder <info>$path</info>");
+ $this->foldersCounter += 1;
+ if ($this->hasBeenInterrupted()) {
+ throw new \Exception('ctrl-c');
+ }
+ });
+ $scanner->listen('\OC\Files\Utils\Scanner', 'StorageNotAvailable', function (StorageNotAvailableException $e) use ($output) {
+ $output->writeln("Error while scanning, storage not available (" . $e->getMessage() . ")");
+ });
+ # count only
+ } else {
+ $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function () use ($output) {
+ $this->filesCounter += 1;
+ if ($this->hasBeenInterrupted()) {
+ throw new \Exception('ctrl-c');
+ }
+ });
+ $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function () use ($output) {
+ $this->foldersCounter += 1;
+ if ($this->hasBeenInterrupted()) {
+ throw new \Exception('ctrl-c');
+ }
+ });
+ }
+ $scanner->listen('\OC\Files\Utils\Scanner', 'scanFile', function($path) use ($output) {
+ $this->checkScanWarning($path, $output);
+ });
+ $scanner->listen('\OC\Files\Utils\Scanner', 'scanFolder', function($path) use ($output) {
+ $this->checkScanWarning($path, $output);
+ });
+
+ try {
+ $scanner->scan($path);
+ } catch (ForbiddenException $e) {
+ $output->writeln("<error>Home storage for user $user not writable</error>");
+ $output->writeln("Make sure you're running the scan command only as the user the web server runs as");
+ } catch (\Exception $e) {
+ if ($e->getMessage() !== 'ctrl-c') {
+ $output->writeln('<error>Exception while scanning: ' . $e->getMessage() . "\n" . $e->getTraceAsString() . '</error>');
+ }
+ return;
+ }
+ }
+
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $inputPath = $input->getOption('path');
+ if ($inputPath) {
+ $inputPath = '/' . trim($inputPath, '/');
+ list (, $user,) = explode('/', $inputPath, 3);
+ $users = array($user);
+ } else if ($input->getOption('all')) {
+ $users = $this->userManager->search('');
+ } else {
+ $users = $input->getArgument('user_id');
+ }
+
+ # no messaging level option means: no full printout but statistics
+ # $quiet means no print at all
+ # $verbose means full printout including statistics
+ # -q -v full stat
+ # 0 0 no yes
+ # 0 1 yes yes
+ # 1 -- no no (quiet overrules verbose)
+ $verbose = $input->getOption('verbose');
+ $quiet = $input->getOption('quiet');
+ # restrict the verbosity level to VERBOSITY_VERBOSE
+ if ($output->getVerbosity()>OutputInterface::VERBOSITY_VERBOSE) {
+ $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
+ }
+ if ($quiet) {
+ $verbose = false;
+ }
+
+ # check quantity of users to be process and show it on the command line
+ $users_total = count($users);
+ if ($users_total === 0) {
+ $output->writeln("<error>Please specify the user id to scan, \"--all\" to scan for all users or \"--path=...\"</error>");
+ return;
+ } else {
+ if ($users_total > 1) {
+ $output->writeln("\nScanning files for $users_total users");
+ }
+ }
+
+ $this->initTools();
+
+ $user_count = 0;
+ foreach ($users as $user) {
+ if (is_object($user)) {
+ $user = $user->getUID();
+ }
+ $path = $inputPath ? $inputPath : '/' . $user;
+ $user_count += 1;
+ if ($this->userManager->userExists($user)) {
+ # add an extra line when verbose is set to optical separate users
+ if ($verbose) {$output->writeln(""); }
+ $output->writeln("Starting scan for user $user_count out of $users_total ($user)");
+ # full: printout data if $verbose was set
+ $this->scanFiles($user, $path, $verbose, $output);
+ } else {
+ $output->writeln("<error>Unknown user $user_count $user</error>");
+ }
+ # check on each user if there was a user interrupt (ctrl-c) and exit foreach
+ if ($this->hasBeenInterrupted()) {
+ break;
+ }
+ }
+
+ # stat: printout statistics if $quiet was not set
+ if (!$quiet) {
+ $this->presentStats($output);
+ }
+ }
+
+ /**
+ * Initialises some useful tools for the Command
+ */
+ protected function initTools() {
+ // Start the timer
+ $this->execTime = -microtime(true);
+ // Convert PHP errors to exceptions
+ set_error_handler([$this, 'exceptionErrorHandler'], E_ALL);
+ }
+
+ /**
+ * Processes PHP errors as exceptions in order to be able to keep track of problems
+ *
+ * @see https://secure.php.net/manual/en/function.set-error-handler.php
+ *
+ * @param int $severity the level of the error raised
+ * @param string $message
+ * @param string $file the filename that the error was raised in
+ * @param int $line the line number the error was raised
+ *
+ * @throws \ErrorException
+ */
+ public function exceptionErrorHandler($severity, $message, $file, $line) {
+ if (!(error_reporting() & $severity)) {
+ // This error code is not included in error_reporting
+ return;
+ }
+ throw new \ErrorException($message, 0, $severity, $file, $line);
+ }
+
+ /**
+ * @param OutputInterface $output
+ */
+ protected function presentStats(OutputInterface $output) {
+ // Stop the timer
+ $this->execTime += microtime(true);
+ $output->writeln("");
+
+ $headers = [
+ 'Folders', 'Files', 'Elapsed time'
+ ];
+
+ $this->showSummary($headers, null, $output);
+ }
+
+ /**
+ * Shows a summary of operations
+ *
+ * @param string[] $headers
+ * @param string[] $rows
+ * @param OutputInterface $output
+ */
+ protected function showSummary($headers, $rows, OutputInterface $output) {
+ $niceDate = $this->formatExecTime();
+ if (!$rows) {
+ $rows = [
+ $this->foldersCounter,
+ $this->filesCounter,
+ $niceDate,
+ ];
+ }
+ $table = new Table($output);
+ $table
+ ->setHeaders($headers)
+ ->setRows([$rows]);
+ $table->render();
+ }
+
+
+ /**
+ * Formats microtime into a human readable format
+ *
+ * @return string
+ */
+ protected function formatExecTime() {
+ list($secs, $tens) = explode('.', sprintf("%.1f", ($this->execTime)));
+
+ # if you want to have microseconds add this: . '.' . $tens;
+ return date('H:i:s', $secs);
+ }
+
+}
diff --git a/apps/files/lib/Command/TransferOwnership.php b/apps/files/lib/Command/TransferOwnership.php
new file mode 100644
index 00000000000..1f46efdde0d
--- /dev/null
+++ b/apps/files/lib/Command/TransferOwnership.php
@@ -0,0 +1,240 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Command;
+
+use OC\Files\Filesystem;
+use OC\Files\View;
+use OCP\Files\FileInfo;
+use OCP\Files\Mount\IMountManager;
+use OCP\IUserManager;
+use OCP\Share\IManager;
+use OCP\Share\IShare;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class TransferOwnership extends Command {
+
+ /** @var IUserManager $userManager */
+ private $userManager;
+
+ /** @var IManager */
+ private $shareManager;
+
+ /** @var IMountManager */
+ private $mountManager;
+
+ /** @var FileInfo[] */
+ private $allFiles = [];
+
+ /** @var FileInfo[] */
+ private $encryptedFiles = [];
+
+ /** @var IShare[] */
+ private $shares = [];
+
+ /** @var string */
+ private $sourceUser;
+
+ /** @var string */
+ private $destinationUser;
+
+ /** @var string */
+ private $finalTarget;
+
+ public function __construct(IUserManager $userManager, IManager $shareManager, IMountManager $mountManager) {
+ $this->userManager = $userManager;
+ $this->shareManager = $shareManager;
+ $this->mountManager = $mountManager;
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('files:transfer-ownership')
+ ->setDescription('All files and folders are moved to another user - shares are moved as well.')
+ ->addArgument(
+ 'source-user',
+ InputArgument::REQUIRED,
+ 'owner of files which shall be moved'
+ )
+ ->addArgument(
+ 'destination-user',
+ InputArgument::REQUIRED,
+ 'user who will be the new owner of the files'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $this->sourceUser = $input->getArgument('source-user');
+ $this->destinationUser = $input->getArgument('destination-user');
+ if (!$this->userManager->userExists($this->sourceUser)) {
+ $output->writeln("<error>Unknown source user $this->sourceUser</error>");
+ return;
+ }
+ if (!$this->userManager->userExists($this->destinationUser)) {
+ $output->writeln("<error>Unknown destination user $this->destinationUser</error>");
+ return;
+ }
+
+ // target user has to be ready
+ if (!\OC::$server->getEncryptionManager()->isReadyForUser($this->destinationUser)) {
+ $output->writeln("<error>The target user is not ready to accept files. The user has at least to be logged in once.</error>");
+ return;
+ }
+
+ $date = date('c');
+ $this->finalTarget = "$this->destinationUser/files/transferred from $this->sourceUser on $date";
+
+ // setup filesystem
+ Filesystem::initMountPoints($this->sourceUser);
+ Filesystem::initMountPoints($this->destinationUser);
+
+ // analyse source folder
+ $this->analyse($output);
+
+ // collect all the shares
+ $this->collectUsersShares($output);
+
+ // transfer the files
+ $this->transfer($output);
+
+ // restore the shares
+ $this->restoreShares($output);
+ }
+
+ private function walkFiles(View $view, $path, \Closure $callBack) {
+ foreach ($view->getDirectoryContent($path) as $fileInfo) {
+ if (!$callBack($fileInfo)) {
+ return;
+ }
+ if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
+ $this->walkFiles($view, $fileInfo->getPath(), $callBack);
+ }
+ }
+ }
+
+ /**
+ * @param OutputInterface $output
+ * @throws \Exception
+ */
+ protected function analyse(OutputInterface $output) {
+ $view = new View();
+ $output->writeln("Analysing files of $this->sourceUser ...");
+ $progress = new ProgressBar($output);
+ $progress->start();
+ $self = $this;
+ $this->walkFiles($view, "$this->sourceUser/files",
+ function (FileInfo $fileInfo) use ($progress, $self) {
+ if ($fileInfo->getType() === FileInfo::TYPE_FOLDER) {
+ return true;
+ }
+ $progress->advance();
+ $this->allFiles[] = $fileInfo;
+ if ($fileInfo->isEncrypted()) {
+ $this->encryptedFiles[] = $fileInfo;
+ }
+ return true;
+ });
+ $progress->finish();
+ $output->writeln('');
+
+ // no file is allowed to be encrypted
+ if (!empty($this->encryptedFiles)) {
+ $output->writeln("<error>Some files are encrypted - please decrypt them first</error>");
+ foreach($this->encryptedFiles as $encryptedFile) {
+ /** @var FileInfo $encryptedFile */
+ $output->writeln(" " . $encryptedFile->getPath());
+ }
+ throw new \Exception('Execution terminated.');
+ }
+
+ }
+
+ /**
+ * @param OutputInterface $output
+ */
+ private function collectUsersShares(OutputInterface $output) {
+ $output->writeln("Collecting all share information for files and folder of $this->sourceUser ...");
+
+ $progress = new ProgressBar($output, count($this->shares));
+ foreach([\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE] as $shareType) {
+ $offset = 0;
+ while (true) {
+ $sharePage = $this->shareManager->getSharesBy($this->sourceUser, $shareType, null, true, 50, $offset);
+ $progress->advance(count($sharePage));
+ if (empty($sharePage)) {
+ break;
+ }
+ $this->shares = array_merge($this->shares, $sharePage);
+ $offset += 50;
+ }
+ }
+
+ $progress->finish();
+ $output->writeln('');
+ }
+
+ /**
+ * @param OutputInterface $output
+ */
+ protected function transfer(OutputInterface $output) {
+ $view = new View();
+ $output->writeln("Transferring files to $this->finalTarget ...");
+ $view->rename("$this->sourceUser/files", $this->finalTarget);
+ // because the files folder is moved away we need to recreate it
+ $view->mkdir("$this->sourceUser/files");
+ }
+
+ /**
+ * @param OutputInterface $output
+ */
+ private function restoreShares(OutputInterface $output) {
+ $output->writeln("Restoring shares ...");
+ $progress = new ProgressBar($output, count($this->shares));
+
+ foreach($this->shares as $share) {
+ if ($share->getSharedWith() === $this->destinationUser) {
+ // Unmount the shares before deleting, so we don't try to get the storage later on.
+ $shareMountPoint = $this->mountManager->find('/' . $this->destinationUser . '/files' . $share->getTarget());
+ if ($shareMountPoint) {
+ $this->mountManager->removeMount($shareMountPoint->getMountPoint());
+ }
+ $this->shareManager->deleteShare($share);
+ } else {
+ if ($share->getShareOwner() === $this->sourceUser) {
+ $share->setShareOwner($this->destinationUser);
+ }
+ if ($share->getSharedBy() === $this->sourceUser) {
+ $share->setSharedBy($this->destinationUser);
+ }
+
+ $this->shareManager->updateShare($share);
+ }
+ $progress->advance();
+ }
+ $progress->finish();
+ $output->writeln('');
+ }
+}
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php
new file mode 100644
index 00000000000..072498c7b5f
--- /dev/null
+++ b/apps/files/lib/Controller/ApiController.php
@@ -0,0 +1,239 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Roeland Jago Douma <rullzer@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Tobias Kaminsky <tobias@kaminsky.me>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Controller;
+
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Controller;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\Http\DataDisplayResponse;
+use OCP\AppFramework\Http\Response;
+use OCA\Files\Service\TagService;
+use OCP\IPreview;
+use OCP\Share\IManager;
+use OCP\Files\Node;
+use OCP\IUserSession;
+
+/**
+ * Class ApiController
+ *
+ * @package OCA\Files\Controller
+ */
+class ApiController extends Controller {
+ /** @var TagService */
+ private $tagService;
+ /** @var IManager **/
+ private $shareManager;
+ /** @var IPreview */
+ private $previewManager;
+ /** IUserSession */
+ private $userSession;
+ /** IConfig */
+ private $config;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param TagService $tagService
+ * @param IPreview $previewManager
+ */
+ public function __construct($appName,
+ IRequest $request,
+ IUserSession $userSession,
+ TagService $tagService,
+ IPreview $previewManager,
+ IManager $shareManager,
+ IConfig $config) {
+ parent::__construct($appName, $request);
+ $this->userSession = $userSession;
+ $this->tagService = $tagService;
+ $this->previewManager = $previewManager;
+ $this->shareManager = $shareManager;
+ $this->config = $config;
+ }
+
+ /**
+ * Gets a thumbnail of the specified file
+ *
+ * @since API version 1.0
+ *
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @param int $x
+ * @param int $y
+ * @param string $file URL-encoded filename
+ * @return DataResponse|DataDisplayResponse
+ */
+ public function getThumbnail($x, $y, $file) {
+ if($x < 1 || $y < 1) {
+ return new DataResponse(['message' => 'Requested size must be numeric and a positive value.'], Http::STATUS_BAD_REQUEST);
+ }
+
+ $preview = $this->previewManager->createPreview('files/'.$file, $x, $y, true);
+ if ($preview->valid()) {
+ return new DataDisplayResponse($preview->data(), Http::STATUS_OK, ['Content-Type' => 'image/png']);
+ } else {
+ return new DataResponse(['message' => 'File not found.'], Http::STATUS_NOT_FOUND);
+ }
+ }
+
+ /**
+ * Updates the info of the specified file path
+ * The passed tags are absolute, which means they will
+ * replace the actual tag selection.
+ *
+ * @NoAdminRequired
+ *
+ * @param string $path path
+ * @param array|string $tags array of tags
+ * @return DataResponse
+ */
+ public function updateFileTags($path, $tags = null) {
+ $result = [];
+ // if tags specified or empty array, update tags
+ if (!is_null($tags)) {
+ try {
+ $this->tagService->updateFileTags($path, $tags);
+ } catch (\OCP\Files\NotFoundException $e) {
+ return new DataResponse([
+ 'message' => $e->getMessage()
+ ], Http::STATUS_NOT_FOUND);
+ } catch (\OCP\Files\StorageNotAvailableException $e) {
+ return new DataResponse([
+ 'message' => $e->getMessage()
+ ], Http::STATUS_SERVICE_UNAVAILABLE);
+ } catch (\Exception $e) {
+ return new DataResponse([
+ 'message' => $e->getMessage()
+ ], Http::STATUS_NOT_FOUND);
+ }
+ $result['tags'] = $tags;
+ }
+ return new DataResponse($result);
+ }
+
+ /**
+ * Returns a list of all files tagged with the given tag.
+ *
+ * @NoAdminRequired
+ *
+ * @param string $tagName tag name to filter by
+ * @return DataResponse
+ */
+ public function getFilesByTag($tagName) {
+ $files = array();
+ $nodes = $this->tagService->getFilesByTag($tagName);
+ foreach ($nodes as &$node) {
+ $shareTypes = $this->getShareTypes($node);
+ $fileInfo = $node->getFileInfo();
+ $file = \OCA\Files\Helper::formatFileInfo($fileInfo);
+ $parts = explode('/', dirname($fileInfo->getPath()), 4);
+ if(isset($parts[3])) {
+ $file['path'] = '/' . $parts[3];
+ } else {
+ $file['path'] = '/';
+ }
+ $file['tags'] = [$tagName];
+ if (!empty($shareTypes)) {
+ $file['shareTypes'] = $shareTypes;
+ }
+ $files[] = $file;
+ }
+ return new DataResponse(['files' => $files]);
+ }
+
+ /**
+ * Return a list of share types for outgoing shares
+ *
+ * @param Node $node file node
+ *
+ * @return int[] array of share types
+ */
+ private function getShareTypes(Node $node) {
+ $userId = $this->userSession->getUser()->getUID();
+ $shareTypes = [];
+ $requestedShareTypes = [
+ \OCP\Share::SHARE_TYPE_USER,
+ \OCP\Share::SHARE_TYPE_GROUP,
+ \OCP\Share::SHARE_TYPE_LINK,
+ \OCP\Share::SHARE_TYPE_REMOTE
+ ];
+ foreach ($requestedShareTypes as $requestedShareType) {
+ // one of each type is enough to find out about the types
+ $shares = $this->shareManager->getSharesBy(
+ $userId,
+ $requestedShareType,
+ $node,
+ false,
+ 1
+ );
+ if (!empty($shares)) {
+ $shareTypes[] = $requestedShareType;
+ }
+ }
+ return $shareTypes;
+ }
+
+ /**
+ * Change the default sort mode
+ *
+ * @NoAdminRequired
+ *
+ * @param string $mode
+ * @param string $direction
+ * @return Response
+ */
+ public function updateFileSorting($mode, $direction) {
+ $allowedMode = ['name', 'size', 'mtime'];
+ $allowedDirection = ['asc', 'desc'];
+ if (!in_array($mode, $allowedMode) || !in_array($direction, $allowedDirection)) {
+ $response = new Response();
+ $response->setStatus(Http::STATUS_UNPROCESSABLE_ENTITY);
+ return $response;
+ }
+ $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting', $mode);
+ $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting_direction', $direction);
+ return new Response();
+ }
+
+ /**
+ * Toggle default for showing/hiding hidden files
+ *
+ * @NoAdminRequired
+ *
+ * @param bool $show
+ */
+ public function showHiddenFiles($show) {
+ $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', (int) $show);
+ return new Response();
+ }
+
+}
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php
new file mode 100644
index 00000000000..7539a3ab649
--- /dev/null
+++ b/apps/files/lib/Controller/ViewController.php
@@ -0,0 +1,304 @@
+<?php
+/**
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Lukas Reschke <lukas@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Controller;
+
+use OC\AppFramework\Http\Request;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\INavigationManager;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IUserSession;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\Files\Folder;
+use OCP\App\IAppManager;
+
+/**
+ * Class ViewController
+ *
+ * @package OCA\Files\Controller
+ */
+class ViewController extends Controller {
+ /** @var string */
+ protected $appName;
+ /** @var IRequest */
+ protected $request;
+ /** @var IURLGenerator */
+ protected $urlGenerator;
+ /** @var INavigationManager */
+ protected $navigationManager;
+ /** @var IL10N */
+ protected $l10n;
+ /** @var IConfig */
+ protected $config;
+ /** @var EventDispatcherInterface */
+ protected $eventDispatcher;
+ /** @var IUserSession */
+ protected $userSession;
+ /** @var IAppManager */
+ protected $appManager;
+ /** @var \OCP\Files\Folder */
+ protected $rootFolder;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IURLGenerator $urlGenerator
+ * @param INavigationManager $navigationManager
+ * @param IL10N $l10n
+ * @param IConfig $config
+ * @param EventDispatcherInterface $eventDispatcherInterface
+ * @param IUserSession $userSession
+ * @param IAppManager $appManager
+ * @param Folder $rootFolder
+ */
+ public function __construct($appName,
+ IRequest $request,
+ IURLGenerator $urlGenerator,
+ INavigationManager $navigationManager,
+ IL10N $l10n,
+ IConfig $config,
+ EventDispatcherInterface $eventDispatcherInterface,
+ IUserSession $userSession,
+ IAppManager $appManager,
+ Folder $rootFolder
+ ) {
+ parent::__construct($appName, $request);
+ $this->appName = $appName;
+ $this->request = $request;
+ $this->urlGenerator = $urlGenerator;
+ $this->navigationManager = $navigationManager;
+ $this->l10n = $l10n;
+ $this->config = $config;
+ $this->eventDispatcher = $eventDispatcherInterface;
+ $this->userSession = $userSession;
+ $this->appManager = $appManager;
+ $this->rootFolder = $rootFolder;
+ }
+
+ /**
+ * @param string $appName
+ * @param string $scriptName
+ * @return string
+ */
+ protected function renderScript($appName, $scriptName) {
+ $content = '';
+ $appPath = \OC_App::getAppPath($appName);
+ $scriptPath = $appPath . '/' . $scriptName;
+ if (file_exists($scriptPath)) {
+ // TODO: sanitize path / script name ?
+ ob_start();
+ include $scriptPath;
+ $content = ob_get_contents();
+ @ob_end_clean();
+ }
+ return $content;
+ }
+
+ /**
+ * FIXME: Replace with non static code
+ *
+ * @return array
+ * @throws \OCP\Files\NotFoundException
+ */
+ protected function getStorageInfo() {
+ $dirInfo = \OC\Files\Filesystem::getFileInfo('/', false);
+ return \OC_Helper::getStorageInfo('/', $dirInfo);
+ }
+
+ /**
+ * @NoCSRFRequired
+ * @NoAdminRequired
+ *
+ * @param string $dir
+ * @param string $view
+ * @param string $fileid
+ * @return TemplateResponse
+ * @throws \OCP\Files\NotFoundException
+ */
+ public function index($dir = '', $view = '', $fileid = null) {
+ if ($fileid !== null) {
+ return $this->showFile($fileid);
+ }
+
+ $nav = new \OCP\Template('files', 'appnavigation', '');
+
+ // Load the files we need
+ \OCP\Util::addStyle('files', 'files');
+ \OCP\Util::addStyle('files', 'upload');
+ \OCP\Util::addStyle('files', 'mobile');
+ \OCP\Util::addscript('files', 'app');
+ \OCP\Util::addscript('files', 'file-upload');
+ \OCP\Util::addscript('files', 'newfilemenu');
+ \OCP\Util::addscript('files', 'jquery.fileupload');
+ \OCP\Util::addscript('files', 'jquery-visibility');
+ \OCP\Util::addscript('files', 'fileinfomodel');
+ \OCP\Util::addscript('files', 'filesummary');
+ \OCP\Util::addscript('files', 'breadcrumb');
+ \OCP\Util::addscript('files', 'filelist');
+ \OCP\Util::addscript('files', 'search');
+
+ \OCP\Util::addScript('files', 'favoritesfilelist');
+ \OCP\Util::addScript('files', 'tagsplugin');
+ \OCP\Util::addScript('files', 'favoritesplugin');
+
+ \OCP\Util::addScript('files', 'detailfileinfoview');
+ \OCP\Util::addScript('files', 'detailtabview');
+ \OCP\Util::addScript('files', 'mainfileinfodetailview');
+ \OCP\Util::addScript('files', 'detailsview');
+ \OCP\Util::addStyle('files', 'detailsView');
+
+ \OC_Util::addVendorScript('core', 'handlebars/handlebars');
+
+ \OCP\Util::addscript('files', 'fileactions');
+ \OCP\Util::addscript('files', 'fileactionsmenu');
+ \OCP\Util::addscript('files', 'files');
+ \OCP\Util::addscript('files', 'keyboardshortcuts');
+ \OCP\Util::addscript('files', 'navigation');
+
+ // if IE8 and "?dir=path&view=someview" was specified, reformat the URL to use a hash like "#?dir=path&view=someview"
+ $isIE8 = $this->request->isUserAgent([Request::USER_AGENT_IE_8]);
+ if ($isIE8 && ($dir !== '' || $view !== '')) {
+ $dir = !empty($dir) ? $dir : '/';
+ $view = !empty($view) ? $view : 'files';
+ $hash = '#?dir=' . \OCP\Util::encodePath($dir);
+ if ($view !== 'files') {
+ $hash .= '&view=' . urlencode($view);
+ }
+ return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index') . $hash);
+ }
+
+ // mostly for the home storage's free space
+ // FIXME: Make non static
+ $storageInfo = $this->getStorageInfo();
+
+ \OCA\Files\App::getNavigationManager()->add(
+ [
+ 'id' => 'favorites',
+ 'appname' => 'files',
+ 'script' => 'simplelist.php',
+ 'order' => 5,
+ 'name' => $this->l10n->t('Favorites')
+ ]
+ );
+
+ $navItems = \OCA\Files\App::getNavigationManager()->getAll();
+ usort($navItems, function($item1, $item2) {
+ return $item1['order'] - $item2['order'];
+ });
+ $nav->assign('navigationItems', $navItems);
+
+ $contentItems = [];
+
+ // render the container content for every navigation item
+ foreach ($navItems as $item) {
+ $content = '';
+ if (isset($item['script'])) {
+ $content = $this->renderScript($item['appname'], $item['script']);
+ }
+ $contentItem = [];
+ $contentItem['id'] = $item['id'];
+ $contentItem['content'] = $content;
+ $contentItems[] = $contentItem;
+ }
+
+ $this->eventDispatcher->dispatch('OCA\Files::loadAdditionalScripts');
+
+ $params = [];
+ $params['usedSpacePercent'] = (int)$storageInfo['relative'];
+ $params['owner'] = $storageInfo['owner'];
+ $params['ownerDisplayName'] = $storageInfo['ownerDisplayName'];
+ $params['isPublic'] = false;
+ $params['mailNotificationEnabled'] = $this->config->getAppValue('core', 'shareapi_allow_mail_notification', 'no');
+ $params['mailPublicNotificationEnabled'] = $this->config->getAppValue('core', 'shareapi_allow_public_notification', 'no');
+ $params['allowShareWithLink'] = $this->config->getAppValue('core', 'shareapi_allow_links', 'yes');
+ $user = $this->userSession->getUser()->getUID();
+ $params['defaultFileSorting'] = $this->config->getUserValue($user, 'files', 'file_sorting', 'name');
+ $params['defaultFileSortingDirection'] = $this->config->getUserValue($user, 'files', 'file_sorting_direction', 'asc');
+ $showHidden = (bool) $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', false);
+ $params['showHiddenFiles'] = $showHidden ? 1 : 0;
+ $params['appNavigation'] = $nav;
+ $params['appContents'] = $contentItems;
+ $this->navigationManager->setActiveEntry('files_index');
+
+ $response = new TemplateResponse(
+ $this->appName,
+ 'index',
+ $params
+ );
+ $policy = new ContentSecurityPolicy();
+ $policy->addAllowedFrameDomain('\'self\'');
+ $response->setContentSecurityPolicy($policy);
+
+ return $response;
+ }
+
+ /**
+ * Redirects to the file list and highlight the given file id
+ *
+ * @param string $fileId file id to show
+ * @return Response redirect response or not found response
+ *
+ * @NoCSRFRequired
+ * @NoAdminRequired
+ */
+ public function showFile($fileId) {
+ try {
+ $uid = $this->userSession->getUser()->getUID();
+ $baseFolder = $this->rootFolder->get($uid . '/files/');
+ $files = $baseFolder->getById($fileId);
+ $params = [];
+
+ if (empty($files) && $this->appManager->isEnabledForUser('files_trashbin')) {
+ $baseFolder = $this->rootFolder->get($uid . '/files_trashbin/files/');
+ $files = $baseFolder->getById($fileId);
+ $params['view'] = 'trashbin';
+ }
+
+ if (!empty($files)) {
+ $file = current($files);
+ if ($file instanceof Folder) {
+ // set the full path to enter the folder
+ $params['dir'] = $baseFolder->getRelativePath($file->getPath());
+ } else {
+ // set parent path as dir
+ $params['dir'] = $baseFolder->getRelativePath($file->getParent()->getPath());
+ // and scroll to the entry
+ $params['scrollto'] = $file->getName();
+ }
+ return new RedirectResponse($this->urlGenerator->linkToRoute('files.view.index', $params));
+ }
+ } catch (\OCP\Files\NotFoundException $e) {
+ return new NotFoundResponse();
+ }
+ return new NotFoundResponse();
+ }
+}
diff --git a/apps/files/lib/helper.php b/apps/files/lib/Helper.php
index d21a65afcee..d21a65afcee 100644
--- a/apps/files/lib/helper.php
+++ b/apps/files/lib/Helper.php
diff --git a/apps/files/lib/Service/TagService.php b/apps/files/lib/Service/TagService.php
new file mode 100644
index 00000000000..57cad43a539
--- /dev/null
+++ b/apps/files/lib/Service/TagService.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Files\Service;
+
+use OC\Files\FileInfo;
+use OCP\Files\Node;
+
+/**
+ * Service class to manage tags on files.
+ */
+class TagService {
+
+ /**
+ * @var \OCP\IUserSession
+ */
+ private $userSession;
+
+ /**
+ * @var \OCP\ITags
+ */
+ private $tagger;
+
+ /**
+ * @var \OCP\Files\Folder
+ */
+ private $homeFolder;
+
+ public function __construct(
+ \OCP\IUserSession $userSession,
+ \OCP\ITags $tagger,
+ \OCP\Files\Folder $homeFolder
+ ) {
+ $this->userSession = $userSession;
+ $this->tagger = $tagger;
+ $this->homeFolder = $homeFolder;
+ }
+
+ /**
+ * Updates the tags of the specified file path.
+ * The passed tags are absolute, which means they will
+ * replace the actual tag selection.
+ *
+ * @param string $path path
+ * @param array $tags array of tags
+ * @return array list of tags
+ * @throws \OCP\Files\NotFoundException if the file does not exist
+ */
+ public function updateFileTags($path, $tags) {
+ $fileId = $this->homeFolder->get($path)->getId();
+
+ $currentTags = $this->tagger->getTagsForObjects(array($fileId));
+
+ if (!empty($currentTags)) {
+ $currentTags = current($currentTags);
+ }
+
+ $newTags = array_diff($tags, $currentTags);
+ foreach ($newTags as $tag) {
+ $this->tagger->tagAs($fileId, $tag);
+ }
+ $deletedTags = array_diff($currentTags, $tags);
+ foreach ($deletedTags as $tag) {
+ $this->tagger->unTag($fileId, $tag);
+ }
+
+ // TODO: re-read from tagger to make sure the
+ // list is up to date, in case of concurrent changes ?
+ return $tags;
+ }
+
+ /**
+ * Get all files for the given tag
+ *
+ * @param string $tagName tag name to filter by
+ * @return Node[] list of matching files
+ * @throws \Exception if the tag does not exist
+ */
+ public function getFilesByTag($tagName) {
+ try {
+ $fileIds = $this->tagger->getIdsForTag($tagName);
+ } catch (\Exception $e) {
+ return [];
+ }
+
+ $allNodes = [];
+ foreach ($fileIds as $fileId) {
+ $allNodes = array_merge($allNodes, $this->homeFolder->getById((int) $fileId));
+ }
+ return $allNodes;
+ }
+}
+