Completed
Pull Request — master (#9255)
by Morris
28:42 queued 07:08
created
apps/files_versions/lib/Storage.php 2 patches
Indentation   +793 added lines, -793 removed lines patch added patch discarded remove patch
@@ -54,798 +54,798 @@
 block discarded – undo
54 54
 
55 55
 class Storage {
56 56
 
57
-	const DEFAULTENABLED=true;
58
-	const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
59
-	const VERSIONS_ROOT = 'files_versions/';
60
-
61
-	const DELETE_TRIGGER_MASTER_REMOVED = 0;
62
-	const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
63
-	const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
64
-
65
-	// files for which we can remove the versions after the delete operation was successful
66
-	private static $deletedFiles = array();
67
-
68
-	private static $sourcePathAndUser = array();
69
-
70
-	private static $max_versions_per_interval = array(
71
-		//first 10sec, one version every 2sec
72
-		1 => array('intervalEndsAfter' => 10,      'step' => 2),
73
-		//next minute, one version every 10sec
74
-		2 => array('intervalEndsAfter' => 60,      'step' => 10),
75
-		//next hour, one version every minute
76
-		3 => array('intervalEndsAfter' => 3600,    'step' => 60),
77
-		//next 24h, one version every hour
78
-		4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
79
-		//next 30days, one version per day
80
-		5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81
-		//until the end one version per week
82
-		6 => array('intervalEndsAfter' => -1,      'step' => 604800),
83
-	);
84
-
85
-	/** @var \OCA\Files_Versions\AppInfo\Application */
86
-	private static $application;
87
-
88
-	/**
89
-	 * get the UID of the owner of the file and the path to the file relative to
90
-	 * owners files folder
91
-	 *
92
-	 * @param string $filename
93
-	 * @return array
94
-	 * @throws \OC\User\NoUserException
95
-	 */
96
-	public static function getUidAndFilename($filename) {
97
-		$uid = Filesystem::getOwner($filename);
98
-		$userManager = \OC::$server->getUserManager();
99
-		// if the user with the UID doesn't exists, e.g. because the UID points
100
-		// to a remote user with a federated cloud ID we use the current logged-in
101
-		// user. We need a valid local user to create the versions
102
-		if (!$userManager->userExists($uid)) {
103
-			$uid = User::getUser();
104
-		}
105
-		Filesystem::initMountPoints($uid);
106
-		if ( $uid !== User::getUser() ) {
107
-			$info = Filesystem::getFileInfo($filename);
108
-			$ownerView = new View('/'.$uid.'/files');
109
-			try {
110
-				$filename = $ownerView->getPath($info['fileid']);
111
-				// make sure that the file name doesn't end with a trailing slash
112
-				// can for example happen single files shared across servers
113
-				$filename = rtrim($filename, '/');
114
-			} catch (NotFoundException $e) {
115
-				$filename = null;
116
-			}
117
-		}
118
-		return [$uid, $filename];
119
-	}
120
-
121
-	/**
122
-	 * Remember the owner and the owner path of the source file
123
-	 *
124
-	 * @param string $source source path
125
-	 */
126
-	public static function setSourcePathAndUser($source) {
127
-		list($uid, $path) = self::getUidAndFilename($source);
128
-		self::$sourcePathAndUser[$source] = array('uid' => $uid, 'path' => $path);
129
-	}
130
-
131
-	/**
132
-	 * Gets the owner and the owner path from the source path
133
-	 *
134
-	 * @param string $source source path
135
-	 * @return array with user id and path
136
-	 */
137
-	public static function getSourcePathAndUser($source) {
138
-
139
-		if (isset(self::$sourcePathAndUser[$source])) {
140
-			$uid = self::$sourcePathAndUser[$source]['uid'];
141
-			$path = self::$sourcePathAndUser[$source]['path'];
142
-			unset(self::$sourcePathAndUser[$source]);
143
-		} else {
144
-			$uid = $path = false;
145
-		}
146
-		return array($uid, $path);
147
-	}
148
-
149
-	/**
150
-	 * get current size of all versions from a given user
151
-	 *
152
-	 * @param string $user user who owns the versions
153
-	 * @return int versions size
154
-	 */
155
-	private static function getVersionsSize($user) {
156
-		$view = new View('/' . $user);
157
-		$fileInfo = $view->getFileInfo('/files_versions');
158
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159
-	}
160
-
161
-	/**
162
-	 * store a new version of a file.
163
-	 */
164
-	public static function store($filename) {
165
-
166
-		// if the file gets streamed we need to remove the .part extension
167
-		// to get the right target
168
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
169
-		if ($ext === 'part') {
170
-			$filename = substr($filename, 0, -5);
171
-		}
172
-
173
-		// we only handle existing files
174
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175
-			return false;
176
-		}
177
-
178
-		list($uid, $filename) = self::getUidAndFilename($filename);
179
-
180
-		$files_view = new View('/'.$uid .'/files');
181
-		$users_view = new View('/'.$uid);
182
-
183
-		$eventDispatcher = \OC::$server->getEventDispatcher();
184
-		$id = $files_view->getFileInfo($filename)->getId();
185
-		$nodes = \OC::$server->getRootFolder()->getById($id);
186
-		foreach ($nodes as $node) {
187
-			$event = new CreateVersionEvent($node);
188
-			$eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
189
-			if ($event->shouldCreateVersion() === false) {
190
-				return false;
191
-			}
192
-		}
193
-
194
-		// no use making versions for empty files
195
-		if ($files_view->filesize($filename) === 0) {
196
-			return false;
197
-		}
198
-
199
-		// create all parent folders
200
-		self::createMissingDirectories($filename, $users_view);
201
-
202
-		self::scheduleExpire($uid, $filename);
203
-
204
-		// store a new version of a file
205
-		$mtime = $users_view->filemtime('files/' . $filename);
206
-		$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
207
-		// call getFileInfo to enforce a file cache entry for the new version
208
-		$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
209
-	}
210
-
211
-
212
-	/**
213
-	 * mark file as deleted so that we can remove the versions if the file is gone
214
-	 * @param string $path
215
-	 */
216
-	public static function markDeletedFile($path) {
217
-		list($uid, $filename) = self::getUidAndFilename($path);
218
-		self::$deletedFiles[$path] = array(
219
-			'uid' => $uid,
220
-			'filename' => $filename);
221
-	}
222
-
223
-	/**
224
-	 * delete the version from the storage and cache
225
-	 *
226
-	 * @param View $view
227
-	 * @param string $path
228
-	 */
229
-	protected static function deleteVersion($view, $path) {
230
-		$view->unlink($path);
231
-		/**
232
-		 * @var \OC\Files\Storage\Storage $storage
233
-		 * @var string $internalPath
234
-		 */
235
-		list($storage, $internalPath) = $view->resolvePath($path);
236
-		$cache = $storage->getCache($internalPath);
237
-		$cache->remove($internalPath);
238
-	}
239
-
240
-	/**
241
-	 * Delete versions of a file
242
-	 */
243
-	public static function delete($path) {
244
-
245
-		$deletedFile = self::$deletedFiles[$path];
246
-		$uid = $deletedFile['uid'];
247
-		$filename = $deletedFile['filename'];
248
-
249
-		if (!Filesystem::file_exists($path)) {
250
-
251
-			$view = new View('/' . $uid . '/files_versions');
252
-
253
-			$versions = self::getVersions($uid, $filename);
254
-			if (!empty($versions)) {
255
-				foreach ($versions as $v) {
256
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
258
-					\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259
-				}
260
-			}
261
-		}
262
-		unset(self::$deletedFiles[$path]);
263
-	}
264
-
265
-	/**
266
-	 * Rename or copy versions of a file of the given paths
267
-	 *
268
-	 * @param string $sourcePath source path of the file to move, relative to
269
-	 * the currently logged in user's "files" folder
270
-	 * @param string $targetPath target path of the file to move, relative to
271
-	 * the currently logged in user's "files" folder
272
-	 * @param string $operation can be 'copy' or 'rename'
273
-	 */
274
-	public static function renameOrCopy($sourcePath, $targetPath, $operation) {
275
-		list($sourceOwner, $sourcePath) = self::getSourcePathAndUser($sourcePath);
276
-
277
-		// it was a upload of a existing file if no old path exists
278
-		// in this case the pre-hook already called the store method and we can
279
-		// stop here
280
-		if ($sourcePath === false) {
281
-			return true;
282
-		}
283
-
284
-		list($targetOwner, $targetPath) = self::getUidAndFilename($targetPath);
285
-
286
-		$sourcePath = ltrim($sourcePath, '/');
287
-		$targetPath = ltrim($targetPath, '/');
288
-
289
-		$rootView = new View('');
290
-
291
-		// did we move a directory ?
292
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
293
-			// does the directory exists for versions too ?
294
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
295
-				// create missing dirs if necessary
296
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
297
-
298
-				// move the directory containing the versions
299
-				$rootView->$operation(
300
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
-					'/' . $targetOwner . '/files_versions/' . $targetPath
302
-				);
303
-			}
304
-		} else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
305
-			// create missing dirs if necessary
306
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
307
-
308
-			foreach ($versions as $v) {
309
-				// move each version one by one to the target directory
310
-				$rootView->$operation(
311
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
313
-				);
314
-			}
315
-		}
316
-
317
-		// if we moved versions directly for a file, schedule expiration check for that file
318
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
319
-			self::scheduleExpire($targetOwner, $targetPath);
320
-		}
321
-
322
-	}
323
-
324
-	/**
325
-	 * Rollback to an old version of a file.
326
-	 *
327
-	 * @param string $file file name
328
-	 * @param int $revision revision timestamp
329
-	 * @return bool
330
-	 */
331
-	public static function rollback($file, $revision) {
332
-
333
-		// add expected leading slash
334
-		$file = '/' . ltrim($file, '/');
335
-		list($uid, $filename) = self::getUidAndFilename($file);
336
-		if ($uid === null || trim($filename, '/') === '') {
337
-			return false;
338
-		}
339
-
340
-		$users_view = new View('/'.$uid);
341
-		$files_view = new View('/'. User::getUser().'/files');
342
-
343
-		$versionCreated = false;
344
-
345
-		$fileInfo = $files_view->getFileInfo($file);
346
-
347
-		// check if user has the permissions to revert a version
348
-		if (!$fileInfo->isUpdateable()) {
349
-			return false;
350
-		}
351
-
352
-		//first create a new version
353
-		$version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
354
-		if (!$users_view->file_exists($version)) {
355
-			$users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
356
-			$versionCreated = true;
357
-		}
358
-
359
-		$fileToRestore =  'files_versions' . $filename . '.v' . $revision;
360
-
361
-		// Restore encrypted version of the old file for the newly restored file
362
-		// This has to happen manually here since the file is manually copied below
363
-		$oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
364
-		$oldFileInfo = $users_view->getFileInfo($fileToRestore);
365
-		$cache = $fileInfo->getStorage()->getCache();
366
-		$cache->update(
367
-			$fileInfo->getId(), [
368
-				'encrypted' => $oldVersion,
369
-				'encryptedVersion' => $oldVersion,
370
-				'size' => $oldFileInfo->getSize()
371
-			]
372
-		);
373
-
374
-		// rollback
375
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
376
-			$files_view->touch($file, $revision);
377
-			Storage::scheduleExpire($uid, $file);
378
-			\OC_Hook::emit('\OCP\Versions', 'rollback', array(
379
-				'path' => $filename,
380
-				'revision' => $revision,
381
-			));
382
-			return true;
383
-		} else if ($versionCreated) {
384
-			self::deleteVersion($users_view, $version);
385
-		}
386
-
387
-		return false;
388
-
389
-	}
390
-
391
-	/**
392
-	 * Stream copy file contents from $path1 to $path2
393
-	 *
394
-	 * @param View $view view to use for copying
395
-	 * @param string $path1 source file to copy
396
-	 * @param string $path2 target file
397
-	 *
398
-	 * @return bool true for success, false otherwise
399
-	 */
400
-	private static function copyFileContents($view, $path1, $path2) {
401
-		/** @var \OC\Files\Storage\Storage $storage1 */
402
-		list($storage1, $internalPath1) = $view->resolvePath($path1);
403
-		/** @var \OC\Files\Storage\Storage $storage2 */
404
-		list($storage2, $internalPath2) = $view->resolvePath($path2);
405
-
406
-		$view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
-		$view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
-
409
-		// TODO add a proper way of overwriting a file while maintaining file ids
410
-		if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
-			$source = $storage1->fopen($internalPath1, 'r');
412
-			$target = $storage2->fopen($internalPath2, 'w');
413
-			list(, $result) = \OC_Helper::streamCopy($source, $target);
414
-			fclose($source);
415
-			fclose($target);
416
-
417
-			if ($result !== false) {
418
-				$storage1->unlink($internalPath1);
419
-			}
420
-		} else {
421
-			$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
-		}
423
-
424
-		$view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
-		$view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
-
427
-		return ($result !== false);
428
-	}
429
-
430
-	/**
431
-	 * get a list of all available versions of a file in descending chronological order
432
-	 * @param string $uid user id from the owner of the file
433
-	 * @param string $filename file to find versions of, relative to the user files dir
434
-	 * @param string $userFullPath
435
-	 * @return array versions newest version first
436
-	 */
437
-	public static function getVersions($uid, $filename, $userFullPath = '') {
438
-		$versions = array();
439
-		if (empty($filename)) {
440
-			return $versions;
441
-		}
442
-		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
444
-
445
-		$pathinfo = pathinfo($filename);
446
-		$versionedFile = $pathinfo['basename'];
447
-
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
-
450
-		$dirContent = false;
451
-		if ($view->is_dir($dir)) {
452
-			$dirContent = $view->opendir($dir);
453
-		}
454
-
455
-		if ($dirContent === false) {
456
-			return $versions;
457
-		}
458
-
459
-		if (is_resource($dirContent)) {
460
-			while (($entryName = readdir($dirContent)) !== false) {
461
-				if (!Filesystem::isIgnoredDir($entryName)) {
462
-					$pathparts = pathinfo($entryName);
463
-					$filename = $pathparts['filename'];
464
-					if ($filename === $versionedFile) {
465
-						$pathparts = pathinfo($entryName);
466
-						$timestamp = substr($pathparts['extension'], 1);
467
-						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
469
-						$versions[$key]['version'] = $timestamp;
470
-						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
-						if (empty($userFullPath)) {
472
-							$versions[$key]['preview'] = '';
473
-						} else {
474
-							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
-						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
-						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
-						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
-					}
481
-				}
482
-			}
483
-			closedir($dirContent);
484
-		}
485
-
486
-		// sort with newest version first
487
-		krsort($versions);
488
-
489
-		return $versions;
490
-	}
491
-
492
-	/**
493
-	 * Expire versions that older than max version retention time
494
-	 * @param string $uid
495
-	 */
496
-	public static function expireOlderThanMaxForUser($uid){
497
-		$expiration = self::getExpiration();
498
-		$threshold = $expiration->getMaxAgeAsTimestamp();
499
-		$versions = self::getAllVersions($uid);
500
-		if (!$threshold || !array_key_exists('all', $versions)) {
501
-			return;
502
-		}
503
-
504
-		$toDelete = [];
505
-		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if ((int)$version['version'] <$threshold) {
507
-				$toDelete[$key] = $version;
508
-			} else {
509
-				//Versions are sorted by time - nothing mo to iterate.
510
-				break;
511
-			}
512
-		}
513
-
514
-		$view = new View('/' . $uid . '/files_versions');
515
-		if (!empty($toDelete)) {
516
-			foreach ($toDelete as $version) {
517
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520
-			}
521
-		}
522
-	}
523
-
524
-	/**
525
-	 * translate a timestamp into a string like "5 days ago"
526
-	 * @param int $timestamp
527
-	 * @return string for example "5 days ago"
528
-	 */
529
-	private static function getHumanReadableTimestamp($timestamp) {
530
-
531
-		$diff = time() - $timestamp;
532
-
533
-		if ($diff < 60) { // first minute
534
-			return  $diff . " seconds ago";
535
-		} elseif ($diff < 3600) { //first hour
536
-			return round($diff / 60) . " minutes ago";
537
-		} elseif ($diff < 86400) { // first day
538
-			return round($diff / 3600) . " hours ago";
539
-		} elseif ($diff < 604800) { //first week
540
-			return round($diff / 86400) . " days ago";
541
-		} elseif ($diff < 2419200) { //first month
542
-			return round($diff / 604800) . " weeks ago";
543
-		} elseif ($diff < 29030400) { // first year
544
-			return round($diff / 2419200) . " months ago";
545
-		} else {
546
-			return round($diff / 29030400) . " years ago";
547
-		}
548
-
549
-	}
550
-
551
-	/**
552
-	 * returns all stored file versions from a given user
553
-	 * @param string $uid id of the user
554
-	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555
-	 */
556
-	private static function getAllVersions($uid) {
557
-		$view = new View('/' . $uid . '/');
558
-		$dirs = array(self::VERSIONS_ROOT);
559
-		$versions = array();
560
-
561
-		while (!empty($dirs)) {
562
-			$dir = array_pop($dirs);
563
-			$files = $view->getDirectoryContent($dir);
564
-
565
-			foreach ($files as $file) {
566
-				$fileData = $file->getData();
567
-				$filePath = $dir . '/' . $fileData['name'];
568
-				if ($file['type'] === 'dir') {
569
-					$dirs[] = $filePath;
570
-				} else {
571
-					$versionsBegin = strrpos($filePath, '.v');
572
-					$relPathStart = strlen(self::VERSIONS_ROOT);
573
-					$version = substr($filePath, $versionsBegin + 2);
574
-					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
-					$key = $version . '#' . $relpath;
576
-					$versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577
-				}
578
-			}
579
-		}
580
-
581
-		// newest version first
582
-		krsort($versions);
583
-
584
-		$result = array();
585
-
586
-		foreach ($versions as $key => $value) {
587
-			$size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
588
-			$filename = $value['path'];
589
-
590
-			$result['all'][$key]['version'] = $value['timestamp'];
591
-			$result['all'][$key]['path'] = $filename;
592
-			$result['all'][$key]['size'] = $size;
593
-
594
-			$result['by_file'][$filename][$key]['version'] = $value['timestamp'];
595
-			$result['by_file'][$filename][$key]['path'] = $filename;
596
-			$result['by_file'][$filename][$key]['size'] = $size;
597
-		}
598
-
599
-		return $result;
600
-	}
601
-
602
-	/**
603
-	 * get list of files we want to expire
604
-	 * @param array $versions list of versions
605
-	 * @param integer $time
606
-	 * @param bool $quotaExceeded is versions storage limit reached
607
-	 * @return array containing the list of to deleted versions and the size of them
608
-	 */
609
-	protected static function getExpireList($time, $versions, $quotaExceeded = false) {
610
-		$expiration = self::getExpiration();
611
-
612
-		if ($expiration->shouldAutoExpire()) {
613
-			list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614
-		} else {
615
-			$size = 0;
616
-			$toDelete = [];  // versions we want to delete
617
-		}
618
-
619
-		foreach ($versions as $key => $version) {
620
-			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621
-				$size += $version['size'];
622
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
623
-			}
624
-		}
625
-
626
-		return [$toDelete, $size];
627
-	}
628
-
629
-	/**
630
-	 * get list of files we want to expire
631
-	 * @param array $versions list of versions
632
-	 * @param integer $time
633
-	 * @return array containing the list of to deleted versions and the size of them
634
-	 */
635
-	protected static function getAutoExpireList($time, $versions) {
636
-		$size = 0;
637
-		$toDelete = array();  // versions we want to delete
638
-
639
-		$interval = 1;
640
-		$step = Storage::$max_versions_per_interval[$interval]['step'];
641
-		if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
642
-			$nextInterval = -1;
643
-		} else {
644
-			$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
645
-		}
646
-
647
-		$firstVersion = reset($versions);
648
-		$firstKey = key($versions);
649
-		$prevTimestamp = $firstVersion['version'];
650
-		$nextVersion = $firstVersion['version'] - $step;
651
-		unset($versions[$firstKey]);
652
-
653
-		foreach ($versions as $key => $version) {
654
-			$newInterval = true;
655
-			while ($newInterval) {
656
-				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657
-					if ($version['version'] > $nextVersion) {
658
-						//distance between two version too small, mark to delete
659
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
660
-						$size += $version['size'];
661
-						\OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
662
-					} else {
663
-						$nextVersion = $version['version'] - $step;
664
-						$prevTimestamp = $version['version'];
665
-					}
666
-					$newInterval = false; // version checked so we can move to the next one
667
-				} else { // time to move on to the next interval
668
-					$interval++;
669
-					$step = Storage::$max_versions_per_interval[$interval]['step'];
670
-					$nextVersion = $prevTimestamp - $step;
671
-					if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
672
-						$nextInterval = -1;
673
-					} else {
674
-						$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
675
-					}
676
-					$newInterval = true; // we changed the interval -> check same version with new interval
677
-				}
678
-			}
679
-		}
680
-
681
-		return array($toDelete, $size);
682
-	}
683
-
684
-	/**
685
-	 * Schedule versions expiration for the given file
686
-	 *
687
-	 * @param string $uid owner of the file
688
-	 * @param string $fileName file/folder for which to schedule expiration
689
-	 */
690
-	private static function scheduleExpire($uid, $fileName) {
691
-		// let the admin disable auto expire
692
-		$expiration = self::getExpiration();
693
-		if ($expiration->isEnabled()) {
694
-			$command = new Expire($uid, $fileName);
695
-			\OC::$server->getCommandBus()->push($command);
696
-		}
697
-	}
698
-
699
-	/**
700
-	 * Expire versions which exceed the quota.
701
-	 *
702
-	 * This will setup the filesystem for the given user but will not
703
-	 * tear it down afterwards.
704
-	 *
705
-	 * @param string $filename path to file to expire
706
-	 * @param string $uid user for which to expire the version
707
-	 * @return bool|int|null
708
-	 */
709
-	public static function expire($filename, $uid) {
710
-		$expiration = self::getExpiration();
711
-
712
-		if ($expiration->isEnabled()) {
713
-			// get available disk space for user
714
-			$user = \OC::$server->getUserManager()->get($uid);
715
-			if (is_null($user)) {
716
-				\OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
717
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
718
-			}
719
-
720
-			\OC_Util::setupFS($uid);
721
-
722
-			if (!Filesystem::file_exists($filename)) {
723
-				return false;
724
-			}
725
-
726
-			if (empty($filename)) {
727
-				// file maybe renamed or deleted
728
-				return false;
729
-			}
730
-			$versionsFileview = new View('/'.$uid.'/files_versions');
731
-
732
-			$softQuota = true;
733
-			$quota = $user->getQuota();
734
-			if ( $quota === null || $quota === 'none' ) {
735
-				$quota = Filesystem::free_space('/');
736
-				$softQuota = false;
737
-			} else {
738
-				$quota = \OCP\Util::computerFileSize($quota);
739
-			}
740
-
741
-			// make sure that we have the current size of the version history
742
-			$versionsSize = self::getVersionsSize($uid);
743
-
744
-			// calculate available space for version history
745
-			// subtract size of files and current versions size from quota
746
-			if ($quota >= 0) {
747
-				if ($softQuota) {
748
-					$files_view = new View('/' . $uid . '/files');
749
-					$rootInfo = $files_view->getFileInfo('/', false);
750
-					$free = $quota - $rootInfo['size']; // remaining free space for user
751
-					if ($free > 0) {
752
-						$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
753
-					} else {
754
-						$availableSpace = $free - $versionsSize;
755
-					}
756
-				} else {
757
-					$availableSpace = $quota;
758
-				}
759
-			} else {
760
-				$availableSpace = PHP_INT_MAX;
761
-			}
762
-
763
-			$allVersions = Storage::getVersions($uid, $filename);
764
-
765
-			$time = time();
766
-			list($toDelete, $sizeOfDeletedVersions) = self::getExpireList($time, $allVersions, $availableSpace <= 0);
767
-
768
-			$availableSpace = $availableSpace + $sizeOfDeletedVersions;
769
-			$versionsSize = $versionsSize - $sizeOfDeletedVersions;
770
-
771
-			// if still not enough free space we rearrange the versions from all files
772
-			if ($availableSpace <= 0) {
773
-				$result = Storage::getAllVersions($uid);
774
-				$allVersions = $result['all'];
775
-
776
-				foreach ($result['by_file'] as $versions) {
777
-					list($toDeleteNew, $size) = self::getExpireList($time, $versions, $availableSpace <= 0);
778
-					$toDelete = array_merge($toDelete, $toDeleteNew);
779
-					$sizeOfDeletedVersions += $size;
780
-				}
781
-				$availableSpace = $availableSpace + $sizeOfDeletedVersions;
782
-				$versionsSize = $versionsSize - $sizeOfDeletedVersions;
783
-			}
784
-
785
-			$logger = \OC::$server->getLogger();
786
-			foreach($toDelete as $key => $path) {
787
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
788
-				self::deleteVersion($versionsFileview, $path);
789
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
790
-				unset($allVersions[$key]); // update array with the versions we keep
791
-				$logger->info('Expire: ' . $path, ['app' => 'files_versions']);
792
-			}
793
-
794
-			// Check if enough space is available after versions are rearranged.
795
-			// If not we delete the oldest versions until we meet the size limit for versions,
796
-			// but always keep the two latest versions
797
-			$numOfVersions = count($allVersions) -2 ;
798
-			$i = 0;
799
-			// sort oldest first and make sure that we start at the first element
800
-			ksort($allVersions);
801
-			reset($allVersions);
802
-			while ($availableSpace < 0 && $i < $numOfVersions) {
803
-				$version = current($allVersions);
804
-				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
805
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
806
-				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
807
-				\OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
808
-				$versionsSize -= $version['size'];
809
-				$availableSpace += $version['size'];
810
-				next($allVersions);
811
-				$i++;
812
-			}
813
-
814
-			return $versionsSize; // finally return the new size of the version history
815
-		}
816
-
817
-		return false;
818
-	}
819
-
820
-	/**
821
-	 * Create recursively missing directories inside of files_versions
822
-	 * that match the given path to a file.
823
-	 *
824
-	 * @param string $filename $path to a file, relative to the user's
825
-	 * "files" folder
826
-	 * @param View $view view on data/user/
827
-	 */
828
-	private static function createMissingDirectories($filename, $view) {
829
-		$dirname = Filesystem::normalizePath(dirname($filename));
830
-		$dirParts = explode('/', $dirname);
831
-		$dir = "/files_versions";
832
-		foreach ($dirParts as $part) {
833
-			$dir = $dir . '/' . $part;
834
-			if (!$view->file_exists($dir)) {
835
-				$view->mkdir($dir);
836
-			}
837
-		}
838
-	}
839
-
840
-	/**
841
-	 * Static workaround
842
-	 * @return Expiration
843
-	 */
844
-	protected static function getExpiration(){
845
-		if (is_null(self::$application)) {
846
-			self::$application = new Application();
847
-		}
848
-		return self::$application->getContainer()->query('Expiration');
849
-	}
57
+    const DEFAULTENABLED=true;
58
+    const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
59
+    const VERSIONS_ROOT = 'files_versions/';
60
+
61
+    const DELETE_TRIGGER_MASTER_REMOVED = 0;
62
+    const DELETE_TRIGGER_RETENTION_CONSTRAINT = 1;
63
+    const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;
64
+
65
+    // files for which we can remove the versions after the delete operation was successful
66
+    private static $deletedFiles = array();
67
+
68
+    private static $sourcePathAndUser = array();
69
+
70
+    private static $max_versions_per_interval = array(
71
+        //first 10sec, one version every 2sec
72
+        1 => array('intervalEndsAfter' => 10,      'step' => 2),
73
+        //next minute, one version every 10sec
74
+        2 => array('intervalEndsAfter' => 60,      'step' => 10),
75
+        //next hour, one version every minute
76
+        3 => array('intervalEndsAfter' => 3600,    'step' => 60),
77
+        //next 24h, one version every hour
78
+        4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
79
+        //next 30days, one version per day
80
+        5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81
+        //until the end one version per week
82
+        6 => array('intervalEndsAfter' => -1,      'step' => 604800),
83
+    );
84
+
85
+    /** @var \OCA\Files_Versions\AppInfo\Application */
86
+    private static $application;
87
+
88
+    /**
89
+     * get the UID of the owner of the file and the path to the file relative to
90
+     * owners files folder
91
+     *
92
+     * @param string $filename
93
+     * @return array
94
+     * @throws \OC\User\NoUserException
95
+     */
96
+    public static function getUidAndFilename($filename) {
97
+        $uid = Filesystem::getOwner($filename);
98
+        $userManager = \OC::$server->getUserManager();
99
+        // if the user with the UID doesn't exists, e.g. because the UID points
100
+        // to a remote user with a federated cloud ID we use the current logged-in
101
+        // user. We need a valid local user to create the versions
102
+        if (!$userManager->userExists($uid)) {
103
+            $uid = User::getUser();
104
+        }
105
+        Filesystem::initMountPoints($uid);
106
+        if ( $uid !== User::getUser() ) {
107
+            $info = Filesystem::getFileInfo($filename);
108
+            $ownerView = new View('/'.$uid.'/files');
109
+            try {
110
+                $filename = $ownerView->getPath($info['fileid']);
111
+                // make sure that the file name doesn't end with a trailing slash
112
+                // can for example happen single files shared across servers
113
+                $filename = rtrim($filename, '/');
114
+            } catch (NotFoundException $e) {
115
+                $filename = null;
116
+            }
117
+        }
118
+        return [$uid, $filename];
119
+    }
120
+
121
+    /**
122
+     * Remember the owner and the owner path of the source file
123
+     *
124
+     * @param string $source source path
125
+     */
126
+    public static function setSourcePathAndUser($source) {
127
+        list($uid, $path) = self::getUidAndFilename($source);
128
+        self::$sourcePathAndUser[$source] = array('uid' => $uid, 'path' => $path);
129
+    }
130
+
131
+    /**
132
+     * Gets the owner and the owner path from the source path
133
+     *
134
+     * @param string $source source path
135
+     * @return array with user id and path
136
+     */
137
+    public static function getSourcePathAndUser($source) {
138
+
139
+        if (isset(self::$sourcePathAndUser[$source])) {
140
+            $uid = self::$sourcePathAndUser[$source]['uid'];
141
+            $path = self::$sourcePathAndUser[$source]['path'];
142
+            unset(self::$sourcePathAndUser[$source]);
143
+        } else {
144
+            $uid = $path = false;
145
+        }
146
+        return array($uid, $path);
147
+    }
148
+
149
+    /**
150
+     * get current size of all versions from a given user
151
+     *
152
+     * @param string $user user who owns the versions
153
+     * @return int versions size
154
+     */
155
+    private static function getVersionsSize($user) {
156
+        $view = new View('/' . $user);
157
+        $fileInfo = $view->getFileInfo('/files_versions');
158
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159
+    }
160
+
161
+    /**
162
+     * store a new version of a file.
163
+     */
164
+    public static function store($filename) {
165
+
166
+        // if the file gets streamed we need to remove the .part extension
167
+        // to get the right target
168
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
169
+        if ($ext === 'part') {
170
+            $filename = substr($filename, 0, -5);
171
+        }
172
+
173
+        // we only handle existing files
174
+        if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175
+            return false;
176
+        }
177
+
178
+        list($uid, $filename) = self::getUidAndFilename($filename);
179
+
180
+        $files_view = new View('/'.$uid .'/files');
181
+        $users_view = new View('/'.$uid);
182
+
183
+        $eventDispatcher = \OC::$server->getEventDispatcher();
184
+        $id = $files_view->getFileInfo($filename)->getId();
185
+        $nodes = \OC::$server->getRootFolder()->getById($id);
186
+        foreach ($nodes as $node) {
187
+            $event = new CreateVersionEvent($node);
188
+            $eventDispatcher->dispatch('OCA\Files_Versions::createVersion', $event);
189
+            if ($event->shouldCreateVersion() === false) {
190
+                return false;
191
+            }
192
+        }
193
+
194
+        // no use making versions for empty files
195
+        if ($files_view->filesize($filename) === 0) {
196
+            return false;
197
+        }
198
+
199
+        // create all parent folders
200
+        self::createMissingDirectories($filename, $users_view);
201
+
202
+        self::scheduleExpire($uid, $filename);
203
+
204
+        // store a new version of a file
205
+        $mtime = $users_view->filemtime('files/' . $filename);
206
+        $users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
207
+        // call getFileInfo to enforce a file cache entry for the new version
208
+        $users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
209
+    }
210
+
211
+
212
+    /**
213
+     * mark file as deleted so that we can remove the versions if the file is gone
214
+     * @param string $path
215
+     */
216
+    public static function markDeletedFile($path) {
217
+        list($uid, $filename) = self::getUidAndFilename($path);
218
+        self::$deletedFiles[$path] = array(
219
+            'uid' => $uid,
220
+            'filename' => $filename);
221
+    }
222
+
223
+    /**
224
+     * delete the version from the storage and cache
225
+     *
226
+     * @param View $view
227
+     * @param string $path
228
+     */
229
+    protected static function deleteVersion($view, $path) {
230
+        $view->unlink($path);
231
+        /**
232
+         * @var \OC\Files\Storage\Storage $storage
233
+         * @var string $internalPath
234
+         */
235
+        list($storage, $internalPath) = $view->resolvePath($path);
236
+        $cache = $storage->getCache($internalPath);
237
+        $cache->remove($internalPath);
238
+    }
239
+
240
+    /**
241
+     * Delete versions of a file
242
+     */
243
+    public static function delete($path) {
244
+
245
+        $deletedFile = self::$deletedFiles[$path];
246
+        $uid = $deletedFile['uid'];
247
+        $filename = $deletedFile['filename'];
248
+
249
+        if (!Filesystem::file_exists($path)) {
250
+
251
+            $view = new View('/' . $uid . '/files_versions');
252
+
253
+            $versions = self::getVersions($uid, $filename);
254
+            if (!empty($versions)) {
255
+                foreach ($versions as $v) {
256
+                    \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
+                    self::deleteVersion($view, $filename . '.v' . $v['version']);
258
+                    \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259
+                }
260
+            }
261
+        }
262
+        unset(self::$deletedFiles[$path]);
263
+    }
264
+
265
+    /**
266
+     * Rename or copy versions of a file of the given paths
267
+     *
268
+     * @param string $sourcePath source path of the file to move, relative to
269
+     * the currently logged in user's "files" folder
270
+     * @param string $targetPath target path of the file to move, relative to
271
+     * the currently logged in user's "files" folder
272
+     * @param string $operation can be 'copy' or 'rename'
273
+     */
274
+    public static function renameOrCopy($sourcePath, $targetPath, $operation) {
275
+        list($sourceOwner, $sourcePath) = self::getSourcePathAndUser($sourcePath);
276
+
277
+        // it was a upload of a existing file if no old path exists
278
+        // in this case the pre-hook already called the store method and we can
279
+        // stop here
280
+        if ($sourcePath === false) {
281
+            return true;
282
+        }
283
+
284
+        list($targetOwner, $targetPath) = self::getUidAndFilename($targetPath);
285
+
286
+        $sourcePath = ltrim($sourcePath, '/');
287
+        $targetPath = ltrim($targetPath, '/');
288
+
289
+        $rootView = new View('');
290
+
291
+        // did we move a directory ?
292
+        if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
293
+            // does the directory exists for versions too ?
294
+            if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
295
+                // create missing dirs if necessary
296
+                self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
297
+
298
+                // move the directory containing the versions
299
+                $rootView->$operation(
300
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
+                    '/' . $targetOwner . '/files_versions/' . $targetPath
302
+                );
303
+            }
304
+        } else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
305
+            // create missing dirs if necessary
306
+            self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
307
+
308
+            foreach ($versions as $v) {
309
+                // move each version one by one to the target directory
310
+                $rootView->$operation(
311
+                    '/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
+                    '/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
313
+                );
314
+            }
315
+        }
316
+
317
+        // if we moved versions directly for a file, schedule expiration check for that file
318
+        if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
319
+            self::scheduleExpire($targetOwner, $targetPath);
320
+        }
321
+
322
+    }
323
+
324
+    /**
325
+     * Rollback to an old version of a file.
326
+     *
327
+     * @param string $file file name
328
+     * @param int $revision revision timestamp
329
+     * @return bool
330
+     */
331
+    public static function rollback($file, $revision) {
332
+
333
+        // add expected leading slash
334
+        $file = '/' . ltrim($file, '/');
335
+        list($uid, $filename) = self::getUidAndFilename($file);
336
+        if ($uid === null || trim($filename, '/') === '') {
337
+            return false;
338
+        }
339
+
340
+        $users_view = new View('/'.$uid);
341
+        $files_view = new View('/'. User::getUser().'/files');
342
+
343
+        $versionCreated = false;
344
+
345
+        $fileInfo = $files_view->getFileInfo($file);
346
+
347
+        // check if user has the permissions to revert a version
348
+        if (!$fileInfo->isUpdateable()) {
349
+            return false;
350
+        }
351
+
352
+        //first create a new version
353
+        $version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
354
+        if (!$users_view->file_exists($version)) {
355
+            $users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
356
+            $versionCreated = true;
357
+        }
358
+
359
+        $fileToRestore =  'files_versions' . $filename . '.v' . $revision;
360
+
361
+        // Restore encrypted version of the old file for the newly restored file
362
+        // This has to happen manually here since the file is manually copied below
363
+        $oldVersion = $users_view->getFileInfo($fileToRestore)->getEncryptedVersion();
364
+        $oldFileInfo = $users_view->getFileInfo($fileToRestore);
365
+        $cache = $fileInfo->getStorage()->getCache();
366
+        $cache->update(
367
+            $fileInfo->getId(), [
368
+                'encrypted' => $oldVersion,
369
+                'encryptedVersion' => $oldVersion,
370
+                'size' => $oldFileInfo->getSize()
371
+            ]
372
+        );
373
+
374
+        // rollback
375
+        if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
376
+            $files_view->touch($file, $revision);
377
+            Storage::scheduleExpire($uid, $file);
378
+            \OC_Hook::emit('\OCP\Versions', 'rollback', array(
379
+                'path' => $filename,
380
+                'revision' => $revision,
381
+            ));
382
+            return true;
383
+        } else if ($versionCreated) {
384
+            self::deleteVersion($users_view, $version);
385
+        }
386
+
387
+        return false;
388
+
389
+    }
390
+
391
+    /**
392
+     * Stream copy file contents from $path1 to $path2
393
+     *
394
+     * @param View $view view to use for copying
395
+     * @param string $path1 source file to copy
396
+     * @param string $path2 target file
397
+     *
398
+     * @return bool true for success, false otherwise
399
+     */
400
+    private static function copyFileContents($view, $path1, $path2) {
401
+        /** @var \OC\Files\Storage\Storage $storage1 */
402
+        list($storage1, $internalPath1) = $view->resolvePath($path1);
403
+        /** @var \OC\Files\Storage\Storage $storage2 */
404
+        list($storage2, $internalPath2) = $view->resolvePath($path2);
405
+
406
+        $view->lockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
407
+        $view->lockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
408
+
409
+        // TODO add a proper way of overwriting a file while maintaining file ids
410
+        if ($storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage') || $storage2->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')) {
411
+            $source = $storage1->fopen($internalPath1, 'r');
412
+            $target = $storage2->fopen($internalPath2, 'w');
413
+            list(, $result) = \OC_Helper::streamCopy($source, $target);
414
+            fclose($source);
415
+            fclose($target);
416
+
417
+            if ($result !== false) {
418
+                $storage1->unlink($internalPath1);
419
+            }
420
+        } else {
421
+            $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
422
+        }
423
+
424
+        $view->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
425
+        $view->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
426
+
427
+        return ($result !== false);
428
+    }
429
+
430
+    /**
431
+     * get a list of all available versions of a file in descending chronological order
432
+     * @param string $uid user id from the owner of the file
433
+     * @param string $filename file to find versions of, relative to the user files dir
434
+     * @param string $userFullPath
435
+     * @return array versions newest version first
436
+     */
437
+    public static function getVersions($uid, $filename, $userFullPath = '') {
438
+        $versions = array();
439
+        if (empty($filename)) {
440
+            return $versions;
441
+        }
442
+        // fetch for old versions
443
+        $view = new View('/' . $uid . '/');
444
+
445
+        $pathinfo = pathinfo($filename);
446
+        $versionedFile = $pathinfo['basename'];
447
+
448
+        $dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
449
+
450
+        $dirContent = false;
451
+        if ($view->is_dir($dir)) {
452
+            $dirContent = $view->opendir($dir);
453
+        }
454
+
455
+        if ($dirContent === false) {
456
+            return $versions;
457
+        }
458
+
459
+        if (is_resource($dirContent)) {
460
+            while (($entryName = readdir($dirContent)) !== false) {
461
+                if (!Filesystem::isIgnoredDir($entryName)) {
462
+                    $pathparts = pathinfo($entryName);
463
+                    $filename = $pathparts['filename'];
464
+                    if ($filename === $versionedFile) {
465
+                        $pathparts = pathinfo($entryName);
466
+                        $timestamp = substr($pathparts['extension'], 1);
467
+                        $filename = $pathparts['filename'];
468
+                        $key = $timestamp . '#' . $filename;
469
+                        $versions[$key]['version'] = $timestamp;
470
+                        $versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471
+                        if (empty($userFullPath)) {
472
+                            $versions[$key]['preview'] = '';
473
+                        } else {
474
+                            $versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475
+                        }
476
+                        $versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
477
+                        $versions[$key]['name'] = $versionedFile;
478
+                        $versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
479
+                        $versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480
+                    }
481
+                }
482
+            }
483
+            closedir($dirContent);
484
+        }
485
+
486
+        // sort with newest version first
487
+        krsort($versions);
488
+
489
+        return $versions;
490
+    }
491
+
492
+    /**
493
+     * Expire versions that older than max version retention time
494
+     * @param string $uid
495
+     */
496
+    public static function expireOlderThanMaxForUser($uid){
497
+        $expiration = self::getExpiration();
498
+        $threshold = $expiration->getMaxAgeAsTimestamp();
499
+        $versions = self::getAllVersions($uid);
500
+        if (!$threshold || !array_key_exists('all', $versions)) {
501
+            return;
502
+        }
503
+
504
+        $toDelete = [];
505
+        foreach (array_reverse($versions['all']) as $key => $version) {
506
+            if ((int)$version['version'] <$threshold) {
507
+                $toDelete[$key] = $version;
508
+            } else {
509
+                //Versions are sorted by time - nothing mo to iterate.
510
+                break;
511
+            }
512
+        }
513
+
514
+        $view = new View('/' . $uid . '/files_versions');
515
+        if (!empty($toDelete)) {
516
+            foreach ($toDelete as $version) {
517
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
+                self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
519
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520
+            }
521
+        }
522
+    }
523
+
524
+    /**
525
+     * translate a timestamp into a string like "5 days ago"
526
+     * @param int $timestamp
527
+     * @return string for example "5 days ago"
528
+     */
529
+    private static function getHumanReadableTimestamp($timestamp) {
530
+
531
+        $diff = time() - $timestamp;
532
+
533
+        if ($diff < 60) { // first minute
534
+            return  $diff . " seconds ago";
535
+        } elseif ($diff < 3600) { //first hour
536
+            return round($diff / 60) . " minutes ago";
537
+        } elseif ($diff < 86400) { // first day
538
+            return round($diff / 3600) . " hours ago";
539
+        } elseif ($diff < 604800) { //first week
540
+            return round($diff / 86400) . " days ago";
541
+        } elseif ($diff < 2419200) { //first month
542
+            return round($diff / 604800) . " weeks ago";
543
+        } elseif ($diff < 29030400) { // first year
544
+            return round($diff / 2419200) . " months ago";
545
+        } else {
546
+            return round($diff / 29030400) . " years ago";
547
+        }
548
+
549
+    }
550
+
551
+    /**
552
+     * returns all stored file versions from a given user
553
+     * @param string $uid id of the user
554
+     * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555
+     */
556
+    private static function getAllVersions($uid) {
557
+        $view = new View('/' . $uid . '/');
558
+        $dirs = array(self::VERSIONS_ROOT);
559
+        $versions = array();
560
+
561
+        while (!empty($dirs)) {
562
+            $dir = array_pop($dirs);
563
+            $files = $view->getDirectoryContent($dir);
564
+
565
+            foreach ($files as $file) {
566
+                $fileData = $file->getData();
567
+                $filePath = $dir . '/' . $fileData['name'];
568
+                if ($file['type'] === 'dir') {
569
+                    $dirs[] = $filePath;
570
+                } else {
571
+                    $versionsBegin = strrpos($filePath, '.v');
572
+                    $relPathStart = strlen(self::VERSIONS_ROOT);
573
+                    $version = substr($filePath, $versionsBegin + 2);
574
+                    $relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
+                    $key = $version . '#' . $relpath;
576
+                    $versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577
+                }
578
+            }
579
+        }
580
+
581
+        // newest version first
582
+        krsort($versions);
583
+
584
+        $result = array();
585
+
586
+        foreach ($versions as $key => $value) {
587
+            $size = $view->filesize(self::VERSIONS_ROOT.'/'.$value['path'].'.v'.$value['timestamp']);
588
+            $filename = $value['path'];
589
+
590
+            $result['all'][$key]['version'] = $value['timestamp'];
591
+            $result['all'][$key]['path'] = $filename;
592
+            $result['all'][$key]['size'] = $size;
593
+
594
+            $result['by_file'][$filename][$key]['version'] = $value['timestamp'];
595
+            $result['by_file'][$filename][$key]['path'] = $filename;
596
+            $result['by_file'][$filename][$key]['size'] = $size;
597
+        }
598
+
599
+        return $result;
600
+    }
601
+
602
+    /**
603
+     * get list of files we want to expire
604
+     * @param array $versions list of versions
605
+     * @param integer $time
606
+     * @param bool $quotaExceeded is versions storage limit reached
607
+     * @return array containing the list of to deleted versions and the size of them
608
+     */
609
+    protected static function getExpireList($time, $versions, $quotaExceeded = false) {
610
+        $expiration = self::getExpiration();
611
+
612
+        if ($expiration->shouldAutoExpire()) {
613
+            list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614
+        } else {
615
+            $size = 0;
616
+            $toDelete = [];  // versions we want to delete
617
+        }
618
+
619
+        foreach ($versions as $key => $version) {
620
+            if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621
+                $size += $version['size'];
622
+                $toDelete[$key] = $version['path'] . '.v' . $version['version'];
623
+            }
624
+        }
625
+
626
+        return [$toDelete, $size];
627
+    }
628
+
629
+    /**
630
+     * get list of files we want to expire
631
+     * @param array $versions list of versions
632
+     * @param integer $time
633
+     * @return array containing the list of to deleted versions and the size of them
634
+     */
635
+    protected static function getAutoExpireList($time, $versions) {
636
+        $size = 0;
637
+        $toDelete = array();  // versions we want to delete
638
+
639
+        $interval = 1;
640
+        $step = Storage::$max_versions_per_interval[$interval]['step'];
641
+        if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
642
+            $nextInterval = -1;
643
+        } else {
644
+            $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
645
+        }
646
+
647
+        $firstVersion = reset($versions);
648
+        $firstKey = key($versions);
649
+        $prevTimestamp = $firstVersion['version'];
650
+        $nextVersion = $firstVersion['version'] - $step;
651
+        unset($versions[$firstKey]);
652
+
653
+        foreach ($versions as $key => $version) {
654
+            $newInterval = true;
655
+            while ($newInterval) {
656
+                if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657
+                    if ($version['version'] > $nextVersion) {
658
+                        //distance between two version too small, mark to delete
659
+                        $toDelete[$key] = $version['path'] . '.v' . $version['version'];
660
+                        $size += $version['size'];
661
+                        \OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
662
+                    } else {
663
+                        $nextVersion = $version['version'] - $step;
664
+                        $prevTimestamp = $version['version'];
665
+                    }
666
+                    $newInterval = false; // version checked so we can move to the next one
667
+                } else { // time to move on to the next interval
668
+                    $interval++;
669
+                    $step = Storage::$max_versions_per_interval[$interval]['step'];
670
+                    $nextVersion = $prevTimestamp - $step;
671
+                    if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
672
+                        $nextInterval = -1;
673
+                    } else {
674
+                        $nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
675
+                    }
676
+                    $newInterval = true; // we changed the interval -> check same version with new interval
677
+                }
678
+            }
679
+        }
680
+
681
+        return array($toDelete, $size);
682
+    }
683
+
684
+    /**
685
+     * Schedule versions expiration for the given file
686
+     *
687
+     * @param string $uid owner of the file
688
+     * @param string $fileName file/folder for which to schedule expiration
689
+     */
690
+    private static function scheduleExpire($uid, $fileName) {
691
+        // let the admin disable auto expire
692
+        $expiration = self::getExpiration();
693
+        if ($expiration->isEnabled()) {
694
+            $command = new Expire($uid, $fileName);
695
+            \OC::$server->getCommandBus()->push($command);
696
+        }
697
+    }
698
+
699
+    /**
700
+     * Expire versions which exceed the quota.
701
+     *
702
+     * This will setup the filesystem for the given user but will not
703
+     * tear it down afterwards.
704
+     *
705
+     * @param string $filename path to file to expire
706
+     * @param string $uid user for which to expire the version
707
+     * @return bool|int|null
708
+     */
709
+    public static function expire($filename, $uid) {
710
+        $expiration = self::getExpiration();
711
+
712
+        if ($expiration->isEnabled()) {
713
+            // get available disk space for user
714
+            $user = \OC::$server->getUserManager()->get($uid);
715
+            if (is_null($user)) {
716
+                \OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
717
+                throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
718
+            }
719
+
720
+            \OC_Util::setupFS($uid);
721
+
722
+            if (!Filesystem::file_exists($filename)) {
723
+                return false;
724
+            }
725
+
726
+            if (empty($filename)) {
727
+                // file maybe renamed or deleted
728
+                return false;
729
+            }
730
+            $versionsFileview = new View('/'.$uid.'/files_versions');
731
+
732
+            $softQuota = true;
733
+            $quota = $user->getQuota();
734
+            if ( $quota === null || $quota === 'none' ) {
735
+                $quota = Filesystem::free_space('/');
736
+                $softQuota = false;
737
+            } else {
738
+                $quota = \OCP\Util::computerFileSize($quota);
739
+            }
740
+
741
+            // make sure that we have the current size of the version history
742
+            $versionsSize = self::getVersionsSize($uid);
743
+
744
+            // calculate available space for version history
745
+            // subtract size of files and current versions size from quota
746
+            if ($quota >= 0) {
747
+                if ($softQuota) {
748
+                    $files_view = new View('/' . $uid . '/files');
749
+                    $rootInfo = $files_view->getFileInfo('/', false);
750
+                    $free = $quota - $rootInfo['size']; // remaining free space for user
751
+                    if ($free > 0) {
752
+                        $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $versionsSize; // how much space can be used for versions
753
+                    } else {
754
+                        $availableSpace = $free - $versionsSize;
755
+                    }
756
+                } else {
757
+                    $availableSpace = $quota;
758
+                }
759
+            } else {
760
+                $availableSpace = PHP_INT_MAX;
761
+            }
762
+
763
+            $allVersions = Storage::getVersions($uid, $filename);
764
+
765
+            $time = time();
766
+            list($toDelete, $sizeOfDeletedVersions) = self::getExpireList($time, $allVersions, $availableSpace <= 0);
767
+
768
+            $availableSpace = $availableSpace + $sizeOfDeletedVersions;
769
+            $versionsSize = $versionsSize - $sizeOfDeletedVersions;
770
+
771
+            // if still not enough free space we rearrange the versions from all files
772
+            if ($availableSpace <= 0) {
773
+                $result = Storage::getAllVersions($uid);
774
+                $allVersions = $result['all'];
775
+
776
+                foreach ($result['by_file'] as $versions) {
777
+                    list($toDeleteNew, $size) = self::getExpireList($time, $versions, $availableSpace <= 0);
778
+                    $toDelete = array_merge($toDelete, $toDeleteNew);
779
+                    $sizeOfDeletedVersions += $size;
780
+                }
781
+                $availableSpace = $availableSpace + $sizeOfDeletedVersions;
782
+                $versionsSize = $versionsSize - $sizeOfDeletedVersions;
783
+            }
784
+
785
+            $logger = \OC::$server->getLogger();
786
+            foreach($toDelete as $key => $path) {
787
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
788
+                self::deleteVersion($versionsFileview, $path);
789
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
790
+                unset($allVersions[$key]); // update array with the versions we keep
791
+                $logger->info('Expire: ' . $path, ['app' => 'files_versions']);
792
+            }
793
+
794
+            // Check if enough space is available after versions are rearranged.
795
+            // If not we delete the oldest versions until we meet the size limit for versions,
796
+            // but always keep the two latest versions
797
+            $numOfVersions = count($allVersions) -2 ;
798
+            $i = 0;
799
+            // sort oldest first and make sure that we start at the first element
800
+            ksort($allVersions);
801
+            reset($allVersions);
802
+            while ($availableSpace < 0 && $i < $numOfVersions) {
803
+                $version = current($allVersions);
804
+                \OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
805
+                self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
806
+                \OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
807
+                \OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
808
+                $versionsSize -= $version['size'];
809
+                $availableSpace += $version['size'];
810
+                next($allVersions);
811
+                $i++;
812
+            }
813
+
814
+            return $versionsSize; // finally return the new size of the version history
815
+        }
816
+
817
+        return false;
818
+    }
819
+
820
+    /**
821
+     * Create recursively missing directories inside of files_versions
822
+     * that match the given path to a file.
823
+     *
824
+     * @param string $filename $path to a file, relative to the user's
825
+     * "files" folder
826
+     * @param View $view view on data/user/
827
+     */
828
+    private static function createMissingDirectories($filename, $view) {
829
+        $dirname = Filesystem::normalizePath(dirname($filename));
830
+        $dirParts = explode('/', $dirname);
831
+        $dir = "/files_versions";
832
+        foreach ($dirParts as $part) {
833
+            $dir = $dir . '/' . $part;
834
+            if (!$view->file_exists($dir)) {
835
+                $view->mkdir($dir);
836
+            }
837
+        }
838
+    }
839
+
840
+    /**
841
+     * Static workaround
842
+     * @return Expiration
843
+     */
844
+    protected static function getExpiration(){
845
+        if (is_null(self::$application)) {
846
+            self::$application = new Application();
847
+        }
848
+        return self::$application->getContainer()->query('Expiration');
849
+    }
850 850
 
