Browse Source

Add a transfer ownership background job

This job can be initiated by a user to transfer a file/folder to a
target user.

The target user will have to accept the job.
Once that is done the transfers is initiated in the background.

Both parties get notified when the job is done.

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Roeland Jago Douma 9 months ago
parent
commit
5274c54268

+ 2 - 0
.gitattributes

@@ -5,6 +5,8 @@
 /apps/accessibility/js/accessibility.js.map binary
 /apps/comments/js/*.js binary
 /apps/comments/js/*.js.map binary
+/apps/files/js/dist/*.js binary
+/apps/files/js/dist/*.js.map binary
 /apps/files_sharing/js/dist/*.js binary
 /apps/files_sharing/js/dist/*.js.map binary
 /apps/files_versions/js/files_versions.js binary

+ 4 - 0
apps/files/appinfo/info.xml

@@ -65,4 +65,8 @@
 		</navigation>
 	</navigations>
 
+	<settings>
+		<personal>OCA\Files\Settings\PersonalSettings</personal>
+	</settings>
+
 </info>

+ 16 - 1
apps/files/appinfo/routes.php

@@ -119,7 +119,22 @@ $application->registerRoutes(
 				'url' => '/api/v1/directEditing/create',
 				'verb' => 'POST'
 			],
-		]
+			[
+				'name' => 'TransferOwnership#transfer',
+				'url' => '/api/v1/transferownership',
+				'verb' => 'POST',
+			],
+			[
+				'name' => 'TransferOwnership#accept',
+				'url' => '/api/v1/transferownership/{id}',
+				'verb' => 'POST',
+			],
+			[
+				'name' => 'TransferOwnership#reject',
+				'url' => '/api/v1/transferownership/{id}',
+				'verb' => 'DELETE',
+			],
+		],
 	]
 );
 

+ 7 - 0
apps/files/composer/composer/autoload_classmap.php

@@ -23,6 +23,7 @@ return array(
     'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => $baseDir . '/../lib/BackgroundJob/CleanupFileLocks.php',
     'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => $baseDir . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
     'OCA\\Files\\BackgroundJob\\ScanFiles' => $baseDir . '/../lib/BackgroundJob/ScanFiles.php',
+    'OCA\\Files\\BackgroundJob\\TransferOwnership' => $baseDir . '/../lib/BackgroundJob/TransferOwnership.php',
     'OCA\\Files\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
     'OCA\\Files\\Collaboration\\Resources\\Listener' => $baseDir . '/../lib/Collaboration/Resources/Listener.php',
     'OCA\\Files\\Collaboration\\Resources\\ResourceProvider' => $baseDir . '/../lib/Collaboration/Resources/ResourceProvider.php',
@@ -34,13 +35,19 @@ return array(
     'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
     'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
     'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
+    'OCA\\Files\\Controller\\TransferOwnershipController' => $baseDir . '/../lib/Controller/TransferOwnershipController.php',
     'OCA\\Files\\Controller\\ViewController' => $baseDir . '/../lib/Controller/ViewController.php',
+    'OCA\\Files\\Db\\TransferOwnership' => $baseDir . '/../lib/Db/TransferOwnership.php',
+    'OCA\\Files\\Db\\TransferOwnershipMapper' => $baseDir . '/../lib/Db/TransferOwnershipMapper.php',
     'OCA\\Files\\Event\\LoadAdditionalScriptsEvent' => $baseDir . '/../lib/Event/LoadAdditionalScriptsEvent.php',
     'OCA\\Files\\Event\\LoadSidebar' => $baseDir . '/../lib/Event/LoadSidebar.php',
     'OCA\\Files\\Exception\\TransferOwnershipException' => $baseDir . '/../lib/Exception/TransferOwnershipException.php',
     'OCA\\Files\\Helper' => $baseDir . '/../lib/Helper.php',
     'OCA\\Files\\Listener\\LegacyLoadAdditionalScriptsAdapter' => $baseDir . '/../lib/Listener/LegacyLoadAdditionalScriptsAdapter.php',
+    'OCA\\Files\\Migration\\Version11301Date20191113195931' => $baseDir . '/../lib/Migration/Version11301Date20191113195931.php',
+    'OCA\\Files\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
     'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php',
     'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php',
     'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php',
+    'OCA\\Files\\Settings\\PersonalSettings' => $baseDir . '/../lib/Settings/PersonalSettings.php',
 );

+ 7 - 0
apps/files/composer/composer/autoload_static.php

@@ -38,6 +38,7 @@ class ComposerStaticInitFiles
         'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupFileLocks.php',
         'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
         'OCA\\Files\\BackgroundJob\\ScanFiles' => __DIR__ . '/..' . '/../lib/BackgroundJob/ScanFiles.php',
+        'OCA\\Files\\BackgroundJob\\TransferOwnership' => __DIR__ . '/..' . '/../lib/BackgroundJob/TransferOwnership.php',
         'OCA\\Files\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
         'OCA\\Files\\Collaboration\\Resources\\Listener' => __DIR__ . '/..' . '/../lib/Collaboration/Resources/Listener.php',
         'OCA\\Files\\Collaboration\\Resources\\ResourceProvider' => __DIR__ . '/..' . '/../lib/Collaboration/Resources/ResourceProvider.php',
@@ -49,15 +50,21 @@ class ComposerStaticInitFiles
         'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
         'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
         'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
+        'OCA\\Files\\Controller\\TransferOwnershipController' => __DIR__ . '/..' . '/../lib/Controller/TransferOwnershipController.php',
         'OCA\\Files\\Controller\\ViewController' => __DIR__ . '/..' . '/../lib/Controller/ViewController.php',
+        'OCA\\Files\\Db\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Db/TransferOwnership.php',
+        'OCA\\Files\\Db\\TransferOwnershipMapper' => __DIR__ . '/..' . '/../lib/Db/TransferOwnershipMapper.php',
         'OCA\\Files\\Event\\LoadAdditionalScriptsEvent' => __DIR__ . '/..' . '/../lib/Event/LoadAdditionalScriptsEvent.php',
         'OCA\\Files\\Event\\LoadSidebar' => __DIR__ . '/..' . '/../lib/Event/LoadSidebar.php',
         'OCA\\Files\\Exception\\TransferOwnershipException' => __DIR__ . '/..' . '/../lib/Exception/TransferOwnershipException.php',
         'OCA\\Files\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php',
         'OCA\\Files\\Listener\\LegacyLoadAdditionalScriptsAdapter' => __DIR__ . '/..' . '/../lib/Listener/LegacyLoadAdditionalScriptsAdapter.php',
+        'OCA\\Files\\Migration\\Version11301Date20191113195931' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191113195931.php',
+        'OCA\\Files\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
         'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php',
         'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php',
         'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php',
+        'OCA\\Files\\Settings\\PersonalSettings' => __DIR__ . '/..' . '/../lib/Settings/PersonalSettings.php',
     );
 
     public static function getInitializer(ClassLoader $loader)

File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/personal-settings.js


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/personal-settings.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/sidebar.js


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/sidebar.js.map


+ 10 - 2
apps/files/lib/AppInfo/Application.php

@@ -35,6 +35,7 @@ use OCA\Files\Controller\ApiController;
 use OCA\Files\Controller\ViewController;
 use OCA\Files\Event\LoadAdditionalScriptsEvent;
 use OCA\Files\Listener\LegacyLoadAdditionalScriptsAdapter;
+use OCA\Files\Notification\Notifier;
 use OCA\Files\Service\TagService;
 use OCP\AppFramework\App;
 use OCP\Collaboration\Resources\IManager;
@@ -42,8 +43,11 @@ use OCP\EventDispatcher\IEventDispatcher;
 use OCP\IContainer;
 
 class Application extends App {
+
+	public const APP_ID = 'files';
+
 	public function __construct(array $urlParams=array()) {
-		parent::__construct('files', $urlParams);
+		parent::__construct(self::APP_ID, $urlParams);
 		$container = $this->getContainer();
 		$server = $container->getServer();
 
@@ -71,7 +75,7 @@ class Application extends App {
 			return new TagService(
 				$c->query('ServerContainer')->getUserSession(),
 				$c->query('ServerContainer')->getActivityManager(),
-				$c->query('ServerContainer')->getTagManager()->load('files'),
+				$c->query('ServerContainer')->getTagManager()->load(self::APP_ID),
 				$homeFolder,
 				$server->getEventDispatcher()
 			);
@@ -93,5 +97,9 @@ class Application extends App {
 		/** @var IEventDispatcher $dispatcher */
 		$dispatcher = $container->query(IEventDispatcher::class);
 		$dispatcher->addServiceListener(LoadAdditionalScriptsEvent::class, LegacyLoadAdditionalScriptsAdapter::class);
