| @@ -36,454 +36,454 @@ | ||
| 36 | 36 | use OCP\Files\ObjectStore\IObjectStore; | 
| 37 | 37 | |
| 38 | 38 |  class ObjectStoreStorage extends \OC\Files\Storage\Common { | 
| 39 | - /** | |
| 40 | - * @var \OCP\Files\ObjectStore\IObjectStore $objectStore | |
| 41 | - */ | |
| 42 | - protected $objectStore; | |
| 43 | - /** | |
| 44 | - * @var string $id | |
| 45 | - */ | |
| 46 | - protected $id; | |
| 47 | - /** | |
| 48 | - * @var \OC\User\User $user | |
| 49 | - */ | |
| 50 | - protected $user; | |
| 51 | - | |
| 52 | - private $objectPrefix = 'urn:oid:'; | |
| 53 | - | |
| 54 | - private $logger; | |
| 55 | - | |
| 56 | -	public function __construct($params) { | |
| 57 | -		if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) { | |
| 58 | - $this->objectStore = $params['objectstore']; | |
| 59 | -		} else { | |
| 60 | -			throw new \Exception('missing IObjectStore instance'); | |
| 61 | - } | |
| 62 | -		if (isset($params['storageid'])) { | |
| 63 | - $this->id = 'object::store:' . $params['storageid']; | |
| 64 | -		} else { | |
| 65 | - $this->id = 'object::store:' . $this->objectStore->getStorageId(); | |
| 66 | - } | |
| 67 | -		if (isset($params['objectPrefix'])) { | |
| 68 | - $this->objectPrefix = $params['objectPrefix']; | |
| 69 | - } | |
| 70 | - //initialize cache with root directory in cache | |
| 71 | -		if (!$this->is_dir('/')) { | |
| 72 | -			$this->mkdir('/'); | |
| 73 | - } | |
| 74 | - | |
| 75 | - $this->logger = \OC::$server->getLogger(); | |
| 76 | - } | |
| 77 | - | |
| 78 | -	public function mkdir($path) { | |
| 79 | - $path = $this->normalizePath($path); | |
| 80 | - | |
| 81 | -		if ($this->file_exists($path)) { | |
| 82 | - return false; | |
| 83 | - } | |
| 84 | - | |
| 85 | - $mTime = time(); | |
| 86 | - $data = [ | |
| 87 | - 'mimetype' => 'httpd/unix-directory', | |
| 88 | - 'size' => 0, | |
| 89 | - 'mtime' => $mTime, | |
| 90 | - 'storage_mtime' => $mTime, | |
| 91 | - 'permissions' => \OCP\Constants::PERMISSION_ALL, | |
| 92 | - ]; | |
| 93 | -		if ($path === '') { | |
| 94 | - //create root on the fly | |
| 95 | -			$data['etag'] = $this->getETag(''); | |
| 96 | -			$this->getCache()->put('', $data); | |
| 97 | - return true; | |
| 98 | -		} else { | |
| 99 | - // if parent does not exist, create it | |
| 100 | - $parent = $this->normalizePath(dirname($path)); | |
| 101 | - $parentType = $this->filetype($parent); | |
| 102 | -			if ($parentType === false) { | |
| 103 | -				if (!$this->mkdir($parent)) { | |
| 104 | - // something went wrong | |
| 105 | - return false; | |
| 106 | - } | |
| 107 | -			} else if ($parentType === 'file') { | |
| 108 | - // parent is a file | |
| 109 | - return false; | |
| 110 | - } | |
| 111 | - // finally create the new dir | |
| 112 | - $mTime = time(); // update mtime | |
| 113 | - $data['mtime'] = $mTime; | |
| 114 | - $data['storage_mtime'] = $mTime; | |
| 115 | - $data['etag'] = $this->getETag($path); | |
| 116 | - $this->getCache()->put($path, $data); | |
| 117 | - return true; | |
| 118 | - } | |
| 119 | - } | |
| 120 | - | |
| 121 | - /** | |
| 122 | - * @param string $path | |
| 123 | - * @return string | |
| 124 | - */ | |
| 125 | -	private function normalizePath($path) { | |
| 126 | - $path = trim($path, '/'); | |
| 127 | - //FIXME why do we sometimes get a path like 'files//username'? | |
| 128 | -		$path = str_replace('//', '/', $path); | |
| 129 | - | |
| 130 | -		// dirname('/folder') returns '.' but internally (in the cache) we store the root as '' | |
| 131 | -		if (!$path || $path === '.') { | |
| 132 | - $path = ''; | |
| 133 | - } | |
| 134 | - | |
| 135 | - return $path; | |
| 136 | - } | |
| 137 | - | |
| 138 | - /** | |
| 139 | - * Object Stores use a NoopScanner because metadata is directly stored in | |
| 140 | - * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere. | |
| 141 | - * | |
| 142 | - * @param string $path | |
| 143 | - * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner | |
| 144 | - * @return \OC\Files\ObjectStore\NoopScanner | |
| 145 | - */ | |
| 146 | -	public function getScanner($path = '', $storage = null) { | |
| 147 | -		if (!$storage) { | |
| 148 | - $storage = $this; | |
| 149 | - } | |
| 150 | -		if (!isset($this->scanner)) { | |
| 151 | - $this->scanner = new NoopScanner($storage); | |
| 152 | - } | |
| 153 | - return $this->scanner; | |
| 154 | - } | |
| 155 | - | |
| 156 | -	public function getId() { | |
| 157 | - return $this->id; | |
| 158 | - } | |
| 159 | - | |
| 160 | -	public function rmdir($path) { | |
| 161 | - $path = $this->normalizePath($path); | |
| 162 | - | |
| 163 | -		if (!$this->is_dir($path)) { | |
| 164 | - return false; | |
| 165 | - } | |
| 166 | - | |
| 167 | -		if (!$this->rmObjects($path)) { | |
| 168 | - return false; | |
| 169 | - } | |
| 170 | - | |
| 171 | - $this->getCache()->remove($path); | |
| 172 | - | |
| 173 | - return true; | |
| 174 | - } | |
| 175 | - | |
| 176 | -	private function rmObjects($path) { | |
| 177 | - $children = $this->getCache()->getFolderContents($path); | |
| 178 | -		foreach ($children as $child) { | |
| 179 | -			if ($child['mimetype'] === 'httpd/unix-directory') { | |
| 180 | -				if (!$this->rmObjects($child['path'])) { | |
| 181 | - return false; | |
| 182 | - } | |
| 183 | -			} else { | |
| 184 | -				if(!$this->unlink($child['path'])) { | |
| 185 | - return false; | |
| 186 | - } | |
| 187 | - } | |
| 188 | - } | |
| 189 | - | |
| 190 | - return true; | |
| 191 | - } | |
| 192 | - | |
| 193 | -	public function unlink($path) { | |
| 194 | - $path = $this->normalizePath($path); | |
| 195 | - $stat = $this->stat($path); | |
| 196 | - | |
| 197 | -		if ($stat && isset($stat['fileid'])) { | |
| 198 | -			if ($stat['mimetype'] === 'httpd/unix-directory') { | |
| 199 | - return $this->rmdir($path); | |
| 200 | - } | |
| 201 | -			try { | |
| 202 | - $this->objectStore->deleteObject($this->getURN($stat['fileid'])); | |
| 203 | -			} catch (\Exception $ex) { | |
| 204 | -				if ($ex->getCode() !== 404) { | |
| 205 | - $this->logger->logException($ex, [ | |
| 206 | - 'app' => 'objectstore', | |
| 207 | - 'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path, | |
| 208 | - ]); | |
| 209 | - return false; | |
| 210 | - } | |
| 211 | - //removing from cache is ok as it does not exist in the objectstore anyway | |
| 212 | - } | |
| 213 | - $this->getCache()->remove($path); | |
| 214 | - return true; | |
| 215 | - } | |
| 216 | - return false; | |
| 217 | - } | |
| 218 | - | |
| 219 | -	public function stat($path) { | |
| 220 | - $path = $this->normalizePath($path); | |
| 221 | - $cacheEntry = $this->getCache()->get($path); | |
| 222 | -		if ($cacheEntry instanceof CacheEntry) { | |
| 223 | - return $cacheEntry->getData(); | |
| 224 | -		} else { | |
| 225 | - return false; | |
| 226 | - } | |
| 227 | - } | |
| 228 | - | |
| 229 | - /** | |
| 230 | - * Override this method if you need a different unique resource identifier for your object storage implementation. | |
| 231 | - * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users. | |
| 232 | - * You may need a mapping table to store your URN if it cannot be generated from the fileid. | |
| 233 | - * | |
| 234 | - * @param int $fileId the fileid | |
| 235 | - * @return null|string the unified resource name used to identify the object | |
| 236 | - */ | |
| 237 | -	public function getURN($fileId) { | |
| 238 | -		if (is_numeric($fileId)) { | |
| 239 | - return $this->objectPrefix . $fileId; | |
| 240 | - } | |
| 241 | - return null; | |
| 242 | - } | |
| 243 | - | |
| 244 | -	public function opendir($path) { | |
| 245 | - $path = $this->normalizePath($path); | |
| 246 | - | |
| 247 | -		try { | |
| 248 | - $files = array(); | |
| 249 | - $folderContents = $this->getCache()->getFolderContents($path); | |
| 250 | -			foreach ($folderContents as $file) { | |
| 251 | - $files[] = $file['name']; | |
| 252 | - } | |
| 253 | - | |
| 254 | - return IteratorDirectory::wrap($files); | |
| 255 | -		} catch (\Exception $e) { | |
| 256 | - $this->logger->logException($e); | |
| 257 | - return false; | |
| 258 | - } | |
| 259 | - } | |
| 260 | - | |
| 261 | -	public function filetype($path) { | |
| 262 | - $path = $this->normalizePath($path); | |
| 263 | - $stat = $this->stat($path); | |
| 264 | -		if ($stat) { | |
| 265 | -			if ($stat['mimetype'] === 'httpd/unix-directory') { | |
| 266 | - return 'dir'; | |
| 267 | - } | |
| 268 | - return 'file'; | |
| 269 | -		} else { | |
| 270 | - return false; | |
| 271 | - } | |
| 272 | - } | |
| 273 | - | |
| 274 | -	public function fopen($path, $mode) { | |
| 275 | - $path = $this->normalizePath($path); | |
| 276 | - | |
| 277 | -		if (strrpos($path, '.') !== false) { | |
| 278 | - $ext = substr($path, strrpos($path, '.')); | |
| 279 | -		} else { | |
| 280 | - $ext = ''; | |
| 281 | - } | |
| 282 | - | |
| 283 | -		switch ($mode) { | |
| 284 | - case 'r': | |
| 285 | - case 'rb': | |
| 286 | - $stat = $this->stat($path); | |
| 287 | -				if (is_array($stat)) { | |
| 288 | -					try { | |
| 289 | - return $this->objectStore->readObject($this->getURN($stat['fileid'])); | |
| 290 | -					} catch (NotFoundException $e) { | |
| 291 | - $this->logger->logException($e, [ | |
| 292 | - 'app' => 'objectstore', | |
| 293 | - 'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path, | |
| 294 | - ]); | |
| 295 | - throw $e; | |
| 296 | -					} catch (\Exception $ex) { | |
| 297 | - $this->logger->logException($ex, [ | |
| 298 | - 'app' => 'objectstore', | |
| 299 | - 'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path, | |
| 300 | - ]); | |
| 301 | - return false; | |
| 302 | - } | |
| 303 | -				} else { | |
| 304 | - return false; | |
| 305 | - } | |
| 306 | - case 'w': | |
| 307 | - case 'wb': | |
| 308 | - case 'w+': | |
| 309 | - case 'wb+': | |
| 310 | - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); | |
| 311 | - $handle = fopen($tmpFile, $mode); | |
| 312 | -				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { | |
| 313 | - $this->writeBack($tmpFile, $path); | |
| 314 | - }); | |
| 315 | - case 'a': | |
| 316 | - case 'ab': | |
| 317 | - case 'r+': | |
| 318 | - case 'a+': | |
| 319 | - case 'x': | |
| 320 | - case 'x+': | |
| 321 | - case 'c': | |
| 322 | - case 'c+': | |
| 323 | - $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); | |
| 324 | -				if ($this->file_exists($path)) { | |
| 325 | - $source = $this->fopen($path, 'r'); | |
| 326 | - file_put_contents($tmpFile, $source); | |
| 327 | - } | |
| 328 | - $handle = fopen($tmpFile, $mode); | |
| 329 | -				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { | |
| 330 | - $this->writeBack($tmpFile, $path); | |
| 331 | - }); | |
| 332 | - } | |
| 333 | - return false; | |
| 334 | - } | |
| 335 | - | |
| 336 | -	public function file_exists($path) { | |
| 337 | - $path = $this->normalizePath($path); | |
| 338 | - return (bool)$this->stat($path); | |
| 339 | - } | |
| 340 | - | |
| 341 | -	public function rename($source, $target) { | |
| 342 | - $source = $this->normalizePath($source); | |
| 343 | - $target = $this->normalizePath($target); | |
| 344 | - $this->remove($target); | |
| 345 | - $this->getCache()->move($source, $target); | |
| 346 | - $this->touch(dirname($target)); | |
| 347 | - return true; | |
| 348 | - } | |
| 349 | - | |
| 350 | -	public function getMimeType($path) { | |
| 351 | - $path = $this->normalizePath($path); | |
| 352 | - return parent::getMimeType($path); | |
| 353 | - } | |
| 354 | - | |
| 355 | -	public function touch($path, $mtime = null) { | |
| 356 | -		if (is_null($mtime)) { | |
| 357 | - $mtime = time(); | |
| 358 | - } | |
| 359 | - | |
| 360 | - $path = $this->normalizePath($path); | |
| 361 | - $dirName = dirname($path); | |
| 362 | - $parentExists = $this->is_dir($dirName); | |
| 363 | -		if (!$parentExists) { | |
| 364 | - return false; | |
| 365 | - } | |
| 366 | - | |
| 367 | - $stat = $this->stat($path); | |
| 368 | -		if (is_array($stat)) { | |
| 369 | - // update existing mtime in db | |
| 370 | - $stat['mtime'] = $mtime; | |
| 371 | - $this->getCache()->update($stat['fileid'], $stat); | |
| 372 | -		} else { | |
| 373 | -			try { | |
| 374 | - //create a empty file, need to have at least on char to make it | |
| 375 | - // work with all object storage implementations | |
| 376 | - $this->file_put_contents($path, ' '); | |
| 377 | - $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); | |
| 378 | - $stat = array( | |
| 379 | - 'etag' => $this->getETag($path), | |
| 380 | - 'mimetype' => $mimeType, | |
| 381 | - 'size' => 0, | |
| 382 | - 'mtime' => $mtime, | |
| 383 | - 'storage_mtime' => $mtime, | |
| 384 | - 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, | |
| 385 | - ); | |
| 386 | - $this->getCache()->put($path, $stat); | |
| 387 | -			} catch (\Exception $ex) { | |
| 388 | - $this->logger->logException($ex, [ | |
| 389 | - 'app' => 'objectstore', | |
| 390 | - 'message' => 'Could not create object for ' . $path, | |
| 391 | - ]); | |
| 392 | - throw $ex; | |
| 393 | - } | |
| 394 | - } | |
| 395 | - return true; | |
| 396 | - } | |
| 397 | - | |
| 398 | -	public function writeBack($tmpFile, $path) { | |
| 399 | - $size = filesize($tmpFile); | |
| 400 | - $this->writeStream($path, fopen($tmpFile, 'r'), $size); | |
| 401 | - } | |
| 402 | - | |
| 403 | - /** | |
| 404 | - * external changes are not supported, exclusive access to the object storage is assumed | |
| 405 | - * | |
| 406 | - * @param string $path | |
| 407 | - * @param int $time | |
| 408 | - * @return false | |
| 409 | - */ | |
| 410 | -	public function hasUpdated($path, $time) { | |
| 411 | - return false; | |
| 412 | - } | |
| 413 | - | |
| 414 | -	public function needsPartFile() { | |
| 415 | - return false; | |
| 416 | - } | |
| 417 | - | |
| 418 | -	public function file_put_contents($path, $data) { | |
| 419 | - $handle = $this->fopen($path, 'w+'); | |
| 420 | - fwrite($handle, $data); | |
| 421 | - fclose($handle); | |
| 422 | - return true; | |
| 423 | - } | |
| 424 | - | |
| 425 | -	public function writeStream(string $path, $stream, int $size = null): int { | |
| 426 | - $stat = $this->stat($path); | |
| 427 | -		if (empty($stat)) { | |
| 428 | - // create new file | |
| 429 | - $stat = [ | |
| 430 | - 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, | |
| 431 | - ]; | |
| 432 | - } | |
| 433 | - // update stat with new data | |
| 434 | - $mTime = time(); | |
| 435 | - $stat['size'] = (int)$size; | |
| 436 | - $stat['mtime'] = $mTime; | |
| 437 | - $stat['storage_mtime'] = $mTime; | |
| 438 | - | |
| 439 | - $mimetypeDetector = \OC::$server->getMimeTypeDetector(); | |
| 440 | - $mimetype = $mimetypeDetector->detectPath($path); | |
| 441 | - | |
| 442 | - $stat['mimetype'] = $mimetype; | |
| 443 | - $stat['etag'] = $this->getETag($path); | |
| 444 | - | |
| 445 | - $exists = $this->getCache()->inCache($path); | |
| 446 | - $uploadPath = $exists ? $path : $path . '.part'; | |
| 447 | - $fileId = $this->getCache()->put($uploadPath, $stat); | |
| 448 | - $urn = $this->getURN($fileId); | |
| 449 | -		try { | |
| 450 | - //upload to object storage | |
| 451 | -			if ($size === null) { | |
| 452 | -				$countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) { | |
| 453 | - $this->getCache()->update($fileId, [ | |
| 454 | - 'size' => $writtenSize | |
| 455 | - ]); | |
| 456 | - $size = $writtenSize; | |
| 457 | - }); | |
| 458 | - $this->objectStore->writeObject($urn, $countStream); | |
| 459 | -				if (is_resource($countStream)) { | |
| 460 | - fclose($countStream); | |
| 461 | - } | |
| 462 | -			} else { | |
| 463 | - $this->objectStore->writeObject($urn, $stream); | |
| 464 | - } | |
| 465 | -		} catch (\Exception $ex) { | |
| 466 | - $this->getCache()->remove($uploadPath); | |
| 467 | - $this->logger->logException($ex, [ | |
| 468 | - 'app' => 'objectstore', | |
| 469 | - 'message' => 'Could not create object ' . $urn . ' for ' . $path, | |
| 470 | - ]); | |
| 471 | - throw $ex; // make this bubble up | |
| 472 | - } | |
| 473 | - | |
| 474 | -		if (!$exists) { | |
| 475 | -			if ($this->objectStore->objectExists($urn)) { | |
| 476 | - $this->getCache()->move($uploadPath, $path); | |
| 477 | -			} else { | |
| 478 | - $this->getCache()->remove($uploadPath); | |
| 479 | -				throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404); | |
| 480 | - } | |
| 481 | - } | |
| 482 | - | |
| 483 | - return $size; | |
| 484 | - } | |
| 485 | - | |
| 486 | -	public function getObjectStore(): IObjectStore { | |
| 487 | - return $this->objectStore; | |
| 488 | - } | |
| 39 | + /** | |
| 40 | + * @var \OCP\Files\ObjectStore\IObjectStore $objectStore | |
| 41 | + */ | |
| 42 | + protected $objectStore; | |
| 43 | + /** | |
| 44 | + * @var string $id | |
| 45 | + */ | |
| 46 | + protected $id; | |
| 47 | + /** | |
| 48 | + * @var \OC\User\User $user | |
| 49 | + */ | |
| 50 | + protected $user; | |
| 51 | + | |
| 52 | + private $objectPrefix = 'urn:oid:'; | |
| 53 | + | |
| 54 | + private $logger; | |
| 55 | + | |
| 56 | +    public function __construct($params) { | |
| 57 | +        if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) { | |
| 58 | + $this->objectStore = $params['objectstore']; | |
| 59 | +        } else { | |
| 60 | +            throw new \Exception('missing IObjectStore instance'); | |
| 61 | + } | |
| 62 | +        if (isset($params['storageid'])) { | |
| 63 | + $this->id = 'object::store:' . $params['storageid']; | |
| 64 | +        } else { | |
| 65 | + $this->id = 'object::store:' . $this->objectStore->getStorageId(); | |
| 66 | + } | |
| 67 | +        if (isset($params['objectPrefix'])) { | |
| 68 | + $this->objectPrefix = $params['objectPrefix']; | |
| 69 | + } | |
| 70 | + //initialize cache with root directory in cache | |
| 71 | +        if (!$this->is_dir('/')) { | |
| 72 | +            $this->mkdir('/'); | |
| 73 | + } | |
| 74 | + | |
| 75 | + $this->logger = \OC::$server->getLogger(); | |
| 76 | + } | |
| 77 | + | |
| 78 | +    public function mkdir($path) { | |
| 79 | + $path = $this->normalizePath($path); | |
| 80 | + | |
| 81 | +        if ($this->file_exists($path)) { | |
| 82 | + return false; | |
| 83 | + } | |
| 84 | + | |
| 85 | + $mTime = time(); | |
| 86 | + $data = [ | |
| 87 | + 'mimetype' => 'httpd/unix-directory', | |
| 88 | + 'size' => 0, | |
| 89 | + 'mtime' => $mTime, | |
| 90 | + 'storage_mtime' => $mTime, | |
| 91 | + 'permissions' => \OCP\Constants::PERMISSION_ALL, | |
| 92 | + ]; | |
| 93 | +        if ($path === '') { | |
| 94 | + //create root on the fly | |
| 95 | +            $data['etag'] = $this->getETag(''); | |
| 96 | +            $this->getCache()->put('', $data); | |
| 97 | + return true; | |
| 98 | +        } else { | |
| 99 | + // if parent does not exist, create it | |
| 100 | + $parent = $this->normalizePath(dirname($path)); | |
| 101 | + $parentType = $this->filetype($parent); | |
| 102 | +            if ($parentType === false) { | |
| 103 | +                if (!$this->mkdir($parent)) { | |
| 104 | + // something went wrong | |
| 105 | + return false; | |
| 106 | + } | |
| 107 | +            } else if ($parentType === 'file') { | |
| 108 | + // parent is a file | |
| 109 | + return false; | |
| 110 | + } | |
| 111 | + // finally create the new dir | |
| 112 | + $mTime = time(); // update mtime | |
| 113 | + $data['mtime'] = $mTime; | |
| 114 | + $data['storage_mtime'] = $mTime; | |
| 115 | + $data['etag'] = $this->getETag($path); | |
| 116 | + $this->getCache()->put($path, $data); | |
| 117 | + return true; | |
| 118 | + } | |
| 119 | + } | |
| 120 | + | |
| 121 | + /** | |
| 122 | + * @param string $path | |
| 123 | + * @return string | |
| 124 | + */ | |
| 125 | +    private function normalizePath($path) { | |
| 126 | + $path = trim($path, '/'); | |
| 127 | + //FIXME why do we sometimes get a path like 'files//username'? | |
| 128 | +        $path = str_replace('//', '/', $path); | |
| 129 | + | |
| 130 | +        // dirname('/folder') returns '.' but internally (in the cache) we store the root as '' | |
| 131 | +        if (!$path || $path === '.') { | |
| 132 | + $path = ''; | |
| 133 | + } | |
| 134 | + | |
| 135 | + return $path; | |
| 136 | + } | |
| 137 | + | |
| 138 | + /** | |
| 139 | + * Object Stores use a NoopScanner because metadata is directly stored in | |
| 140 | + * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere. | |
| 141 | + * | |
| 142 | + * @param string $path | |
| 143 | + * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner | |
| 144 | + * @return \OC\Files\ObjectStore\NoopScanner | |
| 145 | + */ | |
| 146 | +    public function getScanner($path = '', $storage = null) { | |
| 147 | +        if (!$storage) { | |
| 148 | + $storage = $this; | |
| 149 | + } | |
| 150 | +        if (!isset($this->scanner)) { | |
| 151 | + $this->scanner = new NoopScanner($storage); | |
| 152 | + } | |
| 153 | + return $this->scanner; | |
| 154 | + } | |
| 155 | + | |
| 156 | +    public function getId() { | |
| 157 | + return $this->id; | |
| 158 | + } | |
| 159 | + | |
| 160 | +    public function rmdir($path) { | |
| 161 | + $path = $this->normalizePath($path); | |
| 162 | + | |
| 163 | +        if (!$this->is_dir($path)) { | |
| 164 | + return false; | |
| 165 | + } | |
| 166 | + | |
| 167 | +        if (!$this->rmObjects($path)) { | |
| 168 | + return false; | |
| 169 | + } | |
| 170 | + | |
| 171 | + $this->getCache()->remove($path); | |
| 172 | + | |
| 173 | + return true; | |
| 174 | + } | |
| 175 | + | |
| 176 | +    private function rmObjects($path) { | |
| 177 | + $children = $this->getCache()->getFolderContents($path); | |
| 178 | +        foreach ($children as $child) { | |
| 179 | +            if ($child['mimetype'] === 'httpd/unix-directory') { | |
| 180 | +                if (!$this->rmObjects($child['path'])) { | |
| 181 | + return false; | |
| 182 | + } | |
| 183 | +            } else { | |
| 184 | +                if(!$this->unlink($child['path'])) { | |
| 185 | + return false; | |
| 186 | + } | |
| 187 | + } | |
| 188 | + } | |
| 189 | + | |
| 190 | + return true; | |
| 191 | + } | |
| 192 | + | |
| 193 | +    public function unlink($path) { | |
| 194 | + $path = $this->normalizePath($path); | |
| 195 | + $stat = $this->stat($path); | |
| 196 | + | |
| 197 | +        if ($stat && isset($stat['fileid'])) { | |
| 198 | +            if ($stat['mimetype'] === 'httpd/unix-directory') { | |
| 199 | + return $this->rmdir($path); | |
| 200 | + } | |
| 201 | +            try { | |
| 202 | + $this->objectStore->deleteObject($this->getURN($stat['fileid'])); | |
| 203 | +            } catch (\Exception $ex) { | |
| 204 | +                if ($ex->getCode() !== 404) { | |
| 205 | + $this->logger->logException($ex, [ | |
| 206 | + 'app' => 'objectstore', | |
| 207 | + 'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path, | |
| 208 | + ]); | |
| 209 | + return false; | |
| 210 | + } | |
| 211 | + //removing from cache is ok as it does not exist in the objectstore anyway | |
| 212 | + } | |
| 213 | + $this->getCache()->remove($path); | |
| 214 | + return true; | |
| 215 | + } | |
| 216 | + return false; | |
| 217 | + } | |
| 218 | + | |
| 219 | +    public function stat($path) { | |
| 220 | + $path = $this->normalizePath($path); | |
| 221 | + $cacheEntry = $this->getCache()->get($path); | |
| 222 | +        if ($cacheEntry instanceof CacheEntry) { | |
| 223 | + return $cacheEntry->getData(); | |
| 224 | +        } else { | |
| 225 | + return false; | |
| 226 | + } | |
| 227 | + } | |
| 228 | + | |
| 229 | + /** | |
| 230 | + * Override this method if you need a different unique resource identifier for your object storage implementation. | |
| 231 | + * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users. | |
| 232 | + * You may need a mapping table to store your URN if it cannot be generated from the fileid. | |
| 233 | + * | |
| 234 | + * @param int $fileId the fileid | |
| 235 | + * @return null|string the unified resource name used to identify the object | |
| 236 | + */ | |
| 237 | +    public function getURN($fileId) { | |
| 238 | +        if (is_numeric($fileId)) { | |
| 239 | + return $this->objectPrefix . $fileId; | |
| 240 | + } | |
| 241 | + return null; | |
| 242 | + } | |
| 243 | + | |
| 244 | +    public function opendir($path) { | |
| 245 | + $path = $this->normalizePath($path); | |
| 246 | + | |
| 247 | +        try { | |
| 248 | + $files = array(); | |
| 249 | + $folderContents = $this->getCache()->getFolderContents($path); | |
| 250 | +            foreach ($folderContents as $file) { | |
| 251 | + $files[] = $file['name']; | |
| 252 | + } | |
| 253 | + | |
| 254 | + return IteratorDirectory::wrap($files); | |
| 255 | +        } catch (\Exception $e) { | |
| 256 | + $this->logger->logException($e); | |
| 257 | + return false; | |
| 258 | + } | |
| 259 | + } | |
| 260 | + | |
| 261 | +    public function filetype($path) { | |
| 262 | + $path = $this->normalizePath($path); | |
| 263 | + $stat = $this->stat($path); | |
| 264 | +        if ($stat) { | |
| 265 | +            if ($stat['mimetype'] === 'httpd/unix-directory') { | |
| 266 | + return 'dir'; | |
| 267 | + } | |
| 268 | + return 'file'; | |
| 269 | +        } else { | |
| 270 | + return false; | |
| 271 | + } | |
| 272 | + } | |
| 273 | + | |
| 274 | +    public function fopen($path, $mode) { | |
| 275 | + $path = $this->normalizePath($path); | |
| 276 | + | |
| 277 | +        if (strrpos($path, '.') !== false) { | |
| 278 | + $ext = substr($path, strrpos($path, '.')); | |
| 279 | +        } else { | |
| 280 | + $ext = ''; | |
| 281 | + } | |
| 282 | + | |
| 283 | +        switch ($mode) { | |
| 284 | + case 'r': | |
| 285 | + case 'rb': | |
| 286 | + $stat = $this->stat($path); | |
| 287 | +                if (is_array($stat)) { | |
| 288 | +                    try { | |
| 289 | + return $this->objectStore->readObject($this->getURN($stat['fileid'])); | |
| 290 | +                    } catch (NotFoundException $e) { | |
| 291 | + $this->logger->logException($e, [ | |
| 292 | + 'app' => 'objectstore', | |
| 293 | + 'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path, | |
| 294 | + ]); | |
| 295 | + throw $e; | |
| 296 | +                    } catch (\Exception $ex) { | |
| 297 | + $this->logger->logException($ex, [ | |
| 298 | + 'app' => 'objectstore', | |
| 299 | + 'message' => 'Could not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path, | |
| 300 | + ]); | |
| 301 | + return false; | |
| 302 | + } | |
| 303 | +                } else { | |
| 304 | + return false; | |
| 305 | + } | |
| 306 | + case 'w': | |
| 307 | + case 'wb': | |
| 308 | + case 'w+': | |
| 309 | + case 'wb+': | |
| 310 | + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); | |
| 311 | + $handle = fopen($tmpFile, $mode); | |
| 312 | +                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { | |
| 313 | + $this->writeBack($tmpFile, $path); | |
| 314 | + }); | |
| 315 | + case 'a': | |
| 316 | + case 'ab': | |
| 317 | + case 'r+': | |
| 318 | + case 'a+': | |
| 319 | + case 'x': | |
| 320 | + case 'x+': | |
| 321 | + case 'c': | |
| 322 | + case 'c+': | |
| 323 | + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); | |
| 324 | +                if ($this->file_exists($path)) { | |
| 325 | + $source = $this->fopen($path, 'r'); | |
| 326 | + file_put_contents($tmpFile, $source); | |
| 327 | + } | |
| 328 | + $handle = fopen($tmpFile, $mode); | |
| 329 | +                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) { | |
| 330 | + $this->writeBack($tmpFile, $path); | |
| 331 | + }); | |
| 332 | + } | |
| 333 | + return false; | |
| 334 | + } | |
| 335 | + | |
| 336 | +    public function file_exists($path) { | |
| 337 | + $path = $this->normalizePath($path); | |
| 338 | + return (bool)$this->stat($path); | |
| 339 | + } | |
| 340 | + | |
| 341 | +    public function rename($source, $target) { | |
| 342 | + $source = $this->normalizePath($source); | |
| 343 | + $target = $this->normalizePath($target); | |
| 344 | + $this->remove($target); | |
| 345 | + $this->getCache()->move($source, $target); | |
| 346 | + $this->touch(dirname($target)); | |
| 347 | + return true; | |
| 348 | + } | |
| 349 | + | |
| 350 | +    public function getMimeType($path) { | |
| 351 | + $path = $this->normalizePath($path); | |
| 352 | + return parent::getMimeType($path); | |
| 353 | + } | |
| 354 | + | |
| 355 | +    public function touch($path, $mtime = null) { | |
| 356 | +        if (is_null($mtime)) { | |
| 357 | + $mtime = time(); | |
| 358 | + } | |
| 359 | + | |
| 360 | + $path = $this->normalizePath($path); | |
| 361 | + $dirName = dirname($path); | |
| 362 | + $parentExists = $this->is_dir($dirName); | |
| 363 | +        if (!$parentExists) { | |
| 364 | + return false; | |
| 365 | + } | |
| 366 | + | |
| 367 | + $stat = $this->stat($path); | |
| 368 | +        if (is_array($stat)) { | |
| 369 | + // update existing mtime in db | |
| 370 | + $stat['mtime'] = $mtime; | |
| 371 | + $this->getCache()->update($stat['fileid'], $stat); | |
| 372 | +        } else { | |
| 373 | +            try { | |
| 374 | + //create a empty file, need to have at least on char to make it | |
| 375 | + // work with all object storage implementations | |
| 376 | + $this->file_put_contents($path, ' '); | |
| 377 | + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); | |
| 378 | + $stat = array( | |
| 379 | + 'etag' => $this->getETag($path), | |
| 380 | + 'mimetype' => $mimeType, | |
| 381 | + 'size' => 0, | |
| 382 | + 'mtime' => $mtime, | |
| 383 | + 'storage_mtime' => $mtime, | |
| 384 | + 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, | |
| 385 | + ); | |
| 386 | + $this->getCache()->put($path, $stat); | |
| 387 | +            } catch (\Exception $ex) { | |
| 388 | + $this->logger->logException($ex, [ | |
| 389 | + 'app' => 'objectstore', | |
| 390 | + 'message' => 'Could not create object for ' . $path, | |
| 391 | + ]); | |
| 392 | + throw $ex; | |
| 393 | + } | |
| 394 | + } | |
| 395 | + return true; | |
| 396 | + } | |
| 397 | + | |
| 398 | +    public function writeBack($tmpFile, $path) { | |
| 399 | + $size = filesize($tmpFile); | |
| 400 | + $this->writeStream($path, fopen($tmpFile, 'r'), $size); | |
| 401 | + } | |
| 402 | + | |
| 403 | + /** | |
| 404 | + * external changes are not supported, exclusive access to the object storage is assumed | |
| 405 | + * | |
| 406 | + * @param string $path | |
| 407 | + * @param int $time | |
| 408 | + * @return false | |
| 409 | + */ | |
| 410 | +    public function hasUpdated($path, $time) { | |
| 411 | + return false; | |
| 412 | + } | |
| 413 | + | |
| 414 | +    public function needsPartFile() { | |
| 415 | + return false; | |
| 416 | + } | |
| 417 | + | |
| 418 | +    public function file_put_contents($path, $data) { | |
| 419 | + $handle = $this->fopen($path, 'w+'); | |
| 420 | + fwrite($handle, $data); | |
| 421 | + fclose($handle); | |
| 422 | + return true; | |
| 423 | + } | |
| 424 | + | |
| 425 | +    public function writeStream(string $path, $stream, int $size = null): int { | |
| 426 | + $stat = $this->stat($path); | |
| 427 | +        if (empty($stat)) { | |
| 428 | + // create new file | |
| 429 | + $stat = [ | |
| 430 | + 'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE, | |
| 431 | + ]; | |
| 432 | + } | |
| 433 | + // update stat with new data | |
| 434 | + $mTime = time(); | |
| 435 | + $stat['size'] = (int)$size; | |
| 436 | + $stat['mtime'] = $mTime; | |
| 437 | + $stat['storage_mtime'] = $mTime; | |
| 438 | + | |
| 439 | + $mimetypeDetector = \OC::$server->getMimeTypeDetector(); | |
| 440 | + $mimetype = $mimetypeDetector->detectPath($path); | |
| 441 | + | |
| 442 | + $stat['mimetype'] = $mimetype; | |
| 443 | + $stat['etag'] = $this->getETag($path); | |
| 444 | + | |
| 445 | + $exists = $this->getCache()->inCache($path); | |
| 446 | + $uploadPath = $exists ? $path : $path . '.part'; | |
| 447 | + $fileId = $this->getCache()->put($uploadPath, $stat); | |
| 448 | + $urn = $this->getURN($fileId); | |
| 449 | +        try { | |
| 450 | + //upload to object storage | |
| 451 | +            if ($size === null) { | |
| 452 | +                $countStream = CountWrapper::wrap($stream, function ($writtenSize) use ($fileId, &$size) { | |
| 453 | + $this->getCache()->update($fileId, [ | |
| 454 | + 'size' => $writtenSize | |
| 455 | + ]); | |
| 456 | + $size = $writtenSize; | |
| 457 | + }); | |
| 458 | + $this->objectStore->writeObject($urn, $countStream); | |
| 459 | +                if (is_resource($countStream)) { | |
| 460 | + fclose($countStream); | |
| 461 | + } | |
| 462 | +            } else { | |
| 463 | + $this->objectStore->writeObject($urn, $stream); | |
| 464 | + } | |
| 465 | +        } catch (\Exception $ex) { | |
| 466 | + $this->getCache()->remove($uploadPath); | |
| 467 | + $this->logger->logException($ex, [ | |
| 468 | + 'app' => 'objectstore', | |
| 469 | + 'message' => 'Could not create object ' . $urn . ' for ' . $path, | |
| 470 | + ]); | |
| 471 | + throw $ex; // make this bubble up | |
| 472 | + } | |
| 473 | + | |
| 474 | +        if (!$exists) { | |
| 475 | +            if ($this->objectStore->objectExists($urn)) { | |
| 476 | + $this->getCache()->move($uploadPath, $path); | |
| 477 | +            } else { | |
| 478 | + $this->getCache()->remove($uploadPath); | |
| 479 | +                throw new \Exception("Object not found after writing (urn: $urn, path: $path)", 404); | |
| 480 | + } | |
| 481 | + } | |
| 482 | + | |
| 483 | + return $size; | |
| 484 | + } | |
| 485 | + | |
| 486 | +    public function getObjectStore(): IObjectStore { | |
| 487 | + return $this->objectStore; | |
| 488 | + } | |
| 489 | 489 | } |