851 851
 }
Please login to merge, or discard this patch.
Spacing   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -54,8 +54,8 @@  discard block
 block discarded – undo
54 54
 
55 55
 class Storage {
56 56
 
57
-	const DEFAULTENABLED=true;
58
-	const DEFAULTMAXSIZE=50; // unit: percentage; 50% of available disk space/quota
57
+	const DEFAULTENABLED = true;
58
+	const DEFAULTMAXSIZE = 50; // unit: percentage; 50% of available disk space/quota
59 59
 	const VERSIONS_ROOT = 'files_versions/';
60 60
 
61 61
 	const DELETE_TRIGGER_MASTER_REMOVED = 0;
@@ -69,17 +69,17 @@  discard block
 block discarded – undo
69 69
 
70 70
 	private static $max_versions_per_interval = array(
71 71
 		//first 10sec, one version every 2sec
72
-		1 => array('intervalEndsAfter' => 10,      'step' => 2),
72
+		1 => array('intervalEndsAfter' => 10, 'step' => 2),
73 73
 		//next minute, one version every 10sec
74
-		2 => array('intervalEndsAfter' => 60,      'step' => 10),
74
+		2 => array('intervalEndsAfter' => 60, 'step' => 10),
75 75
 		//next hour, one version every minute
76
-		3 => array('intervalEndsAfter' => 3600,    'step' => 60),
76
+		3 => array('intervalEndsAfter' => 3600, 'step' => 60),
77 77
 		//next 24h, one version every hour
78
-		4 => array('intervalEndsAfter' => 86400,   'step' => 3600),
78
+		4 => array('intervalEndsAfter' => 86400, 'step' => 3600),
79 79
 		//next 30days, one version per day
80 80
 		5 => array('intervalEndsAfter' => 2592000, 'step' => 86400),
81 81
 		//until the end one version per week
82
-		6 => array('intervalEndsAfter' => -1,      'step' => 604800),
82
+		6 => array('intervalEndsAfter' => -1, 'step' => 604800),
83 83
 	);
84 84
 
85 85
 	/** @var \OCA\Files_Versions\AppInfo\Application */
@@ -103,7 +103,7 @@  discard block
 block discarded – undo
103 103
 			$uid = User::getUser();
104 104
 		}
105 105
 		Filesystem::initMountPoints($uid);
106
-		if ( $uid !== User::getUser() ) {
106
+		if ($uid !== User::getUser()) {
107 107
 			$info = Filesystem::getFileInfo($filename);
108 108
 			$ownerView = new View('/'.$uid.'/files');
109 109
 			try {
@@ -153,7 +153,7 @@  discard block
 block discarded – undo
153 153
 	 * @return int versions size
154 154
 	 */
155 155
 	private static function getVersionsSize($user) {
156
-		$view = new View('/' . $user);
156
+		$view = new View('/'.$user);
157 157
 		$fileInfo = $view->getFileInfo('/files_versions');
158 158
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
159 159
 	}
@@ -171,13 +171,13 @@  discard block
 block discarded – undo
171 171
 		}
172 172
 
173 173
 		// we only handle existing files
174
-		if (! Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
174
+		if (!Filesystem::file_exists($filename) || Filesystem::is_dir($filename)) {
175 175
 			return false;
176 176
 		}
177 177
 
178 178
 		list($uid, $filename) = self::getUidAndFilename($filename);
179 179
 
180
-		$files_view = new View('/'.$uid .'/files');
180
+		$files_view = new View('/'.$uid.'/files');
181 181
 		$users_view = new View('/'.$uid);
182 182
 
183 183
 		$eventDispatcher = \OC::$server->getEventDispatcher();
@@ -202,10 +202,10 @@  discard block
 block discarded – undo
202 202
 		self::scheduleExpire($uid, $filename);
203 203
 
204 204
 		// store a new version of a file
205
-		$mtime = $users_view->filemtime('files/' . $filename);
206
-		$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
205
+		$mtime = $users_view->filemtime('files/'.$filename);
206
+		$users_view->copy('files/'.$filename, 'files_versions/'.$filename.'.v'.$mtime);
207 207
 		// call getFileInfo to enforce a file cache entry for the new version
208
-		$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
208
+		$users_view->getFileInfo('files_versions/'.$filename.'.v'.$mtime);
209 209
 	}
210 210
 
211 211
 
@@ -248,14 +248,14 @@  discard block
 block discarded – undo
248 248
 
249 249
 		if (!Filesystem::file_exists($path)) {
250 250
 
251
-			$view = new View('/' . $uid . '/files_versions');
251
+			$view = new View('/'.$uid.'/files_versions');
252 252
 
253 253
 			$versions = self::getVersions($uid, $filename);
254 254
 			if (!empty($versions)) {
255 255
 				foreach ($versions as $v) {
256
-					\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
-					self::deleteVersion($view, $filename . '.v' . $v['version']);
258
-					\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path . $v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
256
+					\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path.$v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
257
+					self::deleteVersion($view, $filename.'.v'.$v['version']);
258
+					\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path.$v['version'], 'trigger' => self::DELETE_TRIGGER_MASTER_REMOVED));
259 259
 				}
260 260
 			}
261 261
 		}
@@ -289,33 +289,33 @@  discard block
 block discarded – undo
289 289
 		$rootView = new View('');
290 290
 
291 291
 		// did we move a directory ?
292
-		if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
292
+		if ($rootView->is_dir('/'.$targetOwner.'/files/'.$targetPath)) {
293 293
 			// does the directory exists for versions too ?
294
-			if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
294
+			if ($rootView->is_dir('/'.$sourceOwner.'/files_versions/'.$sourcePath)) {
295 295
 				// create missing dirs if necessary
296
-				self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
296
+				self::createMissingDirectories($targetPath, new View('/'.$targetOwner));
297 297
 
298 298
 				// move the directory containing the versions
299 299
 				$rootView->$operation(
300
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath,
301
-					'/' . $targetOwner . '/files_versions/' . $targetPath
300
+					'/'.$sourceOwner.'/files_versions/'.$sourcePath,
301
+					'/'.$targetOwner.'/files_versions/'.$targetPath
302 302
 				);
303 303
 			}
304
-		} else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
304
+		} else if ($versions = Storage::getVersions($sourceOwner, '/'.$sourcePath)) {
305 305
 			// create missing dirs if necessary
306
-			self::createMissingDirectories($targetPath, new View('/'. $targetOwner));
306
+			self::createMissingDirectories($targetPath, new View('/'.$targetOwner));
307 307
 
308 308
 			foreach ($versions as $v) {
309 309
 				// move each version one by one to the target directory
310 310
 				$rootView->$operation(
311
-					'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
312
-					'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
311
+					'/'.$sourceOwner.'/files_versions/'.$sourcePath.'.v'.$v['version'],
312
+					'/'.$targetOwner.'/files_versions/'.$targetPath.'.v'.$v['version']
313 313
 				);
314 314
 			}
315 315
 		}
316 316
 
317 317
 		// if we moved versions directly for a file, schedule expiration check for that file
318
-		if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
318
+		if (!$rootView->is_dir('/'.$targetOwner.'/files/'.$targetPath)) {
319 319
 			self::scheduleExpire($targetOwner, $targetPath);
320 320
 		}
321 321
 
@@ -331,14 +331,14 @@  discard block
 block discarded – undo
331 331
 	public static function rollback($file, $revision) {
332 332
 
333 333
 		// add expected leading slash
334
-		$file = '/' . ltrim($file, '/');
334
+		$file = '/'.ltrim($file, '/');
335 335
 		list($uid, $filename) = self::getUidAndFilename($file);
336 336
 		if ($uid === null || trim($filename, '/') === '') {
337 337
 			return false;
338 338
 		}
339 339
 
340 340
 		$users_view = new View('/'.$uid);
341
-		$files_view = new View('/'. User::getUser().'/files');
341
+		$files_view = new View('/'.User::getUser().'/files');
342 342
 
343 343
 		$versionCreated = false;
344 344
 
@@ -356,7 +356,7 @@  discard block
 block discarded – undo
356 356
 			$versionCreated = true;
357 357
 		}
358 358
 
359
-		$fileToRestore =  'files_versions' . $filename . '.v' . $revision;
359
+		$fileToRestore = 'files_versions'.$filename.'.v'.$revision;
360 360
 
361 361
 		// Restore encrypted version of the old file for the newly restored file
362 362
 		// This has to happen manually here since the file is manually copied below
@@ -372,7 +372,7 @@  discard block
 block discarded – undo
372 372
 		);
373 373
 
374 374
 		// rollback