+
+		/** @var \OCP\Notification\IManager $notifications */
+		$notifications = $container->query(\OCP\Notification\IManager::class);
+		$notifications->registerNotifierService(Notifier::class);
 	}
 }

+ 183 - 0
apps/files/lib/BackgroundJob/TransferOwnership.php

@@ -0,0 +1,183 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files\BackgroundJob;
+
+use OCA\Files\AppInfo\Application;
+use OCA\Files\Db\TransferOwnership as Transfer;
+use OCA\Files\Db\TransferOwnershipMapper;
+use OCA\Files\Exception\TransferOwnershipException;
+use OCA\Files\Service\OwnershipTransferService;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\QueuedJob;
+use OCP\Files\IRootFolder;
+use OCP\ILogger;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Notification\IManager as NotificationManager;
+use function ltrim;
+
+class TransferOwnership extends QueuedJob {
+
+	/** @var IUserManager $userManager */
+	private $userManager;
+
+	/** @var OwnershipTransferService */
+	private $transferService;
+
+	/** @var ILogger */
+	private $logger;
+
+	/** @var NotificationManager */
+	private $notificationManager;
+
+	/** @var TransferOwnershipMapper */
+	private $mapper;
+	/** @var IRootFolder */
+	private $rootFolder;
+
+	public function __construct(ITimeFactory $timeFactory,
+								IUserManager $userManager,
+								OwnershipTransferService $transferService,
+								ILogger $logger,
+								NotificationManager $notificationManager,
+								TransferOwnershipMapper $mapper,
+								IRootFolder $rootFolder) {
+		parent::__construct($timeFactory);
+
+		$this->userManager = $userManager;
+		$this->transferService = $transferService;
+		$this->logger = $logger;
+		$this->notificationManager = $notificationManager;
+		$this->mapper = $mapper;
+		$this->rootFolder = $rootFolder;
+	}
+
+	protected function run($argument) {
+		$id = $argument['id'];
+
+		$transfer = $this->mapper->getById($id);
+		$sourceUser = $transfer->getSourceUser();
+		$destinationUser = $transfer->getTargetUser();
+		$fileId = $transfer->getFileId();
+
+		$userFolder = $this->rootFolder->getUserFolder($sourceUser);
+		$nodes = $userFolder->getById($fileId);
+
+		if (empty($nodes)) {
+			$this->logger->alert('Could not transfer ownership: Node not found');
+			$this->failedNotication($transfer);
+			return;
+		}
+		$path = $userFolder->getRelativePath($nodes[0]->getPath());
+
+		$sourceUserObject = $this->userManager->get($sourceUser);
+		$destinationUserObject = $this->userManager->get($destinationUser);
+
+		if (!$sourceUserObject instanceof IUser) {
+			$this->logger->alert('Could not transfer ownership: Unknown source user ' . $sourceUser);
+			$this->failedNotication($transfer);
+			return;
+		}
+
+		if (!$destinationUserObject instanceof IUser) {
+			$this->logger->alert("Unknown destination user $destinationUser");
+			$this->failedNotication($transfer);
+			return;
+		}
+
+		try {
+			$this->transferService->transfer(
+				$sourceUserObject,
+				$destinationUserObject,
+				ltrim($path, '/')
+			);
+			$this->successNotification($transfer);
+		} catch (TransferOwnershipException $e) {
+			$this->logger->logException($e);
+			$this->failedNotication($transfer);
+		}
+
+		$this->mapper->delete($transfer);
+
+	}
+
+	private function failedNotication(Transfer $transfer): void {
+		// Send notification to source user
+		$notification = $this->notificationManager->createNotification();
+		$notification->setUser($transfer->getSourceUser())
+			->setApp(Application::APP_ID)
+			->setDateTime($this->time->getDateTime())
+			->setSubject('transferOwnershipFailedSource', [
+				'sourceUser' => $transfer->getSourceUser(),
+				'targetUser' => $transfer->getTargetUser(),
+				'nodeName' => $transfer->getNodeName(),
+			])
+			->setObject('transfer', (string)$transfer->getId());
+		$this->notificationManager->notify($notification);
+
+		// Send notification to source user
+		$notification = $this->notificationManager->createNotification();
+		$notification->setUser($transfer->getTargetUser())
+			->setApp(Application::APP_ID)
+			->setDateTime($this->time->getDateTime())
+			->setSubject('transferOwnershipFailedTarget', [
+				'sourceUser' => $transfer->getSourceUser(),
+				'targetUser' => $transfer->getTargetUser(),
+				'nodeName' => $transfer->getNodeName(),
+			])
+			->setObject('transfer', (string)$transfer->getId());
+		$this->notificationManager->notify($notification);
+	}
+
+	private function successNotification(Transfer $transfer): void {
+		// Send notification to source user
+		$notification = $this->notificationManager->createNotification();
+		$notification->setUser($transfer->getSourceUser())
+			->setApp(Application::APP_ID)
+			->setDateTime($this->time->getDateTime())
+			->setSubject('transferOwnershipDoneSource', [
+				'sourceUser' => $transfer->getSourceUser(),
+				'targetUser' => $transfer->getTargetUser(),
+				'nodeName' => $transfer->getNodeName(),
+			])
+			->setObject('transfer', (string)$transfer->getId());
+		$this->notificationManager->notify($notification);
+
+		// Send notification to source user
+		$notification = $this->notificationManager->createNotification();
+		$notification->setUser($transfer->getTargetUser())
+			->setApp(Application::APP_ID)
+			->setDateTime($this->time->getDateTime())
+			->setSubject('transferOwnershipDoneTarget', [
+				'sourceUser' => $transfer->getSourceUser(),
+				'targetUser' => $transfer->getTargetUser(),
+				'nodeName' => $transfer->getNodeName(),
+			])
+			->setObject('transfer', (string)$transfer->getId());
+		$this->notificationManager->notify($notification);
+	}
+}