375
-		if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
375
+		if (self::copyFileContents($users_view, $fileToRestore, 'files'.$filename)) {
376 376
 			$files_view->touch($file, $revision);
377 377
 			Storage::scheduleExpire($uid, $file);
378 378
 			\OC_Hook::emit('\OCP\Versions', 'rollback', array(
@@ -440,12 +440,12 @@  discard block
 block discarded – undo
440 440
 			return $versions;
441 441
 		}
442 442
 		// fetch for old versions
443
-		$view = new View('/' . $uid . '/');
443
+		$view = new View('/'.$uid.'/');
444 444
 
445 445
 		$pathinfo = pathinfo($filename);
446 446
 		$versionedFile = $pathinfo['basename'];
447 447
 
448
-		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT . '/' . $pathinfo['dirname']);
448
+		$dir = Filesystem::normalizePath(self::VERSIONS_ROOT.'/'.$pathinfo['dirname']);
449 449
 
450 450
 		$dirContent = false;
451 451
 		if ($view->is_dir($dir)) {
@@ -465,7 +465,7 @@  discard block
 block discarded – undo
465 465
 						$pathparts = pathinfo($entryName);
466 466
 						$timestamp = substr($pathparts['extension'], 1);
467 467
 						$filename = $pathparts['filename'];
468
-						$key = $timestamp . '#' . $filename;
468
+						$key = $timestamp.'#'.$filename;
469 469
 						$versions[$key]['version'] = $timestamp;
470 470
 						$versions[$key]['humanReadableTimestamp'] = self::getHumanReadableTimestamp($timestamp);
471 471
 						if (empty($userFullPath)) {
@@ -473,9 +473,9 @@  discard block
 block discarded – undo
473 473
 						} else {
474 474
 							$versions[$key]['preview'] = \OC::$server->getURLGenerator('files_version.Preview.getPreview', ['file' => $userFullPath, 'version' => $timestamp]);
475 475
 						}
476
-						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'] . '/' . $filename);
476
+						$versions[$key]['path'] = Filesystem::normalizePath($pathinfo['dirname'].'/'.$filename);
477 477
 						$versions[$key]['name'] = $versionedFile;
478
-						$versions[$key]['size'] = $view->filesize($dir . '/' . $entryName);
478
+						$versions[$key]['size'] = $view->filesize($dir.'/'.$entryName);
479 479
 						$versions[$key]['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($versionedFile);
480 480
 					}
481 481
 				}
@@ -493,7 +493,7 @@  discard block
 block discarded – undo
493 493
 	 * Expire versions that older than max version retention time
494 494
 	 * @param string $uid
495 495
 	 */
496
-	public static function expireOlderThanMaxForUser($uid){
496
+	public static function expireOlderThanMaxForUser($uid) {
497 497
 		$expiration = self::getExpiration();
498 498
 		$threshold = $expiration->getMaxAgeAsTimestamp();
499 499
 		$versions = self::getAllVersions($uid);
@@ -503,7 +503,7 @@  discard block
 block discarded – undo
503 503
 
504 504
 		$toDelete = [];
505 505
 		foreach (array_reverse($versions['all']) as $key => $version) {
506
-			if ((int)$version['version'] <$threshold) {
506
+			if ((int) $version['version'] < $threshold) {
507 507
 				$toDelete[$key] = $version;
508 508
 			} else {
509 509
 				//Versions are sorted by time - nothing mo to iterate.
@@ -511,11 +511,11 @@  discard block
 block discarded – undo
511 511
 			}
512 512
 		}
513 513
 
514
-		$view = new View('/' . $uid . '/files_versions');
514
+		$view = new View('/'.$uid.'/files_versions');
515 515
 		if (!empty($toDelete)) {
516 516
 			foreach ($toDelete as $version) {
517 517
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
518
-				self::deleteVersion($view, $version['path'] . '.v' . $version['version']);
518
+				self::deleteVersion($view, $version['path'].'.v'.$version['version']);
519 519
 				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_RETENTION_CONSTRAINT));
520 520
 			}
521 521
 		}
@@ -531,19 +531,19 @@  discard block
 block discarded – undo
531 531
 		$diff = time() - $timestamp;
532 532
 
533 533
 		if ($diff < 60) { // first minute
534
-			return  $diff . " seconds ago";
534
+			return  $diff." seconds ago";
535 535
 		} elseif ($diff < 3600) { //first hour
536
-			return round($diff / 60) . " minutes ago";
536
+			return round($diff / 60)." minutes ago";
537 537
 		} elseif ($diff < 86400) { // first day
538
-			return round($diff / 3600) . " hours ago";
538
+			return round($diff / 3600)." hours ago";
539 539
 		} elseif ($diff < 604800) { //first week
540
-			return round($diff / 86400) . " days ago";
540
+			return round($diff / 86400)." days ago";
541 541
 		} elseif ($diff < 2419200) { //first month
542
-			return round($diff / 604800) . " weeks ago";
542
+			return round($diff / 604800)." weeks ago";
543 543
 		} elseif ($diff < 29030400) { // first year
544
-			return round($diff / 2419200) . " months ago";
544
+			return round($diff / 2419200)." months ago";
545 545
 		} else {
546
-			return round($diff / 29030400) . " years ago";
546
+			return round($diff / 29030400)." years ago";
547 547
 		}
548 548
 
549 549
 	}
@@ -554,7 +554,7 @@  discard block
 block discarded – undo
554 554
 	 * @return array with contains two arrays 'all' which contains all versions sorted by age and 'by_file' which contains all versions sorted by filename
555 555
 	 */
556 556
 	private static function getAllVersions($uid) {
557
-		$view = new View('/' . $uid . '/');
557
+		$view = new View('/'.$uid.'/');
558 558
 		$dirs = array(self::VERSIONS_ROOT);
559 559
 		$versions = array();
560 560
 
@@ -564,7 +564,7 @@  discard block
 block discarded – undo
564 564
 
565 565
 			foreach ($files as $file) {
566 566
 				$fileData = $file->getData();
567
-				$filePath = $dir . '/' . $fileData['name'];
567
+				$filePath = $dir.'/'.$fileData['name'];
568 568
 				if ($file['type'] === 'dir') {
569 569
 					$dirs[] = $filePath;
570 570
 				} else {
@@ -572,7 +572,7 @@  discard block
 block discarded – undo
572 572
 					$relPathStart = strlen(self::VERSIONS_ROOT);
573 573
 					$version = substr($filePath, $versionsBegin + 2);
574 574
 					$relpath = substr($filePath, $relPathStart, $versionsBegin - $relPathStart);
575
-					$key = $version . '#' . $relpath;
575
+					$key = $version.'#'.$relpath;
576 576
 					$versions[$key] = array('path' => $relpath, 'timestamp' => $version);
577 577
 				}
578 578
 			}
@@ -613,13 +613,13 @@  discard block
 block discarded – undo
613 613
 			list($toDelete, $size) = self::getAutoExpireList($time, $versions);
614 614
 		} else {
615 615
 			$size = 0;
616
-			$toDelete = [];  // versions we want to delete
616
+			$toDelete = []; // versions we want to delete
617 617
 		}
618 618
 
619 619
 		foreach ($versions as $key => $version) {
620 620
 			if ($expiration->isExpired($version['version'], $quotaExceeded) && !isset($toDelete[$key])) {
621 621
 				$size += $version['size'];
622
-				$toDelete[$key] = $version['path'] . '.v' . $version['version'];
622
+				$toDelete[$key] = $version['path'].'.v'.$version['version'];
623 623
 			}
624 624
 		}
625 625
 
@@ -634,7 +634,7 @@  discard block
 block discarded – undo
634 634
 	 */
635 635
 	protected static function getAutoExpireList($time, $versions) {
636 636
 		$size = 0;
637
-		$toDelete = array();  // versions we want to delete
637
+		$toDelete = array(); // versions we want to delete
638 638
 
639 639
 		$interval = 1;
640 640
 		$step = Storage::$max_versions_per_interval[$interval]['step'];
@@ -656,9 +656,9 @@  discard block
 block discarded – undo
656 656
 				if ($nextInterval === -1 || $prevTimestamp > $nextInterval) {
657 657
 					if ($version['version'] > $nextVersion) {
658 658
 						//distance between two version too small, mark to delete
659
-						$toDelete[$key] = $version['path'] . '.v' . $version['version'];
659
+						$toDelete[$key] = $version['path'].'.v'.$version['version'];
660 660
 						$size += $version['size'];
661
-						\OC::$server->getLogger()->info('Mark to expire '. $version['path'] .' next version should be ' . $nextVersion . " or smaller. (prevTimestamp: " . $prevTimestamp . "; step: " . $step, ['app' => 'files_versions']);
661
+						\OC::$server->getLogger()->info('Mark to expire '.$version['path'].' next version should be '.$nextVersion." or smaller. (prevTimestamp: ".$prevTimestamp."; step: ".$step, ['app' => 'files_versions']);
662 662
 					} else {
663 663
 						$nextVersion = $version['version'] - $step;
664 664
 						$prevTimestamp = $version['version'];
@@ -713,8 +713,8 @@  discard block
 block discarded – undo
713 713
 			// get available disk space for user
714 714
 			$user = \OC::$server->getUserManager()->get($uid);
715 715
 			if (is_null($user)) {
716
-				\OC::$server->getLogger()->error('Backends provided no user object for ' . $uid, ['app' => 'files_versions']);
717
-				throw new \OC\User\NoUserException('Backends provided no user object for ' . $uid);
716
+				\OC::$server->getLogger()->error('Backends provided no user object for '.$uid, ['app' => 'files_versions']);
717
+				throw new \OC\User\NoUserException('Backends provided no user object for '.$uid);
718 718
 			}
719 719
 
720 720
 			\OC_Util::setupFS($uid);
@@ -731,7 +731,7 @@  discard block
 block discarded – undo
731 731
 
732 732
 			$softQuota = true;
733 733
 			$quota = $user->getQuota();
734
-			if ( $quota === null || $quota === 'none' ) {
734
+			if ($quota === null || $quota === 'none') {
735 735
 				$quota = Filesystem::free_space('/');
736 736
 				$softQuota = false;
737 737
 			} else {
@@ -745,7 +745,7 @@  discard block
 block discarded – undo
745 745
 			// subtract size of files and current versions size from quota
746 746
 			if ($quota >= 0) {
747 747
 				if ($softQuota) {
748
-					$files_view = new View('/' . $uid . '/files');
748
+					$files_view = new View('/'.$uid.'/files');
749 749
 					$rootInfo = $files_view->getFileInfo('/', false);
750 750
 					$free = $quota - $rootInfo['size']; // remaining free space for user
751 751
 					if ($free > 0) {
@@ -783,18 +783,18 @@  discard block
 block discarded – undo
783 783
 			}
784 784
 
785 785
 			$logger = \OC::$server->getLogger();
786
-			foreach($toDelete as $key => $path) {
786
+			foreach ($toDelete as $key => $path) {
787 787
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
788 788
 				self::deleteVersion($versionsFileview, $path);
789 789
 				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $path, 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
790 790
 				unset($allVersions[$key]); // update array with the versions we keep
791
-				$logger->info('Expire: ' . $path, ['app' => 'files_versions']);
791
+				$logger->info('Expire: '.$path, ['app' => 'files_versions']);
792 792
 			}
793 793
 
794 794
 			// Check if enough space is available after versions are rearranged.
795 795
 			// If not we delete the oldest versions until we meet the size limit for versions,
796 796
 			// but always keep the two latest versions
797
-			$numOfVersions = count($allVersions) -2 ;
797
+			$numOfVersions = count($allVersions) - 2;
798 798
 			$i = 0;
799 799
 			// sort oldest first and make sure that we start at the first element
800 800
 			ksort($allVersions);
@@ -802,9 +802,9 @@  discard block
 block discarded – undo
802 802
 			while ($availableSpace < 0 && $i < $numOfVersions) {
803 803
 				$version = current($allVersions);
804 804
 				\OC_Hook::emit('\OCP\Versions', 'preDelete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
805
-				self::deleteVersion($versionsFileview, $version['path'] . '.v' . $version['version']);
805
+				self::deleteVersion($versionsFileview, $version['path'].'.v'.$version['version']);
806 806
 				\OC_Hook::emit('\OCP\Versions', 'delete', array('path' => $version['path'].'.v'.$version['version'], 'trigger' => self::DELETE_TRIGGER_QUOTA_EXCEEDED));
807
-				\OC::$server->getLogger()->info('running out of space! Delete oldest version: ' . $version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
807
+				\OC::$server->getLogger()->info('running out of space! Delete oldest version: '.$version['path'].'.v'.$version['version'], ['app' => 'files_versions']);
808 808
 				$versionsSize -= $version['size'];
809 809
 				$availableSpace += $version['size'];
810 810
 				next($allVersions);
@@ -830,7 +830,7 @@  discard block
 block discarded – undo
830 830
 		$dirParts = explode('/', $dirname);
831 831
 		$dir = "/files_versions";
832 832
 		foreach ($dirParts as $part) {
833
-			$dir = $dir . '/' . $part;
833
+			$dir = $dir.'/'.$part;
834 834
 			if (!$view->file_exists($dir)) {
835 835
 				$view->mkdir($dir);
836 836
 			}
@@ -841,7 +841,7 @@  discard block
 block discarded – undo
841 841
 	 * Static workaround
842 842
 	 * @return Expiration
843 843
 	 */
844
-	protected static function getExpiration(){
844
+	protected static function getExpiration() {
845 845
 		if (is_null(self::$application)) {
846 846
 			self::$application = new Application();
847 847
 		}
Please login to merge, or discard this patch.
apps/files_external/lib/Service/LegacyStoragesService.php 2 patches
Indentation   +171 added lines, -171 removed lines patch added patch discarded remove patch
@@ -30,179 +30,179 @@
 block discarded – undo
30 30
  * Read mount config from legacy mount.json
31 31
  */
32 32
 abstract class LegacyStoragesService {
33
-	/** @var BackendService */
34
-	protected $backendService;
33
+    /** @var BackendService */
34
+    protected $backendService;
35 35
 
36
-	/**
37
-	 * Read legacy config data
38
-	 *
39
-	 * @return array list of mount configs
40
-	 */
41
-	abstract protected function readLegacyConfig();
36
+    /**
37
+     * Read legacy config data
38
+     *
39
+     * @return array list of mount configs
40
+     */
41
+    abstract protected function readLegacyConfig();
42 42
 
43
-	/**
44
-	 * Copy legacy storage options into the given storage config object.
45
-	 *
46
-	 * @param StorageConfig $storageConfig storage config to populate
47
-	 * @param string $mountType mount type
48
-	 * @param string $applicable applicable user or group
49
-	 * @param array $storageOptions legacy storage options
50
-	 *
51
-	 * @return StorageConfig populated storage config
52
-	 */
53
-	protected function populateStorageConfigWithLegacyOptions(
54
-		&$storageConfig,
55
-		$mountType,
56
-		$applicable,
57
-		$storageOptions
58
-	) {
59
-		$backend = $this->backendService->getBackend($storageOptions['backend']);
60
-		if (!$backend) {
61
-			throw new \UnexpectedValueException('Invalid backend ' . $storageOptions['backend']);
62
-		}
63
-		$storageConfig->setBackend($backend);
64
-		if (isset($storageOptions['authMechanism']) && $storageOptions['authMechanism'] !== 'builtin::builtin') {
65
-			$authMechanism = $this->backendService->getAuthMechanism($storageOptions['authMechanism']);
66
-		} else {
67
-			$authMechanism = $backend->getLegacyAuthMechanism($storageOptions);
68
-			$storageOptions['authMechanism'] = 'null'; // to make error handling easier
69
-		}
70
-		if (!$authMechanism) {
71
-			throw new \UnexpectedValueException('Invalid authentication mechanism ' . $storageOptions['authMechanism']);
72
-		}
73
-		$storageConfig->setAuthMechanism($authMechanism);
74
-		$storageConfig->setBackendOptions($storageOptions['options']);
75
-		if (isset($storageOptions['mountOptions'])) {
76
-			$storageConfig->setMountOptions($storageOptions['mountOptions']);
77
-		}
78
-		if (!isset($storageOptions['priority'])) {
79
-			$storageOptions['priority'] = $backend->getPriority();
80
-		}
81
-		$storageConfig->setPriority($storageOptions['priority']);
82
-		if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) {
83
-			$applicableUsers = $storageConfig->getApplicableUsers();
84
-			if ($applicable !== 'all') {
85
-				$applicableUsers[] = $applicable;
86
-				$storageConfig->setApplicableUsers($applicableUsers);
87
-			}
88
-		} else if ($mountType === \OC_Mount_Config::MOUNT_TYPE_GROUP) {
89
-			$applicableGroups = $storageConfig->getApplicableGroups();
90
-			$applicableGroups[] = $applicable;
91
-			$storageConfig->setApplicableGroups($applicableGroups);
92
-		}
93
-		return $storageConfig;
94
-	}
43
+    /**
44
+     * Copy legacy storage options into the given storage config object.
45
+     *
46
+     * @param StorageConfig $storageConfig storage config to populate
47
+     * @param string $mountType mount type
48
+     * @param string $applicable applicable user or group
49
+     * @param array $storageOptions legacy storage options
50
+     *
51
+     * @return StorageConfig populated storage config
52
+     */
53
+    protected function populateStorageConfigWithLegacyOptions(
54
+        &$storageConfig,
55
+        $mountType,
56
+        $applicable,
57
+        $storageOptions
58
+    ) {
59
+        $backend = $this->backendService->getBackend($storageOptions['backend']);
60
+        if (!$backend) {
61
+            throw new \UnexpectedValueException('Invalid backend ' . $storageOptions['backend']);
62
+        }
63
+        $storageConfig->setBackend($backend);
64
+        if (isset($storageOptions['authMechanism']) && $storageOptions['authMechanism'] !== 'builtin::builtin') {
65
+            $authMechanism = $this->backendService->getAuthMechanism($storageOptions['authMechanism']);
66
+        } else {
67
+            $authMechanism = $backend->getLegacyAuthMechanism($storageOptions);
68
+            $storageOptions['authMechanism'] = 'null'; // to make error handling easier
69
+        }
70
+        if (!$authMechanism) {
71
+            throw new \UnexpectedValueException('Invalid authentication mechanism ' . $storageOptions['authMechanism']);
72
+        }
73
+        $storageConfig->setAuthMechanism($authMechanism);
74
+        $storageConfig->setBackendOptions($storageOptions['options']);
75
+        if (isset($storageOptions['mountOptions'])) {
76
+            $storageConfig->setMountOptions($storageOptions['mountOptions']);
77
+        }
78
+        if (!isset($storageOptions['priority'])) {
79
+            $storageOptions['priority'] = $backend->getPriority();
80
+        }
81
+        $storageConfig->setPriority($storageOptions['priority']);
82
+        if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) {
83
+            $applicableUsers = $storageConfig->getApplicableUsers();
84
+            if ($applicable !== 'all') {
85
+                $applicableUsers[] = $applicable;
86
+                $storageConfig->setApplicableUsers($applicableUsers);
87
+            }
88
+        } else if ($mountType === \OC_Mount_Config::MOUNT_TYPE_GROUP) {
89
+            $applicableGroups = $storageConfig->getApplicableGroups();
90
+            $applicableGroups[] = $applicable;
91
+            $storageConfig->setApplicableGroups($applicableGroups);
92
+        }
93
+        return $storageConfig;
94
+    }
95 95
 
96
-	/**
97
-	 * Read the external storages config
98
-	 *
99
-	 * @return StorageConfig[] map of storage id to storage config
100
-	 */
101
-	public function getAllStorages() {
102
-		$mountPoints = $this->readLegacyConfig();
103
-		/**
104
-		 * Here is the how the horribly messy mount point array looks like
105
-		 * from the mount.json file:
106
-		 *
107
-		 * $storageOptions = $mountPoints[$mountType][$applicable][$mountPath]
108
-		 *
109
-		 * - $mountType is either "user" or "group"
110
-		 * - $applicable is the name of a user or group (or the current user for personal mounts)
111
-		 * - $mountPath is the mount point path (where the storage must be mounted)
112
-		 * - $storageOptions is a map of storage options:
113
-		 *     - "priority": storage priority
114
-		 *     - "backend": backend identifier
115
-		 *     - "class": LEGACY backend class name
116
-		 *     - "options": backend-specific options
117
-		 *     - "authMechanism": authentication mechanism identifier
118
-		 *     - "mountOptions": mount-specific options (ex: disable previews, scanner, etc)
119
-		 */
120
-		// group by storage id
121
-		/** @var StorageConfig[] $storages */
122
-		$storages = [];
123
-		// for storages without id (legacy), group by config hash for
124
-		// later processing
125
-		$storagesWithConfigHash = [];
126
-		foreach ($mountPoints as $mountType => $applicables) {
127
-			foreach ($applicables as $applicable => $mountPaths) {
128
-				foreach ($mountPaths as $rootMountPath => $storageOptions) {
129
-					$currentStorage = null;
130
-					/**
131
-					 * Flag whether the config that was read already has an id.
132
-					 * If not, it will use a config hash instead and generate
133
-					 * a proper id later
134
-					 *
135
-					 * @var boolean
136
-					 */
137
-					$hasId = false;
138
-					// the root mount point is in the format "/$user/files/the/mount/point"
139
-					// we remove the "/$user/files" prefix
140
-					$parts = explode('/', ltrim($rootMountPath, '/'), 3);
141
-					if (count($parts) < 3) {
142
-						// something went wrong, skip
143
-						\OC::$server->getLogger()->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']);
144
-						continue;
145
-					}
146
-					$relativeMountPath = rtrim($parts[2], '/');
147
-					// note: we cannot do this after the loop because the decrypted config
148
-					// options might be needed for the config hash
149
-					$storageOptions['options'] = \OC_Mount_Config::decryptPasswords($storageOptions['options']);
150
-					if (!isset($storageOptions['backend'])) {
151
-						$storageOptions['backend'] = $storageOptions['class']; // legacy compat
152
-					}
153
-					if (!isset($storageOptions['authMechanism'])) {
154
-						$storageOptions['authMechanism'] = null; // ensure config hash works
155
-					}
156
-					if (isset($storageOptions['id'])) {
157
-						$configId = (int)$storageOptions['id'];
158
-						if (isset($storages[$configId])) {
159
-							$currentStorage = $storages[$configId];
160
-						}
161
-						$hasId = true;
162
-					} else {
163
-						// missing id in legacy config, need to generate
164
-						// but at this point we don't know the max-id, so use
165
-						// first group it by config hash
166
-						$storageOptions['mountpoint'] = $rootMountPath;
167
-						$configId = \OC_Mount_Config::makeConfigHash($storageOptions);
168
-						if (isset($storagesWithConfigHash[$configId])) {
169
-							$currentStorage = $storagesWithConfigHash[$configId];
170
-						}
171
-					}
172
-					if (is_null($currentStorage)) {
173
-						// create new
174
-						$currentStorage = new StorageConfig($configId);
175
-						$currentStorage->setMountPoint($relativeMountPath);
176
-					}
177
-					try {
178
-						$this->populateStorageConfigWithLegacyOptions(
179
-							$currentStorage,
180
-							$mountType,
181
-							$applicable,
182
-							$storageOptions
183
-						);
184
-						if ($hasId) {
185
-							$storages[$configId] = $currentStorage;
186
-						} else {
187
-							$storagesWithConfigHash[$configId] = $currentStorage;
188
-						}
189
-					} catch (\UnexpectedValueException $e) {
190
-						// don't die if a storage backend doesn't exist
191
-						\OC::$server->getLogger()->logException($e, [
192
-							'message' => 'Could not load storage.',
193
-							'level' => \OCP\Util::ERROR,
194
-							'app' => 'files_external',
195
-						]);
196
-					}
197
-				}
198
-			}
199
-		}
96
+    /**
97
+     * Read the external storages config
98
+     *
99
+     * @return StorageConfig[] map of storage id to storage config
100
+     */
101
+    public function getAllStorages() {
102
+        $mountPoints = $this->readLegacyConfig();
103
+        /**
104
+         * Here is the how the horribly messy mount point array looks like
105
+         * from the mount.json file:
106
+         *
107
+         * $storageOptions = $mountPoints[$mountType][$applicable][$mountPath]
108
+         *
109
+         * - $mountType is either "user" or "group"
110
+         * - $applicable is the name of a user or group (or the current user for personal mounts)
111
+         * - $mountPath is the mount point path (where the storage must be mounted)
112
+         * - $storageOptions is a map of storage options:
113
+         *     - "priority": storage priority
114
+         *     - "backend": backend identifier
115
+         *     - "class": LEGACY backend class name
116
+         *     - "options": backend-specific options
117
+         *     - "authMechanism": authentication mechanism identifier
118
+         *     - "mountOptions": mount-specific options (ex: disable previews, scanner, etc)
119
+         */
120
+        // group by storage id
121
+        /** @var StorageConfig[] $storages */
122
+        $storages = [];
123
+        // for storages without id (legacy), group by config hash for
124
+        // later processing
125
+        $storagesWithConfigHash = [];
126
+        foreach ($mountPoints as $mountType => $applicables) {
127
+            foreach ($applicables as $applicable => $mountPaths) {
128
+                foreach ($mountPaths as $rootMountPath => $storageOptions) {
129
+                    $currentStorage = null;
130
+                    /**
131
+                     * Flag whether the config that was read already has an id.
132
+                     * If not, it will use a config hash instead and generate
133
+                     * a proper id later
134
+                     *
135
+                     * @var boolean
136
+                     */
137
+                    $hasId = false;
138
+                    // the root mount point is in the format "/$user/files/the/mount/point"
139
+                    // we remove the "/$user/files" prefix
140
+                    $parts = explode('/', ltrim($rootMountPath, '/'), 3);
141
+                    if (count($parts) < 3) {
142
+                        // something went wrong, skip
143
+                        \OC::$server->getLogger()->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']);
144
+                        continue;
145
+                    }
146
+                    $relativeMountPath = rtrim($parts[2], '/');
147
+                    // note: we cannot do this after the loop because the decrypted config
148
+                    // options might be needed for the config hash
149
+                    $storageOptions['options'] = \OC_Mount_Config::decryptPasswords($storageOptions['options']);
150
+                    if (!isset($storageOptions['backend'])) {
151
+                        $storageOptions['backend'] = $storageOptions['class']; // legacy compat
152
+                    }
153
+                    if (!isset($storageOptions['authMechanism'])) {
154
+                        $storageOptions['authMechanism'] = null; // ensure config hash works
155
+                    }
156
+                    if (isset($storageOptions['id'])) {
157
+                        $configId = (int)$storageOptions['id'];
158
+                        if (isset($storages[$configId])) {
159
+                            $currentStorage = $storages[$configId];
160
+                        }
161
+                        $hasId = true;
162
+                    } else {
163
+                        // missing id in legacy config, need to generate
164
+                        // but at this point we don't know the max-id, so use
165
+                        // first group it by config hash
166
+                        $storageOptions['mountpoint'] = $rootMountPath;
167
+                        $configId = \OC_Mount_Config::makeConfigHash($storageOptions);
168
+                        if (isset($storagesWithConfigHash[$configId])) {
169
+                            $currentStorage = $storagesWithConfigHash[$configId];
170
+                        }
171
+                    }
172
+                    if (is_null($currentStorage)) {
173
+                        // create new
174
+                        $currentStorage = new StorageConfig($configId);
175
+                        $currentStorage->setMountPoint($relativeMountPath);
176
+                    }
177
+                    try {
178
+                        $this->populateStorageConfigWithLegacyOptions(
179
+                            $currentStorage,
180
+                            $mountType,
181
+                            $applicable,
182
+                            $storageOptions
183
+                        );
184
+                        if ($hasId) {
185
+                            $storages[$configId] = $currentStorage;
186
+                        } else {
187
+                            $storagesWithConfigHash[$configId] = $currentStorage;
188
+                        }
189
+                    } catch (\UnexpectedValueException $e) {
190
+                        // don't die if a storage backend doesn't exist
191
+                        \OC::$server->getLogger()->logException($e, [
192
+                            'message' => 'Could not load storage.',
193
+                            'level' => \OCP\Util::ERROR,
194
+                            'app' => 'files_external',
195
+                        ]);
196
+                    }
197
+                }
198
+            }
199
+        }
200 200
 
201
-		// convert parameter values
202
-		foreach ($storages as $storage) {
203
-			$storage->getBackend()->validateStorageDefinition($storage);
204
-			$storage->getAuthMechanism()->validateStorageDefinition($storage);
205
-		}
206
-		return $storages;
207
-	}
201
+        // convert parameter values
202
+        foreach ($storages as $storage) {
203
+            $storage->getBackend()->validateStorageDefinition($storage);
204
+            $storage->getAuthMechanism()->validateStorageDefinition($storage);
205
+        }
206
+        return $storages;
207
+    }
208 208
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -58,7 +58,7 @@  discard block
 block discarded – undo
58 58
 	) {
59 59
 		$backend = $this->backendService->getBackend($storageOptions['backend']);
60 60
 		if (!$backend) {
61
-			throw new \UnexpectedValueException('Invalid backend ' . $storageOptions['backend']);
61
+			throw new \UnexpectedValueException('Invalid backend '.$storageOptions['backend']);
62 62
 		}
63 63
 		$storageConfig->setBackend($backend);
64 64
 		if (isset($storageOptions['authMechanism']) && $storageOptions['authMechanism'] !== 'builtin::builtin') {
@@ -68,7 +68,7 @@  discard block
 block discarded – undo
68 68
 			$storageOptions['authMechanism'] = 'null'; // to make error handling easier
69 69
 		}
70 70
 		if (!$authMechanism) {
71
-			throw new \UnexpectedValueException('Invalid authentication mechanism ' . $storageOptions['authMechanism']);
71
+			throw new \UnexpectedValueException('Invalid authentication mechanism '.$storageOptions['authMechanism']);
72 72
 		}
73 73
 		$storageConfig->setAuthMechanism($authMechanism);
74 74
 		$storageConfig->setBackendOptions($storageOptions['options']);
@@ -140,7 +140,7 @@  discard block
 block discarded – undo
140 140
 					$parts = explode('/', ltrim($rootMountPath, '/'), 3);
141 141
 					if (count($parts) < 3) {
142 142
 						// something went wrong, skip
143
-						\OC::$server->getLogger()->error('Could not parse mount point "' . $rootMountPath . '"', ['app' => 'files_external']);
143
+						\OC::$server->getLogger()->error('Could not parse mount point "'.$rootMountPath.'"', ['app' => 'files_external']);
144 144
 						continue;
145 145
 					}
146 146
 					$relativeMountPath = rtrim($parts[2], '/');
@@ -154,7 +154,7 @@  discard block
 block discarded – undo
154 154
 						$storageOptions['authMechanism'] = null; // ensure config hash works
155 155
 					}
156 156
 					if (isset($storageOptions['id'])) {
157
-						$configId = (int)$storageOptions['id'];
157
+						$configId = (int) $storageOptions['id'];
158 158
 						if (isset($storages[$configId])) {
159 159
 							$currentStorage = $storages[$configId];
160 160
 						}
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/File.php 2 patches
Indentation   +537 added lines, -537 removed lines patch added patch discarded remove patch
@@ -65,542 +65,542 @@
 block discarded – undo
65 65
 
66 66
 class File extends Node implements IFile {
67 67
 
68
-	protected $request;
69
-
70
-	/**
71
-	 * Sets up the node, expects a full path name
72
-	 *
73
-	 * @param \OC\Files\View $view
74
-	 * @param \OCP\Files\FileInfo $info
75
-	 * @param \OCP\Share\IManager $shareManager
76
-	 * @param \OC\AppFramework\Http\Request $request
77
-	 */
78
-	public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
79
-		parent::__construct($view, $info, $shareManager);
80
-
81
-		if (isset($request)) {
82
-			$this->request = $request;
83
-		} else {
84
-			$this->request = \OC::$server->getRequest();
85
-		}
86
-	}
87
-
88
-	/**
89
-	 * Updates the data
90
-	 *
91
-	 * The data argument is a readable stream resource.
92
-	 *
93
-	 * After a successful put operation, you may choose to return an ETag. The
94
-	 * etag must always be surrounded by double-quotes. These quotes must
95
-	 * appear in the actual string you're returning.
96
-	 *
97
-	 * Clients may use the ETag from a PUT request to later on make sure that
98
-	 * when they update the file, the contents haven't changed in the mean
99
-	 * time.
100
-	 *
101
-	 * If you don't plan to store the file byte-by-byte, and you return a
102
-	 * different object on a subsequent GET you are strongly recommended to not
103
-	 * return an ETag, and just return null.
104
-	 *
105
-	 * @param resource $data
106
-	 *
107
-	 * @throws Forbidden
108
-	 * @throws UnsupportedMediaType
109
-	 * @throws BadRequest
110
-	 * @throws Exception
111
-	 * @throws EntityTooLarge
112
-	 * @throws ServiceUnavailable
113
-	 * @throws FileLocked
114
-	 * @return string|null
115
-	 */
116
-	public function put($data) {
117
-		try {
118
-			$exists = $this->fileView->file_exists($this->path);
119
-			if ($this->info && $exists && !$this->info->isUpdateable()) {
120
-				throw new Forbidden();
121
-			}
122
-		} catch (StorageNotAvailableException $e) {
123
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
124
-		}
125
-
126
-		// verify path of the target
127
-		$this->verifyPath();
128
-
129
-		// chunked handling
130
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
131
-			try {
132
-				return $this->createFileChunked($data);
133
-			} catch (\Exception $e) {
134
-				$this->convertToSabreException($e);
135
-			}
136
-		}
137
-
138
-		list($partStorage) = $this->fileView->resolvePath($this->path);
139
-		$needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1);
140
-
141
-		if ($needsPartFile) {
142
-			// mark file as partial while uploading (ignored by the scanner)
143
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
144
-		} else {
145
-			// upload file directly as the final path
146
-			$partFilePath = $this->path;
147
-
148
-			$this->emitPreHooks($exists);
149
-		}
150
-
151
-		// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
152
-		/** @var \OC\Files\Storage\Storage $partStorage */
153
-		list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
154
-		/** @var \OC\Files\Storage\Storage $storage */
155
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
156
-		try {
157
-			$target = $partStorage->fopen($internalPartPath, 'wb');
158
-			if ($target === false) {
159
-				\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
160
-				// because we have no clue about the cause we can only throw back a 500/Internal Server Error
161
-				throw new Exception('Could not write file contents');
162
-			}
163
-			list($count, $result) = \OC_Helper::streamCopy($data, $target);
164
-			fclose($target);
165
-
166
-			if ($result === false) {
167
-				$expected = -1;
168
-				if (isset($_SERVER['CONTENT_LENGTH'])) {
169
-					$expected = $_SERVER['CONTENT_LENGTH'];
170
-				}
171
-				throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
172
-			}
173
-
174
-			// if content length is sent by client:
175
-			// double check if the file was fully received
176
-			// compare expected and actual size
177
-			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
178
-				$expected = (int) $_SERVER['CONTENT_LENGTH'];
179
-				if ($count !== $expected) {
180
-					throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
181
-				}
182
-			}
183
-
184
-		} catch (\Exception $e) {
185
-			if ($needsPartFile) {
186
-				$partStorage->unlink($internalPartPath);
187
-			}
188
-			$this->convertToSabreException($e);
189
-		}
190
-
191
-		try {
192
-			$view = \OC\Files\Filesystem::getView();
193
-			$run = ($view && $needsPartFile) ? $this->emitPreHooks($exists) : true;
194
-
195
-			try {
196
-				$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
197
-			} catch (LockedException $e) {
198
-				if ($needsPartFile) {
199
-					$partStorage->unlink($internalPartPath);
200
-				}
201
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
202
-			}
203
-
204
-			if ($needsPartFile) {
205
-				// rename to correct path
206
-				try {
207
-					if ($run) {
208
-						$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
209
-						$fileExists = $storage->file_exists($internalPath);
210
-					}
211
-					if (!$run || $renameOkay === false || $fileExists === false) {
212
-						\OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: '  . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']);
213
-						throw new Exception('Could not rename part file to final file');
214
-					}
215
-				} catch (ForbiddenException $ex) {
216
-					throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
217
-				} catch (\Exception $e) {
218
-					$partStorage->unlink($internalPartPath);
219
-					$this->convertToSabreException($e);
220
-				}
221
-			}
222
-
223
-			// since we skipped the view we need to scan and emit the hooks ourselves
224
-			$storage->getUpdater()->update($internalPath);
225
-
226
-			try {
227
-				$this->changeLock(ILockingProvider::LOCK_SHARED);
228
-			} catch (LockedException $e) {
229
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
230
-			}
231
-
232
-			// allow sync clients to send the mtime along in a header
233
-			if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
234
-				$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
235
-				if ($this->fileView->touch($this->path, $mtime)) {
236
-					$this->header('X-OC-MTime: accepted');
237
-				}
238
-			}
68
+    protected $request;
69
+
70
+    /**
71
+     * Sets up the node, expects a full path name
72
+     *
73
+     * @param \OC\Files\View $view
74
+     * @param \OCP\Files\FileInfo $info
75
+     * @param \OCP\Share\IManager $shareManager
76
+     * @param \OC\AppFramework\Http\Request $request
77
+     */
78
+    public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
79
+        parent::__construct($view, $info, $shareManager);
80
+
81
+        if (isset($request)) {
82
+            $this->request = $request;
83
+        } else {
84
+            $this->request = \OC::$server->getRequest();
85
+        }
86
+    }
87
+
88
+    /**
89
+     * Updates the data
90
+     *
91
+     * The data argument is a readable stream resource.
92
+     *
93
+     * After a successful put operation, you may choose to return an ETag. The
94
+     * etag must always be surrounded by double-quotes. These quotes must
95
+     * appear in the actual string you're returning.
96
+     *
97
+     * Clients may use the ETag from a PUT request to later on make sure that
98
+     * when they update the file, the contents haven't changed in the mean
99
+     * time.
100
+     *
101
+     * If you don't plan to store the file byte-by-byte, and you return a
102
+     * different object on a subsequent GET you are strongly recommended to not
103
+     * return an ETag, and just return null.
104
+     *
105
+     * @param resource $data
106
+     *
107
+     * @throws Forbidden
108
+     * @throws UnsupportedMediaType
109
+     * @throws BadRequest
110
+     * @throws Exception
111
+     * @throws EntityTooLarge
112
+     * @throws ServiceUnavailable
113
+     * @throws FileLocked
114
+     * @return string|null
115
+     */
116
+    public function put($data) {
117
+        try {
118
+            $exists = $this->fileView->file_exists($this->path);
119
+            if ($this->info && $exists && !$this->info->isUpdateable()) {
120
+                throw new Forbidden();
121
+            }
122
+        } catch (StorageNotAvailableException $e) {
123
+            throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
124
+        }
125
+
126
+        // verify path of the target
127
+        $this->verifyPath();
128
+
129
+        // chunked handling
130
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
131
+            try {
132
+                return $this->createFileChunked($data);
133
+            } catch (\Exception $e) {
134
+                $this->convertToSabreException($e);
135
+            }
136
+        }
137
+
138
+        list($partStorage) = $this->fileView->resolvePath($this->path);
139
+        $needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1);
140
+
141
+        if ($needsPartFile) {
142
+            // mark file as partial while uploading (ignored by the scanner)
143
+            $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
144
+        } else {
145
+            // upload file directly as the final path
146
+            $partFilePath = $this->path;
147
+
148
+            $this->emitPreHooks($exists);
149
+        }
150
+
151
+        // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
152
+        /** @var \OC\Files\Storage\Storage $partStorage */
153
+        list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
154
+        /** @var \OC\Files\Storage\Storage $storage */
155
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
156
+        try {
157
+            $target = $partStorage->fopen($internalPartPath, 'wb');
158
+            if ($target === false) {
159
+                \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
160
+                // because we have no clue about the cause we can only throw back a 500/Internal Server Error
161
+                throw new Exception('Could not write file contents');
162
+            }
163
+            list($count, $result) = \OC_Helper::streamCopy($data, $target);
164
+            fclose($target);
165
+
166
+            if ($result === false) {
167
+                $expected = -1;
168
+                if (isset($_SERVER['CONTENT_LENGTH'])) {
169
+                    $expected = $_SERVER['CONTENT_LENGTH'];
170
+                }
171
+                throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
172
+            }
173
+
174
+            // if content length is sent by client:
175
+            // double check if the file was fully received
176
+            // compare expected and actual size
177
+            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
178
+                $expected = (int) $_SERVER['CONTENT_LENGTH'];
179
+                if ($count !== $expected) {
180
+                    throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
181
+                }
182
+            }
183
+
184
+        } catch (\Exception $e) {
185
+            if ($needsPartFile) {
186
+                $partStorage->unlink($internalPartPath);
187
+            }
188
+            $this->convertToSabreException($e);
189
+        }
190
+
191
+        try {
192
+            $view = \OC\Files\Filesystem::getView();
193
+            $run = ($view && $needsPartFile) ? $this->emitPreHooks($exists) : true;
194
+
195
+            try {
196
+                $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
197
+            } catch (LockedException $e) {
198
+                if ($needsPartFile) {
199
+                    $partStorage->unlink($internalPartPath);
200
+                }
201
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
202
+            }
203
+
204
+            if ($needsPartFile) {
205
+                // rename to correct path
206
+                try {
207
+                    if ($run) {
208
+                        $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
209
+                        $fileExists = $storage->file_exists($internalPath);
210
+                    }
211
+                    if (!$run || $renameOkay === false || $fileExists === false) {
212
+                        \OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: '  . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']);
213
+                        throw new Exception('Could not rename part file to final file');
214
+                    }
215
+                } catch (ForbiddenException $ex) {
216
+                    throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
217
+                } catch (\Exception $e) {
218
+                    $partStorage->unlink($internalPartPath);
219
+                    $this->convertToSabreException($e);
220
+                }
221
+            }
222
+
223
+            // since we skipped the view we need to scan and emit the hooks ourselves
224
+            $storage->getUpdater()->update($internalPath);
225
+
226
+            try {
227
+                $this->changeLock(ILockingProvider::LOCK_SHARED);
228
+            } catch (LockedException $e) {
229
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
230
+            }
231
+
232
+            // allow sync clients to send the mtime along in a header
233
+            if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
234
+                $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
235
+                if ($this->fileView->touch($this->path, $mtime)) {
236
+                    $this->header('X-OC-MTime: accepted');
237
+                }
238
+            }
239 239
 					
240
-			if ($view) {
241
-				$this->emitPostHooks($exists);
242
-			}
243
-
244
-			$this->refreshInfo();
245
-
246
-			if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
247
-				$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
248
-				$this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
249
-				$this->refreshInfo();
250
-			} else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
251
-				$this->fileView->putFileInfo($this->path, ['checksum' => '']);
252
-				$this->refreshInfo();
253
-			}
254
-
255
-		} catch (StorageNotAvailableException $e) {
256
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
257
-		}
258
-
259
-		return '"' . $this->info->getEtag() . '"';
260
-	}
261
-
262
-	private function getPartFileBasePath($path) {
263
-		$partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
264
-		if ($partFileInStorage) {
265
-			return $path;
266
-		} else {
267
-			return md5($path); // will place it in the root of the view with a unique name
268
-		}
269
-	}
270
-
271
-	/**
272
-	 * @param string $path
273
-	 */
274
-	private function emitPreHooks($exists, $path = null) {
275
-		if (is_null($path)) {
276
-			$path = $this->path;
277
-		}
278
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
279
-		$run = true;
280
-
281
-		if (!$exists) {
282
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
283
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
284
-				\OC\Files\Filesystem::signal_param_run => &$run,
285
-			));
286
-		} else {
287
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
288
-				\OC\Files\Filesystem::signal_param_path => $hookPath,
289
-				\OC\Files\Filesystem::signal_param_run => &$run,
290
-			));
291
-		}
292
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
293
-			\OC\Files\Filesystem::signal_param_path => $hookPath,
294
-			\OC\Files\Filesystem::signal_param_run => &$run,
295
-		));
296
-		return $run;
297
-	}
298
-
299
-	/**
300
-	 * @param string $path
301
-	 */
302
-	private function emitPostHooks($exists, $path = null) {
303
-		if (is_null($path)) {
304
-			$path = $this->path;
305
-		}
306
-		$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
307
-		if (!$exists) {
308
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
309
-				\OC\Files\Filesystem::signal_param_path => $hookPath
310
-			));
311
-		} else {
312
-			\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
313
-				\OC\Files\Filesystem::signal_param_path => $hookPath
314
-			));
315
-		}
316
-		\OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
317
-			\OC\Files\Filesystem::signal_param_path => $hookPath
318
-		));
319
-	}
320
-
321
-	/**
322
-	 * Returns the data
323
-	 *
324
-	 * @return resource
325
-	 * @throws Forbidden
326
-	 * @throws ServiceUnavailable
327
-	 */
328
-	public function get() {
329
-		//throw exception if encryption is disabled but files are still encrypted
330
-		try {
331
-			if (!$this->info->isReadable()) {
332
-				// do a if the file did not exist
333
-				throw new NotFound();
334
-			}
335
-			try {
336
-				$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
337
-			} catch (\Exception $e) {
338
-				$this->convertToSabreException($e);
339
-			}
340
-			if ($res === false) {
341
-				throw new ServiceUnavailable("Could not open file");
342
-			}
343
-			return $res;
344
-		} catch (GenericEncryptionException $e) {
345
-			// returning 503 will allow retry of the operation at a later point in time
346
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
347
-		} catch (StorageNotAvailableException $e) {
348
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
349
-		} catch (ForbiddenException $ex) {
350
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
351
-		} catch (LockedException $e) {
352
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
353
-		}
354
-	}
355
-
356
-	/**
357
-	 * Delete the current file
358
-	 *
359
-	 * @throws Forbidden
360
-	 * @throws ServiceUnavailable
361
-	 */
362
-	public function delete() {
363
-		if (!$this->info->isDeletable()) {
364
-			throw new Forbidden();
365
-		}
366
-
367
-		try {
368
-			if (!$this->fileView->unlink($this->path)) {
369
-				// assume it wasn't possible to delete due to permissions
370
-				throw new Forbidden();
371
-			}
372
-		} catch (StorageNotAvailableException $e) {
373
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
374
-		} catch (ForbiddenException $ex) {
375
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
376
-		} catch (LockedException $e) {
377
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
378
-		}
379
-	}
380
-
381
-	/**
382
-	 * Returns the mime-type for a file
383
-	 *
384
-	 * If null is returned, we'll assume application/octet-stream
385
-	 *
386
-	 * @return string
387
-	 */
388
-	public function getContentType() {
389
-		$mimeType = $this->info->getMimetype();
390
-
391
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
392
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
393
-			return $mimeType;
394
-		}
395
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
396
-	}
397
-
398
-	/**
399
-	 * @return array|false
400
-	 */
401
-	public function getDirectDownload() {
402
-		if (\OCP\App::isEnabled('encryption')) {
403
-			return [];
404
-		}
405
-		/** @var \OCP\Files\Storage $storage */
406
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
407
-		if (is_null($storage)) {
408
-			return [];
409
-		}
410
-
411
-		return $storage->getDirectDownload($internalPath);
412
-	}
413
-
414
-	/**
415
-	 * @param resource $data
416
-	 * @return null|string
417
-	 * @throws Exception
418
-	 * @throws BadRequest
419
-	 * @throws NotImplemented
420
-	 * @throws ServiceUnavailable
421
-	 */
422
-	private function createFileChunked($data) {
423
-		list($path, $name) = \Sabre\Uri\split($this->path);
424
-
425
-		$info = \OC_FileChunking::decodeName($name);
426
-		if (empty($info)) {
427
-			throw new NotImplemented('Invalid chunk name');
428
-		}
429
-
430
-		$chunk_handler = new \OC_FileChunking($info);
431
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
432
-
433
-		//detect aborted upload
434
-		if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
435
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
436
-				$expected = (int) $_SERVER['CONTENT_LENGTH'];
437
-				if ($bytesWritten !== $expected) {
438
-					$chunk_handler->remove($info['index']);
439
-					throw new BadRequest(
440
-						'expected filesize ' . $expected . ' got ' . $bytesWritten);
441
-				}
442
-			}
443
-		}
444
-
445
-		if ($chunk_handler->isComplete()) {
446
-			list($storage,) = $this->fileView->resolvePath($path);
447
-			$needsPartFile = $this->needsPartFile($storage);
448
-			$partFile = null;
449
-
450
-			$targetPath = $path . '/' . $info['name'];
451
-			/** @var \OC\Files\Storage\Storage $targetStorage */
452
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
453
-
454
-			$exists = $this->fileView->file_exists($targetPath);
455
-
456
-			try {
457
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
458
-
459
-				$this->emitPreHooks($exists, $targetPath);
460
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
461
-				/** @var \OC\Files\Storage\Storage $targetStorage */
462
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
463
-
464
-				if ($needsPartFile) {
465
-					// we first assembly the target file as a part file
466
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
467
-					/** @var \OC\Files\Storage\Storage $targetStorage */
468
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
469
-
470
-
471
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
472
-
473
-					// here is the final atomic rename
474
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
475
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
476
-					if ($renameOkay === false || $fileExists === false) {
477
-						\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
478
-						// only delete if an error occurred and the target file was already created
479
-						if ($fileExists) {
480
-							// set to null to avoid double-deletion when handling exception
481
-							// stray part file
482
-							$partFile = null;
483
-							$targetStorage->unlink($targetInternalPath);
484
-						}
485
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
486
-						throw new Exception('Could not rename part file assembled from chunks');
487
-					}
488
-				} else {
489
-					// assemble directly into the final file
490
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
491
-				}
492
-
493
-				// allow sync clients to send the mtime along in a header
494
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
495
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
496
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
497
-						$this->header('X-OC-MTime: accepted');
498
-					}
499
-				}
500
-
501
-				// since we skipped the view we need to scan and emit the hooks ourselves
502
-				$targetStorage->getUpdater()->update($targetInternalPath);
503
-
504
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
505
-
506
-				$this->emitPostHooks($exists, $targetPath);
507
-
508
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
509
-				$info = $this->fileView->getFileInfo($targetPath);
510
-
511
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
512
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
513
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
514
-				} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
515
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
516
-				}
517
-
518
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
519
-
520
-				return $info->getEtag();
521
-			} catch (\Exception $e) {
522
-				if ($partFile !== null) {
523
-					$targetStorage->unlink($targetInternalPath);
524
-				}
525
-				$this->convertToSabreException($e);
526
-			}
527
-		}
528
-
529
-		return null;
530
-	}
531
-
532
-	/**
533
-	 * Returns whether a part file is needed for the given storage
534
-	 * or whether the file can be assembled/uploaded directly on the
535
-	 * target storage.
536
-	 *
537
-	 * @param \OCP\Files\Storage $storage
538
-	 * @return bool true if the storage needs part file handling
539
-	 */
540
-	private function needsPartFile($storage) {
541
-		// TODO: in the future use ChunkHandler provided by storage
542
-		return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
543
-			!$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
544
-			$storage->needsPartFile();
545
-	}
546
-
547
-	/**
548
-	 * Convert the given exception to a SabreException instance
549
-	 *
550
-	 * @param \Exception $e
551
-	 *
552
-	 * @throws \Sabre\DAV\Exception
553
-	 */
554
-	private function convertToSabreException(\Exception $e) {
555
-		if ($e instanceof \Sabre\DAV\Exception) {
556
-			throw $e;
557
-		}
558
-		if ($e instanceof NotPermittedException) {
559
-			// a more general case - due to whatever reason the content could not be written
560
-			throw new Forbidden($e->getMessage(), 0, $e);
561
-		}
562
-		if ($e instanceof ForbiddenException) {
563
-			// the path for the file was forbidden
564
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
565
-		}
566
-		if ($e instanceof EntityTooLargeException) {
567
-			// the file is too big to be stored
568
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
569
-		}
570
-		if ($e instanceof InvalidContentException) {
571
-			// the file content is not permitted
572
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
573
-		}
574
-		if ($e instanceof InvalidPathException) {
575
-			// the path for the file was not valid
576
-			// TODO: find proper http status code for this case
577
-			throw new Forbidden($e->getMessage(), 0, $e);
578
-		}
579
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
580
-			// the file is currently being written to by another process
581
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
582
-		}
583
-		if ($e instanceof GenericEncryptionException) {
584
-			// returning 503 will allow retry of the operation at a later point in time
585
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
586
-		}
587
-		if ($e instanceof StorageNotAvailableException) {
588
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
589
-		}
590
-
591
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
592
-	}
593
-
594
-	/**
595
-	 * Get the checksum for this file
596
-	 *
597
-	 * @return string
598
-	 */
599
-	public function getChecksum() {
600
-		return $this->info->getChecksum();
601
-	}
602
-
603
-	protected function header($string) {
604
-		\header($string);
605
-	}
240
+            if ($view) {
241
+                $this->emitPostHooks($exists);
242
+            }
243
+
244
+            $this->refreshInfo();
245
+
246
+            if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
247
+                $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
248
+                $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]);
249
+                $this->refreshInfo();
250
+            } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') {
251
+                $this->fileView->putFileInfo($this->path, ['checksum' => '']);
252
+                $this->refreshInfo();
253
+            }
254
+
255
+        } catch (StorageNotAvailableException $e) {
256
+            throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
257
+        }
258
+
259
+        return '"' . $this->info->getEtag() . '"';
260
+    }
261
+
262
+    private function getPartFileBasePath($path) {
263
+        $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
264
+        if ($partFileInStorage) {
265
+            return $path;
266
+        } else {
267
+            return md5($path); // will place it in the root of the view with a unique name
268
+        }
269
+    }
270
+
271
+    /**
272
+     * @param string $path
273
+     */
274
+    private function emitPreHooks($exists, $path = null) {
275
+        if (is_null($path)) {
276
+            $path = $this->path;
277
+        }
278
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
279
+        $run = true;
280
+
281
+        if (!$exists) {
282
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array(
283
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
284
+                \OC\Files\Filesystem::signal_param_run => &$run,
285
+            ));
286
+        } else {
287
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array(
288
+                \OC\Files\Filesystem::signal_param_path => $hookPath,
289
+                \OC\Files\Filesystem::signal_param_run => &$run,
290
+            ));
291
+        }
292
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array(
293
+            \OC\Files\Filesystem::signal_param_path => $hookPath,
294
+            \OC\Files\Filesystem::signal_param_run => &$run,
295
+        ));
296
+        return $run;
297
+    }
298
+
299
+    /**
300
+     * @param string $path
301
+     */
302
+    private function emitPostHooks($exists, $path = null) {
303
+        if (is_null($path)) {
304
+            $path = $this->path;
305
+        }
306
+        $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
307
+        if (!$exists) {
308
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array(
309
+                \OC\Files\Filesystem::signal_param_path => $hookPath
310
+            ));
311
+        } else {
312
+            \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array(
313
+                \OC\Files\Filesystem::signal_param_path => $hookPath
314
+            ));
315
+        }
316
+        \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array(
317
+            \OC\Files\Filesystem::signal_param_path => $hookPath
318
+        ));
319
+    }
320
+
321
+    /**
322
+     * Returns the data
323
+     *
324
+     * @return resource
325
+     * @throws Forbidden
326
+     * @throws ServiceUnavailable
327
+     */
328
+    public function get() {
329
+        //throw exception if encryption is disabled but files are still encrypted
330
+        try {
331
+            if (!$this->info->isReadable()) {
332
+                // do a if the file did not exist
333
+                throw new NotFound();
334
+            }
335
+            try {
336
+                $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
337
+            } catch (\Exception $e) {
338
+                $this->convertToSabreException($e);
339
+            }
340
+            if ($res === false) {
341
+                throw new ServiceUnavailable("Could not open file");
342
+            }
343
+            return $res;
344
+        } catch (GenericEncryptionException $e) {
345
+            // returning 503 will allow retry of the operation at a later point in time
346
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
347
+        } catch (StorageNotAvailableException $e) {
348
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
349
+        } catch (ForbiddenException $ex) {
350
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
351
+        } catch (LockedException $e) {
352
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
353
+        }
354
+    }
355
+
356
+    /**
357
+     * Delete the current file
358
+     *
359
+     * @throws Forbidden
360
+     * @throws ServiceUnavailable
361
+     */
362
+    public function delete() {
363
+        if (!$this->info->isDeletable()) {
364
+            throw new Forbidden();
365
+        }
366
+
367
+        try {
368
+            if (!$this->fileView->unlink($this->path)) {
369
+                // assume it wasn't possible to delete due to permissions
370
+                throw new Forbidden();
371
+            }
372
+        } catch (StorageNotAvailableException $e) {
373
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
374
+        } catch (ForbiddenException $ex) {
375
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
376
+        } catch (LockedException $e) {
377
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
378
+        }
379
+    }
380
+
381
+    /**
382
+     * Returns the mime-type for a file
383
+     *
384
+     * If null is returned, we'll assume application/octet-stream
385
+     *
386
+     * @return string
387
+     */
388
+    public function getContentType() {
389
+        $mimeType = $this->info->getMimetype();
390
+
391
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
392
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
393
+            return $mimeType;
394
+        }
395
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
396
+    }
397
+
398
+    /**
399
+     * @return array|false
400
+     */
401
+    public function getDirectDownload() {
402
+        if (\OCP\App::isEnabled('encryption')) {
403
+            return [];
404
+        }
405
+        /** @var \OCP\Files\Storage $storage */
406
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
407
+        if (is_null($storage)) {
408
+            return [];
409
+        }
410
+
411
+        return $storage->getDirectDownload($internalPath);
412
+    }
413
+
414
+    /**
415
+     * @param resource $data
416
+     * @return null|string
417
+     * @throws Exception
418
+     * @throws BadRequest
419
+     * @throws NotImplemented
420
+     * @throws ServiceUnavailable
421
+     */
422
+    private function createFileChunked($data) {
423
+        list($path, $name) = \Sabre\Uri\split($this->path);
424
+
425
+        $info = \OC_FileChunking::decodeName($name);
426
+        if (empty($info)) {
427
+            throw new NotImplemented('Invalid chunk name');
428
+        }
429
+
430
+        $chunk_handler = new \OC_FileChunking($info);
431
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
432
+
433
+        //detect aborted upload
434
+        if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
435
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
436
+                $expected = (int) $_SERVER['CONTENT_LENGTH'];
437
+                if ($bytesWritten !== $expected) {
438
+                    $chunk_handler->remove($info['index']);
439
+                    throw new BadRequest(
440
+                        'expected filesize ' . $expected . ' got ' . $bytesWritten);
441
+                }
442
+            }
443
+        }
444
+
445
+        if ($chunk_handler->isComplete()) {
446
+            list($storage,) = $this->fileView->resolvePath($path);
447
+            $needsPartFile = $this->needsPartFile($storage);
448
+            $partFile = null;
449
+
450
+            $targetPath = $path . '/' . $info['name'];
451
+            /** @var \OC\Files\Storage\Storage $targetStorage */
452
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
453
+
454
+            $exists = $this->fileView->file_exists($targetPath);
455
+
456
+            try {
457
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
458
+
459
+                $this->emitPreHooks($exists, $targetPath);
460
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
461
+                /** @var \OC\Files\Storage\Storage $targetStorage */
462
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
463
+
464
+                if ($needsPartFile) {
465
+                    // we first assembly the target file as a part file
466
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
467
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
468
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
469
+
470
+
471
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
472
+
473
+                    // here is the final atomic rename
474
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
475
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
476
+                    if ($renameOkay === false || $fileExists === false) {
477
+                        \OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
478
+                        // only delete if an error occurred and the target file was already created
479
+                        if ($fileExists) {
480
+                            // set to null to avoid double-deletion when handling exception
481
+                            // stray part file
482
+                            $partFile = null;
483
+                            $targetStorage->unlink($targetInternalPath);
484
+                        }
485
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
486
+                        throw new Exception('Could not rename part file assembled from chunks');
487
+                    }
488
+                } else {
489
+                    // assemble directly into the final file
490
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
491
+                }
492
+
493
+                // allow sync clients to send the mtime along in a header
494
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
495
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
496
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
497
+                        $this->header('X-OC-MTime: accepted');
498
+                    }
499
+                }
500
+
501
+                // since we skipped the view we need to scan and emit the hooks ourselves
502
+                $targetStorage->getUpdater()->update($targetInternalPath);
503
+
504
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
505
+
506
+                $this->emitPostHooks($exists, $targetPath);
507
+
508
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
509
+                $info = $this->fileView->getFileInfo($targetPath);
510
+
511
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
512
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
513
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
514
+                } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
515
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
516
+                }
517
+
518
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
519
+
520
+                return $info->getEtag();
521
+            } catch (\Exception $e) {
522
+                if ($partFile !== null) {
523
+                    $targetStorage->unlink($targetInternalPath);
524
+                }
525
+                $this->convertToSabreException($e);
526
+            }
527
+        }
528
+
529
+        return null;
530
+    }
531
+
532
+    /**
533
+     * Returns whether a part file is needed for the given storage
534
+     * or whether the file can be assembled/uploaded directly on the
535
+     * target storage.
536
+     *
537
+     * @param \OCP\Files\Storage $storage
538
+     * @return bool true if the storage needs part file handling
539
+     */
540
+    private function needsPartFile($storage) {
541
+        // TODO: in the future use ChunkHandler provided by storage
542
+        return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
543
+            !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
544
+            $storage->needsPartFile();
545
+    }
546
+
547
+    /**
548
+     * Convert the given exception to a SabreException instance
549
+     *
550
+     * @param \Exception $e
551
+     *
552
+     * @throws \Sabre\DAV\Exception
553
+     */
554
+    private function convertToSabreException(\Exception $e) {
555
+        if ($e instanceof \Sabre\DAV\Exception) {
556
+            throw $e;
557
+        }
558
+        if ($e instanceof NotPermittedException) {
559
+            // a more general case - due to whatever reason the content could not be written
560
+            throw new Forbidden($e->getMessage(), 0, $e);
561
+        }
562
+        if ($e instanceof ForbiddenException) {
563
+            // the path for the file was forbidden
564
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
565
+        }
566
+        if ($e instanceof EntityTooLargeException) {
567
+            // the file is too big to be stored
568
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
569
+        }
570
+        if ($e instanceof InvalidContentException) {
571
+            // the file content is not permitted
572
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
573
+        }
574
+        if ($e instanceof InvalidPathException) {
575
+            // the path for the file was not valid
576
+            // TODO: find proper http status code for this case
577
+            throw new Forbidden($e->getMessage(), 0, $e);
578
+        }
579
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
580
+            // the file is currently being written to by another process
581
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
582
+        }
583
+        if ($e instanceof GenericEncryptionException) {
584
+            // returning 503 will allow retry of the operation at a later point in time
585
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
586
+        }
587
+        if ($e instanceof StorageNotAvailableException) {
588
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
589
+        }
590
+
591
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
592
+    }
593
+
594
+    /**
595
+     * Get the checksum for this file
596
+     *
597
+     * @return string
598
+     */
599
+    public function getChecksum() {
600
+        return $this->info->getChecksum();
601
+    }
602
+
603
+    protected function header($string) {
604
+        \header($string);
605
+    }
606 606
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -120,7 +120,7 @@  discard block
 block discarded – undo
120 120
 				throw new Forbidden();
121 121
 			}
122 122
 		} catch (StorageNotAvailableException $e) {
123
-			throw new ServiceUnavailable("File is not updatable: " . $e->getMessage());
123
+			throw new ServiceUnavailable("File is not updatable: ".$e->getMessage());
124 124
 		}
125 125
 
126 126
 		// verify path of the target
@@ -140,7 +140,7 @@  discard block
 block discarded – undo
140 140
 
141 141
 		if ($needsPartFile) {
142 142
 			// mark file as partial while uploading (ignored by the scanner)
143
-			$partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
143
+			$partFilePath = $this->getPartFileBasePath($this->path).'.ocTransferId'.rand().'.part';
144 144
 		} else {
145 145
 			// upload file directly as the final path
146 146
 			$partFilePath = $this->path;
@@ -168,7 +168,7 @@  discard block
 block discarded – undo
168 168
 				if (isset($_SERVER['CONTENT_LENGTH'])) {
169 169
 					$expected = $_SERVER['CONTENT_LENGTH'];
170 170
 				}
171
-				throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )');
171
+				throw new Exception('Error while copying file to target location (copied bytes: '.$count.', expected filesize: '.$expected.' )');
172 172
 			}
173 173
 
174 174
 			// if content length is sent by client:
@@ -177,7 +177,7 @@  discard block
 block discarded – undo
177 177
 			if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
178 178
 				$expected = (int) $_SERVER['CONTENT_LENGTH'];
179 179
 				if ($count !== $expected) {
180
-					throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
180
+					throw new BadRequest('expected filesize '.$expected.' got '.$count);
181 181
 				}
182 182
 			}
183 183
 
@@ -209,7 +209,7 @@  discard block
 block discarded – undo
209 209
 						$fileExists = $storage->file_exists($internalPath);
210 210
 					}
211 211
 					if (!$run || $renameOkay === false || $fileExists === false) {
212
-						\OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: '  . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']);
212
+						\OC::$server->getLogger()->error('renaming part file to final file failed ($run: '.($run ? 'true' : 'false').', $renameOkay: '.($renameOkay ? 'true' : 'false').', $fileExists: '.($fileExists ? 'true' : 'false').')', ['app' => 'webdav']);
213 213
 						throw new Exception('Could not rename part file to final file');
214 214
 					}
215 215
 				} catch (ForbiddenException $ex) {
@@ -253,10 +253,10 @@  discard block
 block discarded – undo
253 253
 			}
254 254
 
255 255
 		} catch (StorageNotAvailableException $e) {
256
-			throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
256
+			throw new ServiceUnavailable("Failed to check file size: ".$e->getMessage());
257 257
 		}
258 258
 
259
-		return '"' . $this->info->getEtag() . '"';
259
+		return '"'.$this->info->getEtag().'"';
260 260
 	}
261 261
 
262 262
 	private function getPartFileBasePath($path) {
@@ -343,9 +343,9 @@  discard block
 block discarded – undo
343 343
 			return $res;
344 344
 		} catch (GenericEncryptionException $e) {
345 345
 			// returning 503 will allow retry of the operation at a later point in time
346
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
346
+			throw new ServiceUnavailable("Encryption not ready: ".$e->getMessage());
347 347
 		} catch (StorageNotAvailableException $e) {
348
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
348
+			throw new ServiceUnavailable("Failed to open file: ".$e->getMessage());
349 349
 		} catch (ForbiddenException $ex) {
350 350
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
351 351
 		} catch (LockedException $e) {
@@ -370,7 +370,7 @@  discard block
 block discarded – undo
370 370
 				throw new Forbidden();
371 371
 			}
372 372
 		} catch (StorageNotAvailableException $e) {
373
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
373
+			throw new ServiceUnavailable("Failed to unlink: ".$e->getMessage());
374 374
 		} catch (ForbiddenException $ex) {
375 375
 			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
376 376
 		} catch (LockedException $e) {
@@ -437,7 +437,7 @@  discard block
 block discarded – undo
437 437
 				if ($bytesWritten !== $expected) {
438 438
 					$chunk_handler->remove($info['index']);
439 439
 					throw new BadRequest(
440
-						'expected filesize ' . $expected . ' got ' . $bytesWritten);
440
+						'expected filesize '.$expected.' got '.$bytesWritten);
441 441
 				}
442 442
 			}
443 443
 		}
@@ -447,7 +447,7 @@  discard block
 block discarded – undo
447 447
 			$needsPartFile = $this->needsPartFile($storage);
448 448
 			$partFile = null;
449 449
 
450
-			$targetPath = $path . '/' . $info['name'];
450
+			$targetPath = $path.'/'.$info['name'];
451 451
 			/** @var \OC\Files\Storage\Storage $targetStorage */
452 452
 			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
453 453
 
@@ -463,7 +463,7 @@  discard block
 block discarded – undo
463 463
 
464 464
 				if ($needsPartFile) {
465 465
 					// we first assembly the target file as a part file
466
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
466
+					$partFile = $this->getPartFileBasePath($path.'/'.$info['name']).'.ocTransferId'.$info['transferid'].'.part';
467 467
 					/** @var \OC\Files\Storage\Storage $targetStorage */
468 468
 					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
469 469
 
@@ -582,10 +582,10 @@  discard block
 block discarded – undo
582 582
 		}
583 583
 		if ($e instanceof GenericEncryptionException) {
584 584
 			// returning 503 will allow retry of the operation at a later point in time
585
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
585
+			throw new ServiceUnavailable('Encryption not ready: '.$e->getMessage(), 0, $e);
586 586
 		}
587 587
 		if ($e instanceof StorageNotAvailableException) {
588
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
588
+			throw new ServiceUnavailable('Failed to write file contents: '.$e->getMessage(), 0, $e);
589 589
 		}
590 590
 
591 591
 		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
Please login to merge, or discard this patch.
apps/files_sharing/lib/ShareBackend/File.php 1 patch
Indentation   +211 added lines, -211 removed lines patch added patch discarded remove patch
@@ -37,215 +37,215 @@
 block discarded – undo
37 37
 
38 38
 class File implements \OCP\Share_Backend_File_Dependent {
39 39
 
40
-	const FORMAT_SHARED_STORAGE = 0;
41
-	const FORMAT_GET_FOLDER_CONTENTS = 1;
42
-	const FORMAT_FILE_APP_ROOT = 2;
43
-	const FORMAT_OPENDIR = 3;
44
-	const FORMAT_GET_ALL = 4;
45
-	const FORMAT_PERMISSIONS = 5;
46
-	const FORMAT_TARGET_NAMES = 6;
47
-
48
-	private $path;
49
-
50
-	/** @var FederatedShareProvider */
51
-	private $federatedShareProvider;
52
-
53
-	public function __construct(FederatedShareProvider $federatedShareProvider = null) {
54
-		if ($federatedShareProvider) {
55
-			$this->federatedShareProvider = $federatedShareProvider;
56
-		} else {
57
-			$federatedSharingApp = new \OCA\FederatedFileSharing\AppInfo\Application();
58
-			$this->federatedShareProvider = $federatedSharingApp->getFederatedShareProvider();
59
-		}
60
-	}
61
-
62
-	public function isValidSource($itemSource, $uidOwner) {
63
-		try {
64
-			$path = \OC\Files\Filesystem::getPath($itemSource);
65
-			// FIXME: attributes should not be set here,
66
-			// keeping this pattern for now to avoid unexpected
67
-			// regressions
68
-			$this->path = \OC\Files\Filesystem::normalizePath(basename($path));
69
-			return true;
70
-		} catch (\OCP\Files\NotFoundException $e) {
71
-			return false;
72
-		}
73
-	}
74
-
75
-	public function getFilePath($itemSource, $uidOwner) {
76
-		if (isset($this->path)) {
77
-			$path = $this->path;
78
-			$this->path = null;
79
-			return $path;
80
-		} else {
81
-			try {
82
-				$path = \OC\Files\Filesystem::getPath($itemSource);
83
-				return $path;
84
-			} catch (\OCP\Files\NotFoundException $e) {
85
-				return false;
86
-			}
87
-		}
88
-	}
89
-
90
-	/**
91
-	 * create unique target
92
-	 * @param string $filePath
93
-	 * @param string $shareWith
94
-	 * @param array $exclude (optional)
95
-	 * @return string
96
-	 */
97
-	public function generateTarget($filePath, $shareWith, $exclude = null) {
98
-		$shareFolder = \OCA\Files_Sharing\Helper::getShareFolder();
99
-		$target = \OC\Files\Filesystem::normalizePath($shareFolder . '/' . basename($filePath));
100
-
101
-		// for group shares we return the target right away
102
-		if ($shareWith === false) {
103
-			return $target;
104
-		}
105
-
106
-		\OC\Files\Filesystem::initMountPoints($shareWith);
107
-		$view = new \OC\Files\View('/' . $shareWith . '/files');
108
-
109
-		if (!$view->is_dir($shareFolder)) {
110
-			$dir = '';
111
-			$subdirs = explode('/', $shareFolder);
112
-			foreach ($subdirs as $subdir) {
113
-				$dir = $dir . '/' . $subdir;
114
-				if (!$view->is_dir($dir)) {
115
-					$view->mkdir($dir);
116
-				}
117
-			}
118
-		}
119
-
120
-		$excludeList = is_array($exclude) ? $exclude : array();
121
-
122
-		return \OCA\Files_Sharing\Helper::generateUniqueTarget($target, $excludeList, $view);
123
-	}
124
-
125
-	public function formatItems($items, $format, $parameters = null) {
126
-		if ($format === self::FORMAT_SHARED_STORAGE) {
127
-			// Only 1 item should come through for this format call
128
-			$item = array_shift($items);
129
-			return array(
130
-				'parent' => $item['parent'],
131
-				'path' => $item['path'],
132
-				'storage' => $item['storage'],
133
-				'permissions' => $item['permissions'],
134
-				'uid_owner' => $item['uid_owner'],
135
-			);
136
-		} else if ($format === self::FORMAT_GET_FOLDER_CONTENTS) {
137
-			$files = array();
138
-			foreach ($items as $item) {
139
-				$file = array();
140
-				$file['fileid'] = $item['file_source'];
141
-				$file['storage'] = $item['storage'];
142
-				$file['path'] = $item['file_target'];
143
-				$file['parent'] = $item['file_parent'];
144
-				$file['name'] = basename($item['file_target']);
145
-				$file['mimetype'] = $item['mimetype'];
146
-				$file['mimepart'] = $item['mimepart'];
147
-				$file['mtime'] = $item['mtime'];
148
-				$file['encrypted'] = $item['encrypted'];
149
-				$file['etag'] = $item['etag'];
150
-				$file['uid_owner'] = $item['uid_owner'];
151
-				$file['displayname_owner'] = $item['displayname_owner'];
152
-
153
-				$storage = \OC\Files\Filesystem::getStorage('/');
154
-				$cache = $storage->getCache();
155
-				$file['size'] = $item['size'];
156
-				$files[] = $file;
157
-			}
158
-			return $files;
159
-		} else if ($format === self::FORMAT_OPENDIR) {
160
-			$files = array();
161
-			foreach ($items as $item) {
162
-				$files[] = basename($item['file_target']);
163
-			}
164
-			return $files;
165
-		} else if ($format === self::FORMAT_GET_ALL) {
166
-			$ids = array();
167
-			foreach ($items as $item) {
168
-				$ids[] = $item['file_source'];
169
-			}
170
-			return $ids;
171
-		} else if ($format === self::FORMAT_PERMISSIONS) {
172
-			$filePermissions = array();
173
-			foreach ($items as $item) {
174
-				$filePermissions[$item['file_source']] = $item['permissions'];
175
-			}
176
-			return $filePermissions;
177
-		} else if ($format === self::FORMAT_TARGET_NAMES) {
178
-			$targets = array();
179
-			foreach ($items as $item) {
180
-				$targets[] = $item['file_target'];
181
-			}
182
-			return $targets;
183
-		}
184
-		return array();
185
-	}
186
-
187
-	/**
188
-	 * check if server2server share is enabled
189
-	 *
190
-	 * @param int $shareType
191
-	 * @return boolean
192
-	 */
193
-	public function isShareTypeAllowed($shareType) {
194
-		if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
195
-			return $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
196
-		}
197
-
198
-		return true;
199
-	}
200
-
201
-	/**
202
-	 * resolve reshares to return the correct source item
203
-	 * @param array $source
204
-	 * @return array source item
205
-	 */
206
-	protected static function resolveReshares($source) {
207
-		if (isset($source['parent'])) {
208
-			$parent = $source['parent'];
209
-			while (isset($parent)) {
210
-				$qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
211
-				$qb->select('parent', 'uid_owner')
212
-					->from('share')
213
-					->where(
214
-						$qb->expr()->eq('id', $qb->createNamedParameter($parent))
215
-					);
216
-				$result = $qb->execute();
217
-				$item = $result->fetch();
218
-				$result->closeCursor();
219
-				if (isset($item['parent'])) {
220
-					$parent = $item['parent'];
221
-				} else {
222
-					$fileOwner = $item['uid_owner'];
223
-					break;
224
-				}
225
-			}
226
-		} else {
227
-			$fileOwner = $source['uid_owner'];
228
-		}
229
-		if (isset($fileOwner)) {
230
-			$source['fileOwner'] = $fileOwner;
231
-		} else {
232
-			\OC::$server->getLogger()->error('No owner found for reshare', ['app' => 'files_sharing']);
233
-		}
234
-
235
-		return $source;
236
-	}
237
-
238
-	/**
239
-	 * @param string $target
240
-	 * @param array $share
241
-	 * @return array|false source item
242
-	 */
243
-	public static function getSource($target, $share) {
244
-		if ($share['item_type'] === 'folder' && $target !== '') {
245
-			// note: in case of ext storage mount points the path might be empty
246
-			// which would cause a leading slash to appear
247
-			$share['path'] = ltrim($share['path'] . '/' . $target, '/');
248
-		}
249
-		return self::resolveReshares($share);
250
-	}
40
+    const FORMAT_SHARED_STORAGE = 0;
41
+    const FORMAT_GET_FOLDER_CONTENTS = 1;
42
+    const FORMAT_FILE_APP_ROOT = 2;
43
+    const FORMAT_OPENDIR = 3;
44
+    const FORMAT_GET_ALL = 4;
45
+    const FORMAT_PERMISSIONS = 5;
46
+    const FORMAT_TARGET_NAMES = 6;
47
+
48
+    private $path;
49
+
50
+    /** @var FederatedShareProvider */
51
+    private $federatedShareProvider;
52
+
53
+    public function __construct(FederatedShareProvider $federatedShareProvider = null) {
54
+        if ($federatedShareProvider) {
55
+            $this->federatedShareProvider = $federatedShareProvider;
56
+        } else {
57
+            $federatedSharingApp = new \OCA\FederatedFileSharing\AppInfo\Application();
58
+            $this->federatedShareProvider = $federatedSharingApp->getFederatedShareProvider();
59
+        }
60
+    }
61
+
62
+    public function isValidSource($itemSource, $uidOwner) {
63
+        try {
64
+            $path = \OC\Files\Filesystem::getPath($itemSource);
65
+            // FIXME: attributes should not be set here,
66
+            // keeping this pattern for now to avoid unexpected
67
+            // regressions
68
+            $this->path = \OC\Files\Filesystem::normalizePath(basename($path));
69
+            return true;
70
+        } catch (\OCP\Files\NotFoundException $e) {
71
+            return false;
72
+        }
73
+    }
74
+
75
+    public function getFilePath($itemSource, $uidOwner) {
76
+        if (isset($this->path)) {
77
+            $path = $this->path;
78
+            $this->path = null;
79
+            return $path;
80
+        } else {
81
+            try {
82
+                $path = \OC\Files\Filesystem::getPath($itemSource);
83
+                return $path;
84
+            } catch (\OCP\Files\NotFoundException $e) {
85
+                return false;
86
+            }
87
+        }
88
+    }
89
+
90
+    /**
91
+     * create unique target
92
+     * @param string $filePath
93
+     * @param string $shareWith
94
+     * @param array $exclude (optional)
95
+     * @return string
96
+     */
97
+    public function generateTarget($filePath, $shareWith, $exclude = null) {
98
+        $shareFolder = \OCA\Files_Sharing\Helper::getShareFolder();
99
+        $target = \OC\Files\Filesystem::normalizePath($shareFolder . '/' . basename($filePath));
100
+
101
+        // for group shares we return the target right away
102
+        if ($shareWith === false) {
103
+            return $target;
104
+        }
105
+
106
+        \OC\Files\Filesystem::initMountPoints($shareWith);
107
+        $view = new \OC\Files\View('/' . $shareWith . '/files');
108
+
109
+        if (!$view->is_dir($shareFolder)) {
110
+            $dir = '';
111
+            $subdirs = explode('/', $shareFolder);
112
+            foreach ($subdirs as $subdir) {
113
+                $dir = $dir . '/' . $subdir;
114
+                if (!$view->is_dir($dir)) {
115
+                    $view->mkdir($dir);
116
+                }
117
+            }
118
+        }
119
+
120
+        $excludeList = is_array($exclude) ? $exclude : array();
121
+
122
+        return \OCA\Files_Sharing\Helper::generateUniqueTarget($target, $excludeList, $view);
123
+    }
124
+
125
+    public function formatItems($items, $format, $parameters = null) {
126
+        if ($format === self::FORMAT_SHARED_STORAGE) {
127
+            // Only 1 item should come through for this format call
128
+            $item = array_shift($items);
129
+            return array(
130
+                'parent' => $item['parent'],
131
+                'path' => $item['path'],
132
+                'storage' => $item['storage'],
133
+                'permissions' => $item['permissions'],
134
+                'uid_owner' => $item['uid_owner'],
135
+            );
136
+        } else if ($format === self::FORMAT_GET_FOLDER_CONTENTS) {
137
+            $files = array();
138
+            foreach ($items as $item) {
139
+                $file = array();
140
+                $file['fileid'] = $item['file_source'];
141
+                $file['storage'] = $item['storage'];
142
+                $file['path'] = $item['file_target'];
143
+                $file['parent'] = $item['file_parent'];
144
+                $file['name'] = basename($item['file_target']);
145
+                $file['mimetype'] = $item['mimetype'];
146
+                $file['mimepart'] = $item['mimepart'];
147
+                $file['mtime'] = $item['mtime'];
148
+                $file['encrypted'] = $item['encrypted'];
149
+                $file['etag'] = $item['etag'];
150
+                $file['uid_owner'] = $item['uid_owner'];
151
+                $file['displayname_owner'] = $item['displayname_owner'];
152
+
153
+                $storage = \OC\Files\Filesystem::getStorage('/');
154
+                $cache = $storage->getCache();
155
+                $file['size'] = $item['size'];
156
+                $files[] = $file;
157
+            }
158
+            return $files;
159
+        } else if ($format === self::FORMAT_OPENDIR) {
160
+            $files = array();
161
+            foreach ($items as $item) {
162
+                $files[] = basename($item['file_target']);
163
+            }
164
+            return $files;
165
+        } else if ($format === self::FORMAT_GET_ALL) {
166
+            $ids = array();
167
+            foreach ($items as $item) {
168
+                $ids[] = $item['file_source'];
169
+            }
170
+            return $ids;
171
+        } else if ($format === self::FORMAT_PERMISSIONS) {
172
+            $filePermissions = array();
173
+            foreach ($items as $item) {
174
+                $filePermissions[$item['file_source']] = $item['permissions'];
175
+            }
176
+            return $filePermissions;
177
+        } else if ($format === self::FORMAT_TARGET_NAMES) {
178
+            $targets = array();
179
+            foreach ($items as $item) {
180
+                $targets[] = $item['file_target'];
181
+            }
182
+            return $targets;
183
+        }
184
+        return array();
185
+    }
186
+
187
+    /**
188
+     * check if server2server share is enabled
189
+     *
190
+     * @param int $shareType
191
+     * @return boolean
192
+     */
193
+    public function isShareTypeAllowed($shareType) {
194
+        if ($shareType === \OCP\Share::SHARE_TYPE_REMOTE) {
195
+            return $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
196
+        }
197
+
198
+        return true;
199
+    }
200
+
201
+    /**
202
+     * resolve reshares to return the correct source item
203
+     * @param array $source
204
+     * @return array source item
205
+     */
206
+    protected static function resolveReshares($source) {
207
+        if (isset($source['parent'])) {
208
+            $parent = $source['parent'];
209
+            while (isset($parent)) {
210
+                $qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
211
+                $qb->select('parent', 'uid_owner')
212
+                    ->from('share')
213
+                    ->where(
214
+                        $qb->expr()->eq('id', $qb->createNamedParameter($parent))
215
+                    );
216
+                $result = $qb->execute();
217
+                $item = $result->fetch();
218
+                $result->closeCursor();
219
+                if (isset($item['parent'])) {
220
+                    $parent = $item['parent'];
221
+                } else {
222
+                    $fileOwner = $item['uid_owner'];
223
+                    break;
224
+                }
225
+            }
226
+        } else {
227
+            $fileOwner = $source['uid_owner'];
228
+        }
229
+        if (isset($fileOwner)) {
230
+            $source['fileOwner'] = $fileOwner;
231
+        } else {
232
+            \OC::$server->getLogger()->error('No owner found for reshare', ['app' => 'files_sharing']);
233
+        }
234
+
235
+        return $source;
236
+    }
237
+
238
+    /**
239
+     * @param string $target
240
+     * @param array $share
241
+     * @return array|false source item
242
+     */
243
+    public static function getSource($target, $share) {
244
+        if ($share['item_type'] === 'folder' && $target !== '') {
245
+            // note: in case of ext storage mount points the path might be empty
246
+            // which would cause a leading slash to appear
247
+            $share['path'] = ltrim($share['path'] . '/' . $target, '/');
248
+        }
249
+        return self::resolveReshares($share);
250
+    }
251 251
 }
Please login to merge, or discard this patch.
apps/files_sharing/lib/SharedMount.php 2 patches
Indentation   +222 added lines, -222 removed lines patch added patch discarded remove patch
@@ -37,226 +37,226 @@
 block discarded – undo
37 37
  * Shared mount points can be moved by the user
38 38
  */
39 39
 class SharedMount extends MountPoint implements MoveableMount {
40
-	/**
41
-	 * @var \OCA\Files_Sharing\SharedStorage $storage
42
-	 */
43
-	protected $storage = null;
44
-
45
-	/**
46
-	 * @var \OC\Files\View
47
-	 */
48
-	private $recipientView;
49
-
50
-	/**
51
-	 * @var string
52
-	 */
53
-	private $user;
54
-
55
-	/** @var \OCP\Share\IShare */
56
-	private $superShare;
57
-
58
-	/** @var \OCP\Share\IShare[] */
59
-	private $groupedShares;
60
-
61
-	/**
62
-	 * @param string $storage
63
-	 * @param SharedMount[] $mountpoints
64
-	 * @param array|null $arguments
65
-	 * @param \OCP\Files\Storage\IStorageFactory $loader
66
-	 */
67
-	public function __construct($storage, array $mountpoints, $arguments = null, $loader = null) {
68
-		$this->user = $arguments['user'];
69
-		$this->recipientView = new View('/' . $this->user . '/files');
70
-
71
-		$this->superShare = $arguments['superShare'];
72
-		$this->groupedShares = $arguments['groupedShares'];
73
-
74
-		$newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints);
75
-		$absMountPoint = '/' . $this->user . '/files' . $newMountPoint;
76
-		$arguments['ownerView'] = new View('/' . $this->superShare->getShareOwner() . '/files');
77
-		parent::__construct($storage, $absMountPoint, $arguments, $loader);
78
-	}
79
-
80
-	/**
81
-	 * check if the parent folder exists otherwise move the mount point up
82
-	 *
83
-	 * @param \OCP\Share\IShare $share
84
-	 * @param SharedMount[] $mountpoints
85
-	 * @return string
86
-	 */
87
-	private function verifyMountPoint(\OCP\Share\IShare $share, array $mountpoints) {
88
-
89
-		$mountPoint = basename($share->getTarget());
90
-		$parent = dirname($share->getTarget());
91
-
92
-		if (!$this->recipientView->is_dir($parent)) {
93
-			$parent = Helper::getShareFolder($this->recipientView);
94
-		}
95
-
96
-		$newMountPoint = $this->generateUniqueTarget(
97
-			\OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint),
98
-			$this->recipientView,
99
-			$mountpoints
100
-		);
101
-
102
-		if ($newMountPoint !== $share->getTarget()) {
103
-			$this->updateFileTarget($newMountPoint, $share);
104
-		}
105
-
106
-		return $newMountPoint;
107
-	}
108
-
109
-	/**
110
-	 * update fileTarget in the database if the mount point changed
111
-	 *
112
-	 * @param string $newPath
113
-	 * @param \OCP\Share\IShare $share
114
-	 * @return bool
115
-	 */
116
-	private function updateFileTarget($newPath, &$share) {
117
-		$share->setTarget($newPath);
118
-
119
-		foreach ($this->groupedShares as $tmpShare) {
120
-			$tmpShare->setTarget($newPath);
121
-			\OC::$server->getShareManager()->moveShare($tmpShare, $this->user);
122
-		}
123
-	}
124
-
125
-
126
-	/**
127
-	 * @param string $path
128
-	 * @param View $view
129
-	 * @param SharedMount[] $mountpoints
130
-	 * @return mixed
131
-	 */
132
-	private function generateUniqueTarget($path, $view, array $mountpoints) {
133
-		$pathinfo = pathinfo($path);
134
-		$ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : '';
135
-		$name = $pathinfo['filename'];
136
-		$dir = $pathinfo['dirname'];
137
-
138
-		// Helper function to find existing mount points
139
-		$mountpointExists = function ($path) use ($mountpoints) {
140
-			foreach ($mountpoints as $mountpoint) {
141
-				if ($mountpoint->getShare()->getTarget() === $path) {
142
-					return true;
143
-				}
144
-			}
145
-			return false;
146
-		};
147
-
148
-		$i = 2;
149
-		while ($view->file_exists($path) || $mountpointExists($path)) {
150
-			$path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
151
-			$i++;
152
-		}
153
-
154
-		return $path;
155
-	}
156
-
157
-	/**
158
-	 * Format a path to be relative to the /user/files/ directory
159
-	 *
160
-	 * @param string $path the absolute path
161
-	 * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
162
-	 * @throws \OCA\Files_Sharing\Exceptions\BrokenPath
163
-	 */
164
-	protected function stripUserFilesPath($path) {
165
-		$trimmed = ltrim($path, '/');
166
-		$split = explode('/', $trimmed);
167
-
168
-		// it is not a file relative to data/user/files
169
-		if (count($split) < 3 || $split[1] !== 'files') {
170
-			\OC::$server->getLogger()->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
171
-			throw new \OCA\Files_Sharing\Exceptions\BrokenPath('Path does not start with /user/files', 10);
172
-		}
173
-
174
-		// skip 'user' and 'files'
175
-		$sliced = array_slice($split, 2);
176
-		$relPath = implode('/', $sliced);
177
-
178
-		return '/' . $relPath;
179
-	}
180
-
181
-	/**
182
-	 * Move the mount point to $target
183
-	 *
184
-	 * @param string $target the target mount point
185
-	 * @return bool
186
-	 */
187
-	public function moveMount($target) {
188
-
189
-		$relTargetPath = $this->stripUserFilesPath($target);
190
-		$share = $this->storage->getShare();
191
-
192
-		$result = true;
193
-
194
-		try {
195
-			$this->updateFileTarget($relTargetPath, $share);
196
-			$this->setMountPoint($target);
197
-			$this->storage->setMountPoint($relTargetPath);
198
-		} catch (\Exception $e) {
199
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_sharing', 'message' => 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"']);
200
-		}
201
-
202
-		return $result;
203
-	}
204
-
205
-	/**
206
-	 * Remove the mount points
207
-	 *
208
-	 * @return bool
209
-	 */
210
-	public function removeMount() {
211
-		$mountManager = \OC\Files\Filesystem::getMountManager();
212
-		/** @var $storage \OCA\Files_Sharing\SharedStorage */
213
-		$storage = $this->getStorage();
214
-		$result = $storage->unshareStorage();
215
-		$mountManager->removeMount($this->mountPoint);
216
-
217
-		return $result;
218
-	}
219
-
220
-	/**
221
-	 * @return \OCP\Share\IShare
222
-	 */
223
-	public function getShare() {
224
-		return $this->superShare;
225
-	}
226
-
227
-	/**
228
-	 * Get the file id of the root of the storage
229
-	 *
230
-	 * @return int
231
-	 */
232
-	public function getStorageRootId() {
233
-		return $this->getShare()->getNodeId();
234
-	}
235
-
236
-	/**
237
-	 * @return int
238
-	 */
239
-	public function getNumericStorageId() {
240
-		if (!is_null($this->getShare()->getNodeCacheEntry())) {
241
-			return $this->getShare()->getNodeCacheEntry()->getStorageId();
242
-		} else {
243
-			$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
244
-
245
-			$query = $builder->select('storage')
246
-				->from('filecache')
247
-				->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId())));
248
-
249
-			$result = $query->execute();
250
-			$row = $result->fetch();
251
-			$result->closeCursor();
252
-			if ($row) {
253
-				return (int)$row['storage'];
254
-			}
255
-			return -1;
256
-		}
257
-	}
258
-
259
-	public function getMountType() {
260
-		return 'shared';
261
-	}
40
+    /**
41
+     * @var \OCA\Files_Sharing\SharedStorage $storage
42
+     */
43
+    protected $storage = null;
44
+
45
+    /**
46
+     * @var \OC\Files\View
47
+     */
48
+    private $recipientView;
49
+
50
+    /**
51
+     * @var string
52
+     */
53
+    private $user;
54
+
55
+    /** @var \OCP\Share\IShare */
56
+    private $superShare;
57
+
58
+    /** @var \OCP\Share\IShare[] */
59
+    private $groupedShares;
60
+
61
+    /**
62
+     * @param string $storage
63
+     * @param SharedMount[] $mountpoints
64
+     * @param array|null $arguments
65
+     * @param \OCP\Files\Storage\IStorageFactory $loader
66
+     */
67
+    public function __construct($storage, array $mountpoints, $arguments = null, $loader = null) {
68
+        $this->user = $arguments['user'];
69
+        $this->recipientView = new View('/' . $this->user . '/files');
70
+
71
+        $this->superShare = $arguments['superShare'];
72
+        $this->groupedShares = $arguments['groupedShares'];
73
+
74
+        $newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints);
75
+        $absMountPoint = '/' . $this->user . '/files' . $newMountPoint;
76
+        $arguments['ownerView'] = new View('/' . $this->superShare->getShareOwner() . '/files');
77
+        parent::__construct($storage, $absMountPoint, $arguments, $loader);
78
+    }
79
+
80
+    /**
81
+     * check if the parent folder exists otherwise move the mount point up
82
+     *
83
+     * @param \OCP\Share\IShare $share
84
+     * @param SharedMount[] $mountpoints
85
+     * @return string
86
+     */
87
+    private function verifyMountPoint(\OCP\Share\IShare $share, array $mountpoints) {
88
+
89
+        $mountPoint = basename($share->getTarget());
90
+        $parent = dirname($share->getTarget());
91
+
92
+        if (!$this->recipientView->is_dir($parent)) {
93
+            $parent = Helper::getShareFolder($this->recipientView);
94
+        }
95
+
96
+        $newMountPoint = $this->generateUniqueTarget(
97
+            \OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint),
98
+            $this->recipientView,
99
+            $mountpoints
100
+        );
101
+
102
+        if ($newMountPoint !== $share->getTarget()) {
103
+            $this->updateFileTarget($newMountPoint, $share);
104
+        }
105
+
106
+        return $newMountPoint;
107
+    }
108
+
109
+    /**
110
+     * update fileTarget in the database if the mount point changed
111
+     *
112
+     * @param string $newPath
113
+     * @param \OCP\Share\IShare $share
114
+     * @return bool
115
+     */
116
+    private function updateFileTarget($newPath, &$share) {
117
+        $share->setTarget($newPath);
118
+
119
+        foreach ($this->groupedShares as $tmpShare) {
120
+            $tmpShare->setTarget($newPath);
121
+            \OC::$server->getShareManager()->moveShare($tmpShare, $this->user);
122
+        }
123
+    }
124
+
125
+
126
+    /**
127
+     * @param string $path
128
+     * @param View $view
129
+     * @param SharedMount[] $mountpoints
130
+     * @return mixed
131
+     */
132
+    private function generateUniqueTarget($path, $view, array $mountpoints) {
133
+        $pathinfo = pathinfo($path);
134
+        $ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : '';
135
+        $name = $pathinfo['filename'];
136
+        $dir = $pathinfo['dirname'];
137
+
138
+        // Helper function to find existing mount points
139
+        $mountpointExists = function ($path) use ($mountpoints) {
140
+            foreach ($mountpoints as $mountpoint) {
141
+                if ($mountpoint->getShare()->getTarget() === $path) {
142
+                    return true;
143
+                }
144
+            }
145
+            return false;
146
+        };
147
+
148
+        $i = 2;
149
+        while ($view->file_exists($path) || $mountpointExists($path)) {
150
+            $path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
151
+            $i++;
152
+        }
153
+
154
+        return $path;
155
+    }
156
+
157
+    /**
158
+     * Format a path to be relative to the /user/files/ directory
159
+     *
160
+     * @param string $path the absolute path
161
+     * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
162
+     * @throws \OCA\Files_Sharing\Exceptions\BrokenPath
163
+     */
164
+    protected function stripUserFilesPath($path) {
165
+        $trimmed = ltrim($path, '/');
166
+        $split = explode('/', $trimmed);
167
+
168
+        // it is not a file relative to data/user/files
169
+        if (count($split) < 3 || $split[1] !== 'files') {
170
+            \OC::$server->getLogger()->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
171
+            throw new \OCA\Files_Sharing\Exceptions\BrokenPath('Path does not start with /user/files', 10);
172
+        }
173
+
174
+        // skip 'user' and 'files'
175
+        $sliced = array_slice($split, 2);
176
+        $relPath = implode('/', $sliced);
177
+
178
+        return '/' . $relPath;
179
+    }
180
+
181
+    /**
182
+     * Move the mount point to $target
183
+     *
184
+     * @param string $target the target mount point
185
+     * @return bool
186
+     */
187
+    public function moveMount($target) {
188
+
189
+        $relTargetPath = $this->stripUserFilesPath($target);
190
+        $share = $this->storage->getShare();
191
+
192
+        $result = true;
193
+
194
+        try {
195
+            $this->updateFileTarget($relTargetPath, $share);
196
+            $this->setMountPoint($target);
197
+            $this->storage->setMountPoint($relTargetPath);
198
+        } catch (\Exception $e) {
199
+            \OC::$server->getLogger()->logException($e, ['app' => 'files_sharing', 'message' => 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"']);
200
+        }
201
+
202
+        return $result;
203
+    }
204
+
205
+    /**
206
+     * Remove the mount points
207
+     *
208
+     * @return bool
209
+     */
210
+    public function removeMount() {
211
+        $mountManager = \OC\Files\Filesystem::getMountManager();
212
+        /** @var $storage \OCA\Files_Sharing\SharedStorage */
213
+        $storage = $this->getStorage();
214
+        $result = $storage->unshareStorage();
215
+        $mountManager->removeMount($this->mountPoint);
216
+
217
+        return $result;
218
+    }
219
+
220
+    /**
221
+     * @return \OCP\Share\IShare
222
+     */
223
+    public function getShare() {
224
+        return $this->superShare;
225
+    }
226
+
227
+    /**
228
+     * Get the file id of the root of the storage
229
+     *
230
+     * @return int
231
+     */
232
+    public function getStorageRootId() {
233
+        return $this->getShare()->getNodeId();
234
+    }
235
+
236
+    /**
237
+     * @return int
238
+     */
239
+    public function getNumericStorageId() {
240
+        if (!is_null($this->getShare()->getNodeCacheEntry())) {
241
+            return $this->getShare()->getNodeCacheEntry()->getStorageId();
242
+        } else {
243
+            $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
244
+
245
+            $query = $builder->select('storage')
246
+                ->from('filecache')
247
+                ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($this->getStorageRootId())));
248
+
249
+            $result = $query->execute();
250
+            $row = $result->fetch();
251
+            $result->closeCursor();
252
+            if ($row) {
253
+                return (int)$row['storage'];
254
+            }
255
+            return -1;
256
+        }
257
+    }
258
+
259
+    public function getMountType() {
260
+        return 'shared';
261
+    }
262 262
 }