+ 181 - 0
apps/files/lib/Controller/TransferOwnershipController.php

@@ -0,0 +1,181 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files\Controller;
+
+use OCA\Files\BackgroundJob\TransferOwnership;
+use OCA\Files\Db\TransferOwnershipMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\IJobList;
+use OCP\Files\IRootFolder;
+use OCP\IRequest;
+use OCP\IUserManager;
+use OCP\Notification\IManager as NotificationManager;
+
+class TransferOwnershipController extends OCSController {
+
+	/** @var string */
+	private $userId;
+	/** @var NotificationManager */
+	private $notificationManager;
+	/** @var ITimeFactory */
+	private $timeFactory;
+	/** @var IJobList */
+	private $jobList;
+	/** @var TransferOwnershipMapper */
+	private $mapper;
+	/** @var IUserManager */
+	private $userManager;
+	/** @var IRootFolder */
+	private $rootFolder;
+
+	public function __construct(string $appName,
+								IRequest $request,
+								string $userId,
+								NotificationManager $notificationManager,
+								ITimeFactory $timeFactory,
+								IJobList $jobList,
+								TransferOwnershipMapper $mapper,
+								IUserManager $userManager,
+								IRootFolder $rootFolder) {
+		parent::__construct($appName, $request);
+
+		$this->userId = $userId;
+		$this->notificationManager = $notificationManager;
+		$this->timeFactory = $timeFactory;
+		$this->jobList = $jobList;
+		$this->mapper = $mapper;
+		$this->userManager = $userManager;
+		$this->rootFolder = $rootFolder;
+	}
+
+
+	/**
+	 * @NoAdminRequired
+	 */
+	public function transfer(string $recipient, string $path): DataResponse {
+		$recipientUser = $this->userManager->get($recipient);
+
+		if ($recipientUser === null) {
+			return new DataResponse([], Http::STATUS_BAD_REQUEST);
+		}
+
+		$userRoot = $this->rootFolder->getUserFolder($this->userId);
+
+		try {
+			$node = $userRoot->get($path);
+		} catch (\Exception $e) {
+			return new DataResponse([], Http::STATUS_BAD_REQUEST);
+		}
+
+		$transferOwnership = new \OCA\Files\Db\TransferOwnership();
+		$transferOwnership->setSourceUser($this->userId);
+		$transferOwnership->setTargetUser($recipient);
+		$transferOwnership->setFileId($node->getId());
+		$transferOwnership->setNodeName($node->getName());
+		$transferOwnership = $this->mapper->insert($transferOwnership);
+
+		$notification = $this->notificationManager->createNotification();
+		$notification->setUser($recipient)
+			->setApp($this->appName)
+			->setDateTime($this->timeFactory->getDateTime())
+			->setSubject('transferownershipRequest', [
+				'sourceUser' => $this->userId,
+				'targetUser' => $recipient,
+				'nodeName' => $node->getName(),
+			])
+			->setObject('transfer', (string)$transferOwnership->getId());
+
+		$this->notificationManager->notify($notification);
+
+		return new DataResponse([]);
+	}
+
+	/**
+	 * @NoAdminRequired
+	 */
+	public function accept(int $id): DataResponse {
+		try {
+			$transferOwnership = $this->mapper->getById($id);
+		} catch (DoesNotExistException $e) {
+			return new DataResponse([], Http::STATUS_NOT_FOUND);
+		}
+
+		if ($transferOwnership->getTargetUser() !== $this->userId) {
+			return new DataResponse([], Http::STATUS_FORBIDDEN);
+		}
+
+		$this->jobList->add(TransferOwnership::class, [
+			'id' => $transferOwnership->getId(),
+		]);
+
+		$notification = $this->notificationManager->createNotification();
+		$notification->setApp('files')
+			->setObject('transfer', (string)$id);
+		$this->notificationManager->markProcessed($notification);
+
+		return new DataResponse([], Http::STATUS_OK);
+	}
+
+	/**
+	 * @NoAdminRequired
+	 */
+	public function reject(int $id): DataResponse {
+		try {
+			$transferOwnership = $this->mapper->getById($id);
+		} catch (DoesNotExistException $e) {
+			return new DataResponse([], Http::STATUS_NOT_FOUND);
+		}
+
+		if ($transferOwnership->getTargetUser() !== $this->userId) {
+			return new DataResponse([], Http::STATUS_FORBIDDEN);
+		}
+
+		$notification = $this->notificationManager->createNotification();
+		$notification->setApp('files')
+			->setObject('transfer', (string)$id);
+		$this->notificationManager->markProcessed($notification);
+
+		$notification = $this->notificationManager->createNotification();
+		$notification->setUser($transferOwnership->getSourceUser())
+			->setApp($this->appName)
+			->setDateTime($this->timeFactory->getDateTime())
+			->setSubject('transferownershipRequestDenied', [
+				'sourceUser' => $transferOwnership->getSourceUser(),
+				'targetUser' => $transferOwnership->getTargetUser(),
+				'nodeName' => $transferOwnership->getNodeName()
+			])
+			->setObject('transfer', (string)$transferOwnership->getId());
+		$this->notificationManager->notify($notification);
+
+		$this->mapper->delete($transferOwnership);
+
+		return new DataResponse([], Http::STATUS_OK);
+	}
+
+}