Please login to merge, or discard this patch.
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -66,14 +66,14 @@  discard block
 block discarded – undo
66 66
 	 */
67 67
 	public function __construct($storage, array $mountpoints, $arguments = null, $loader = null) {
68 68
 		$this->user = $arguments['user'];
69
-		$this->recipientView = new View('/' . $this->user . '/files');
69
+		$this->recipientView = new View('/'.$this->user.'/files');
70 70
 
71 71
 		$this->superShare = $arguments['superShare'];
72 72
 		$this->groupedShares = $arguments['groupedShares'];
73 73
 
74 74
 		$newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints);
75
-		$absMountPoint = '/' . $this->user . '/files' . $newMountPoint;
76
-		$arguments['ownerView'] = new View('/' . $this->superShare->getShareOwner() . '/files');
75
+		$absMountPoint = '/'.$this->user.'/files'.$newMountPoint;
76
+		$arguments['ownerView'] = new View('/'.$this->superShare->getShareOwner().'/files');
77 77
 		parent::__construct($storage, $absMountPoint, $arguments, $loader);
78 78
 	}
79 79
 
@@ -94,7 +94,7 @@  discard block
 block discarded – undo
94 94
 		}
95 95
 
96 96
 		$newMountPoint = $this->generateUniqueTarget(
97
-			\OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint),
97
+			\OC\Files\Filesystem::normalizePath($parent.'/'.$mountPoint),
98 98
 			$this->recipientView,
99 99
 			$mountpoints
100 100
 		);
@@ -131,12 +131,12 @@  discard block
 block discarded – undo
131 131
 	 */
132 132
 	private function generateUniqueTarget($path, $view, array $mountpoints) {
133 133
 		$pathinfo = pathinfo($path);
134
-		$ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : '';
134
+		$ext = isset($pathinfo['extension']) ? '.'.$pathinfo['extension'] : '';
135 135
 		$name = $pathinfo['filename'];
136 136
 		$dir = $pathinfo['dirname'];
137 137
 
138 138
 		// Helper function to find existing mount points
139
-		$mountpointExists = function ($path) use ($mountpoints) {
139
+		$mountpointExists = function($path) use ($mountpoints) {
140 140
 			foreach ($mountpoints as $mountpoint) {
141 141
 				if ($mountpoint->getShare()->getTarget() === $path) {
142 142
 					return true;
@@ -147,7 +147,7 @@  discard block
 block discarded – undo
147 147
 
148 148
 		$i = 2;
149 149
 		while ($view->file_exists($path) || $mountpointExists($path)) {
150
-			$path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
150
+			$path = Filesystem::normalizePath($dir.'/'.$name.' ('.$i.')'.$ext);
151 151
 			$i++;
152 152
 		}
153 153
 
@@ -167,7 +167,7 @@  discard block
 block discarded – undo
167 167
 
168 168
 		// it is not a file relative to data/user/files
169 169
 		if (count($split) < 3 || $split[1] !== 'files') {
170
-			\OC::$server->getLogger()->error('Can not strip userid and "files/" from path: ' . $path, ['app' => 'files_sharing']);
170
+			\OC::$server->getLogger()->error('Can not strip userid and "files/" from path: '.$path, ['app' => 'files_sharing']);
171 171
 			throw new \OCA\Files_Sharing\Exceptions\BrokenPath('Path does not start with /user/files', 10);
172 172
 		}
173 173
 
@@ -175,7 +175,7 @@  discard block
 block discarded – undo
175 175
 		$sliced = array_slice($split, 2);
176 176
 		$relPath = implode('/', $sliced);
177 177
 
178
-		return '/' . $relPath;
178
+		return '/'.$relPath;
179 179
 	}
180 180
 
181 181
 	/**
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 			$this->setMountPoint($target);
197 197
 			$this->storage->setMountPoint($relTargetPath);
198 198
 		} catch (\Exception $e) {
199
-			\OC::$server->getLogger()->logException($e, ['app' => 'files_sharing', 'message' => 'Could not rename mount point for shared folder "' . $this->getMountPoint() . '" to "' . $target . '"']);
199
+			\OC::$server->getLogger()->logException($e, ['app' => 'files_sharing', 'message' => 'Could not rename mount point for shared folder "'.$this->getMountPoint().'" to "'.$target.'"']);
200 200
 		}
201 201
 
202 202
 		return $result;
@@ -250,7 +250,7 @@  discard block
 block discarded – undo
250 250
 			$row = $result->fetch();
251 251
 			$result->closeCursor();
252 252
 			if ($row) {
253
-				return (int)$row['storage'];
253
+				return (int) $row['storage'];
254 254
 			}
255 255
 			return -1;
256 256
 		}
Please login to merge, or discard this patch.
apps/files_trashbin/lib/Trashbin.php 2 patches
Indentation   +950 added lines, -950 removed lines patch added patch discarded remove patch
@@ -52,954 +52,954 @@
 block discarded – undo
52 52
 
53 53
 class Trashbin {
54 54
 
55
-	// unit: percentage; 50% of available disk space/quota
56
-	const DEFAULTMAXSIZE = 50;
57
-
58
-	/**
59
-	 * Whether versions have already be rescanned during this PHP request
60
-	 *
61
-	 * @var bool
62
-	 */
63
-	private static $scannedVersions = false;
64
-
65
-	/**
66
-	 * Ensure we don't need to scan the file during the move to trash
67
-	 * by triggering the scan in the pre-hook
68
-	 *
69
-	 * @param array $params
70
-	 */
71
-	public static function ensureFileScannedHook($params) {
72
-		try {
73
-			self::getUidAndFilename($params['path']);
74
-		} catch (NotFoundException $e) {
75
-			// nothing to scan for non existing files
76
-		}
77
-	}
78
-
79
-	/**
80
-	 * get the UID of the owner of the file and the path to the file relative to
81
-	 * owners files folder
82
-	 *
83
-	 * @param string $filename
84
-	 * @return array
85
-	 * @throws \OC\User\NoUserException
86
-	 */
87
-	public static function getUidAndFilename($filename) {
88
-		$uid = Filesystem::getOwner($filename);
89
-		$userManager = \OC::$server->getUserManager();
90
-		// if the user with the UID doesn't exists, e.g. because the UID points
91
-		// to a remote user with a federated cloud ID we use the current logged-in
92
-		// user. We need a valid local user to move the file to the right trash bin
93
-		if (!$userManager->userExists($uid)) {
94
-			$uid = User::getUser();
95
-		}
96
-		if (!$uid) {
97
-			// no owner, usually because of share link from ext storage
98
-			return [null, null];
99
-		}
100
-		Filesystem::initMountPoints($uid);
101
-		if ($uid !== User::getUser()) {
102
-			$info = Filesystem::getFileInfo($filename);
103
-			$ownerView = new View('/' . $uid . '/files');
104
-			try {
105
-				$filename = $ownerView->getPath($info['fileid']);
106
-			} catch (NotFoundException $e) {
107
-				$filename = null;
108
-			}
109
-		}
110
-		return [$uid, $filename];
111
-	}
112
-
113
-	/**
114
-	 * get original location of files for user
115
-	 *
116
-	 * @param string $user
117
-	 * @return array (filename => array (timestamp => original location))
118
-	 */
119
-	public static function getLocations($user) {
120
-		$query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
121
-			. ' FROM `*PREFIX*files_trash` WHERE `user`=?');
122
-		$result = $query->execute(array($user));
123
-		$array = array();
124
-		while ($row = $result->fetchRow()) {
125
-			if (isset($array[$row['id']])) {
126
-				$array[$row['id']][$row['timestamp']] = $row['location'];
127
-			} else {
128
-				$array[$row['id']] = array($row['timestamp'] => $row['location']);
129
-			}
130
-		}
131
-		return $array;
132
-	}
133
-
134
-	/**
135
-	 * get original location of file
136
-	 *
137
-	 * @param string $user
138
-	 * @param string $filename
139
-	 * @param string $timestamp
140
-	 * @return string original location
141
-	 */
142
-	public static function getLocation($user, $filename, $timestamp) {
143
-		$query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
144
-			. ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
145
-		$result = $query->execute(array($user, $filename, $timestamp))->fetchAll();
146
-		if (isset($result[0]['location'])) {
147
-			return $result[0]['location'];
148
-		} else {
149
-			return false;
150
-		}
151
-	}
152
-
153
-	private static function setUpTrash($user) {
154
-		$view = new View('/' . $user);
155
-		if (!$view->is_dir('files_trashbin')) {
156
-			$view->mkdir('files_trashbin');
157
-		}
158
-		if (!$view->is_dir('files_trashbin/files')) {
159
-			$view->mkdir('files_trashbin/files');
160
-		}
161
-		if (!$view->is_dir('files_trashbin/versions')) {
162
-			$view->mkdir('files_trashbin/versions');
163
-		}
164
-		if (!$view->is_dir('files_trashbin/keys')) {
165
-			$view->mkdir('files_trashbin/keys');
166
-		}
167
-	}
168
-
169
-
170
-	/**
171
-	 * copy file to owners trash
172
-	 *
173
-	 * @param string $sourcePath
174
-	 * @param string $owner
175
-	 * @param string $targetPath
176
-	 * @param $user
177
-	 * @param integer $timestamp
178
-	 */
179
-	private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
180
-		self::setUpTrash($owner);
181
-
182
-		$targetFilename = basename($targetPath);
183
-		$targetLocation = dirname($targetPath);
184
-
185
-		$sourceFilename = basename($sourcePath);
186
-
187
-		$view = new View('/');
188
-
189
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
190
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
191
-		self::copy_recursive($source, $target, $view);
192
-
193
-
194
-		if ($view->file_exists($target)) {
195
-			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
196
-			$result = $query->execute(array($targetFilename, $timestamp, $targetLocation, $user));
197
-			if (!$result) {
198
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
199
-			}
200
-		}
201
-	}
202
-
203
-
204
-	/**
205
-	 * move file to the trash bin
206
-	 *
207
-	 * @param string $file_path path to the deleted file/directory relative to the files root directory
208
-	 * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
209
-	 *
210
-	 * @return bool
211
-	 */
212
-	public static function move2trash($file_path, $ownerOnly = false) {
213
-		// get the user for which the filesystem is setup
214
-		$root = Filesystem::getRoot();
215
-		list(, $user) = explode('/', $root);
216
-		list($owner, $ownerPath) = self::getUidAndFilename($file_path);
217
-
218
-		// if no owner found (ex: ext storage + share link), will use the current user's trashbin then
219
-		if (is_null($owner)) {
220
-			$owner = $user;
221
-			$ownerPath = $file_path;
222
-		}
223
-
224
-		$ownerView = new View('/' . $owner);
225
-		// file has been deleted in between
226
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
227
-			return true;
228
-		}
229
-
230
-		self::setUpTrash($user);
231
-		if ($owner !== $user) {
232
-			// also setup for owner
233
-			self::setUpTrash($owner);
234
-		}
235
-
236
-		$path_parts = pathinfo($ownerPath);
237
-
238
-		$filename = $path_parts['basename'];
239
-		$location = $path_parts['dirname'];
240
-		$timestamp = time();
241
-
242
-		// disable proxy to prevent recursive calls
243
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
244
-
245
-		/** @var \OC\Files\Storage\Storage $trashStorage */
246
-		list($trashStorage, $trashInternalPath) = $ownerView->resolvePath($trashPath);
247
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
248
-		list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath);
249
-		try {
250
-			$moveSuccessful = true;
251
-			if ($trashStorage->file_exists($trashInternalPath)) {
252
-				$trashStorage->unlink($trashInternalPath);
253
-			}
254
-			$trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
255
-		} catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
256
-			$moveSuccessful = false;
257
-			if ($trashStorage->file_exists($trashInternalPath)) {
258
-				$trashStorage->unlink($trashInternalPath);
259
-			}
260
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
261
-		}
262
-
263
-		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
264
-			if ($sourceStorage->is_dir($sourceInternalPath)) {
265
-				$sourceStorage->rmdir($sourceInternalPath);
266
-			} else {
267
-				$sourceStorage->unlink($sourceInternalPath);
268
-			}
269
-			return false;
270
-		}
271
-
272
-		$trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
273
-
274
-		if ($moveSuccessful) {
275
-			$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
276
-			$result = $query->execute(array($filename, $timestamp, $location, $owner));
277
-			if (!$result) {
278
-				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
279
-			}
280
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', array('filePath' => Filesystem::normalizePath($file_path),
281
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)));
282
-
283
-			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
284
-
285
-			// if owner !== user we need to also add a copy to the users trash
286
-			if ($user !== $owner && $ownerOnly === false) {
287
-				self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
288
-			}
289
-		}
290
-
291
-		self::scheduleExpire($user);
292
-
293
-		// if owner !== user we also need to update the owners trash size
294
-		if ($owner !== $user) {
295
-			self::scheduleExpire($owner);
296
-		}
297
-
298
-		return $moveSuccessful;
299
-	}
300
-
301
-	/**
302
-	 * Move file versions to trash so that they can be restored later
303
-	 *
304
-	 * @param string $filename of deleted file
305
-	 * @param string $owner owner user id
306
-	 * @param string $ownerPath path relative to the owner's home storage
307
-	 * @param integer $timestamp when the file was deleted
308
-	 */
309
-	private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
310
-		if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
311
-
312
-			$user = User::getUser();
313
-			$rootView = new View('/');
314
-
315
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
316
-				if ($owner !== $user) {
317
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
318
-				}
319
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
320
-			} else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
321
-
322
-				foreach ($versions as $v) {
323
-					if ($owner !== $user) {
324
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
325
-					}
326
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
327
-				}
328
-			}
329
-		}
330
-	}
331
-
332
-	/**
333
-	 * Move a file or folder on storage level
334
-	 *
335
-	 * @param View $view
336
-	 * @param string $source
337
-	 * @param string $target
338
-	 * @return bool
339
-	 */
340
-	private static function move(View $view, $source, $target) {
341
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
342
-		list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
343
-		/** @var \OC\Files\Storage\Storage $targetStorage */
344
-		list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
345
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
346
-
347
-		$result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
348
-		if ($result) {
349
-			$targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
350
-		}
351
-		return $result;
352
-	}
353
-
354
-	/**
355
-	 * Copy a file or folder on storage level
356
-	 *
357
-	 * @param View $view
358
-	 * @param string $source
359
-	 * @param string $target
360
-	 * @return bool
361
-	 */
362
-	private static function copy(View $view, $source, $target) {
363
-		/** @var \OC\Files\Storage\Storage $sourceStorage */
364
-		list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
365
-		/** @var \OC\Files\Storage\Storage $targetStorage */
366
-		list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
367
-		/** @var \OC\Files\Storage\Storage $ownerTrashStorage */
368
-
369
-		$result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
370
-		if ($result) {
371
-			$targetStorage->getUpdater()->update($targetInternalPath);
372
-		}
373
-		return $result;
374
-	}
375
-
376
-	/**
377
-	 * Restore a file or folder from trash bin
378
-	 *
379
-	 * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
380
-	 * including the timestamp suffix ".d12345678"
381
-	 * @param string $filename name of the file/folder
382
-	 * @param int $timestamp time when the file/folder was deleted
383
-	 *
384
-	 * @return bool true on success, false otherwise
385
-	 */
386
-	public static function restore($file, $filename, $timestamp) {
387
-		$user = User::getUser();
388
-		$view = new View('/' . $user);
389
-
390
-		$location = '';
391
-		if ($timestamp) {
392
-			$location = self::getLocation($user, $filename, $timestamp);
393
-			if ($location === false) {
394
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
395
-			} else {
396
-				// if location no longer exists, restore file in the root directory
397
-				if ($location !== '/' &&
398
-					(!$view->is_dir('files/' . $location) ||
399
-						!$view->isCreatable('files/' . $location))
400
-				) {
401
-					$location = '';
402
-				}
403
-			}
404
-		}
405
-
406
-		// we need a  extension in case a file/dir with the same name already exists
407
-		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
408
-
409
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
410
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
411
-		if (!$view->file_exists($source)) {
412
-			return false;
413
-		}
414
-		$mtime = $view->filemtime($source);
415
-
416
-		// restore file
417
-		$restoreResult = $view->rename($source, $target);
418
-
419
-		// handle the restore result
420
-		if ($restoreResult) {
421
-			$fakeRoot = $view->getRoot();
422
-			$view->chroot('/' . $user . '/files');
423
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
424
-			$view->chroot($fakeRoot);
425
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
426
-				'trashPath' => Filesystem::normalizePath($file)));
427
-
428
-			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
429
-
430
-			if ($timestamp) {
431
-				$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
432
-				$query->execute(array($user, $filename, $timestamp));
433
-			}
434
-
435
-			return true;
436
-		}
437
-
438
-		return false;
439
-	}
440
-
441
-	/**
442
-	 * restore versions from trash bin
443
-	 *
444
-	 * @param View $view file view
445
-	 * @param string $file complete path to file
446
-	 * @param string $filename name of file once it was deleted
447
-	 * @param string $uniqueFilename new file name to restore the file without overwriting existing files
448
-	 * @param string $location location if file
449
-	 * @param int $timestamp deletion time
450
-	 * @return false|null
451
-	 */
452
-	private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
453
-
454
-		if (\OCP\App::isEnabled('files_versions')) {
455
-
456
-			$user = User::getUser();
457
-			$rootView = new View('/');
458
-
459
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
460
-
461
-			list($owner, $ownerPath) = self::getUidAndFilename($target);
462
-
463
-			// file has been deleted in between
464
-			if (empty($ownerPath)) {
465
-				return false;
466
-			}
467
-
468
-			if ($timestamp) {
469
-				$versionedFile = $filename;
470
-			} else {
471
-				$versionedFile = $file;
472
-			}
473
-
474
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
475
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
476
-			} else if ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
477
-				foreach ($versions as $v) {
478
-					if ($timestamp) {
479
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
480
-					} else {
481
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
482
-					}
483
-				}
484
-			}
485
-		}
486
-	}
487
-
488
-	/**
489
-	 * delete all files from the trash
490
-	 */
491
-	public static function deleteAll() {
492
-		$user = User::getUser();
493
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
494
-		$view = new View('/' . $user);
495
-		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
496
-
497
-		try {
498
-			$trash = $userRoot->get('files_trashbin');
499
-		} catch (NotFoundException $e) {
500
-			return false;
501
-		}
502
-
503
-		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
504
-		$filePaths = array();
505
-		foreach($fileInfos as $fileInfo){
506
-			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
507
-		}
508
-		unset($fileInfos); // save memory
509
-
510
-		// Bulk PreDelete-Hook
511
-		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', array('paths' => $filePaths));
512
-
513
-		// Single-File Hooks
514
-		foreach($filePaths as $path){
515
-			self::emitTrashbinPreDelete($path);
516
-		}
517
-
518
-		// actual file deletion
519
-		$trash->delete();
520
-		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
521
-		$query->execute(array($user));
522
-
523
-		// Bulk PostDelete-Hook
524
-		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', array('paths' => $filePaths));
525
-
526
-		// Single-File Hooks
527
-		foreach($filePaths as $path){
528
-			self::emitTrashbinPostDelete($path);
529
-		}
530
-
531
-		$trash = $userRoot->newFolder('files_trashbin');
532
-		$trash->newFolder('files');
533
-
534
-		return true;
535
-	}
536
-
537
-	/**
538
-	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
539
-	 * @param string $path
540
-	 */
541
-	protected static function emitTrashbinPreDelete($path){
542
-		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', array('path' => $path));
543
-	}
544
-
545
-	/**
546
-	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
547
-	 * @param string $path
548
-	 */
549
-	protected static function emitTrashbinPostDelete($path){
550
-		\OC_Hook::emit('\OCP\Trashbin', 'delete', array('path' => $path));
551
-	}
552
-
553
-	/**
554
-	 * delete file from trash bin permanently
555
-	 *
556
-	 * @param string $filename path to the file
557
-	 * @param string $user
558
-	 * @param int $timestamp of deletion time
559
-	 *
560
-	 * @return int size of deleted files
561
-	 */
562
-	public static function delete($filename, $user, $timestamp = null) {
563
-		$userRoot = \OC::$server->getUserFolder($user)->getParent();
564
-		$view = new View('/' . $user);
565
-		$size = 0;
566
-
567
-		if ($timestamp) {
568
-			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
569
-			$query->execute(array($user, $filename, $timestamp));
570
-			$file = $filename . '.d' . $timestamp;
571
-		} else {
572
-			$file = $filename;
573
-		}
574
-
575
-		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
576
-
577
-		try {
578
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
579
-		} catch (NotFoundException $e) {
580
-			return $size;
581
-		}
582
-
583
-		if ($node instanceof Folder) {
584
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
585
-		} else if ($node instanceof File) {
586
-			$size += $view->filesize('/files_trashbin/files/' . $file);
587
-		}
588
-
589
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
590
-		$node->delete();
591
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
592
-
593
-		return $size;
594
-	}
595
-
596
-	/**
597
-	 * @param View $view
598
-	 * @param string $file
599
-	 * @param string $filename
600
-	 * @param integer|null $timestamp
601
-	 * @param string $user
602
-	 * @return int
603
-	 */
604
-	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
605
-		$size = 0;
606
-		if (\OCP\App::isEnabled('files_versions')) {
607
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
608
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
609
-				$view->unlink('files_trashbin/versions/' . $file);
610
-			} else if ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
611
-				foreach ($versions as $v) {
612
-					if ($timestamp) {
613
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
614
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
615
-					} else {
616
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
617
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
618
-					}
619
-				}
620
-			}
621
-		}
622
-		return $size;
623
-	}
624
-
625
-	/**
626
-	 * check to see whether a file exists in trashbin
627
-	 *
628
-	 * @param string $filename path to the file
629
-	 * @param int $timestamp of deletion time
630
-	 * @return bool true if file exists, otherwise false
631
-	 */
632
-	public static function file_exists($filename, $timestamp = null) {
633
-		$user = User::getUser();
634
-		$view = new View('/' . $user);
635
-
636
-		if ($timestamp) {
637
-			$filename = $filename . '.d' . $timestamp;
638
-		}
639
-
640
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
641
-		return $view->file_exists($target);
642
-	}
643
-
644
-	/**
645
-	 * deletes used space for trash bin in db if user was deleted
646
-	 *
647
-	 * @param string $uid id of deleted user
648
-	 * @return bool result of db delete operation
649
-	 */
650
-	public static function deleteUser($uid) {
651
-		$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
652
-		return $query->execute(array($uid));
653
-	}
654
-
655
-	/**
656
-	 * calculate remaining free space for trash bin
657
-	 *
658
-	 * @param integer $trashbinSize current size of the trash bin
659
-	 * @param string $user
660
-	 * @return int available free space for trash bin
661
-	 */
662
-	private static function calculateFreeSpace($trashbinSize, $user) {
663
-		$softQuota = true;
664
-		$userObject = \OC::$server->getUserManager()->get($user);
665
-		if(is_null($userObject)) {
666
-			return 0;
667
-		}
668
-		$quota = $userObject->getQuota();
669
-		if ($quota === null || $quota === 'none') {
670
-			$quota = Filesystem::free_space('/');
671
-			$softQuota = false;
672
-			// inf or unknown free space
673
-			if ($quota < 0) {
674
-				$quota = PHP_INT_MAX;
675
-			}
676
-		} else {
677
-			$quota = \OCP\Util::computerFileSize($quota);
678
-		}
679
-
680
-		// calculate available space for trash bin
681
-		// subtract size of files and current trash bin size from quota
682
-		if ($softQuota) {
683
-			$userFolder = \OC::$server->getUserFolder($user);
684
-			if(is_null($userFolder)) {
685
-				return 0;
686
-			}
687
-			$free = $quota - $userFolder->getSize(); // remaining free space for user
688
-			if ($free > 0) {
689
-				$availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
690
-			} else {
691
-				$availableSpace = $free - $trashbinSize;
692
-			}
693
-		} else {
694
-			$availableSpace = $quota;
695
-		}
696
-
697
-		return $availableSpace;
698
-	}
699
-
700
-	/**
701
-	 * resize trash bin if necessary after a new file was added to Nextcloud
702
-	 *
703
-	 * @param string $user user id
704
-	 */
705
-	public static function resizeTrash($user) {
706
-
707
-		$size = self::getTrashbinSize($user);
708
-
709
-		$freeSpace = self::calculateFreeSpace($size, $user);
710
-
711
-		if ($freeSpace < 0) {
712
-			self::scheduleExpire($user);
713
-		}
714
-	}
715
-
716
-	/**
717
-	 * clean up the trash bin
718
-	 *
719
-	 * @param string $user
720
-	 */
721
-	public static function expire($user) {
722
-		$trashBinSize = self::getTrashbinSize($user);
723
-		$availableSpace = self::calculateFreeSpace($trashBinSize, $user);
724
-
725
-		$dirContent = Helper::getTrashFiles('/', $user, 'mtime');
726
-
727
-		// delete all files older then $retention_obligation
728
-		list($delSize, $count) = self::deleteExpiredFiles($dirContent, $user);
729
-
730
-		$availableSpace += $delSize;
731
-
732
-		// delete files from trash until we meet the trash bin size limit again
733
-		self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
734
-	}
735
-
736
-	/**
737
-	 * @param string $user
738
-	 */
739
-	private static function scheduleExpire($user) {
740
-		// let the admin disable auto expire
741
-		$application = new Application();
742
-		$expiration = $application->getContainer()->query('Expiration');
743
-		if ($expiration->isEnabled()) {
744
-			\OC::$server->getCommandBus()->push(new Expire($user));
745
-		}
746
-	}
747
-
748
-	/**
749
-	 * if the size limit for the trash bin is reached, we delete the oldest
750
-	 * files in the trash bin until we meet the limit again
751
-	 *
752
-	 * @param array $files
753
-	 * @param string $user
754
-	 * @param int $availableSpace available disc space
755
-	 * @return int size of deleted files
756
-	 */
757
-	protected static function deleteFiles($files, $user, $availableSpace) {
758
-		$application = new Application();
759
-		$expiration = $application->getContainer()->query('Expiration');
760
-		$size = 0;
761
-
762
-		if ($availableSpace < 0) {
763
-			foreach ($files as $file) {
764
-				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
765
-					$tmp = self::delete($file['name'], $user, $file['mtime']);
766
-					\OC::$server->getLogger()->info('files_trashbin', 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
767
-					$availableSpace += $tmp;
768
-					$size += $tmp;
769
-				} else {
770
-					break;
771
-				}
772
-			}
773
-		}
774
-		return $size;
775
-	}
776
-
777
-	/**
778
-	 * delete files older then max storage time
779
-	 *
780
-	 * @param array $files list of files sorted by mtime
781
-	 * @param string $user
782
-	 * @return integer[] size of deleted files and number of deleted files
783
-	 */
784
-	public static function deleteExpiredFiles($files, $user) {
785
-		$application = new Application();
786
-		$expiration = $application->getContainer()->query('Expiration');
787
-		$size = 0;
788
-		$count = 0;
789
-		foreach ($files as $file) {
790
-			$timestamp = $file['mtime'];
791
-			$filename = $file['name'];
792
-			if ($expiration->isExpired($timestamp)) {
793
-				$count++;
794
-				$size += self::delete($filename, $user, $timestamp);
795
-				\OC::$server->getLogger()->info(
796
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
797
-					['app' => 'files_trashbin']
798
-				);
799
-			} else {
800
-				break;
801
-			}
802
-		}
803
-
804
-		return array($size, $count);
805
-	}
806
-
807
-	/**
808
-	 * recursive copy to copy a whole directory
809
-	 *
810
-	 * @param string $source source path, relative to the users files directory
811
-	 * @param string $destination destination path relative to the users root directoy
812
-	 * @param View $view file view for the users root directory
813
-	 * @return int
814
-	 * @throws Exceptions\CopyRecursiveException
815
-	 */
816
-	private static function copy_recursive($source, $destination, View $view) {
817
-		$size = 0;
818
-		if ($view->is_dir($source)) {
819
-			$view->mkdir($destination);
820
-			$view->touch($destination, $view->filemtime($source));
821
-			foreach ($view->getDirectoryContent($source) as $i) {
822
-				$pathDir = $source . '/' . $i['name'];
823
-				if ($view->is_dir($pathDir)) {
824
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
825
-				} else {
826
-					$size += $view->filesize($pathDir);
827
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
828
-					if (!$result) {
829
-						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
830
-					}
831
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
832
-				}
833
-			}
834
-		} else {
835
-			$size += $view->filesize($source);
836
-			$result = $view->copy($source, $destination);
837
-			if (!$result) {
838
-				throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
839
-			}
840
-			$view->touch($destination, $view->filemtime($source));
841
-		}
842
-		return $size;
843
-	}
844
-
845
-	/**
846
-	 * find all versions which belong to the file we want to restore
847
-	 *
848
-	 * @param string $filename name of the file which should be restored
849
-	 * @param int $timestamp timestamp when the file was deleted
850
-	 * @return array
851
-	 */
852
-	private static function getVersionsFromTrash($filename, $timestamp, $user) {
853
-		$view = new View('/' . $user . '/files_trashbin/versions');
854
-		$versions = array();
855
-
856
-		//force rescan of versions, local storage may not have updated the cache
857
-		if (!self::$scannedVersions) {
858
-			/** @var \OC\Files\Storage\Storage $storage */
859
-			list($storage,) = $view->resolvePath('/');
860
-			$storage->getScanner()->scan('files_trashbin/versions');
861
-			self::$scannedVersions = true;
862
-		}
863
-
864
-		if ($timestamp) {
865
-			// fetch for old versions
866
-			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
867
-			$offset = -strlen($timestamp) - 2;
868
-		} else {
869
-			$matches = $view->searchRaw($filename . '.v%');
870
-		}
871
-
872
-		if (is_array($matches)) {
873
-			foreach ($matches as $ma) {
874
-				if ($timestamp) {
875
-					$parts = explode('.v', substr($ma['path'], 0, $offset));
876
-					$versions[] = end($parts);
877
-				} else {
878
-					$parts = explode('.v', $ma);
879
-					$versions[] = end($parts);
880
-				}
881
-			}
882
-		}
883
-		return $versions;
884
-	}
885
-
886
-	/**
887
-	 * find unique extension for restored file if a file with the same name already exists
888
-	 *
889
-	 * @param string $location where the file should be restored
890
-	 * @param string $filename name of the file
891
-	 * @param View $view filesystem view relative to users root directory
892
-	 * @return string with unique extension
893
-	 */
894
-	private static function getUniqueFilename($location, $filename, View $view) {
895
-		$ext = pathinfo($filename, PATHINFO_EXTENSION);
896
-		$name = pathinfo($filename, PATHINFO_FILENAME);
897
-		$l = \OC::$server->getL10N('files_trashbin');
898
-
899
-		$location = '/' . trim($location, '/');
900
-
901
-		// if extension is not empty we set a dot in front of it
902
-		if ($ext !== '') {
903
-			$ext = '.' . $ext;
904
-		}
905
-
906
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
907
-			$i = 2;
908
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
909
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
910
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
911
-				$i++;
912
-			}
913
-
914
-			return $uniqueName;
915
-		}
916
-
917
-		return $filename;
918
-	}
919
-
920
-	/**
921
-	 * get the size from a given root folder
922
-	 *
923
-	 * @param View $view file view on the root folder
924
-	 * @return integer size of the folder
925
-	 */
926
-	private static function calculateSize($view) {
927
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
928
-		if (!file_exists($root)) {
929
-			return 0;
930
-		}
931
-		$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
932
-		$size = 0;
933
-
934
-		/**
935
-		 * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
936
-		 * This bug is fixed in PHP 5.5.9 or before
937
-		 * See #8376
938
-		 */
939
-		$iterator->rewind();
940
-		while ($iterator->valid()) {
941
-			$path = $iterator->current();
942
-			$relpath = substr($path, strlen($root) - 1);
943
-			if (!$view->is_dir($relpath)) {
944
-				$size += $view->filesize($relpath);
945
-			}
946
-			$iterator->next();
947
-		}
948
-		return $size;
949
-	}
950
-
951
-	/**
952
-	 * get current size of trash bin from a given user
953
-	 *
954
-	 * @param string $user user who owns the trash bin
955
-	 * @return integer trash bin size
956
-	 */
957
-	private static function getTrashbinSize($user) {
958
-		$view = new View('/' . $user);
959
-		$fileInfo = $view->getFileInfo('/files_trashbin');
960
-		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
961
-	}
962
-
963
-	/**
964
-	 * register hooks
965
-	 */
966
-	public static function registerHooks() {
967
-		// create storage wrapper on setup
968
-		\OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
969
-		//Listen to delete user signal
970
-		\OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
971
-		//Listen to post write hook
972
-		\OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
973
-		// pre and post-rename, disable trash logic for the copy+unlink case
974
-		\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook');
975
-		\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook');
976
-		\OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook');
977
-	}
978
-
979
-	/**
980
-	 * check if trash bin is empty for a given user
981
-	 *
982
-	 * @param string $user
983
-	 * @return bool
984
-	 */
985
-	public static function isEmpty($user) {
986
-
987
-		$view = new View('/' . $user . '/files_trashbin');
988
-		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
989
-			while ($file = readdir($dh)) {
990
-				if (!Filesystem::isIgnoredDir($file)) {
991
-					return false;
992
-				}
993
-			}
994
-		}
995
-		return true;
996
-	}
997
-
998
-	/**
999
-	 * @param $path
1000
-	 * @return string
1001
-	 */
1002
-	public static function preview_icon($path) {
1003
-		return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', array('x' => 32, 'y' => 32, 'file' => $path));
1004
-	}
55
+    // unit: percentage; 50% of available disk space/quota
56
+    const DEFAULTMAXSIZE = 50;
57
+
58
+    /**
59
+     * Whether versions have already be rescanned during this PHP request
60
+     *
61
+     * @var bool
62
+     */
63
+    private static $scannedVersions = false;
64
+
65
+    /**
66
+     * Ensure we don't need to scan the file during the move to trash
67
+     * by triggering the scan in the pre-hook
68
+     *
69
+     * @param array $params
70
+     */
71
+    public static function ensureFileScannedHook($params) {
72
+        try {
73
+            self::getUidAndFilename($params['path']);
74
+        } catch (NotFoundException $e) {
75
+            // nothing to scan for non existing files
76
+        }
77
+    }
78
+
79
+    /**
80
+     * get the UID of the owner of the file and the path to the file relative to
81
+     * owners files folder
82
+     *
83
+     * @param string $filename
84
+     * @return array
85
+     * @throws \OC\User\NoUserException
86
+     */
87
+    public static function getUidAndFilename($filename) {
88
+        $uid = Filesystem::getOwner($filename);
89
+        $userManager = \OC::$server->getUserManager();
90
+        // if the user with the UID doesn't exists, e.g. because the UID points
91
+        // to a remote user with a federated cloud ID we use the current logged-in
92
+        // user. We need a valid local user to move the file to the right trash bin
93
+        if (!$userManager->userExists($uid)) {
94
+            $uid = User::getUser();
95
+        }
96
+        if (!$uid) {
97
+            // no owner, usually because of share link from ext storage
98
+            return [null, null];
99
+        }
100
+        Filesystem::initMountPoints($uid);
101
+        if ($uid !== User::getUser()) {
102
+            $info = Filesystem::getFileInfo($filename);
103
+            $ownerView = new View('/' . $uid . '/files');
104
+            try {
105
+                $filename = $ownerView->getPath($info['fileid']);
106
+            } catch (NotFoundException $e) {
107
+                $filename = null;
108
+            }
109
+        }
110
+        return [$uid, $filename];
111
+    }
112
+
113
+    /**
114
+     * get original location of files for user
115
+     *
116
+     * @param string $user
117
+     * @return array (filename => array (timestamp => original location))
118
+     */
119
+    public static function getLocations($user) {
120
+        $query = \OC_DB::prepare('SELECT `id`, `timestamp`, `location`'
121
+            . ' FROM `*PREFIX*files_trash` WHERE `user`=?');
122
+        $result = $query->execute(array($user));
123
+        $array = array();
124
+        while ($row = $result->fetchRow()) {
125
+            if (isset($array[$row['id']])) {
126
+                $array[$row['id']][$row['timestamp']] = $row['location'];
127
+            } else {
128
+                $array[$row['id']] = array($row['timestamp'] => $row['location']);
129
+            }
130
+        }
131
+        return $array;
132
+    }
133
+
134
+    /**
135
+     * get original location of file
136
+     *
137
+     * @param string $user
138
+     * @param string $filename
139
+     * @param string $timestamp
140
+     * @return string original location
141
+     */
142
+    public static function getLocation($user, $filename, $timestamp) {
143
+        $query = \OC_DB::prepare('SELECT `location` FROM `*PREFIX*files_trash`'
144
+            . ' WHERE `user`=? AND `id`=? AND `timestamp`=?');
145
+        $result = $query->execute(array($user, $filename, $timestamp))->fetchAll();
146
+        if (isset($result[0]['location'])) {
147
+            return $result[0]['location'];
148
+        } else {
149
+            return false;
150
+        }
151
+    }
152
+
153
+    private static function setUpTrash($user) {
154
+        $view = new View('/' . $user);
155
+        if (!$view->is_dir('files_trashbin')) {
156
+            $view->mkdir('files_trashbin');
157
+        }
158
+        if (!$view->is_dir('files_trashbin/files')) {
159
+            $view->mkdir('files_trashbin/files');
160
+        }
161
+        if (!$view->is_dir('files_trashbin/versions')) {
162
+            $view->mkdir('files_trashbin/versions');
163
+        }
164
+        if (!$view->is_dir('files_trashbin/keys')) {
165
+            $view->mkdir('files_trashbin/keys');
166
+        }
167
+    }
168
+
169
+
170
+    /**
171
+     * copy file to owners trash
172
+     *
173
+     * @param string $sourcePath
174
+     * @param string $owner
175
+     * @param string $targetPath
176
+     * @param $user
177
+     * @param integer $timestamp
178
+     */
179
+    private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user, $timestamp) {
180
+        self::setUpTrash($owner);
181
+
182
+        $targetFilename = basename($targetPath);
183
+        $targetLocation = dirname($targetPath);
184
+
185
+        $sourceFilename = basename($sourcePath);
186
+
187
+        $view = new View('/');
188
+
189
+        $target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
190
+        $source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
191
+        self::copy_recursive($source, $target, $view);
192
+
193
+
194
+        if ($view->file_exists($target)) {
195
+            $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
196
+            $result = $query->execute(array($targetFilename, $timestamp, $targetLocation, $user));
197
+            if (!$result) {
198
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated for the files owner', ['app' => 'files_trashbin']);
199
+            }
200
+        }
201
+    }
202
+
203
+
204
+    /**
205
+     * move file to the trash bin
206
+     *
207
+     * @param string $file_path path to the deleted file/directory relative to the files root directory
208
+     * @param bool $ownerOnly delete for owner only (if file gets moved out of a shared folder)
209
+     *
210
+     * @return bool
211
+     */
212
+    public static function move2trash($file_path, $ownerOnly = false) {
213
+        // get the user for which the filesystem is setup
214
+        $root = Filesystem::getRoot();
215
+        list(, $user) = explode('/', $root);
216
+        list($owner, $ownerPath) = self::getUidAndFilename($file_path);
217
+
218
+        // if no owner found (ex: ext storage + share link), will use the current user's trashbin then
219
+        if (is_null($owner)) {
220
+            $owner = $user;
221
+            $ownerPath = $file_path;
222
+        }
223
+
224
+        $ownerView = new View('/' . $owner);
225
+        // file has been deleted in between
226
+        if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
227
+            return true;
228
+        }
229
+
230
+        self::setUpTrash($user);
231
+        if ($owner !== $user) {
232
+            // also setup for owner
233
+            self::setUpTrash($owner);
234
+        }
235
+
236
+        $path_parts = pathinfo($ownerPath);
237
+
238
+        $filename = $path_parts['basename'];
239
+        $location = $path_parts['dirname'];
240
+        $timestamp = time();
241
+
242
+        // disable proxy to prevent recursive calls
243
+        $trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
244
+
245
+        /** @var \OC\Files\Storage\Storage $trashStorage */
246
+        list($trashStorage, $trashInternalPath) = $ownerView->resolvePath($trashPath);
247
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
248
+        list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath);
249
+        try {
250
+            $moveSuccessful = true;
251
+            if ($trashStorage->file_exists($trashInternalPath)) {
252
+                $trashStorage->unlink($trashInternalPath);
253
+            }
254
+            $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
255
+        } catch (\OCA\Files_Trashbin\Exceptions\CopyRecursiveException $e) {
256
+            $moveSuccessful = false;
257
+            if ($trashStorage->file_exists($trashInternalPath)) {
258
+                $trashStorage->unlink($trashInternalPath);
259
+            }
260
+            \OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
261
+        }
262
+
263
+        if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
264
+            if ($sourceStorage->is_dir($sourceInternalPath)) {
265
+                $sourceStorage->rmdir($sourceInternalPath);
266
+            } else {
267
+                $sourceStorage->unlink($sourceInternalPath);
268
+            }
269
+            return false;
270
+        }
271
+
272
+        $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath);
273
+
274
+        if ($moveSuccessful) {
275
+            $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
276
+            $result = $query->execute(array($filename, $timestamp, $location, $owner));
277
+            if (!$result) {
278
+                \OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
279
+            }
280
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', array('filePath' => Filesystem::normalizePath($file_path),
281
+                'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)));
282
+
283
+            self::retainVersions($filename, $owner, $ownerPath, $timestamp);
284
+
285
+            // if owner !== user we need to also add a copy to the users trash
286
+            if ($user !== $owner && $ownerOnly === false) {
287
+                self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp);
288
+            }
289
+        }
290
+
291
+        self::scheduleExpire($user);
292
+
293
+        // if owner !== user we also need to update the owners trash size
294
+        if ($owner !== $user) {
295
+            self::scheduleExpire($owner);
296
+        }
297
+
298
+        return $moveSuccessful;
299
+    }
300
+
301
+    /**
302
+     * Move file versions to trash so that they can be restored later
303
+     *
304
+     * @param string $filename of deleted file
305
+     * @param string $owner owner user id
306
+     * @param string $ownerPath path relative to the owner's home storage
307
+     * @param integer $timestamp when the file was deleted
308
+     */
309
+    private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
310
+        if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
311
+
312
+            $user = User::getUser();
313
+            $rootView = new View('/');
314
+
315
+            if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
316
+                if ($owner !== $user) {
317
+                    self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
318
+                }
319
+                self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
320
+            } else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
321
+
322
+                foreach ($versions as $v) {
323
+                    if ($owner !== $user) {
324
+                        self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
325
+                    }
326
+                    self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
327
+                }
328
+            }
329
+        }
330
+    }
331
+
332
+    /**
333
+     * Move a file or folder on storage level
334
+     *
335
+     * @param View $view
336
+     * @param string $source
337
+     * @param string $target
338
+     * @return bool
339
+     */
340
+    private static function move(View $view, $source, $target) {
341
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
342
+        list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
343
+        /** @var \OC\Files\Storage\Storage $targetStorage */
344
+        list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
345
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
346
+
347
+        $result = $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
348
+        if ($result) {
349
+            $targetStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
350
+        }
351
+        return $result;
352
+    }
353
+
354
+    /**
355
+     * Copy a file or folder on storage level
356
+     *
357
+     * @param View $view
358
+     * @param string $source
359
+     * @param string $target
360
+     * @return bool
361
+     */
362
+    private static function copy(View $view, $source, $target) {
363
+        /** @var \OC\Files\Storage\Storage $sourceStorage */
364
+        list($sourceStorage, $sourceInternalPath) = $view->resolvePath($source);
365
+        /** @var \OC\Files\Storage\Storage $targetStorage */
366
+        list($targetStorage, $targetInternalPath) = $view->resolvePath($target);
367
+        /** @var \OC\Files\Storage\Storage $ownerTrashStorage */
368
+
369
+        $result = $targetStorage->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
370
+        if ($result) {
371
+            $targetStorage->getUpdater()->update($targetInternalPath);
372
+        }
373
+        return $result;
374
+    }
375
+
376
+    /**
377
+     * Restore a file or folder from trash bin
378
+     *
379
+     * @param string $file path to the deleted file/folder relative to "files_trashbin/files/",
380
+     * including the timestamp suffix ".d12345678"
381
+     * @param string $filename name of the file/folder
382
+     * @param int $timestamp time when the file/folder was deleted
383
+     *
384
+     * @return bool true on success, false otherwise
385
+     */
386
+    public static function restore($file, $filename, $timestamp) {
387
+        $user = User::getUser();
388
+        $view = new View('/' . $user);
389
+
390
+        $location = '';
391
+        if ($timestamp) {
392
+            $location = self::getLocation($user, $filename, $timestamp);
393
+            if ($location === false) {
394
+                \OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
395
+            } else {
396
+                // if location no longer exists, restore file in the root directory
397
+                if ($location !== '/' &&
398
+                    (!$view->is_dir('files/' . $location) ||
399
+                        !$view->isCreatable('files/' . $location))
400
+                ) {
401
+                    $location = '';
402
+                }
403
+            }
404
+        }
405
+
406
+        // we need a  extension in case a file/dir with the same name already exists
407
+        $uniqueFilename = self::getUniqueFilename($location, $filename, $view);
408
+
409
+        $source = Filesystem::normalizePath('files_trashbin/files/' . $file);
410
+        $target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
411
+        if (!$view->file_exists($source)) {
412
+            return false;
413
+        }
414
+        $mtime = $view->filemtime($source);
415
+
416
+        // restore file
417
+        $restoreResult = $view->rename($source, $target);
418
+
419
+        // handle the restore result
420
+        if ($restoreResult) {
421
+            $fakeRoot = $view->getRoot();
422
+            $view->chroot('/' . $user . '/files');
423
+            $view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
424
+            $view->chroot($fakeRoot);
425
+            \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
426
+                'trashPath' => Filesystem::normalizePath($file)));
427
+
428
+            self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
429
+
430
+            if ($timestamp) {
431
+                $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
432
+                $query->execute(array($user, $filename, $timestamp));
433
+            }
434
+
435
+            return true;
436
+        }
437
+
438
+        return false;
439
+    }
440
+
441
+    /**
442
+     * restore versions from trash bin
443
+     *
444
+     * @param View $view file view
445
+     * @param string $file complete path to file
446
+     * @param string $filename name of file once it was deleted
447
+     * @param string $uniqueFilename new file name to restore the file without overwriting existing files
448
+     * @param string $location location if file
449
+     * @param int $timestamp deletion time
450
+     * @return false|null
451
+     */
452
+    private static function restoreVersions(View $view, $file, $filename, $uniqueFilename, $location, $timestamp) {
453
+
454
+        if (\OCP\App::isEnabled('files_versions')) {
455
+
456
+            $user = User::getUser();
457
+            $rootView = new View('/');
458
+
459
+            $target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
460
+
461
+            list($owner, $ownerPath) = self::getUidAndFilename($target);
462
+
463
+            // file has been deleted in between
464
+            if (empty($ownerPath)) {
465
+                return false;
466
+            }
467
+
468
+            if ($timestamp) {
469
+                $versionedFile = $filename;
470
+            } else {
471
+                $versionedFile = $file;
472
+            }
473
+
474
+            if ($view->is_dir('/files_trashbin/versions/' . $file)) {
475
+                $rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
476
+            } else if ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
477
+                foreach ($versions as $v) {
478
+                    if ($timestamp) {
479
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
480
+                    } else {
481
+                        $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
482
+                    }
483
+                }
484
+            }
485
+        }
486
+    }
487
+
488
+    /**
489
+     * delete all files from the trash
490
+     */
491
+    public static function deleteAll() {
492
+        $user = User::getUser();
493
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
494
+        $view = new View('/' . $user);
495
+        $fileInfos = $view->getDirectoryContent('files_trashbin/files');
496
+
497
+        try {
498
+            $trash = $userRoot->get('files_trashbin');
499
+        } catch (NotFoundException $e) {
500
+            return false;
501
+        }
502
+
503
+        // Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
504
+        $filePaths = array();
505
+        foreach($fileInfos as $fileInfo){
506
+            $filePaths[] = $view->getRelativePath($fileInfo->getPath());
507
+        }
508
+        unset($fileInfos); // save memory
509
+
510
+        // Bulk PreDelete-Hook
511
+        \OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', array('paths' => $filePaths));
512
+
513
+        // Single-File Hooks
514
+        foreach($filePaths as $path){
515
+            self::emitTrashbinPreDelete($path);
516
+        }
517
+
518
+        // actual file deletion
519
+        $trash->delete();
520
+        $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
521
+        $query->execute(array($user));
522
+
523
+        // Bulk PostDelete-Hook
524
+        \OC_Hook::emit('\OCP\Trashbin', 'deleteAll', array('paths' => $filePaths));
525
+
526
+        // Single-File Hooks
527
+        foreach($filePaths as $path){
528
+            self::emitTrashbinPostDelete($path);
529
+        }
530
+
531
+        $trash = $userRoot->newFolder('files_trashbin');
532
+        $trash->newFolder('files');
533
+
534
+        return true;
535
+    }
536
+
537
+    /**
538
+     * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
539
+     * @param string $path
540
+     */
541
+    protected static function emitTrashbinPreDelete($path){
542
+        \OC_Hook::emit('\OCP\Trashbin', 'preDelete', array('path' => $path));
543
+    }
544
+
545
+    /**
546
+     * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
547
+     * @param string $path
548
+     */
549
+    protected static function emitTrashbinPostDelete($path){
550
+        \OC_Hook::emit('\OCP\Trashbin', 'delete', array('path' => $path));
551
+    }
552
+
553
+    /**
554
+     * delete file from trash bin permanently
555
+     *
556
+     * @param string $filename path to the file
557
+     * @param string $user
558
+     * @param int $timestamp of deletion time
559
+     *
560
+     * @return int size of deleted files
561
+     */
562
+    public static function delete($filename, $user, $timestamp = null) {
563
+        $userRoot = \OC::$server->getUserFolder($user)->getParent();
564
+        $view = new View('/' . $user);
565
+        $size = 0;
566
+
567
+        if ($timestamp) {
568
+            $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
569
+            $query->execute(array($user, $filename, $timestamp));
570
+            $file = $filename . '.d' . $timestamp;
571
+        } else {
572
+            $file = $filename;
573
+        }
574
+
575
+        $size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
576
+
577
+        try {
578
+            $node = $userRoot->get('/files_trashbin/files/' . $file);
579
+        } catch (NotFoundException $e) {
580
+            return $size;
581
+        }
582
+
583
+        if ($node instanceof Folder) {
584
+            $size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
585
+        } else if ($node instanceof File) {
586
+            $size += $view->filesize('/files_trashbin/files/' . $file);
587
+        }
588
+
589
+        self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
590
+        $node->delete();
591
+        self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
592
+
593
+        return $size;
594
+    }
595
+
596
+    /**
597
+     * @param View $view
598
+     * @param string $file
599
+     * @param string $filename
600
+     * @param integer|null $timestamp
601
+     * @param string $user
602
+     * @return int
603
+     */
604
+    private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
605
+        $size = 0;
606
+        if (\OCP\App::isEnabled('files_versions')) {
607
+            if ($view->is_dir('files_trashbin/versions/' . $file)) {
608
+                $size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
609
+                $view->unlink('files_trashbin/versions/' . $file);
610
+            } else if ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
611
+                foreach ($versions as $v) {
612
+                    if ($timestamp) {
613
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
614
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
615
+                    } else {
616
+                        $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
617
+                        $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
618
+                    }
619
+                }
620
+            }
621
+        }
622
+        return $size;
623
+    }
624
+
625
+    /**
626
+     * check to see whether a file exists in trashbin
627
+     *
628
+     * @param string $filename path to the file
629
+     * @param int $timestamp of deletion time
630
+     * @return bool true if file exists, otherwise false
631
+     */
632
+    public static function file_exists($filename, $timestamp = null) {
633
+        $user = User::getUser();
634
+        $view = new View('/' . $user);
635
+
636
+        if ($timestamp) {
637
+            $filename = $filename . '.d' . $timestamp;
638
+        }
639
+
640
+        $target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
641
+        return $view->file_exists($target);
642
+    }
643
+
644
+    /**
645
+     * deletes used space for trash bin in db if user was deleted
646
+     *
647
+     * @param string $uid id of deleted user
648
+     * @return bool result of db delete operation
649
+     */
650
+    public static function deleteUser($uid) {
651
+        $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=?');
652
+        return $query->execute(array($uid));
653
+    }
654
+
655
+    /**
656
+     * calculate remaining free space for trash bin
657
+     *
658
+     * @param integer $trashbinSize current size of the trash bin
659
+     * @param string $user
660
+     * @return int available free space for trash bin
661
+     */
662
+    private static function calculateFreeSpace($trashbinSize, $user) {
663
+        $softQuota = true;
664
+        $userObject = \OC::$server->getUserManager()->get($user);
665
+        if(is_null($userObject)) {
666
+            return 0;
667
+        }
668
+        $quota = $userObject->getQuota();
669
+        if ($quota === null || $quota === 'none') {
670
+            $quota = Filesystem::free_space('/');
671
+            $softQuota = false;
672
+            // inf or unknown free space
673
+            if ($quota < 0) {
674
+                $quota = PHP_INT_MAX;
675
+            }
676
+        } else {
677
+            $quota = \OCP\Util::computerFileSize($quota);
678
+        }
679
+
680
+        // calculate available space for trash bin
681
+        // subtract size of files and current trash bin size from quota
682
+        if ($softQuota) {
683
+            $userFolder = \OC::$server->getUserFolder($user);
684
+            if(is_null($userFolder)) {
685
+                return 0;
686
+            }
687
+            $free = $quota - $userFolder->getSize(); // remaining free space for user
688
+            if ($free > 0) {
689
+                $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions
690
+            } else {
691
+                $availableSpace = $free - $trashbinSize;
692
+            }
693
+        } else {
694
+            $availableSpace = $quota;
695
+        }
696
+
697
+        return $availableSpace;
698
+    }
699
+
700
+    /**
701
+     * resize trash bin if necessary after a new file was added to Nextcloud
702
+     *
703
+     * @param string $user user id
704
+     */
705
+    public static function resizeTrash($user) {
706
+
707
+        $size = self::getTrashbinSize($user);
708
+
709
+        $freeSpace = self::calculateFreeSpace($size, $user);
710
+
711
+        if ($freeSpace < 0) {
712
+            self::scheduleExpire($user);
713
+        }
714
+    }
715
+
716
+    /**
717
+     * clean up the trash bin
718
+     *
719
+     * @param string $user
720
+     */
721
+    public static function expire($user) {
722
+        $trashBinSize = self::getTrashbinSize($user);
723
+        $availableSpace = self::calculateFreeSpace($trashBinSize, $user);
724
+
725
+        $dirContent = Helper::getTrashFiles('/', $user, 'mtime');
726
+
727
+        // delete all files older then $retention_obligation
728
+        list($delSize, $count) = self::deleteExpiredFiles($dirContent, $user);
729
+
730
+        $availableSpace += $delSize;
731
+
732
+        // delete files from trash until we meet the trash bin size limit again
733
+        self::deleteFiles(array_slice($dirContent, $count), $user, $availableSpace);
734
+    }
735
+
736
+    /**
737
+     * @param string $user
738
+     */
739
+    private static function scheduleExpire($user) {
740
+        // let the admin disable auto expire
741
+        $application = new Application();
742
+        $expiration = $application->getContainer()->query('Expiration');
743
+        if ($expiration->isEnabled()) {
744
+            \OC::$server->getCommandBus()->push(new Expire($user));
745
+        }
746
+    }
747
+
748
+    /**
749
+     * if the size limit for the trash bin is reached, we delete the oldest
750
+     * files in the trash bin until we meet the limit again
751
+     *
752
+     * @param array $files
753
+     * @param string $user
754
+     * @param int $availableSpace available disc space
755
+     * @return int size of deleted files
756
+     */
757
+    protected static function deleteFiles($files, $user, $availableSpace) {
758
+        $application = new Application();
759
+        $expiration = $application->getContainer()->query('Expiration');
760
+        $size = 0;
761
+
762
+        if ($availableSpace < 0) {
763
+            foreach ($files as $file) {
764
+                if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
765
+                    $tmp = self::delete($file['name'], $user, $file['mtime']);
766
+                    \OC::$server->getLogger()->info('files_trashbin', 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
767
+                    $availableSpace += $tmp;
768
+                    $size += $tmp;
769
+                } else {
770
+                    break;
771
+                }
772
+            }
773
+        }
774
+        return $size;
775
+    }
776
+
777
+    /**
778
+     * delete files older then max storage time
779
+     *
780
+     * @param array $files list of files sorted by mtime
781
+     * @param string $user
782
+     * @return integer[] size of deleted files and number of deleted files
783
+     */
784
+    public static function deleteExpiredFiles($files, $user) {
785
+        $application = new Application();
786
+        $expiration = $application->getContainer()->query('Expiration');
787
+        $size = 0;
788
+        $count = 0;
789
+        foreach ($files as $file) {
790
+            $timestamp = $file['mtime'];
791
+            $filename = $file['name'];
792
+            if ($expiration->isExpired($timestamp)) {
793
+                $count++;
794
+                $size += self::delete($filename, $user, $timestamp);
795
+                \OC::$server->getLogger()->info(
796
+                    'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
797
+                    ['app' => 'files_trashbin']
798
+                );
799
+            } else {
800
+                break;
801
+            }
802
+        }
803
+
804
+        return array($size, $count);
805
+    }
806
+
807
+    /**
808
+     * recursive copy to copy a whole directory
809
+     *
810
+     * @param string $source source path, relative to the users files directory
811
+     * @param string $destination destination path relative to the users root directoy
812
+     * @param View $view file view for the users root directory
813
+     * @return int
814
+     * @throws Exceptions\CopyRecursiveException
815
+     */
816
+    private static function copy_recursive($source, $destination, View $view) {
817
+        $size = 0;
818
+        if ($view->is_dir($source)) {
819
+            $view->mkdir($destination);
820
+            $view->touch($destination, $view->filemtime($source));
821
+            foreach ($view->getDirectoryContent($source) as $i) {
822
+                $pathDir = $source . '/' . $i['name'];
823
+                if ($view->is_dir($pathDir)) {
824
+                    $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
825
+                } else {
826
+                    $size += $view->filesize($pathDir);
827
+                    $result = $view->copy($pathDir, $destination . '/' . $i['name']);
828
+                    if (!$result) {
829
+                        throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
830
+                    }
831
+                    $view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
832
+                }
833
+            }
834
+        } else {
835
+            $size += $view->filesize($source);
836
+            $result = $view->copy($source, $destination);
837
+            if (!$result) {
838
+                throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
839
+            }
840
+            $view->touch($destination, $view->filemtime($source));
841
+        }
842
+        return $size;
843
+    }
844
+
845
+    /**
846
+     * find all versions which belong to the file we want to restore
847
+     *
848
+     * @param string $filename name of the file which should be restored
849
+     * @param int $timestamp timestamp when the file was deleted
850
+     * @return array
851
+     */
852
+    private static function getVersionsFromTrash($filename, $timestamp, $user) {
853
+        $view = new View('/' . $user . '/files_trashbin/versions');
854
+        $versions = array();
855
+
856
+        //force rescan of versions, local storage may not have updated the cache
857
+        if (!self::$scannedVersions) {
858
+            /** @var \OC\Files\Storage\Storage $storage */
859
+            list($storage,) = $view->resolvePath('/');
860
+            $storage->getScanner()->scan('files_trashbin/versions');
861
+            self::$scannedVersions = true;
862
+        }
863
+
864
+        if ($timestamp) {
865
+            // fetch for old versions
866
+            $matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
867
+            $offset = -strlen($timestamp) - 2;
868
+        } else {
869
+            $matches = $view->searchRaw($filename . '.v%');
870
+        }
871
+
872
+        if (is_array($matches)) {
873
+            foreach ($matches as $ma) {
874
+                if ($timestamp) {
875
+                    $parts = explode('.v', substr($ma['path'], 0, $offset));
876
+                    $versions[] = end($parts);
877
+                } else {
878
+                    $parts = explode('.v', $ma);
879
+                    $versions[] = end($parts);
880
+                }
881
+            }
882
+        }
883
+        return $versions;
884
+    }
885
+
886
+    /**
887
+     * find unique extension for restored file if a file with the same name already exists
888
+     *
889
+     * @param string $location where the file should be restored
890
+     * @param string $filename name of the file
891
+     * @param View $view filesystem view relative to users root directory
892
+     * @return string with unique extension
893
+     */
894
+    private static function getUniqueFilename($location, $filename, View $view) {
895
+        $ext = pathinfo($filename, PATHINFO_EXTENSION);
896
+        $name = pathinfo($filename, PATHINFO_FILENAME);
897
+        $l = \OC::$server->getL10N('files_trashbin');
898
+
899
+        $location = '/' . trim($location, '/');
900
+
901
+        // if extension is not empty we set a dot in front of it
902
+        if ($ext !== '') {
903
+            $ext = '.' . $ext;
904
+        }
905
+
906
+        if ($view->file_exists('files' . $location . '/' . $filename)) {
907
+            $i = 2;
908
+            $uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
909
+            while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
910
+                $uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
911
+                $i++;
912
+            }
913
+
914
+            return $uniqueName;
915
+        }
916
+
917
+        return $filename;
918
+    }
919
+
920
+    /**
921
+     * get the size from a given root folder
922
+     *
923
+     * @param View $view file view on the root folder
924
+     * @return integer size of the folder
925
+     */
926
+    private static function calculateSize($view) {
927
+        $root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
928
+        if (!file_exists($root)) {
929
+            return 0;
930
+        }
931
+        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST);
932
+        $size = 0;
933
+
934
+        /**
935
+         * RecursiveDirectoryIterator on an NFS path isn't iterable with foreach
936
+         * This bug is fixed in PHP 5.5.9 or before
937
+         * See #8376
938
+         */
939
+        $iterator->rewind();
940
+        while ($iterator->valid()) {
941
+            $path = $iterator->current();
942
+            $relpath = substr($path, strlen($root) - 1);
943
+            if (!$view->is_dir($relpath)) {
944
+                $size += $view->filesize($relpath);
945
+            }
946
+            $iterator->next();
947
+        }
948
+        return $size;
949
+    }
950
+
951
+    /**
952
+     * get current size of trash bin from a given user
953
+     *
954
+     * @param string $user user who owns the trash bin
955
+     * @return integer trash bin size
956
+     */
957
+    private static function getTrashbinSize($user) {
958
+        $view = new View('/' . $user);
959
+        $fileInfo = $view->getFileInfo('/files_trashbin');
960
+        return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
961
+    }
962
+
963
+    /**
964
+     * register hooks
965
+     */
966
+    public static function registerHooks() {
967
+        // create storage wrapper on setup
968
+        \OCP\Util::connectHook('OC_Filesystem', 'preSetup', 'OCA\Files_Trashbin\Storage', 'setupStorage');
969
+        //Listen to delete user signal
970
+        \OCP\Util::connectHook('OC_User', 'pre_deleteUser', 'OCA\Files_Trashbin\Hooks', 'deleteUser_hook');
971
+        //Listen to post write hook
972
+        \OCP\Util::connectHook('OC_Filesystem', 'post_write', 'OCA\Files_Trashbin\Hooks', 'post_write_hook');
973
+        // pre and post-rename, disable trash logic for the copy+unlink case
974
+        \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Trashbin\Trashbin', 'ensureFileScannedHook');
975
+        \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Trashbin\Storage', 'preRenameHook');
976
+        \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Trashbin\Storage', 'postRenameHook');
977
+    }
978
+
979
+    /**
980
+     * check if trash bin is empty for a given user
981
+     *
982
+     * @param string $user
983
+     * @return bool
984
+     */
985
+    public static function isEmpty($user) {
986
+
987
+        $view = new View('/' . $user . '/files_trashbin');
988
+        if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
989
+            while ($file = readdir($dh)) {
990
+                if (!Filesystem::isIgnoredDir($file)) {
991
+                    return false;
992
+                }
993
+            }
994
+        }
995
+        return true;
996
+    }
997
+
998
+    /**
999
+     * @param $path
1000
+     * @return string
1001
+     */
1002
+    public static function preview_icon($path) {
1003
+        return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_trashbin_preview', array('x' => 32, 'y' => 32, 'file' => $path));
1004
+    }
1005 1005
 }
Please login to merge, or discard this patch.
Spacing   +72 added lines, -72 removed lines patch added patch discarded remove patch
@@ -100,7 +100,7 @@  discard block
 block discarded – undo
100 100
 		Filesystem::initMountPoints($uid);
101 101
 		if ($uid !== User::getUser()) {
102 102
 			$info = Filesystem::getFileInfo($filename);
103
-			$ownerView = new View('/' . $uid . '/files');
103
+			$ownerView = new View('/'.$uid.'/files');
104 104
 			try {
105 105
 				$filename = $ownerView->getPath($info['fileid']);
106 106
 			} catch (NotFoundException $e) {
@@ -151,7 +151,7 @@  discard block
 block discarded – undo
151 151
 	}
152 152
 
153 153
 	private static function setUpTrash($user) {
154
-		$view = new View('/' . $user);
154
+		$view = new View('/'.$user);
155 155
 		if (!$view->is_dir('files_trashbin')) {
156 156
 			$view->mkdir('files_trashbin');
157 157
 		}
@@ -186,8 +186,8 @@  discard block
 block discarded – undo
186 186
 
187 187
 		$view = new View('/');
188 188
 
189
-		$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
190
-		$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
189
+		$target = $user.'/files_trashbin/files/'.$targetFilename.'.d'.$timestamp;
190
+		$source = $owner.'/files_trashbin/files/'.$sourceFilename.'.d'.$timestamp;
191 191
 		self::copy_recursive($source, $target, $view);
192 192
 
193 193
 
@@ -221,9 +221,9 @@  discard block
 block discarded – undo
221 221
 			$ownerPath = $file_path;
222 222
 		}
223 223
 
224
-		$ownerView = new View('/' . $owner);
224
+		$ownerView = new View('/'.$owner);
225 225
 		// file has been deleted in between
226
-		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/' . $ownerPath)) {
226
+		if (is_null($ownerPath) || $ownerPath === '' || !$ownerView->file_exists('/files/'.$ownerPath)) {
227 227
 			return true;
228 228
 		}
229 229
 
@@ -240,12 +240,12 @@  discard block
 block discarded – undo
240 240
 		$timestamp = time();
241 241
 
242 242
 		// disable proxy to prevent recursive calls
243
-		$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;
243
+		$trashPath = '/files_trashbin/files/'.$filename.'.d'.$timestamp;
244 244
 
245 245
 		/** @var \OC\Files\Storage\Storage $trashStorage */
246 246
 		list($trashStorage, $trashInternalPath) = $ownerView->resolvePath($trashPath);
247 247
 		/** @var \OC\Files\Storage\Storage $sourceStorage */
248
-		list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/' . $ownerPath);
248
+		list($sourceStorage, $sourceInternalPath) = $ownerView->resolvePath('/files/'.$ownerPath);
249 249
 		try {
250 250
 			$moveSuccessful = true;
251 251
 			if ($trashStorage->file_exists($trashInternalPath)) {
@@ -257,7 +257,7 @@  discard block
 block discarded – undo
257 257
 			if ($trashStorage->file_exists($trashInternalPath)) {
258 258
 				$trashStorage->unlink($trashInternalPath);
259 259
 			}
260
-			\OC::$server->getLogger()->error('Couldn\'t move ' . $file_path . ' to the trash bin', ['app' => 'files_trashbin']);
260
+			\OC::$server->getLogger()->error('Couldn\'t move '.$file_path.' to the trash bin', ['app' => 'files_trashbin']);
261 261
 		}
262 262
 
263 263
 		if ($sourceStorage->file_exists($sourceInternalPath)) { // failed to delete the original file, abort
@@ -278,7 +278,7 @@  discard block
 block discarded – undo
278 278
 				\OC::$server->getLogger()->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']);
279 279
 			}
280 280
 			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', array('filePath' => Filesystem::normalizePath($file_path),
281
-				'trashPath' => Filesystem::normalizePath($filename . '.d' . $timestamp)));
281
+				'trashPath' => Filesystem::normalizePath($filename.'.d'.$timestamp)));
282 282
 
283 283
 			self::retainVersions($filename, $owner, $ownerPath, $timestamp);
284 284
 
@@ -312,18 +312,18 @@  discard block
 block discarded – undo
312 312
 			$user = User::getUser();
313 313
 			$rootView = new View('/');
314 314
 
315
-			if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
315
+			if ($rootView->is_dir($owner.'/files_versions/'.$ownerPath)) {
316 316
 				if ($owner !== $user) {
317
-					self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
317
+					self::copy_recursive($owner.'/files_versions/'.$ownerPath, $owner.'/files_trashbin/versions/'.basename($ownerPath).'.d'.$timestamp, $rootView);
318 318
 				}
319
-				self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
319
+				self::move($rootView, $owner.'/files_versions/'.$ownerPath, $user.'/files_trashbin/versions/'.$filename.'.d'.$timestamp);
320 320
 			} else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
321 321
 
322 322
 				foreach ($versions as $v) {
323 323
 					if ($owner !== $user) {
324
-						self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
324
+						self::copy($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $owner.'/files_trashbin/versions/'.$v['name'].'.v'.$v['version'].'.d'.$timestamp);
325 325
 					}
326
-					self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
326
+					self::move($rootView, $owner.'/files_versions'.$v['path'].'.v'.$v['version'], $user.'/files_trashbin/versions/'.$filename.'.v'.$v['version'].'.d'.$timestamp);
327 327
 				}
328 328
 			}
329 329
 		}
@@ -385,18 +385,18 @@  discard block
 block discarded – undo
385 385
 	 */
386 386
 	public static function restore($file, $filename, $timestamp) {
387 387
 		$user = User::getUser();
388
-		$view = new View('/' . $user);
388
+		$view = new View('/'.$user);
389 389
 
390 390
 		$location = '';
391 391
 		if ($timestamp) {
392 392
 			$location = self::getLocation($user, $filename, $timestamp);
393 393
 			if ($location === false) {
394
-				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: ' . $user . ' $filename: ' . $filename . ', $timestamp: ' . $timestamp . ')', ['app' => 'files_trashbin']);
394
+				\OC::$server->getLogger()->error('trash bin database inconsistent! ($user: '.$user.' $filename: '.$filename.', $timestamp: '.$timestamp.')', ['app' => 'files_trashbin']);
395 395
 			} else {
396 396
 				// if location no longer exists, restore file in the root directory
397 397
 				if ($location !== '/' &&
398
-					(!$view->is_dir('files/' . $location) ||
399
-						!$view->isCreatable('files/' . $location))
398
+					(!$view->is_dir('files/'.$location) ||
399
+						!$view->isCreatable('files/'.$location))
400 400
 				) {
401 401
 					$location = '';
402 402
 				}
@@ -406,8 +406,8 @@  discard block
 block discarded – undo
406 406
 		// we need a  extension in case a file/dir with the same name already exists
407 407
 		$uniqueFilename = self::getUniqueFilename($location, $filename, $view);
408 408
 
409
-		$source = Filesystem::normalizePath('files_trashbin/files/' . $file);
410
-		$target = Filesystem::normalizePath('files/' . $location . '/' . $uniqueFilename);
409
+		$source = Filesystem::normalizePath('files_trashbin/files/'.$file);
410
+		$target = Filesystem::normalizePath('files/'.$location.'/'.$uniqueFilename);
411 411
 		if (!$view->file_exists($source)) {
412 412
 			return false;
413 413
 		}
@@ -419,10 +419,10 @@  discard block
 block discarded – undo
419 419
 		// handle the restore result
420 420
 		if ($restoreResult) {
421 421
 			$fakeRoot = $view->getRoot();
422
-			$view->chroot('/' . $user . '/files');
423
-			$view->touch('/' . $location . '/' . $uniqueFilename, $mtime);
422
+			$view->chroot('/'.$user.'/files');
423
+			$view->touch('/'.$location.'/'.$uniqueFilename, $mtime);
424 424
 			$view->chroot($fakeRoot);
425
-			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename),
425
+			\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename),
426 426
 				'trashPath' => Filesystem::normalizePath($file)));