+ 60 - 0
apps/files/lib/Db/TransferOwnership.php

@@ -0,0 +1,60 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files\Db;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * @method void setSourceUser(string $uid)
+ * @method string getSourceUser()
+ * @method void setTargetUser(string $uid)
+ * @method string getTargetUser()
+ * @method void setFileId(int $fileId)
+ * @method int getFileId()
+ * @method void setNodeName(string $name)
+ * @method string getNodeName()
+ */
+class TransferOwnership extends Entity {
+	/** @var string */
+	protected $sourceUser;
+
+	/** @var string */
+	protected $targetUser;
+
+	/** @var integer */
+	protected $fileId;
+
+	/** @var string */
+	protected $nodeName;
+
+	public function __construct() {
+		$this->addType('sourceUser', 'string');
+		$this->addType('targetUser', 'string');
+		$this->addType('fileId', 'integer');
+		$this->addType('nodeName', 'string');
+	}
+
+
+}

+ 47 - 0
apps/files/lib/Db/TransferOwnershipMapper.php

@@ -0,0 +1,47 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files\Db;
+
+use OCP\AppFramework\Db\QBMapper;
+use OCP\IDBConnection;
+
+class TransferOwnershipMapper extends QBMapper {
+	public function __construct(IDBConnection $db) {
+		parent::__construct($db, 'user_transfer_ownership', TransferOwnership::class);
+	}
+
+	public function getById(int $id): TransferOwnership {
+		$qb = $this->db->getQueryBuilder();
+
+		$qb->select('*')
+			->from($this->getTableName())
+			->where(
+				$qb->expr()->eq('id', $qb->createNamedParameter($id))
+			);
+
+		return $this->findEntity($qb);
+	}
+
+}

+ 72 - 0
apps/files/lib/Migration/Version11301Date20191113195931.php

@@ -0,0 +1,72 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+namespace OCA\Files\Migration;
+
+use Closure;
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\SimpleMigrationStep;
+use OCP\Migration\IOutput;
+
+class Version11301Date20191113195931 extends SimpleMigrationStep {
+
+	/**
+	 * @param IOutput $output
+	 * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+	 * @param array $options
+	 * @return null|ISchemaWrapper
+	 */
+	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
+		/** @var ISchemaWrapper $schema */
+		$schema = $schemaClosure();
+
+		$table = $schema->createTable('user_transfer_ownership');
+		$table->addColumn('id', 'integer', [
+			'autoincrement' => true,
+			'notnull' => true,
+			'length' => 4,
+		]);
+		$table->addColumn('source_user', 'string', [
+			'notnull' => true,
+			'length' => 64,
+		]);
+		$table->addColumn('target_user', 'string', [
+			'notnull' => true,
+			'length' => 64,
+		]);
+		$table->addColumn('file_id', 'bigint', [
+			'notnull' => true,
+			'length' => 20,
+		]);
+		$table->addColumn('node_name', 'string', [
+			'notnull' => true,
+			'length' => 255,
+		]);
+		$table->setPrimaryKey(['id']);
+
+		return $schema;
+	}
+
+}

+ 243 - 0
apps/files/lib/Notification/Notifier.php

@@ -0,0 +1,243 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files\Notification;
+
+use OCP\IURLGenerator;
+use OCP\L10N\IFactory;
+use OCP\Notification\IAction;
+use OCP\Notification\INotification;
+use OCP\Notification\INotifier;
+
+class Notifier implements INotifier {
+
+	/** @var IFactory */
+	protected $l10nFactory;
+
+	/** @var IURLGenerator */
+	protected $urlGenerator;
+
+	/**
+	 * @param IFactory $l10nFactory
+	 * @param IURLGenerator $urlGenerator
+	 */
+	public function __construct(IFactory $l10nFactory, IURLGenerator $urlGenerator) {
+		$this->l10nFactory = $l10nFactory;
+		$this->urlGenerator = $urlGenerator;
+	}
+
+	public function getID(): string {
+		return 'files';
+	}
+
+	public function getName(): string {
+		return $this->l10nFactory->get('files')->t('Files');
+	}
+
+	/**
+	 * @param INotification $notification
+	 * @param string $languageCode The code of the language that should be used to prepare the notification
+	 * @return INotification
+	 * @throws \InvalidArgumentException When the notification was not prepared by a notifier
+	 */
+	public function prepare(INotification $notification, string $languageCode): INotification {
+		if ($notification->getApp() !== 'files') {
+			throw new \InvalidArgumentException('Unhandled app');
+		}
+
+		if ($notification->getSubject() === 'transferownershipRequest') {
+			return $this->handleTransferownershipRequest($notification, $languageCode);
+		}
+		if ($notification->getSubject() === 'transferOwnershipFailedSource') {
+			return $this->handleTransferOwnershipFailedSource($notification, $languageCode);
+		}
+		if ($notification->getSubject() === 'transferOwnershipFailedTarget') {
+			return $this->handleTransferOwnershipFailedTarget($notification, $languageCode);
+		}
+		if ($notification->getSubject() === 'transferOwnershipDoneSource') {
+			return $this->handleTransferOwnershipDoneSource($notification, $languageCode);
+		}
+		if ($notification->getSubject() === 'transferOwnershipDoneTarget') {
+			return $this->handleTransferOwnershipDoneTarget($notification, $languageCode);
+		}
+
+		throw new \InvalidArgumentException('Unhandled subject');
+	}
+
+	public function handleTransferownershipRequest(INotification $notification, string $languageCode): INotification {
+		$l = $this->l10nFactory->get('files', $languageCode);
+		$id = $notification->getObjectId();
+		$param = $notification->getSubjectParameters();
+
+		$approveAction = $notification->createAction()
+			->setParsedLabel($l->t('Accept'))
+			->setPrimary(true)
+			->setLink(
+				$this->urlGenerator->getAbsoluteURL(
+					$this->urlGenerator->linkTo(
+						'',
+						'ocs/v2.php/apps/files/api/v1/transferownership/' . $id
+					)
+				),
+				IAction::TYPE_POST
+			);
+
+		$disapproveAction = $notification->createAction()
+			->setParsedLabel($l->t('Decline'))
+			->setPrimary(false)
+			->setLink(
+				$this->urlGenerator->getAbsoluteURL(
+					$this->urlGenerator->linkTo(
+						'',
+						'ocs/v2.php/apps/files/api/v1/transferownership/' . $id
+					)
+				),
+				IAction::TYPE_DELETE
+			);
+
+		$notification->addParsedAction($approveAction)
+			->addParsedAction($disapproveAction)
+			->setRichSubject(
+				$l->t('Incomming file transfer from {user}'),
+				[
+					'user' => [
+						'type' => 'user',
+						'id' => $param['sourceUser'],
+						'name' => $param['sourceUser'],
+					],
+				])
+			->setParsedSubject(str_replace('{user}', $param['sourceUser'], $l->t('Incomming file transfer from {user}')))
+			->setRichMessage(
+				$l->t('Do you want to accept {path}?'),
+				[
+					'path' => [
+						'type' => 'highlight',
+						'id' => $param['targetUser'] . '::' . $param['nodeName'],
+						'name' => $param['nodeName'],
+					]
+				])
+			->setParsedMessage(str_replace('{path}', $param['nodeName'], $l->t('Do you want to accept {path}?')));
+
+		return $notification;
+	}
+
+	public function handleTransferOwnershipFailedSource(INotification $notification,  string $languageCode): INotification {
+		$l = $this->l10nFactory->get('files', $languageCode);
+		$param = $notification->getSubjectParameters();
+
+		$notification->setRichSubject($l->t('File transfer failed'))
+			->setParsedSubject(str_replace(['{path}', '{user}'], [$param['nodeName'], $param['targetUser']], $l->t('Your transfer of {path} to {user} failed.')))
+			->setRichMessage(
+				$l->t('Your transfer of {path} to {user} failed.'),
+				[
+					'path' => [
+						'type' => 'highlight',
+						'id' => $param['targetUser'] . '::' . $param['nodeName'],
+						'name' => $param['nodeName'],
+					],
+					'user' => [
+						'type' => 'user',
+						'id' => $param['targetUser'],
+						'name' => $param['targetUser'],
+					],
+				])
+				->setParsedMessage($l->t('File transfer failed'));
+		return $notification;
+	}
+
+	public function handleTransferOwnershipFailedTarget(INotification $notification,  string $languageCode): INotification {
+		$l = $this->l10nFactory->get('files', $languageCode);
+		$param = $notification->getSubjectParameters();
+
+		$notification->setRichSubject($l->t('File transfer failed'))
+			->setParsedSubject(str_replace(['{path}', '{user}'], [$param['nodeName'], $param['sourceUser']], $l->t('The transfer of {path} from {user} failed.')))
+			->setRichMessage(
+				$l->t('The transfer of {path} from {user} failed.'),
+				[
+					'path' => [
+						'type' => 'highlight',
+						'id' => $param['sourceUser'] . '::' . $param['nodeName'],
+						'name' => $param['nodeName'],
+					],
+					'user' => [
+						'type' => 'user',
+						'id' => $param['sourceUser'],
+						'name' => $param['sourceUser'],
+					],
+				])
+			->setParsedMessage($l->t('File transfer failed'));
+
+		return $notification;
+	}
+
+	public function handleTransferOwnershipDoneSource(INotification $notification,  string $languageCode): INotification {
+		$l = $this->l10nFactory->get('files', $languageCode);
+		$param = $notification->getSubjectParameters();
+
+		$notification->setRichSubject($l->t('File transfer done'))
+			->setParsedSubject(str_replace(['{path}', '{user}'], [$param['nodeName'], $param['targetUser']], $l->t('Your transfer of {path} to {user} has completed.')))
+			->setRichMessage(
+				$l->t('Your transfer of {path} to {user} has completed.'),
+				[
+					'path' => [
+						'type' => 'highlight',
+						'id' => $param['targetUser'] . '::' . $param['nodeName'],
+						'name' => $param['nodeName'],
+					],
+					'user' => [
+						'type' => 'user',
+						'id' => $param['targetUser'],
+						'name' => $param['targetUser'],
+					],
+				])
+			->setParsedMessage($l->t('File transfer done'));
+
+		return $notification;
+	}
+
+	public function handleTransferOwnershipDoneTarget(INotification $notification,  string $languageCode): INotification {
+		$l = $this->l10nFactory->get('files', $languageCode);
+		$param = $notification->getSubjectParameters();
+
+		$notification->setRichSubject($l->t('File transfer done'))
+			->setParsedSubject(str_replace(['{path}', '{user}'], [$param['nodeName'], $param['sourceUser']], $l->t('The transfer of {path} from {user} has completed.')))
+			->setRichMessage(
+				$l->t('The transfer of {path} from {user} has completed.'),
+				[
+					'path' => [
+						'type' => 'highlight',
+						'id' => $param['sourceUser'] . '::' . $param['nodeName'],
+						'name' => $param['nodeName'],
+					],
+					'user' => [
+						'type' => 'user',
+						'id' => $param['sourceUser'],
+						'name' => $param['sourceUser'],
+					],
+				])
+			->setParsedMessage($l->t('File transfer done'));
+
+		return $notification;
+	}
+}