427 427
 
428 428
 			self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp);
@@ -456,7 +456,7 @@  discard block
 block discarded – undo
456 456
 			$user = User::getUser();
457 457
 			$rootView = new View('/');
458 458
 
459
-			$target = Filesystem::normalizePath('/' . $location . '/' . $uniqueFilename);
459
+			$target = Filesystem::normalizePath('/'.$location.'/'.$uniqueFilename);
460 460
 
461 461
 			list($owner, $ownerPath) = self::getUidAndFilename($target);
462 462
 
@@ -471,14 +471,14 @@  discard block
 block discarded – undo
471 471
 				$versionedFile = $file;
472 472
 			}
473 473
 
474
-			if ($view->is_dir('/files_trashbin/versions/' . $file)) {
475
-				$rootView->rename(Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath));
474
+			if ($view->is_dir('/files_trashbin/versions/'.$file)) {
475
+				$rootView->rename(Filesystem::normalizePath($user.'/files_trashbin/versions/'.$file), Filesystem::normalizePath($owner.'/files_versions/'.$ownerPath));
476 476
 			} else if ($versions = self::getVersionsFromTrash($versionedFile, $timestamp, $user)) {
477 477
 				foreach ($versions as $v) {
478 478
 					if ($timestamp) {
479
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
479
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v.'.d'.$timestamp, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
480 480
 					} else {
481
-						$rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v);
481
+						$rootView->rename($user.'/files_trashbin/versions/'.$versionedFile.'.v'.$v, $owner.'/files_versions/'.$ownerPath.'.v'.$v);
482 482
 					}
483 483
 				}
484 484
 			}
@@ -491,7 +491,7 @@  discard block
 block discarded – undo