+ 46 - 0
apps/files/lib/Settings/PersonalSettings.php

@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OCA\Files\Settings;
+
+use OCA\Files\AppInfo\Application;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Settings\ISettings;
+
+class PersonalSettings implements ISettings {
+
+	public function getForm(): TemplateResponse {
+		return new TemplateResponse(Application::APP_ID, 'settings-personal');
+	}
+
+	public function getSection(): string {
+		return 'sharing';
+	}
+
+	public function getPriority(): int {
+		return 90;
+	}
+
+}

+ 38 - 0
apps/files/src/components/PersonalSettings.vue

@@ -0,0 +1,38 @@
+<!--
+  - @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+  -
+  - @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+  -
+  - @license GNU AGPL version 3 or any later version
+  -
+  - This program is free software: you can redistribute it and/or modify
+  - it under the terms of the GNU Affero General Public License as
+  - published by the Free Software Foundation, either version 3 of the
+  - License, or (at your option) any later version.
+  -
+  - 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
+  - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<template>
+	<div id="files-personal-settings" class="section">
+		<h2>{{ t('files', 'Files') }}</h2>
+		<TransferOwnershipDialogue />
+	</div>
+</template>
+
+<script>
+import TransferOwnershipDialogue from './TransferOwnershipDialogue'
+
+export default {
+	name: 'PersonalSettings',
+	components: {
+		TransferOwnershipDialogue
+	}
+}
+</script>

+ 143 - 0
apps/files/src/components/TransferOwnershipDialogue.vue

@@ -0,0 +1,143 @@
+<!--
+  - @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+  -
+  - @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+  -
+  - @license GNU AGPL version 3 or any later version
+  -
+  - This program is free software: you can redistribute it and/or modify
+  - it under the terms of the GNU Affero General Public License as
+  - published by the Free Software Foundation, either version 3 of the
+  - License, or (at your option) any later version.
+  -
+  - 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
+  - along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<template>
+	<div>
+		<h3>{{ t('files', 'Transfer ownership') }} </h3>
+		<p>
+			{{ t('files', 'Here you can select a directory that is transferred to another user. It may take some time until the process is done.') }}
+		</p>
+		<form @submit.prevent="submit">
+			<ol>
+				<li>
+					<div class="step-header">
+						{{ t('files', 'Directory to move') }}
+					</div>
+					<span v-if="directory === undefined">{{ t('files', 'No directory selected') }}</span>
+					<span v-else>{{ directory }}</span>
+					<button class="primary" @click.prevent="start">
+						{{ t('files', 'Select') }}
+					</button>
+					<span class="error">{{ directoryPickerError }}</span>
+				</li>
+				<li>
+					<div class="step-header">
+						{{ t('files', 'Target user') }}
+					</div>
+					<input id="files-transfer-user" v-model="uid" type="text">
+				</li>
+				<li>
+					<input type="submit"
+						class="primary"
+						:value="t('files', 'Submit')"
+						:disabled="!canSubmit">
+					<span class="error">{{ submitError }}</span>
+				</li>
+			</ol>
+		</form>
+	</div>
+</template>
+
+<script>
+import axios from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+import { getFilePickerBuilder } from '@nextcloud/dialogs'
+
+import logger from '../logger'
+
+const picker = getFilePickerBuilder(t('files', 'Select directory to transfer'))
+	.setMultiSelect(false)
+	.setModal(true)
+	.setType(1)
+	.allowDirectories()
+	.build()
+
+export default {
+	name: 'TransferOwnershipDialogue',
+	data() {
+		return {
+			directory: undefined,
+			directoryPickerError: undefined,
+			submitError: undefined,
+			uid: ''
+		}
+	},
+	computed: {
+		canSubmit() {
+			return !!this.directory && !!this.uid
+		}
+	},
+	methods: {
+		start() {
+			this.directoryPickerError = undefined
+
+			picker.pick()
+				.then(dir => dir === '' ? '/' : dir)
+				.then(dir => {
+					logger.debug(`path ${dir} selected for transfer ownership`)
+					if (!dir.startsWith('/')) {
+						throw new Error(t('files', 'Invalid path selected'))
+					}
+					// /ocs/v2.php/apps/files/api/v1/transferownership
+					// /ocs/v2.php/apps/files/api/v1/transferownership
+					this.directory = dir
+				}).catch(error => {
+					logger.error(`Selecting dir for transfer aborted: ${error.message || 'Unknown error'}`, { error })
+
+					this.directoryPickerError = error.message || t('files', 'Unknown error')
+				})
+		},
+		submit() {
+			if (!this.canSubmit) {
+				logger.warn('ignoring form submit')
+			}
+
+			this.submitError = undefined
+			const data = {
+				path: this.directory,
+				recipient: this.uid
+			}
+			logger.debug('submit transfer ownership form', data)
+
+			const url = generateOcsUrl('apps/files/api/v1/', 2) + 'transferownership'
+
+			axios.post(url, data)
+				.then(resp => resp.data)
+				.then(data => {
+					logger.info('Transfer ownership request sent', { data })
+
+					this.directory = undefined
+					this.recipient = undefined
+					OCP.Toast.success(t('files', 'Ownership transfer request sent'))
+				})
+				.catch(error => {
+					logger.error('Could not send ownership transfer request', { error })
+
+					this.submitError = error.message || t('files', 'Unknown error')
+				})
+		}
+	}
+}
+</script>
+
+<style scoped>
+
+</style>

+ 28 - 0
apps/files/src/logger.js

@@ -0,0 +1,28 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import { getCurrentUser } from '@nextcloud/auth'
+import { getLoggerBuilder } from '@nextcloud/logger'
+
+export default getLoggerBuilder()
+	.setApp('files')
+	.setUid(getCurrentUser().uid)
+	.build()

+ 38 - 0
apps/files/src/main-personal-settings.js

@@ -0,0 +1,38 @@
+// global t
+
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import Vue from 'vue'
+import { getRequestToken } from '@nextcloud/auth'
+import { generateFilePath } from '@nextcloud/router'
+
+import PersonalSettings from './components/PersonalSettings'
+
+// eslint-disable-next-line camelcase
+__webpack_nonce__ = btoa(getRequestToken())
+// eslint-disable-next-line camelcase
+__webpack_public_path__ = generateFilePath('files', '', 'js/')
+
+Vue.prototype.t = t
+
+const View = Vue.extend(PersonalSettings)
+new View().$mount('#files-personal-settings')

+ 29 - 0
apps/files/templates/settings-personal.php

@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+script(\OCA\Files\AppInfo\Application::APP_ID, 'dist/personal-settings');
+
+?>
+<div id="files-personal-settings" class="section">
+</div>

+ 1 - 0
apps/files/webpack.js

@@ -3,6 +3,7 @@ const path = require('path');
 module.exports = {
 	entry: {
 		'sidebar': path.join(__dirname, 'src', 'sidebar.js'),
+		'personal-settings': path.join(__dirname, 'src', 'main-personal-settings.js'),
 	},
 	output: {
 		path: path.resolve(__dirname, './js/dist/'),

File diff suppressed because it is too large
+ 0 - 0
core/js/dist/login.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/login.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/main.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/main.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/maintenance.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/maintenance.js.map


+ 2 - 2
core/src/OC/dialogs.js

@@ -486,7 +486,7 @@ const Dialogs = {
 			// Hence this is one of the approach to get the choose button.
 			var getOcDialog = self.$filePicker.closest('.oc-dialog')
 			var buttonEnableDisable = getOcDialog.find('.primary')
-			if (self.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1 && !self.$filePicker.data('.allowDirectoryChooser')) {
+			if (self.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1 || self.$filePicker.data('allowDirectoryChooser')) {
 				buttonEnableDisable.prop('disabled', false)
 			} else {
 				buttonEnableDisable.prop('disabled', true)
@@ -1213,7 +1213,7 @@ const Dialogs = {
 		var getOcDialog = (event.target).closest('.oc-dialog')
 		var buttonEnableDisable = $('.primary', getOcDialog)
 		this._changeButtonsText(type, dir.split(/[/]+/).pop())
-		if (this.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1) {
+		if (this.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1 || this.$filePicker.data('allowDirectoryChooser')) {
 			buttonEnableDisable.prop('disabled', false)
 		} else {
 			buttonEnableDisable.prop('disabled', true)

+ 188 - 7
package-lock.json

@@ -2142,6 +2142,21 @@
       "integrity": "sha512-f+sKpdLZXkODV+OY39K1M+Spmd4RgxmtEXmNn4Bviv4R7uBFHXuw+JX9ZdfDeOryfHjJ/TRQxQEp0GMpBwZFUw==",
       "dev": true
     },
+    "@nextcloud/dialogs": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-0.1.1.tgz",
+      "integrity": "sha512-eO3qfMdxg+ZRrP3lYX5B6R/DyEEuBieOwI6N42yaGmsniBmPnAGt7uxWKMBBCeOQLDJT2ni805GUkU2hMA3xNw==",
+      "requires": {
+        "core-js": "3.4.2"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "3.4.2",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.2.tgz",
+          "integrity": "sha512-bUTfqFWtNKWp73oNIfRkqwYZJeNT3lstzZcAkhhiuvDraRSgOH1/+F9ZklbpR4zpdKuo4cpXN8tKP7s61yjX+g=="
+        }
+      }
+    },
     "@nextcloud/event-bus": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-0.2.1.tgz",
@@ -2172,6 +2187,22 @@
         }
       }
     },
+    "@nextcloud/logger": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-0.1.0.tgz",
+      "integrity": "sha512-8ZI9SkuY3vRe7IoQV9J83zUf6s8UpXHsG9vH8cTLiCyQihiJ6xpdvmbBk509v6MitG7H7Nx83vygSLM1gkMnNQ==",
+      "requires": {
+        "babel-plugin-transform-class-properties": "6.24.1",
+        "core-js": "3.1.4"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "3.1.4",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz",
+          "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ=="
+        }
+      }
+    },
     "@nextcloud/paths": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/@nextcloud/paths/-/paths-0.2.0.tgz",