491 491
 	public static function deleteAll() {
492 492
 		$user = User::getUser();
493 493
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
494
-		$view = new View('/' . $user);
494
+		$view = new View('/'.$user);
495 495
 		$fileInfos = $view->getDirectoryContent('files_trashbin/files');
496 496
 
497 497
 		try {
@@ -502,7 +502,7 @@  discard block
 block discarded – undo
502 502
 
503 503
 		// Array to store the relative path in (after the file is deleted, the view won't be able to relativise the path anymore)
504 504
 		$filePaths = array();
505
-		foreach($fileInfos as $fileInfo){
505
+		foreach ($fileInfos as $fileInfo) {
506 506
 			$filePaths[] = $view->getRelativePath($fileInfo->getPath());
507 507
 		}
508 508
 		unset($fileInfos); // save memory
@@ -511,7 +511,7 @@  discard block
 block discarded – undo
511 511
 		\OC_Hook::emit('\OCP\Trashbin', 'preDeleteAll', array('paths' => $filePaths));
512 512
 
513 513
 		// Single-File Hooks
514
-		foreach($filePaths as $path){
514
+		foreach ($filePaths as $path) {
515 515
 			self::emitTrashbinPreDelete($path);
516 516
 		}
517 517
 
@@ -524,7 +524,7 @@  discard block
 block discarded – undo
524 524
 		\OC_Hook::emit('\OCP\Trashbin', 'deleteAll', array('paths' => $filePaths));
525 525
 
526 526
 		// Single-File Hooks
527
-		foreach($filePaths as $path){
527
+		foreach ($filePaths as $path) {
528 528
 			self::emitTrashbinPostDelete($path);
529 529
 		}
530 530
 
@@ -538,7 +538,7 @@  discard block
 block discarded – undo
538 538
 	 * wrapper function to emit the 'preDelete' hook of \OCP\Trashbin before a file is deleted
539 539
 	 * @param string $path
540 540
 	 */
541
-	protected static function emitTrashbinPreDelete($path){
541
+	protected static function emitTrashbinPreDelete($path) {
542 542
 		\OC_Hook::emit('\OCP\Trashbin', 'preDelete', array('path' => $path));
543 543
 	}
544 544
 
@@ -546,7 +546,7 @@  discard block
 block discarded – undo
546 546
 	 * wrapper function to emit the 'delete' hook of \OCP\Trashbin after a file has been deleted
547 547
 	 * @param string $path
548 548
 	 */
549
-	protected static function emitTrashbinPostDelete($path){
549
+	protected static function emitTrashbinPostDelete($path) {
550 550
 		\OC_Hook::emit('\OCP\Trashbin', 'delete', array('path' => $path));
551 551
 	}
552 552
 
@@ -561,13 +561,13 @@  discard block
 block discarded – undo
561 561
 	 */
562 562
 	public static function delete($filename, $user, $timestamp = null) {
563 563
 		$userRoot = \OC::$server->getUserFolder($user)->getParent();
564
-		$view = new View('/' . $user);
564
+		$view = new View('/'.$user);
565 565
 		$size = 0;
566 566
 
567 567
 		if ($timestamp) {
568 568
 			$query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?');
569 569
 			$query->execute(array($user, $filename, $timestamp));
570
-			$file = $filename . '.d' . $timestamp;
570
+			$file = $filename.'.d'.$timestamp;
571 571
 		} else {
572 572
 			$file = $filename;
573 573
 		}
@@ -575,20 +575,20 @@  discard block
 block discarded – undo
575 575
 		$size += self::deleteVersions($view, $file, $filename, $timestamp, $user);
576 576
 
577 577
 		try {
578
-			$node = $userRoot->get('/files_trashbin/files/' . $file);
578
+			$node = $userRoot->get('/files_trashbin/files/'.$file);
579 579
 		} catch (NotFoundException $e) {
580 580
 			return $size;
581 581
 		}
582 582
 
583 583
 		if ($node instanceof Folder) {
584
-			$size += self::calculateSize(new View('/' . $user . '/files_trashbin/files/' . $file));
584
+			$size += self::calculateSize(new View('/'.$user.'/files_trashbin/files/'.$file));
585 585
 		} else if ($node instanceof File) {
586
-			$size += $view->filesize('/files_trashbin/files/' . $file);
586
+			$size += $view->filesize('/files_trashbin/files/'.$file);
587 587
 		}
588 588
 
589
-		self::emitTrashbinPreDelete('/files_trashbin/files/' . $file);
589
+		self::emitTrashbinPreDelete('/files_trashbin/files/'.$file);
590 590
 		$node->delete();
591
-		self::emitTrashbinPostDelete('/files_trashbin/files/' . $file);
591
+		self::emitTrashbinPostDelete('/files_trashbin/files/'.$file);
592 592
 
593 593
 		return $size;
594 594
 	}
@@ -604,17 +604,17 @@  discard block
 block discarded – undo
604 604
 	private static function deleteVersions(View $view, $file, $filename, $timestamp, $user) {
605 605
 		$size = 0;
606 606
 		if (\OCP\App::isEnabled('files_versions')) {
607
-			if ($view->is_dir('files_trashbin/versions/' . $file)) {
608
-				$size += self::calculateSize(new View('/' . $user . '/files_trashbin/versions/' . $file));
609
-				$view->unlink('files_trashbin/versions/' . $file);
607
+			if ($view->is_dir('files_trashbin/versions/'.$file)) {
608
+				$size += self::calculateSize(new View('/'.$user.'/files_trashbin/versions/'.$file));
609
+				$view->unlink('files_trashbin/versions/'.$file);
610 610
 			} else if ($versions = self::getVersionsFromTrash($filename, $timestamp, $user)) {
611 611
 				foreach ($versions as $v) {
612 612
 					if ($timestamp) {
613
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
614
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp);
613
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
614
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp);
615 615
 					} else {
616
-						$size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v);
617
-						$view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v);
616
+						$size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v);
617
+						$view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v);
618 618
 					}
619 619
 				}
620 620
 			}
@@ -631,13 +631,13 @@  discard block
 block discarded – undo
631 631
 	 */
632 632
 	public static function file_exists($filename, $timestamp = null) {
633 633
 		$user = User::getUser();
634
-		$view = new View('/' . $user);
634
+		$view = new View('/'.$user);
635 635
 
636 636
 		if ($timestamp) {
637
-			$filename = $filename . '.d' . $timestamp;
637
+			$filename = $filename.'.d'.$timestamp;
638 638
 		}
639 639
 
640
-		$target = Filesystem::normalizePath('files_trashbin/files/' . $filename);
640
+		$target = Filesystem::normalizePath('files_trashbin/files/'.$filename);
641 641
 		return $view->file_exists($target);
642 642
 	}
643 643
 
@@ -662,7 +662,7 @@  discard block
 block discarded – undo
662 662
 	private static function calculateFreeSpace($trashbinSize, $user) {
663 663
 		$softQuota = true;
664 664
 		$userObject = \OC::$server->getUserManager()->get($user);
665
-		if(is_null($userObject)) {
665
+		if (is_null($userObject)) {
666 666
 			return 0;
667 667
 		}
668 668
 		$quota = $userObject->getQuota();
@@ -681,7 +681,7 @@  discard block
 block discarded – undo
681 681
 		// subtract size of files and current trash bin size from quota
682 682
 		if ($softQuota) {
683 683
 			$userFolder = \OC::$server->getUserFolder($user);
684
-			if(is_null($userFolder)) {
684
+			if (is_null($userFolder)) {
685 685
 				return 0;
686 686
 			}
687 687
 			$free = $quota - $userFolder->getSize(); // remaining free space for user
@@ -763,7 +763,7 @@  discard block
 block discarded – undo
763 763
 			foreach ($files as $file) {
764 764
 				if ($availableSpace < 0 && $expiration->isExpired($file['mtime'], true)) {
765 765
 					$tmp = self::delete($file['name'], $user, $file['mtime']);
766
-					\OC::$server->getLogger()->info('files_trashbin', 'remove "' . $file['name'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
766
+					\OC::$server->getLogger()->info('files_trashbin', 'remove "'.$file['name'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota)', ['app' => 'files_trashbin']);
767 767
 					$availableSpace += $tmp;
768 768
 					$size += $tmp;
769 769
 				} else {
@@ -793,7 +793,7 @@  discard block
 block discarded – undo
793 793
 				$count++;
794 794
 				$size += self::delete($filename, $user, $timestamp);
795 795
 				\OC::$server->getLogger()->info(
796
-					'Remove "' . $filename . '" from trashbin because it exceeds max retention obligation term.',
796
+					'Remove "'.$filename.'" from trashbin because it exceeds max retention obligation term.',
797 797
 					['app' => 'files_trashbin']
798 798
 				);
799 799
 			} else {
@@ -819,16 +819,16 @@  discard block
 block discarded – undo
819 819
 			$view->mkdir($destination);
820 820
 			$view->touch($destination, $view->filemtime($source));
821 821
 			foreach ($view->getDirectoryContent($source) as $i) {
822
-				$pathDir = $source . '/' . $i['name'];
822
+				$pathDir = $source.'/'.$i['name'];
823 823
 				if ($view->is_dir($pathDir)) {
824
-					$size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view);
824
+					$size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view);
825 825
 				} else {
826 826
 					$size += $view->filesize($pathDir);
827
-					$result = $view->copy($pathDir, $destination . '/' . $i['name']);
827
+					$result = $view->copy($pathDir, $destination.'/'.$i['name']);
828 828
 					if (!$result) {
829 829
 						throw new \OCA\Files_Trashbin\Exceptions\CopyRecursiveException();
830 830
 					}
831
-					$view->touch($destination . '/' . $i['name'], $view->filemtime($pathDir));
831
+					$view->touch($destination.'/'.$i['name'], $view->filemtime($pathDir));
832 832
 				}
833 833
 			}
834 834
 		} else {
@@ -850,7 +850,7 @@  discard block
 block discarded – undo
850 850
 	 * @return array
851 851
 	 */
852 852
 	private static function getVersionsFromTrash($filename, $timestamp, $user) {
853
-		$view = new View('/' . $user . '/files_trashbin/versions');
853
+		$view = new View('/'.$user.'/files_trashbin/versions');
854 854
 		$versions = array();
855 855
 
856 856
 		//force rescan of versions, local storage may not have updated the cache
@@ -863,10 +863,10 @@  discard block
 block discarded – undo
863 863
 
864 864
 		if ($timestamp) {
865 865
 			// fetch for old versions
866
-			$matches = $view->searchRaw($filename . '.v%.d' . $timestamp);
866
+			$matches = $view->searchRaw($filename.'.v%.d'.$timestamp);
867 867
 			$offset = -strlen($timestamp) - 2;
868 868
 		} else {
869
-			$matches = $view->searchRaw($filename . '.v%');
869
+			$matches = $view->searchRaw($filename.'.v%');
870 870
 		}
871 871
 
872 872
 		if (is_array($matches)) {
@@ -896,18 +896,18 @@  discard block
 block discarded – undo
896 896
 		$name = pathinfo($filename, PATHINFO_FILENAME);
897 897
 		$l = \OC::$server->getL10N('files_trashbin');
898 898
 
899
-		$location = '/' . trim($location, '/');
899
+		$location = '/'.trim($location, '/');
900 900
 
901 901
 		// if extension is not empty we set a dot in front of it
902 902
 		if ($ext !== '') {
903
-			$ext = '.' . $ext;
903
+			$ext = '.'.$ext;
904 904
 		}
905 905
 
906
-		if ($view->file_exists('files' . $location . '/' . $filename)) {
906
+		if ($view->file_exists('files'.$location.'/'.$filename)) {
907 907
 			$i = 2;
908
-			$uniqueName = $name . " (" . $l->t("restored") . ")" . $ext;
909
-			while ($view->file_exists('files' . $location . '/' . $uniqueName)) {
910
-				$uniqueName = $name . " (" . $l->t("restored") . " " . $i . ")" . $ext;
908
+			$uniqueName = $name." (".$l->t("restored").")".$ext;
909
+			while ($view->file_exists('files'.$location.'/'.$uniqueName)) {
910
+				$uniqueName = $name." (".$l->t("restored")." ".$i.")".$ext;
911 911
 				$i++;
912 912
 			}
913 913
 
@@ -924,7 +924,7 @@  discard block
 block discarded – undo
924 924
 	 * @return integer size of the folder
925 925
 	 */
926 926
 	private static function calculateSize($view) {
927
-		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . $view->getAbsolutePath('');
927
+		$root = \OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').$view->getAbsolutePath('');
928 928
 		if (!file_exists($root)) {
929 929
 			return 0;
930 930
 		}
@@ -955,7 +955,7 @@  discard block
 block discarded – undo
955 955
 	 * @return integer trash bin size
956 956
 	 */
957 957
 	private static function getTrashbinSize($user) {
958
-		$view = new View('/' . $user);
958
+		$view = new View('/'.$user);
959 959
 		$fileInfo = $view->getFileInfo('/files_trashbin');
960 960
 		return isset($fileInfo['size']) ? $fileInfo['size'] : 0;
961 961
 	}
@@ -984,7 +984,7 @@  discard block
 block discarded – undo
984 984
 	 */
985 985
 	public static function isEmpty($user) {
986 986
 
987
-		$view = new View('/' . $user . '/files_trashbin');
987
+		$view = new View('/'.$user.'/files_trashbin');
988 988
 		if ($view->is_dir('/files') && $dh = $view->opendir('/files')) {
989 989
 			while ($file = readdir($dh)) {
990 990
 				if (!Filesystem::isIgnoredDir($file)) {
Please login to merge, or discard this patch.
apps/federatedfilesharing/lib/Controller/RequestHandlerController.php 2 patches
Indentation   +631 added lines, -631 removed lines patch added patch discarded remove patch
@@ -50,635 +50,635 @@
 block discarded – undo
50 50
 
51 51
 class RequestHandlerController extends OCSController {
52 52
 
53
-	/** @var FederatedShareProvider */
54
-	private $federatedShareProvider;
55
-
56
-	/** @var IDBConnection */
57
-	private $connection;
58
-
59
-	/** @var Share\IManager */
60
-	private $shareManager;
61
-
62
-	/** @var Notifications */
63
-	private $notifications;
64
-
65
-	/** @var AddressHandler */
66
-	private $addressHandler;
67
-
68
-	/** @var  IUserManager */
69
-	private $userManager;
70
-
71
-	/** @var string */
72
-	private $shareTable = 'share';
73
-
74
-	/** @var ICloudIdManager */
75
-	private $cloudIdManager;
76
-
77
-	/**
78
-	 * Server2Server constructor.
79
-	 *
80
-	 * @param string $appName
81
-	 * @param IRequest $request
82
-	 * @param FederatedShareProvider $federatedShareProvider
83
-	 * @param IDBConnection $connection
84
-	 * @param Share\IManager $shareManager
85
-	 * @param Notifications $notifications
86
-	 * @param AddressHandler $addressHandler
87
-	 * @param IUserManager $userManager
88
-	 * @param ICloudIdManager $cloudIdManager
89
-	 */
90
-	public function __construct($appName,
91
-								IRequest $request,
92
-								FederatedShareProvider $federatedShareProvider,
93
-								IDBConnection $connection,
94
-								Share\IManager $shareManager,
95
-								Notifications $notifications,
96
-								AddressHandler $addressHandler,
97
-								IUserManager $userManager,
98
-								ICloudIdManager $cloudIdManager
99
-	) {
100
-		parent::__construct($appName, $request);
101
-
102
-		$this->federatedShareProvider = $federatedShareProvider;
103
-		$this->connection = $connection;
104
-		$this->shareManager = $shareManager;
105
-		$this->notifications = $notifications;
106
-		$this->addressHandler = $addressHandler;
107
-		$this->userManager = $userManager;
108
-		$this->cloudIdManager = $cloudIdManager;
109
-	}
110
-
111
-	/**
112
-	 * @NoCSRFRequired
113
-	 * @PublicPage
114
-	 *
115
-	 * create a new share
116
-	 *
117
-	 * @return Http\DataResponse
118
-	 * @throws OCSException
119
-	 */
120
-	public function createShare() {
121
-
122
-		if (!$this->isS2SEnabled(true)) {
123
-			throw new OCSException('Server does not support federated cloud sharing', 503);
124
-		}
125
-
126
-		$remote = isset($_POST['remote']) ? $_POST['remote'] : null;
127
-		$token = isset($_POST['token']) ? $_POST['token'] : null;
128
-		$name = isset($_POST['name']) ? $_POST['name'] : null;
129
-		$owner = isset($_POST['owner']) ? $_POST['owner'] : null;
130
-		$sharedBy = isset($_POST['sharedBy']) ? $_POST['sharedBy'] : null;
131
-		$shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null;
132
-		$remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null;
133
-		$sharedByFederatedId = isset($_POST['sharedByFederatedId']) ? $_POST['sharedByFederatedId'] : null;
134
-		$ownerFederatedId = isset($_POST['ownerFederatedId']) ? $_POST['ownerFederatedId'] : null;
135
-
136
-		if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
137
-
138
-			if (!\OCP\Util::isValidFileName($name)) {
139
-				throw new OCSException('The mountpoint name contains invalid characters.', 400);
140
-			}
141
-
142
-			// FIXME this should be a method in the user management instead
143
-			$logger = \OC::$server->getLogger();
144
-			$logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
145
-			\OCP\Util::emitHook(
146
-				'\OCA\Files_Sharing\API\Server2Server',
147
-				'preLoginNameUsedAsUserName',
148
-				array('uid' => &$shareWith)
149
-			);
150
-			$logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
151
-
152
-			if (!\OC::$server->getUserManager()->userExists($shareWith)) {
153
-				throw new OCSException('User does not exists', 400);
154
-			}
155
-
156
-			\OC_Util::setupFS($shareWith);
157
-
158
-			$externalManager = new \OCA\Files_Sharing\External\Manager(
159
-					\OC::$server->getDatabaseConnection(),
160
-					\OC\Files\Filesystem::getMountManager(),
161
-					\OC\Files\Filesystem::getLoader(),
162
-					\OC::$server->getHTTPClientService(),
163
-					\OC::$server->getNotificationManager(),
164
-					\OC::$server->query(\OCP\OCS\IDiscoveryService::class),
165
-					$shareWith
166
-				);
167
-
168
-			try {
169
-				$externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId);
170
-				$shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
171
-
172
-				if ($ownerFederatedId === null) {
173
-					$ownerFederatedId = $this->cloudIdManager->getCloudId($owner, $this->cleanupRemote($remote))->getId();
174
-				}
175
-				// if the owner of the share and the initiator are the same user
176
-				// we also complete the federated share ID for the initiator
177
-				if ($sharedByFederatedId === null && $owner === $sharedBy) {
178
-					$sharedByFederatedId = $ownerFederatedId;
179
-				}
180
-
181
-				$event = \OC::$server->getActivityManager()->generateEvent();
182
-				$event->setApp('files_sharing')
183
-					->setType('remote_share')
184
-					->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
185
-					->setAffectedUser($shareWith)
186
-					->setObject('remote_share', (int)$shareId, $name);
187
-				\OC::$server->getActivityManager()->publish($event);
188
-
189
-				$urlGenerator = \OC::$server->getURLGenerator();
190
-
191
-				$notificationManager = \OC::$server->getNotificationManager();
192
-				$notification = $notificationManager->createNotification();
193
-				$notification->setApp('files_sharing')
194
-					->setUser($shareWith)
195
-					->setDateTime(new \DateTime())
196
-					->setObject('remote_share', $shareId)
197
-					->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/')]);
198
-
199
-				$declineAction = $notification->createAction();
200
-				$declineAction->setLabel('decline')
201
-					->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
202
-				$notification->addAction($declineAction);
203
-
204
-				$acceptAction = $notification->createAction();
205
-				$acceptAction->setLabel('accept')
206
-					->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
207
-				$notification->addAction($acceptAction);
208
-
209
-				$notificationManager->notify($notification);
210
-
211
-				return new Http\DataResponse();
212
-			} catch (\Exception $e) {
213
-				\OC::$server->getLogger()->logException($e, [
214
-					'message' => 'Server can not add remote share.',
215
-					'level' => \OCP\Util::ERROR,
216
-					'app' => 'files_sharing'
217
-				]);
218
-				throw new OCSException('internal server error, was not able to add share from ' . $remote, 500);
219
-			}
220
-		}
221
-
222
-		throw new OCSException('server can not add remote share, missing parameter', 400);
223
-	}
224
-
225
-	/**
226
-	 * @NoCSRFRequired
227
-	 * @PublicPage
228
-	 *
229
-	 * create re-share on behalf of another user
230
-	 *
231
-	 * @param int $id
232
-	 * @return Http\DataResponse
233
-	 * @throws OCSBadRequestException
234
-	 * @throws OCSForbiddenException
235
-	 * @throws OCSNotFoundException
236
-	 */
237
-	public function reShare($id) {
238
-
239
-		$token = $this->request->getParam('token', null);
240
-		$shareWith = $this->request->getParam('shareWith', null);
241
-		$permission = (int)$this->request->getParam('permission', null);
242
-		$remoteId = (int)$this->request->getParam('remoteId', null);
243
-
244
-		if ($id === null ||
245
-			$token === null ||
246
-			$shareWith === null ||
247
-			$permission === null ||
248
-			$remoteId === null
249
-		) {
250
-			throw new OCSBadRequestException();
251
-		}
252
-
253
-		try {
254
-			$share = $this->federatedShareProvider->getShareById($id);
255
-		} catch (Share\Exceptions\ShareNotFound $e) {
256
-			throw new OCSNotFoundException();
257
-		}
258
-
259
-		// don't allow to share a file back to the owner
260
-		list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
261
-		$owner = $share->getShareOwner();
262
-		$currentServer = $this->addressHandler->generateRemoteURL();
263
-		if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
264
-			throw new OCSForbiddenException();
265
-		}
266
-
267
-		if ($this->verifyShare($share, $token)) {
268
-
269
-			// check if re-sharing is allowed
270
-			if ($share->getPermissions() | ~Constants::PERMISSION_SHARE) {
271
-				$share->setPermissions($share->getPermissions() & $permission);
272
-				// the recipient of the initial share is now the initiator for the re-share
273
-				$share->setSharedBy($share->getSharedWith());
274
-				$share->setSharedWith($shareWith);
275
-				try {
276
-					$result = $this->federatedShareProvider->create($share);
277
-					$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $remoteId);
278
-					return new Http\DataResponse([
279
-						'token' => $result->getToken(),
280
-						'remoteId' => $result->getId()
281
-					]);
282
-				} catch (\Exception $e) {
283
-					throw new OCSBadRequestException();
284
-				}
285
-			} else {
286
-				throw new OCSForbiddenException();
287
-			}
288
-		}
289
-		throw new OCSBadRequestException();
290
-	}
291
-
292
-	/**
293
-	 * @NoCSRFRequired
294
-	 * @PublicPage
295
-	 *
296
-	 * accept server-to-server share
297
-	 *
298
-	 * @param int $id
299
-	 * @return Http\DataResponse
300
-	 * @throws OCSException
301
-	 */
302
-	public function acceptShare($id) {
303
-
304
-		if (!$this->isS2SEnabled()) {
305
-			throw new OCSException('Server does not support federated cloud sharing', 503);
306
-		}
307
-
308
-		$token = isset($_POST['token']) ? $_POST['token'] : null;
309
-
310
-		try {
311
-			$share = $this->federatedShareProvider->getShareById($id);
312
-		} catch (Share\Exceptions\ShareNotFound $e) {
313
-			return new Http\DataResponse();
314
-		}
315
-
316
-		if ($this->verifyShare($share, $token)) {
317
-			$this->executeAcceptShare($share);
318
-			if ($share->getShareOwner() !== $share->getSharedBy()) {
319
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
320
-				$remoteId = $this->federatedShareProvider->getRemoteId($share);
321
-				$this->notifications->sendAcceptShare($remote, $remoteId, $share->getToken());
322
-			}
323
-		}
324
-
325
-		return new Http\DataResponse();
326
-	}
327
-
328
-	protected function executeAcceptShare(Share\IShare $share) {
329
-		$fileId = (int) $share->getNode()->getId();
330
-		list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
331
-
332
-		$event = \OC::$server->getActivityManager()->generateEvent();
333
-		$event->setApp('files_sharing')
334
-			->setType('remote_share')
335
-			->setAffectedUser($this->getCorrectUid($share))
336
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
337
-			->setObject('files', $fileId, $file)
338
-			->setLink($link);
339
-		\OC::$server->getActivityManager()->publish($event);
340
-	}
341
-
342
-	/**
343
-	 * @NoCSRFRequired
344
-	 * @PublicPage
345
-	 *
346
-	 * decline server-to-server share
347
-	 *
348
-	 * @param int $id
349
-	 * @return Http\DataResponse
350
-	 * @throws OCSException
351
-	 */
352
-	public function declineShare($id) {
353
-
354
-		if (!$this->isS2SEnabled()) {
355
-			throw new OCSException('Server does not support federated cloud sharing', 503);
356
-		}
357
-
358
-		$token = isset($_POST['token']) ? $_POST['token'] : null;
359
-
360
-		try {
361
-			$share = $this->federatedShareProvider->getShareById($id);
362
-		} catch (Share\Exceptions\ShareNotFound $e) {
363
-			return new Http\DataResponse();
364
-		}
365
-
366
-		if ($this->verifyShare($share, $token)) {
367
-			if ($share->getShareOwner() !== $share->getSharedBy()) {
368
-				list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
369
-				$remoteId = $this->federatedShareProvider->getRemoteId($share);
370
-				$this->notifications->sendDeclineShare($remote, $remoteId, $share->getToken());
371
-			}
372
-			$this->executeDeclineShare($share);
373
-		}
374
-
375
-		return new Http\DataResponse();
376
-	}
377
-
378
-	/**
379
-	 * delete declined share and create a activity
380
-	 *
381
-	 * @param Share\IShare $share
382
-	 */
383
-	protected function executeDeclineShare(Share\IShare $share) {
384
-		$this->federatedShareProvider->removeShareFromTable($share);
385
-		$fileId = (int) $share->getNode()->getId();
386
-		list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
387
-
388
-		$event = \OC::$server->getActivityManager()->generateEvent();
389
-		$event->setApp('files_sharing')
390
-			->setType('remote_share')
391
-			->setAffectedUser($this->getCorrectUid($share))
392
-			->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
393
-			->setObject('files', $fileId, $file)
394
-			->setLink($link);
395
-		\OC::$server->getActivityManager()->publish($event);
396
-
397
-	}
398
-
399
-	/**
400
-	 * check if we are the initiator or the owner of a re-share and return the correct UID
401
-	 *
402
-	 * @param Share\IShare $share
403
-	 * @return string
404
-	 */
405
-	protected function getCorrectUid(Share\IShare $share) {
406
-		if ($this->userManager->userExists($share->getShareOwner())) {
407
-			return $share->getShareOwner();
408
-		}
409
-
410
-		return $share->getSharedBy();
411
-	}
412
-
413
-	/**
414
-	 * @NoCSRFRequired
415
-	 * @PublicPage
416
-	 *
417
-	 * remove server-to-server share if it was unshared by the owner
418
-	 *
419
-	 * @param int $id
420
-	 * @return Http\DataResponse
421
-	 * @throws OCSException
422
-	 */
423
-	public function unshare($id) {
424
-
425
-		if (!$this->isS2SEnabled()) {
426
-			throw new OCSException('Server does not support federated cloud sharing', 503);
427
-		}
428
-
429
-		$token = isset($_POST['token']) ? $_POST['token'] : null;
430
-
431
-		$qb = $this->connection->getQueryBuilder();
432
-		$qb->select('*')
433
-			->from('share_external')
434
-			->where(
435
-				$qb->expr()->andX(
436
-					$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
437
-					$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
438
-				)
439
-			);
440
-
441
-		$result = $qb->execute();
442
-		$share = $result->fetch();
443
-		$result->closeCursor();
444
-
445
-		if ($token && $id && !empty($share)) {
446
-
447
-			$remote = $this->cleanupRemote($share['remote']);
448
-
449
-			$owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
450
-			$mountpoint = $share['mountpoint'];
451
-			$user = $share['user'];
452
-
453
-			$qb = $this->connection->getQueryBuilder();
454
-			$qb->delete('share_external')
455
-				->where(
456
-					$qb->expr()->andX(
457
-						$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
458
-						$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
459
-					)
460
-				);
461
-
462
-			$result = $qb->execute();
463
-			$result->closeCursor();
464
-
465
-			if ($share['accepted']) {
466
-				$path = trim($mountpoint, '/');
467
-			} else {
468
-				$path = trim($share['name'], '/');
469
-			}
470
-
471
-			$notificationManager = \OC::$server->getNotificationManager();
472
-			$notification = $notificationManager->createNotification();
473
-			$notification->setApp('files_sharing')
474
-				->setUser($share['user'])
475
-				->setObject('remote_share', (int)$share['id']);
476
-			$notificationManager->markProcessed($notification);
477
-
478
-			$event = \OC::$server->getActivityManager()->generateEvent();
479
-			$event->setApp('files_sharing')
480
-				->setType('remote_share')
481
-				->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
482
-				->setAffectedUser($user)
483
-				->setObject('remote_share', (int)$share['id'], $path);
484
-			\OC::$server->getActivityManager()->publish($event);
485
-		}
486
-
487
-		return new Http\DataResponse();
488
-	}
489
-
490
-	private function cleanupRemote($remote) {
491
-		$remote = substr($remote, strpos($remote, '://') + 3);
492
-
493
-		return rtrim($remote, '/');
494
-	}
495
-
496
-
497
-	/**
498
-	 * @NoCSRFRequired
499
-	 * @PublicPage
500
-	 *
501
-	 * federated share was revoked, either by the owner or the re-sharer
502
-	 *
503
-	 * @param int $id
504
-	 * @return Http\DataResponse
505
-	 * @throws OCSBadRequestException
506
-	 */
507
-	public function revoke($id) {
508
-		$token = $this->request->getParam('token');
509
-
510
-		$share = $this->federatedShareProvider->getShareById($id);
511
-
512
-		if ($this->verifyShare($share, $token)) {
513
-			$this->federatedShareProvider->removeShareFromTable($share);
514
-			return new Http\DataResponse();
515
-		}
516
-
517
-		throw new OCSBadRequestException();
518
-	}
519
-
520
-	/**
521
-	 * get share
522
-	 *
523
-	 * @param int $id
524
-	 * @param string $token
525
-	 * @return array|bool
526
-	 */
527
-	protected function getShare($id, $token) {
528
-		$query = $this->connection->getQueryBuilder();
529
-		$query->select('*')->from($this->shareTable)
530
-			->where($query->expr()->eq('token', $query->createNamedParameter($token)))
531
-			->andWhere($query->expr()->eq('share_type', $query->createNamedParameter(FederatedShareProvider::SHARE_TYPE_REMOTE)))
532
-			->andWhere($query->expr()->eq('id', $query->createNamedParameter($id)));
533
-
534
-		$result = $query->execute()->fetchAll();
535
-
536
-		if (!empty($result) && isset($result[0])) {
537
-			return $result[0];
538
-		}
539
-
540
-		return false;
541
-	}
542
-
543
-	/**
544
-	 * get file
545
-	 *
546
-	 * @param string $user
547
-	 * @param int $fileSource
548
-	 * @return array with internal path of the file and a absolute link to it
549
-	 */
550
-	private function getFile($user, $fileSource) {
551
-		\OC_Util::setupFS($user);
552
-
553
-		try {
554
-			$file = \OC\Files\Filesystem::getPath($fileSource);
555
-		} catch (NotFoundException $e) {
556
-			$file = null;
557
-		}
558
-		$args = \OC\Files\Filesystem::is_dir($file) ? array('dir' => $file) : array('dir' => dirname($file), 'scrollto' => $file);
559
-		$link = \OCP\Util::linkToAbsolute('files', 'index.php', $args);
560
-
561
-		return array($file, $link);
562
-
563
-	}
564
-
565
-	/**
566
-	 * check if server-to-server sharing is enabled
567
-	 *
568
-	 * @param bool $incoming
569
-	 * @return bool
570
-	 */
571
-	private function isS2SEnabled($incoming = false) {
572
-
573
-		$result = \OCP\App::isEnabled('files_sharing');
574
-
575
-		if ($incoming) {
576
-			$result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
577
-		} else {
578
-			$result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
579
-		}
580
-
581
-		return $result;
582
-	}
583
-
584
-	/**
585
-	 * check if we got the right share
586
-	 *
587
-	 * @param Share\IShare $share
588
-	 * @param string $token
589
-	 * @return bool
590
-	 */
591
-	protected function verifyShare(Share\IShare $share, $token) {
592
-		if (
593
-			$share->getShareType() === FederatedShareProvider::SHARE_TYPE_REMOTE &&
594
-			$share->getToken() === $token
595
-		) {
596
-			return true;
597
-		}
598
-
599
-		return false;
600
-	}
601
-
602
-	/**
603
-	 * @NoCSRFRequired
604
-	 * @PublicPage
605
-	 *
606
-	 * update share information to keep federated re-shares in sync
607
-	 *
608
-	 * @param int $id
609
-	 * @return Http\DataResponse
610
-	 * @throws OCSBadRequestException
611
-	 */
612
-	public function updatePermissions($id) {
613
-		$token = $this->request->getParam('token', null);
614
-		$permissions = $this->request->getParam('permissions', null);
615
-
616
-		try {
617
-			$share = $this->federatedShareProvider->getShareById($id);
618
-		} catch (Share\Exceptions\ShareNotFound $e) {
619
-			throw new OCSBadRequestException();
620
-		}
621
-
622
-		$validPermission = ctype_digit($permissions);
623
-		$validToken = $this->verifyShare($share, $token);
624
-		if ($validPermission && $validToken) {
625
-			$this->updatePermissionsInDatabase($share, (int)$permissions);
626
-		} else {
627
-			throw new OCSBadRequestException();
628
-		}
629
-
630
-		return new Http\DataResponse();
631
-	}
632
-
633
-	/**
634
-	 * update permissions in database
635
-	 *
636
-	 * @param IShare $share
637
-	 * @param int $permissions
638
-	 */
639
-	protected function updatePermissionsInDatabase(IShare $share, $permissions) {
640
-		$query = $this->connection->getQueryBuilder();
641
-		$query->update('share')
642
-			->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
643
-			->set('permissions', $query->createNamedParameter($permissions))
644
-			->execute();
645
-	}
646
-
647
-	/**
648
-	 * @NoCSRFRequired
649
-	 * @PublicPage
650
-	 *
651
-	 * change the owner of a server-to-server share
652
-	 *
653
-	 * @param int $id
654
-	 * @return Http\DataResponse
655
-	 * @throws \InvalidArgumentException
656
-	 * @throws OCSException
657
-	 */
658
-	public function move($id) {
659
-
660
-		if (!$this->isS2SEnabled()) {
661
-			throw new OCSException('Server does not support federated cloud sharing', 503);
662
-		}
663
-
664
-		$token = $this->request->getParam('token');
665
-		$remote = $this->request->getParam('remote');
666
-		$newRemoteId = $this->request->getParam('remote_id', $id);
667
-		$cloudId = $this->cloudIdManager->resolveCloudId($remote);
668
-
669
-		$qb = $this->connection->getQueryBuilder();
670
-		$query = $qb->update('share_external')
671
-			->set('remote', $qb->createNamedParameter($cloudId->getRemote()))
672
-			->set('owner', $qb->createNamedParameter($cloudId->getUser()))
673
-			->set('remote_id', $qb->createNamedParameter($newRemoteId))
674
-			->where($qb->expr()->eq('remote_id', $qb->createNamedParameter($id)))
675
-			->andWhere($qb->expr()->eq('share_token', $qb->createNamedParameter($token)));
676
-		$affected = $query->execute();
677
-
678
-		if ($affected > 0) {
679
-			return new Http\DataResponse(['remote' => $cloudId->getRemote(), 'owner' => $cloudId->getUser()]);
680
-		} else {
681
-			throw new OCSBadRequestException('Share not found or token invalid');
682
-		}
683
-	}
53
+    /** @var FederatedShareProvider */
54
+    private $federatedShareProvider;
55
+
56
+    /** @var IDBConnection */
57
+    private $connection;
58
+
59
+    /** @var Share\IManager */
60
+    private $shareManager;
61
+
62
+    /** @var Notifications */
63
+    private $notifications;
64
+
65
+    /** @var AddressHandler */
66
+    private $addressHandler;
67
+
68
+    /** @var  IUserManager */
69
+    private $userManager;
70
+
71
+    /** @var string */
72
+    private $shareTable = 'share';
73
+
74
+    /** @var ICloudIdManager */
75
+    private $cloudIdManager;
76
+
77
+    /**
78
+     * Server2Server constructor.
79
+     *
80
+     * @param string $appName
81
+     * @param IRequest $request
82
+     * @param FederatedShareProvider $federatedShareProvider
83
+     * @param IDBConnection $connection
84
+     * @param Share\IManager $shareManager
85
+     * @param Notifications $notifications
86
+     * @param AddressHandler $addressHandler
87
+     * @param IUserManager $userManager
88
+     * @param ICloudIdManager $cloudIdManager
89
+     */
90
+    public function __construct($appName,
91
+                                IRequest $request,
92
+                                FederatedShareProvider $federatedShareProvider,
93
+                                IDBConnection $connection,
94
+                                Share\IManager $shareManager,
95
+                                Notifications $notifications,
96
+                                AddressHandler $addressHandler,
97
+                                IUserManager $userManager,
98
+                                ICloudIdManager $cloudIdManager
99
+    ) {
100
+        parent::__construct($appName, $request);
101
+
102
+        $this->federatedShareProvider = $federatedShareProvider;
103
+        $this->connection = $connection;
104
+        $this->shareManager = $shareManager;
105
+        $this->notifications = $notifications;
106
+        $this->addressHandler = $addressHandler;
107
+        $this->userManager = $userManager;
108
+        $this->cloudIdManager = $cloudIdManager;
109
+    }
110
+
111
+    /**
112
+     * @NoCSRFRequired
113
+     * @PublicPage
114
+     *
115
+     * create a new share
116
+     *
117
+     * @return Http\DataResponse
118
+     * @throws OCSException
119
+     */
120
+    public function createShare() {
121
+
122
+        if (!$this->isS2SEnabled(true)) {
123
+            throw new OCSException('Server does not support federated cloud sharing', 503);
124
+        }
125
+
126
+        $remote = isset($_POST['remote']) ? $_POST['remote'] : null;
127
+        $token = isset($_POST['token']) ? $_POST['token'] : null;
128
+        $name = isset($_POST['name']) ? $_POST['name'] : null;
129
+        $owner = isset($_POST['owner']) ? $_POST['owner'] : null;
130
+        $sharedBy = isset($_POST['sharedBy']) ? $_POST['sharedBy'] : null;
131
+        $shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null;
132
+        $remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null;
133
+        $sharedByFederatedId = isset($_POST['sharedByFederatedId']) ? $_POST['sharedByFederatedId'] : null;
134
+        $ownerFederatedId = isset($_POST['ownerFederatedId']) ? $_POST['ownerFederatedId'] : null;
135
+
136
+        if ($remote && $token && $name && $owner && $remoteId && $shareWith) {
137
+
138
+            if (!\OCP\Util::isValidFileName($name)) {
139
+                throw new OCSException('The mountpoint name contains invalid characters.', 400);
140
+            }
141
+
142
+            // FIXME this should be a method in the user management instead
143
+            $logger = \OC::$server->getLogger();
144
+            $logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
145
+            \OCP\Util::emitHook(
146
+                '\OCA\Files_Sharing\API\Server2Server',
147
+                'preLoginNameUsedAsUserName',
148
+                array('uid' => &$shareWith)
149
+            );
150
+            $logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
151
+
152
+            if (!\OC::$server->getUserManager()->userExists($shareWith)) {
153
+                throw new OCSException('User does not exists', 400);
154
+            }
155
+
156
+            \OC_Util::setupFS($shareWith);
157
+
158
+            $externalManager = new \OCA\Files_Sharing\External\Manager(
159
+                    \OC::$server->getDatabaseConnection(),
160
+                    \OC\Files\Filesystem::getMountManager(),
161
+                    \OC\Files\Filesystem::getLoader(),
162
+                    \OC::$server->getHTTPClientService(),
163
+                    \OC::$server->getNotificationManager(),
164
+                    \OC::$server->query(\OCP\OCS\IDiscoveryService::class),
165
+                    $shareWith
166
+                );
167
+
168
+            try {
169
+                $externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId);
170
+                $shareId = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share_external');
171
+
172
+                if ($ownerFederatedId === null) {
173
+                    $ownerFederatedId = $this->cloudIdManager->getCloudId($owner, $this->cleanupRemote($remote))->getId();
174
+                }
175
+                // if the owner of the share and the initiator are the same user
176
+                // we also complete the federated share ID for the initiator
177
+                if ($sharedByFederatedId === null && $owner === $sharedBy) {
178
+                    $sharedByFederatedId = $ownerFederatedId;
179
+                }
180
+
181
+                $event = \OC::$server->getActivityManager()->generateEvent();
182
+                $event->setApp('files_sharing')
183
+                    ->setType('remote_share')
184
+                    ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
185
+                    ->setAffectedUser($shareWith)
186
+                    ->setObject('remote_share', (int)$shareId, $name);
187
+                \OC::$server->getActivityManager()->publish($event);
188
+
189
+                $urlGenerator = \OC::$server->getURLGenerator();
190
+
191
+                $notificationManager = \OC::$server->getNotificationManager();
192
+                $notification = $notificationManager->createNotification();
193
+                $notification->setApp('files_sharing')
194
+                    ->setUser($shareWith)
195
+                    ->setDateTime(new \DateTime())
196
+                    ->setObject('remote_share', $shareId)
197
+                    ->setSubject('remote_share', [$ownerFederatedId, $sharedByFederatedId, trim($name, '/')]);
198
+
199
+                $declineAction = $notification->createAction();
200
+                $declineAction->setLabel('decline')
201
+                    ->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
202
+                $notification->addAction($declineAction);
203
+
204
+                $acceptAction = $notification->createAction();
205
+                $acceptAction->setLabel('accept')
206
+                    ->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
207
+                $notification->addAction($acceptAction);
208
+
209
+                $notificationManager->notify($notification);
210
+
211
+                return new Http\DataResponse();
212
+            } catch (\Exception $e) {
213
+                \OC::$server->getLogger()->logException($e, [
214
+                    'message' => 'Server can not add remote share.',
215
+                    'level' => \OCP\Util::ERROR,
216
+                    'app' => 'files_sharing'
217
+                ]);
218
+                throw new OCSException('internal server error, was not able to add share from ' . $remote, 500);
219
+            }
220
+        }
221
+
222
+        throw new OCSException('server can not add remote share, missing parameter', 400);
223
+    }
224
+
225
+    /**
226
+     * @NoCSRFRequired
227
+     * @PublicPage
228
+     *
229
+     * create re-share on behalf of another user
230
+     *
231
+     * @param int $id
232
+     * @return Http\DataResponse
233
+     * @throws OCSBadRequestException
234
+     * @throws OCSForbiddenException
235
+     * @throws OCSNotFoundException
236
+     */
237
+    public function reShare($id) {
238
+
239
+        $token = $this->request->getParam('token', null);
240
+        $shareWith = $this->request->getParam('shareWith', null);
241
+        $permission = (int)$this->request->getParam('permission', null);
242
+        $remoteId = (int)$this->request->getParam('remoteId', null);
243
+
244
+        if ($id === null ||
245
+            $token === null ||
246
+            $shareWith === null ||
247
+            $permission === null ||
248
+            $remoteId === null
249
+        ) {
250
+            throw new OCSBadRequestException();
251
+        }
252
+
253
+        try {
254
+            $share = $this->federatedShareProvider->getShareById($id);
255
+        } catch (Share\Exceptions\ShareNotFound $e) {
256
+            throw new OCSNotFoundException();
257
+        }
258
+
259
+        // don't allow to share a file back to the owner
260
+        list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
261
+        $owner = $share->getShareOwner();
262
+        $currentServer = $this->addressHandler->generateRemoteURL();
263
+        if ($this->addressHandler->compareAddresses($user, $remote, $owner, $currentServer)) {
264
+            throw new OCSForbiddenException();
265
+        }
266
+
267
+        if ($this->verifyShare($share, $token)) {
268
+
269
+            // check if re-sharing is allowed
270
+            if ($share->getPermissions() | ~Constants::PERMISSION_SHARE) {
271
+                $share->setPermissions($share->getPermissions() & $permission);
272
+                // the recipient of the initial share is now the initiator for the re-share
273
+                $share->setSharedBy($share->getSharedWith());
274
+                $share->setSharedWith($shareWith);
275
+                try {
276
+                    $result = $this->federatedShareProvider->create($share);
277
+                    $this->federatedShareProvider->storeRemoteId((int)$result->getId(), $remoteId);
278
+                    return new Http\DataResponse([
279
+                        'token' => $result->getToken(),
280
+                        'remoteId' => $result->getId()
281
+                    ]);
282
+                } catch (\Exception $e) {
283
+                    throw new OCSBadRequestException();
284
+                }
285
+            } else {
286
+                throw new OCSForbiddenException();
287
+            }
288
+        }
289
+        throw new OCSBadRequestException();
290
+    }
291
+
292
+    /**
293
+     * @NoCSRFRequired
294
+     * @PublicPage
295
+     *
296
+     * accept server-to-server share
297
+     *
298
+     * @param int $id
299
+     * @return Http\DataResponse
300
+     * @throws OCSException
301
+     */
302
+    public function acceptShare($id) {
303
+
304
+        if (!$this->isS2SEnabled()) {
305
+            throw new OCSException('Server does not support federated cloud sharing', 503);
306
+        }
307
+
308
+        $token = isset($_POST['token']) ? $_POST['token'] : null;
309
+
310
+        try {
311
+            $share = $this->federatedShareProvider->getShareById($id);
312
+        } catch (Share\Exceptions\ShareNotFound $e) {
313
+            return new Http\DataResponse();
314
+        }
315
+
316
+        if ($this->verifyShare($share, $token)) {
317
+            $this->executeAcceptShare($share);
318
+            if ($share->getShareOwner() !== $share->getSharedBy()) {
319
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
320
+                $remoteId = $this->federatedShareProvider->getRemoteId($share);
321
+                $this->notifications->sendAcceptShare($remote, $remoteId, $share->getToken());
322
+            }
323
+        }
324
+
325
+        return new Http\DataResponse();
326
+    }
327
+
328
+    protected function executeAcceptShare(Share\IShare $share) {
329
+        $fileId = (int) $share->getNode()->getId();
330
+        list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
331
+
332
+        $event = \OC::$server->getActivityManager()->generateEvent();
333
+        $event->setApp('files_sharing')
334
+            ->setType('remote_share')
335
+            ->setAffectedUser($this->getCorrectUid($share))
336
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
337
+            ->setObject('files', $fileId, $file)
338
+            ->setLink($link);
339
+        \OC::$server->getActivityManager()->publish($event);
340
+    }
341
+
342
+    /**
343
+     * @NoCSRFRequired
344
+     * @PublicPage
345
+     *
346
+     * decline server-to-server share
347
+     *
348
+     * @param int $id
349
+     * @return Http\DataResponse
350
+     * @throws OCSException
351
+     */
352
+    public function declineShare($id) {
353
+
354
+        if (!$this->isS2SEnabled()) {
355
+            throw new OCSException('Server does not support federated cloud sharing', 503);
356
+        }
357
+
358
+        $token = isset($_POST['token']) ? $_POST['token'] : null;
359
+
360
+        try {
361
+            $share = $this->federatedShareProvider->getShareById($id);
362
+        } catch (Share\Exceptions\ShareNotFound $e) {
363
+            return new Http\DataResponse();
364
+        }
365
+
366
+        if ($this->verifyShare($share, $token)) {
367
+            if ($share->getShareOwner() !== $share->getSharedBy()) {
368
+                list(, $remote) = $this->addressHandler->splitUserRemote($share->getSharedBy());
369
+                $remoteId = $this->federatedShareProvider->getRemoteId($share);
370
+                $this->notifications->sendDeclineShare($remote, $remoteId, $share->getToken());
371
+            }
372
+            $this->executeDeclineShare($share);
373
+        }
374
+
375
+        return new Http\DataResponse();
376
+    }
377
+
378
+    /**
379
+     * delete declined share and create a activity
380
+     *
381
+     * @param Share\IShare $share
382
+     */
383
+    protected function executeDeclineShare(Share\IShare $share) {
384
+        $this->federatedShareProvider->removeShareFromTable($share);
385
+        $fileId = (int) $share->getNode()->getId();
386
+        list($file, $link) = $this->getFile($this->getCorrectUid($share), $fileId);
387
+
388
+        $event = \OC::$server->getActivityManager()->generateEvent();
389
+        $event->setApp('files_sharing')
390
+            ->setType('remote_share')
391
+            ->setAffectedUser($this->getCorrectUid($share))
392
+            ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
393
+            ->setObject('files', $fileId, $file)
394
+            ->setLink($link);
395
+        \OC::$server->getActivityManager()->publish($event);
396
+
397
+    }
398
+
399
+    /**
400
+     * check if we are the initiator or the owner of a re-share and return the correct UID
401
+     *
402
+     * @param Share\IShare $share
403
+     * @return string
404
+     */
405
+    protected function getCorrectUid(Share\IShare $share) {
406
+        if ($this->userManager->userExists($share->getShareOwner())) {
407
+            return $share->getShareOwner();
408
+        }
409
+
410
+        return $share->getSharedBy();
411
+    }
412
+
413
+    /**
414
+     * @NoCSRFRequired
415
+     * @PublicPage
416
+     *
417
+     * remove server-to-server share if it was unshared by the owner
418
+     *
419
+     * @param int $id
420
+     * @return Http\DataResponse
421
+     * @throws OCSException
422
+     */
423
+    public function unshare($id) {
424
+
425
+        if (!$this->isS2SEnabled()) {
426
+            throw new OCSException('Server does not support federated cloud sharing', 503);
427
+        }
428
+
429
+        $token = isset($_POST['token']) ? $_POST['token'] : null;
430
+
431
+        $qb = $this->connection->getQueryBuilder();
432
+        $qb->select('*')
433
+            ->from('share_external')
434
+            ->where(
435
+                $qb->expr()->andX(
436
+                    $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
437
+                    $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
438
+                )
439
+            );
440
+
441
+        $result = $qb->execute();
442
+        $share = $result->fetch();
443
+        $result->closeCursor();
444
+
445
+        if ($token && $id && !empty($share)) {
446
+
447
+            $remote = $this->cleanupRemote($share['remote']);
448
+
449
+            $owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
450
+            $mountpoint = $share['mountpoint'];
451
+            $user = $share['user'];
452
+
453
+            $qb = $this->connection->getQueryBuilder();
454
+            $qb->delete('share_external')
455
+                ->where(
456
+                    $qb->expr()->andX(
457
+                        $qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
458
+                        $qb->expr()->eq('share_token', $qb->createNamedParameter($token))
459
+                    )
460
+                );
461
+
462
+            $result = $qb->execute();
463
+            $result->closeCursor();
464
+
465
+            if ($share['accepted']) {
466
+                $path = trim($mountpoint, '/');
467
+            } else {
468
+                $path = trim($share['name'], '/');
469
+            }
470
+
471
+            $notificationManager = \OC::$server->getNotificationManager();
472
+            $notification = $notificationManager->createNotification();
473
+            $notification->setApp('files_sharing')
474
+                ->setUser($share['user'])
475
+                ->setObject('remote_share', (int)$share['id']);
476
+            $notificationManager->markProcessed($notification);
477
+
478
+            $event = \OC::$server->getActivityManager()->generateEvent();
479
+            $event->setApp('files_sharing')
480
+                ->setType('remote_share')
481
+                ->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
482
+                ->setAffectedUser($user)
483
+                ->setObject('remote_share', (int)$share['id'], $path);
484
+            \OC::$server->getActivityManager()->publish($event);
485
+        }
486
+
487
+        return new Http\DataResponse();
488
+    }
489
+
490
+    private function cleanupRemote($remote) {
491
+        $remote = substr($remote, strpos($remote, '://') + 3);
492
+
493
+        return rtrim($remote, '/');
494
+    }
495
+
496
+
497
+    /**
498
+     * @NoCSRFRequired
499
+     * @PublicPage
500
+     *
501
+     * federated share was revoked, either by the owner or the re-sharer
502
+     *
503
+     * @param int $id
504
+     * @return Http\DataResponse
505
+     * @throws OCSBadRequestException
506
+     */
507
+    public function revoke($id) {
508
+        $token = $this->request->getParam('token');
509
+
510
+        $share = $this->federatedShareProvider->getShareById($id);
511
+
512
+        if ($this->verifyShare($share, $token)) {
513
+            $this->federatedShareProvider->removeShareFromTable($share);
514
+            return new Http\DataResponse();
515
+        }
516
+
517
+        throw new OCSBadRequestException();
518
+    }
519
+
520
+    /**
521
+     * get share
522
+     *
523
+     * @param int $id
524
+     * @param string $token
525
+     * @return array|bool
526
+     */
527
+    protected function getShare($id, $token) {
528
+        $query = $this->connection->getQueryBuilder();
529
+        $query->select('*')->from($this->shareTable)
530
+            ->where($query->expr()->eq('token', $query->createNamedParameter($token)))
531
+            ->andWhere($query->expr()->eq('share_type', $query->createNamedParameter(FederatedShareProvider::SHARE_TYPE_REMOTE)))
532
+            ->andWhere($query->expr()->eq('id', $query->createNamedParameter($id)));
533
+
534
+        $result = $query->execute()->fetchAll();
535
+
536
+        if (!empty($result) && isset($result[0])) {
537
+            return $result[0];
538
+        }
539
+
540
+        return false;
541
+    }
542
+
543
+    /**
544
+     * get file
545
+     *
546
+     * @param string $user
547
+     * @param int $fileSource
548
+     * @return array with internal path of the file and a absolute link to it
549
+     */
550
+    private function getFile($user, $fileSource) {
551
+        \OC_Util::setupFS($user);
552
+
553
+        try {
554
+            $file = \OC\Files\Filesystem::getPath($fileSource);
555
+        } catch (NotFoundException $e) {
556
+            $file = null;
557
+        }
558
+        $args = \OC\Files\Filesystem::is_dir($file) ? array('dir' => $file) : array('dir' => dirname($file), 'scrollto' => $file);
559
+        $link = \OCP\Util::linkToAbsolute('files', 'index.php', $args);
560
+
561
+        return array($file, $link);
562
+
563
+    }
564
+
565
+    /**
566
+     * check if server-to-server sharing is enabled
567
+     *
568
+     * @param bool $incoming
569
+     * @return bool
570
+     */
571
+    private function isS2SEnabled($incoming = false) {
572
+
573
+        $result = \OCP\App::isEnabled('files_sharing');
574
+
575
+        if ($incoming) {
576
+            $result = $result && $this->federatedShareProvider->isIncomingServer2serverShareEnabled();
577
+        } else {
578
+            $result = $result && $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
579
+        }
580
+
581
+        return $result;
582
+    }
583
+
584
+    /**
585
+     * check if we got the right share
586
+     *
587
+     * @param Share\IShare $share
588
+     * @param string $token
589
+     * @return bool
590
+     */
591
+    protected function verifyShare(Share\IShare $share, $token) {
592
+        if (
593
+            $share->getShareType() === FederatedShareProvider::SHARE_TYPE_REMOTE &&
594
+            $share->getToken() === $token
595
+        ) {
596
+            return true;
597
+        }
598
+
599
+        return false;
600
+    }
601
+
602
+    /**
603
+     * @NoCSRFRequired
604
+     * @PublicPage
605
+     *
606
+     * update share information to keep federated re-shares in sync
607
+     *
608
+     * @param int $id
609
+     * @return Http\DataResponse
610
+     * @throws OCSBadRequestException
611
+     */
612
+    public function updatePermissions($id) {
613
+        $token = $this->request->getParam('token', null);
614
+        $permissions = $this->request->getParam('permissions', null);
615
+
616
+        try {
617
+            $share = $this->federatedShareProvider->getShareById($id);
618
+        } catch (Share\Exceptions\ShareNotFound $e) {
619
+            throw new OCSBadRequestException();
620
+        }
621
+
622
+        $validPermission = ctype_digit($permissions);
623
+        $validToken = $this->verifyShare($share, $token);
624
+        if ($validPermission && $validToken) {
625
+            $this->updatePermissionsInDatabase($share, (int)$permissions);
626
+        } else {
627
+            throw new OCSBadRequestException();
628
+        }
629
+
630
+        return new Http\DataResponse();
631
+    }
632
+
633
+    /**
634
+     * update permissions in database
635
+     *
636
+     * @param IShare $share
637
+     * @param int $permissions
638
+     */
639
+    protected function updatePermissionsInDatabase(IShare $share, $permissions) {
640
+        $query = $this->connection->getQueryBuilder();
641
+        $query->update('share')
642
+            ->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
643
+            ->set('permissions', $query->createNamedParameter($permissions))
644
+            ->execute();
645
+    }
646
+
647
+    /**
648
+     * @NoCSRFRequired
649
+     * @PublicPage
650
+     *
651
+     * change the owner of a server-to-server share
652
+     *
653
+     * @param int $id
654
+     * @return Http\DataResponse
655
+     * @throws \InvalidArgumentException
656
+     * @throws OCSException
657
+     */
658
+    public function move($id) {
659
+
660
+        if (!$this->isS2SEnabled()) {
661
+            throw new OCSException('Server does not support federated cloud sharing', 503);
662
+        }
663
+
664
+        $token = $this->request->getParam('token');
665
+        $remote = $this->request->getParam('remote');
666
+        $newRemoteId = $this->request->getParam('remote_id', $id);
667
+        $cloudId = $this->cloudIdManager->resolveCloudId($remote);
668
+
669
+        $qb = $this->connection->getQueryBuilder();
670
+        $query = $qb->update('share_external')
671
+            ->set('remote', $qb->createNamedParameter($cloudId->getRemote()))
672
+            ->set('owner', $qb->createNamedParameter($cloudId->getUser()))
673
+            ->set('remote_id', $qb->createNamedParameter($newRemoteId))
674
+            ->where($qb->expr()->eq('remote_id', $qb->createNamedParameter($id)))
675
+            ->andWhere($qb->expr()->eq('share_token', $qb->createNamedParameter($token)));
676
+        $affected = $query->execute();
677
+
678
+        if ($affected > 0) {
679
+            return new Http\DataResponse(['remote' => $cloudId->getRemote(), 'owner' => $cloudId->getUser()]);
680
+        } else {
681
+            throw new OCSBadRequestException('Share not found or token invalid');
682
+        }
683
+    }
684 684
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -129,7 +129,7 @@  discard block
 block discarded – undo
129 129
 		$owner = isset($_POST['owner']) ? $_POST['owner'] : null;
130 130
 		$sharedBy = isset($_POST['sharedBy']) ? $_POST['sharedBy'] : null;
131 131
 		$shareWith = isset($_POST['shareWith']) ? $_POST['shareWith'] : null;
132
-		$remoteId = isset($_POST['remoteId']) ? (int)$_POST['remoteId'] : null;
132
+		$remoteId = isset($_POST['remoteId']) ? (int) $_POST['remoteId'] : null;
133 133
 		$sharedByFederatedId = isset($_POST['sharedByFederatedId']) ? $_POST['sharedByFederatedId'] : null;
134 134
 		$ownerFederatedId = isset($_POST['ownerFederatedId']) ? $_POST['ownerFederatedId'] : null;
135 135
 
@@ -141,13 +141,13 @@  discard block
 block discarded – undo
141 141
 
142 142
 			// FIXME this should be a method in the user management instead
143 143
 			$logger = \OC::$server->getLogger();
144
-			$logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
144
+			$logger->debug('shareWith before, '.$shareWith, ['app' => 'files_sharing']);
145 145
 			\OCP\Util::emitHook(
146 146
 				'\OCA\Files_Sharing\API\Server2Server',
147 147
 				'preLoginNameUsedAsUserName',
148 148
 				array('uid' => &$shareWith)
149 149
 			);
150
-			$logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
150
+			$logger->debug('shareWith after, '.$shareWith, ['app' => 'files_sharing']);
151 151
 
152 152
 			if (!\OC::$server->getUserManager()->userExists($shareWith)) {
153 153
 				throw new OCSException('User does not exists', 400);
@@ -183,7 +183,7 @@  discard block
 block discarded – undo
183 183
 					->setType('remote_share')
184 184
 					->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/')])
185 185
 					->setAffectedUser($shareWith)
186
-					->setObject('remote_share', (int)$shareId, $name);
186
+					->setObject('remote_share', (int) $shareId, $name);
187 187
 				\OC::$server->getActivityManager()->publish($event);
188 188
 
189 189
 				$urlGenerator = \OC::$server->getURLGenerator();
@@ -198,12 +198,12 @@  discard block
 block discarded – undo
198 198
 
199 199
 				$declineAction = $notification->createAction();
200 200
 				$declineAction->setLabel('decline')
201
-					->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'DELETE');
201
+					->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/'.$shareId)), 'DELETE');
202 202
 				$notification->addAction($declineAction);
203 203
 
204 204
 				$acceptAction = $notification->createAction();
205 205
 				$acceptAction->setLabel('accept')
206
-					->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/' . $shareId)), 'POST');
206
+					->setLink($urlGenerator->getAbsoluteURL($urlGenerator->linkTo('', 'ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/'.$shareId)), 'POST');
207 207
 				$notification->addAction($acceptAction);
208 208
 
209 209
 				$notificationManager->notify($notification);
@@ -215,7 +215,7 @@  discard block
 block discarded – undo
215 215
 					'level' => \OCP\Util::ERROR,
216 216
 					'app' => 'files_sharing'
217 217
 				]);
218
-				throw new OCSException('internal server error, was not able to add share from ' . $remote, 500);
218
+				throw new OCSException('internal server error, was not able to add share from '.$remote, 500);
219 219
 			}
220 220
 		}
221 221
 
@@ -238,8 +238,8 @@  discard block
 block discarded – undo
238 238
 
239 239
 		$token = $this->request->getParam('token', null);
240 240
 		$shareWith = $this->request->getParam('shareWith', null);
241
-		$permission = (int)$this->request->getParam('permission', null);
242
-		$remoteId = (int)$this->request->getParam('remoteId', null);
241
+		$permission = (int) $this->request->getParam('permission', null);
242
+		$remoteId = (int) $this->request->getParam('remoteId', null);
243 243
 
244 244
 		if ($id === null ||
245 245
 			$token === null ||
@@ -274,7 +274,7 @@  discard block
 block discarded – undo
274 274
 				$share->setSharedWith($shareWith);
275 275
 				try {
276 276
 					$result = $this->federatedShareProvider->create($share);
277
-					$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $remoteId);
277
+					$this->federatedShareProvider->storeRemoteId((int) $result->getId(), $remoteId);
278 278
 					return new Http\DataResponse([
279 279
 						'token' => $result->getToken(),
280 280
 						'remoteId' => $result->getId()
@@ -472,7 +472,7 @@  discard block
 block discarded – undo
472 472
 			$notification = $notificationManager->createNotification();
473 473
 			$notification->setApp('files_sharing')
474 474
 				->setUser($share['user'])
475
-				->setObject('remote_share', (int)$share['id']);
475
+				->setObject('remote_share', (int) $share['id']);
476 476
 			$notificationManager->markProcessed($notification);
477 477
 
478 478
 			$event = \OC::$server->getActivityManager()->generateEvent();
@@ -480,7 +480,7 @@  discard block
 block discarded – undo
480 480
 				->setType('remote_share')
481 481
 				->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path])
482 482
 				->setAffectedUser($user)
483
-				->setObject('remote_share', (int)$share['id'], $path);
483
+				->setObject('remote_share', (int) $share['id'], $path);
484 484
 			\OC::$server->getActivityManager()->publish($event);
485 485
 		}
486 486
 
@@ -622,7 +622,7 @@  discard block
 block discarded – undo
622 622
 		$validPermission = ctype_digit($permissions);
623 623
 		$validToken = $this->verifyShare($share, $token);
624 624
 		if ($validPermission && $validToken) {
625
-			$this->updatePermissionsInDatabase($share, (int)$permissions);
625
+			$this->updatePermissionsInDatabase($share, (int) $permissions);
626 626
 		} else {
627 627
 			throw new OCSBadRequestException();
628 628
 		}
Please login to merge, or discard this patch.
cron.php 2 patches
Indentation   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -38,124 +38,124 @@
 block discarded – undo
38 38
 
39 39
 try {
40 40
 
41
-	require_once __DIR__ . '/lib/base.php';
42
-
43
-	if (\OCP\Util::needUpgrade()) {
44
-		\OC::$server->getLogger()->debug('Update required, skipping cron', ['app' => 'cron']);
45
-		exit;
46
-	}
47
-	if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
48
-		\OC::$server->getLogger()->debug('We are in maintenance mode, skipping cron', ['app' => 'cron']);
49
-		exit;
50
-	}
51
-
52
-	// load all apps to get all api routes properly setup
53
-	OC_App::loadApps();
54
-
55
-	\OC::$server->getSession()->close();
56
-
57
-	// initialize a dummy memory session
58
-	$session = new \OC\Session\Memory('');
59
-	$cryptoWrapper = \OC::$server->getSessionCryptoWrapper();
60
-	$session = $cryptoWrapper->wrapSession($session);
61
-	\OC::$server->setSession($session);
62
-
63
-	$logger = \OC::$server->getLogger();
64
-	$config = \OC::$server->getConfig();
65
-
66
-	// Don't do anything if Nextcloud has not been installed
67
-	if (!$config->getSystemValue('installed', false)) {
68
-		exit(0);
69
-	}
70
-
71
-	\OC::$server->getTempManager()->cleanOld();
72
-
73
-	// Exit if background jobs are disabled!
74
-	$appMode = $config->getAppValue('core', 'backgroundjobs_mode', 'ajax');
75
-	if ($appMode === 'none') {
76
-		if (OC::$CLI) {
77
-			echo 'Background Jobs are disabled!' . PHP_EOL;
78
-		} else {
79
-			OC_JSON::error(array('data' => array('message' => 'Background jobs disabled!')));
80
-		}
81
-		exit(1);
82
-	}
83
-
84
-	if (OC::$CLI) {
85
-		// set to run indefinitely if needed
86
-		if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
87
-			@set_time_limit(0);
88
-		}
89
-
90
-		// the cron job must be executed with the right user
91
-		if (!function_exists('posix_getuid')) {
92
-			echo "The posix extensions are required - see http://php.net/manual/en/book.posix.php" . PHP_EOL;
93
-			exit(1);
94
-		}
95
-		$user = posix_getpwuid(posix_getuid());
96
-		$configUser = posix_getpwuid(fileowner(OC::$configDir . 'config.php'));
97
-		if ($user['name'] !== $configUser['name']) {
98
-			echo "Console has to be executed with the same user as the web server is operated" . PHP_EOL;
99
-			echo "Current user: " . $user['name'] . PHP_EOL;
100
-			echo "Web server user: " . $configUser['name'] . PHP_EOL;
101
-			exit(1);
102
-		}
103
-
104
-		// We call Nextcloud from the CLI (aka cron)
105
-		if ($appMode !== 'cron') {
106
-			$config->setAppValue('core', 'backgroundjobs_mode', 'cron');
107
-		}
108
-
109
-		// Work
110
-		$jobList = \OC::$server->getJobList();
111
-
112
-		// We only ask for jobs for 14 minutes, because after 15 minutes the next
113
-		// system cron task should spawn.
114
-		$endTime = time() + 14 * 60;
115
-
116
-		$executedJobs = [];
117
-		while ($job = $jobList->getNext()) {
118
-			if (isset($executedJobs[$job->getId()])) {
119
-				$jobList->unlockJob($job);
120
-				break;
121
-			}
122
-
123
-			$job->execute($jobList, $logger);
124
-			// clean up after unclean jobs
125
-			\OC_Util::tearDownFS();
126
-
127
-			$jobList->setLastJob($job);
128
-			$executedJobs[$job->getId()] = true;
129
-			unset($job);
130
-
131
-			if (time() > $endTime) {
132
-				break;
133
-			}
134
-		}
135
-
136
-	} else {
137
-		// We call cron.php from some website
138
-		if ($appMode == 'cron') {
139
-			// Cron is cron :-P
140
-			OC_JSON::error(array('data' => array('message' => 'Backgroundjobs are using system cron!')));
141
-		} else {
142
-			// Work and success :-)
143
-			$jobList = \OC::$server->getJobList();
144
-			$job = $jobList->getNext();
145
-			if ($job != null) {
146
-				$job->execute($jobList, $logger);
147
-				$jobList->setLastJob($job);
148
-			}
149
-			OC_JSON::success();
150
-		}
151
-	}
152
-
153
-	// Log the successful cron execution
154
-	\OC::$server->getConfig()->setAppValue('core', 'lastcron', time());
155
-	exit();
41
+    require_once __DIR__ . '/lib/base.php';
42
+
43
+    if (\OCP\Util::needUpgrade()) {
44
+        \OC::$server->getLogger()->debug('Update required, skipping cron', ['app' => 'cron']);
45
+        exit;
46
+    }
47
+    if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) {
48
+        \OC::$server->getLogger()->debug('We are in maintenance mode, skipping cron', ['app' => 'cron']);
49
+        exit;
50
+    }
51
+
52
+    // load all apps to get all api routes properly setup
53
+    OC_App::loadApps();
54
+
55
+    \OC::$server->getSession()->close();
56
+
57
+    // initialize a dummy memory session
58
+    $session = new \OC\Session\Memory('');
59
+    $cryptoWrapper = \OC::$server->getSessionCryptoWrapper();
60
+    $session = $cryptoWrapper->wrapSession($session);
61
+    \OC::$server->setSession($session);
62
+
63
+    $logger = \OC::$server->getLogger();
64
+    $config = \OC::$server->getConfig();
65
+
66
+    // Don't do anything if Nextcloud has not been installed
67
+    if (!$config->getSystemValue('installed', false)) {
68
+        exit(0);
69
+    }
70
+
71
+    \OC::$server->getTempManager()->cleanOld();
72
+
73
+    // Exit if background jobs are disabled!
74
+    $appMode = $config->getAppValue('core', 'backgroundjobs_mode', 'ajax');
75
+    if ($appMode === 'none') {
76
+        if (OC::$CLI) {
77
+            echo 'Background Jobs are disabled!' . PHP_EOL;
78
+        } else {
79
+            OC_JSON::error(array('data' => array('message' => 'Background jobs disabled!')));
80
+        }
81
+        exit(1);
82
+    }
83
+
84
+    if (OC::$CLI) {
85
+        // set to run indefinitely if needed
86
+        if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
87
+            @set_time_limit(0);
88
+        }
89
+
90
+        // the cron job must be executed with the right user
91
+        if (!function_exists('posix_getuid')) {
92
+            echo "The posix extensions are required - see http://php.net/manual/en/book.posix.php" . PHP_EOL;
93
+            exit(1);
94
+        }
95
+        $user = posix_getpwuid(posix_getuid());
96
+        $configUser = posix_getpwuid(fileowner(OC::$configDir . 'config.php'));
97
+        if ($user['name'] !== $configUser['name']) {
98
+            echo "Console has to be executed with the same user as the web server is operated" . PHP_EOL;
99
+            echo "Current user: " . $user['name'] . PHP_EOL;
100
+            echo "Web server user: " . $configUser['name'] . PHP_EOL;
101
+            exit(1);
102
+        }
103
+
104
+        // We call Nextcloud from the CLI (aka cron)
105
+        if ($appMode !== 'cron') {
106
+            $config->setAppValue('core', 'backgroundjobs_mode', 'cron');
107
+        }
108
+
109
+        // Work
110
+        $jobList = \OC::$server->getJobList();
111
+
112
+        // We only ask for jobs for 14 minutes, because after 15 minutes the next
113
+        // system cron task should spawn.
114
+        $endTime = time() + 14 * 60;
115
+
116
+        $executedJobs = [];
117
+        while ($job = $jobList->getNext()) {
118
+            if (isset($executedJobs[$job->getId()])) {
119
+                $jobList->unlockJob($job);
120
+                break;
121
+            }
122
+
123
+            $job->execute($jobList, $logger);
124
+            // clean up after unclean jobs
125
+            \OC_Util::tearDownFS();
126
+
127
+            $jobList->setLastJob($job);
128
+            $executedJobs[$job->getId()] = true;
129
+            unset($job);
130
+
131
+            if (time() > $endTime) {
132
+                break;
133
+            }
134
+        }
135
+
136
+    } else {
137
+        // We call cron.php from some website
138
+        if ($appMode == 'cron') {
139
+            // Cron is cron :-P
140
+            OC_JSON::error(array('data' => array('message' => 'Backgroundjobs are using system cron!')));
141
+        } else {
142
+            // Work and success :-)
143
+            $jobList = \OC::$server->getJobList();
144
+            $job = $jobList->getNext();
145
+            if ($job != null) {
146
+                $job->execute($jobList, $logger);
147
+                $jobList->setLastJob($job);
148
+            }
149
+            OC_JSON::success();
150
+        }
151
+    }
152
+
153
+    // Log the successful cron execution
154
+    \OC::$server->getConfig()->setAppValue('core', 'lastcron', time());
155
+    exit();
156 156
 
157 157
 } catch (Exception $ex) {
158
-	\OC::$server->getLogger()->logException($ex, ['app' => 'cron']);
158
+    \OC::$server->getLogger()->logException($ex, ['app' => 'cron']);
159 159
 } catch (Error $ex) {
160
-	\OC::$server->getLogger()->logException($ex, ['app' => 'cron']);
160
+    \OC::$server->getLogger()->logException($ex, ['app' => 'cron']);
161 161
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -34,11 +34,11 @@  discard block
 block discarded – undo
34 34
  *
35 35
  */
36 36
 
37
-require_once __DIR__ . '/lib/versioncheck.php';
37
+require_once __DIR__.'/lib/versioncheck.php';
38 38
 
39 39
 try {
40 40
 
41
-	require_once __DIR__ . '/lib/base.php';
41
+	require_once __DIR__.'/lib/base.php';
42 42
 
43 43
 	if (\OCP\Util::needUpgrade()) {
44 44
 		\OC::$server->getLogger()->debug('Update required, skipping cron', ['app' => 'cron']);
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 	$appMode = $config->getAppValue('core', 'backgroundjobs_mode', 'ajax');
75 75
 	if ($appMode === 'none') {
76 76
 		if (OC::$CLI) {
77
-			echo 'Background Jobs are disabled!' . PHP_EOL;
77
+			echo 'Background Jobs are disabled!'.PHP_EOL;
78 78
 		} else {
79 79
 			OC_JSON::error(array('data' => array('message' => 'Background jobs disabled!')));
80 80
 		}
@@ -89,15 +89,15 @@  discard block
 block discarded – undo
89 89
 
90 90
 		// the cron job must be executed with the right user
91 91
 		if (!function_exists('posix_getuid')) {
92
-			echo "The posix extensions are required - see http://php.net/manual/en/book.posix.php" . PHP_EOL;
92
+			echo "The posix extensions are required - see http://php.net/manual/en/book.posix.php".PHP_EOL;
93 93
 			exit(1);
94 94
 		}
95 95
 		$user = posix_getpwuid(posix_getuid());
96
-		$configUser = posix_getpwuid(fileowner(OC::$configDir . 'config.php'));
96
+		$configUser = posix_getpwuid(fileowner(OC::$configDir.'config.php'));
97 97
 		if ($user['name'] !== $configUser['name']) {
98
-			echo "Console has to be executed with the same user as the web server is operated" . PHP_EOL;
99
-			echo "Current user: " . $user['name'] . PHP_EOL;
100
-			echo "Web server user: " . $configUser['name'] . PHP_EOL;
98
+			echo "Console has to be executed with the same user as the web server is operated".PHP_EOL;
99
+			echo "Current user: ".$user['name'].PHP_EOL;
100
+			echo "Web server user: ".$configUser['name'].PHP_EOL;
101 101
 			exit(1);
102 102
 		}
103 103
 
Please login to merge, or discard this patch.