@@ -2688,6 +2719,58 @@
         }
       }
     },
+    "babel-code-frame": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+      "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+      "requires": {
+        "chalk": "^1.1.3",
+        "esutils": "^2.0.2",
+        "js-tokens": "^3.0.2"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+        },
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+        }
+      }
+    },
     "babel-eslint": {
       "version": "10.0.3",
       "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz",
@@ -2713,6 +2796,27 @@
         }
       }
     },
+    "babel-helper-function-name": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
+      "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+      "requires": {
+        "babel-helper-get-function-arity": "^6.24.1",
+        "babel-runtime": "^6.22.0",
+        "babel-template": "^6.24.1",
+        "babel-traverse": "^6.24.1",
+        "babel-types": "^6.24.1"
+      }
+    },
+    "babel-helper-get-function-arity": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
+      "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+      "requires": {
+        "babel-runtime": "^6.22.0",
+        "babel-types": "^6.24.1"
+      }
+    },
     "babel-loader": {
       "version": "8.0.6",
       "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz",
@@ -2725,6 +2829,14 @@
         "pify": "^4.0.1"
       }
     },
+    "babel-messages": {
+      "version": "6.23.0",
+      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+      "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+      "requires": {
+        "babel-runtime": "^6.22.0"
+      }
+    },
     "babel-plugin-dynamic-import-node": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz",
@@ -2733,6 +2845,22 @@
         "object.assign": "^4.1.0"
       }
     },
+    "babel-plugin-syntax-class-properties": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
+      "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94="
+    },
+    "babel-plugin-transform-class-properties": {
+      "version": "6.24.1",
+      "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz",
+      "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=",
+      "requires": {
+        "babel-helper-function-name": "^6.24.1",
+        "babel-plugin-syntax-class-properties": "^6.8.0",
+        "babel-runtime": "^6.22.0",
+        "babel-template": "^6.24.1"
+      }
+    },
     "babel-plugin-transform-es2015-arrow-functions": {
       "version": "6.22.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
@@ -2746,7 +2874,6 @@
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
       "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
-      "dev": true,
       "requires": {
         "core-js": "^2.4.0",
         "regenerator-runtime": "^0.11.0"
@@ -2755,11 +2882,68 @@
         "regenerator-runtime": {
           "version": "0.11.1",
           "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
-          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
-          "dev": true
+          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
         }
       }
     },
+    "babel-template": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+      "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+      "requires": {
+        "babel-runtime": "^6.26.0",
+        "babel-traverse": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "babylon": "^6.18.0",
+        "lodash": "^4.17.4"
+      }
+    },
+    "babel-traverse": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+      "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+      "requires": {
+        "babel-code-frame": "^6.26.0",
+        "babel-messages": "^6.23.0",
+        "babel-runtime": "^6.26.0",
+        "babel-types": "^6.26.0",
+        "babylon": "^6.18.0",
+        "debug": "^2.6.8",
+        "globals": "^9.18.0",
+        "invariant": "^2.2.2",
+        "lodash": "^4.17.4"
+      },
+      "dependencies": {
+        "globals": {
+          "version": "9.18.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+          "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ=="
+        }
+      }
+    },
+    "babel-types": {
+      "version": "6.26.0",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+      "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+      "requires": {
+        "babel-runtime": "^6.26.0",
+        "esutils": "^2.0.2",
+        "lodash": "^4.17.4",
+        "to-fast-properties": "^1.0.3"
+      },
+      "dependencies": {
+        "to-fast-properties": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+          "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
+        }
+      }
+    },
+    "babylon": {
+      "version": "6.18.0",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+      "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
+    },
     "backbone": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz",
@@ -3588,7 +3772,6 @@
       "version": "2.6.9",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
       "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-      "dev": true,
       "requires": {
         "ms": "2.0.0"
       }
@@ -5515,7 +5698,6 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
       "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
-      "dev": true,
       "requires": {
         "ansi-regex": "^2.0.0"
       },
@@ -5523,8 +5705,7 @@
         "ansi-regex": {
           "version": "2.1.1",
           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-          "dev": true
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
         }
       }
     },

+ 2 - 0
package.json

@@ -27,8 +27,10 @@
     "@chenfengyuan/vue-qrcode": "^1.0.1",
     "@nextcloud/auth": "^0.3.1",
     "@nextcloud/axios": "^0.5.0",
+    "@nextcloud/dialogs": "^0.1.1",
     "@nextcloud/event-bus": "^0.2.1",
     "@nextcloud/initial-state": "^0.2.0",
+    "@nextcloud/logger": "^0.1.0",
     "@nextcloud/paths": "^0.2.0",
     "@nextcloud/router": "^0.1.0",
     "autosize": "^4.0.2",

Some files were not shown because too many files changed in this diff