Completed
Pull Request — master (#8715)
by Robin
15:19
created
lib/private/Files/ObjectStore/ObjectStoreStorage.php 1 patch
Indentation   +402 added lines, -402 removed lines patch added patch discarded remove patch
@@ -31,406 +31,406 @@
 block discarded – undo
31 31
 use OCP\Files\ObjectStore\IObjectStore;
32 32
 
33 33
 class ObjectStoreStorage extends \OC\Files\Storage\Common {
34
-	/**
35
-	 * @var \OCP\Files\ObjectStore\IObjectStore $objectStore
36
-	 */
37
-	protected $objectStore;
38
-	/**
39
-	 * @var string $id
40
-	 */
41
-	protected $id;
42
-	/**
43
-	 * @var \OC\User\User $user
44
-	 */
45
-	protected $user;
46
-
47
-	private $objectPrefix = 'urn:oid:';
48
-
49
-	private $logger;
50
-
51
-	public function __construct($params) {
52
-		if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
53
-			$this->objectStore = $params['objectstore'];
54
-		} else {
55
-			throw new \Exception('missing IObjectStore instance');
56
-		}
57
-		if (isset($params['storageid'])) {
58
-			$this->id = 'object::store:' . $params['storageid'];
59
-		} else {
60
-			$this->id = 'object::store:' . $this->objectStore->getStorageId();
61
-		}
62
-		if (isset($params['objectPrefix'])) {
63
-			$this->objectPrefix = $params['objectPrefix'];
64
-		}
65
-		//initialize cache with root directory in cache
66
-		if (!$this->is_dir('/')) {
67
-			$this->mkdir('/');
68
-		}
69
-
70
-		$this->logger = \OC::$server->getLogger();
71
-	}
72
-
73
-	public function mkdir($path) {
74
-		$path = $this->normalizePath($path);
75
-
76
-		if ($this->file_exists($path)) {
77
-			return false;
78
-		}
79
-
80
-		$mTime = time();
81
-		$data = [
82
-			'mimetype' => 'httpd/unix-directory',
83
-			'size' => 0,
84
-			'mtime' => $mTime,
85
-			'storage_mtime' => $mTime,
86
-			'permissions' => \OCP\Constants::PERMISSION_ALL,
87
-		];
88
-		if ($path === '') {
89
-			//create root on the fly
90
-			$data['etag'] = $this->getETag('');
91
-			$this->getCache()->put('', $data);
92
-			return true;
93
-		} else {
94
-			// if parent does not exist, create it
95
-			$parent = $this->normalizePath(dirname($path));
96
-			$parentType = $this->filetype($parent);
97
-			if ($parentType === false) {
98
-				if (!$this->mkdir($parent)) {
99
-					// something went wrong
100
-					return false;
101
-				}
102
-			} else if ($parentType === 'file') {
103
-				// parent is a file
104
-				return false;
105
-			}
106
-			// finally create the new dir
107
-			$mTime = time(); // update mtime
108
-			$data['mtime'] = $mTime;
109
-			$data['storage_mtime'] = $mTime;
110
-			$data['etag'] = $this->getETag($path);
111
-			$this->getCache()->put($path, $data);
112
-			return true;
113
-		}
114
-	}
115
-
116
-	/**
117
-	 * @param string $path
118
-	 * @return string
119
-	 */
120
-	private function normalizePath($path) {
121
-		$path = trim($path, '/');
122
-		//FIXME why do we sometimes get a path like 'files//username'?
123
-		$path = str_replace('//', '/', $path);
124
-
125
-		// dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
126
-		if (!$path || $path === '.') {
127
-			$path = '';
128
-		}
129
-
130
-		return $path;
131
-	}
132
-
133
-	/**
134
-	 * Object Stores use a NoopScanner because metadata is directly stored in
135
-	 * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
136
-	 *
137
-	 * @param string $path
138
-	 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
139
-	 * @return \OC\Files\ObjectStore\NoopScanner
140
-	 */
141
-	public function getScanner($path = '', $storage = null) {
142
-		if (!$storage) {
143
-			$storage = $this;
144
-		}
145
-		if (!isset($this->scanner)) {
146
-			$this->scanner = new NoopScanner($storage);
147
-		}
148
-		return $this->scanner;
149
-	}
150
-
151
-	public function getId() {
152
-		return $this->id;
153
-	}
154
-
155
-	public function rmdir($path) {
156
-		$path = $this->normalizePath($path);
157
-
158
-		if (!$this->is_dir($path)) {
159
-			return false;
160
-		}
161
-
162
-		$this->rmObjects($path);
163
-
164
-		$this->getCache()->remove($path);
165
-
166
-		return true;
167
-	}
168
-
169
-	private function rmObjects($path) {
170
-		$children = $this->getCache()->getFolderContents($path);
171
-		foreach ($children as $child) {
172
-			if ($child['mimetype'] === 'httpd/unix-directory') {
173
-				$this->rmObjects($child['path']);
174
-			} else {
175
-				$this->unlink($child['path']);
176
-			}
177
-		}
178
-	}
179
-
180
-	public function unlink($path) {
181
-		$path = $this->normalizePath($path);
182
-		$stat = $this->stat($path);
183
-
184
-		if ($stat && isset($stat['fileid'])) {
185
-			if ($stat['mimetype'] === 'httpd/unix-directory') {
186
-				return $this->rmdir($path);
187
-			}
188
-			try {
189
-				$this->objectStore->deleteObject($this->getURN($stat['fileid']));
190
-			} catch (\Exception $ex) {
191
-				if ($ex->getCode() !== 404) {
192
-					$this->logger->logException($ex, [
193
-						'app' => 'objectstore',
194
-						'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
195
-					]);
196
-					return false;
197
-				}
198
-				//removing from cache is ok as it does not exist in the objectstore anyway
199
-			}
200
-			$this->getCache()->remove($path);
201
-			return true;
202
-		}
203
-		return false;
204
-	}
205
-
206
-	public function stat($path) {
207
-		$path = $this->normalizePath($path);
208
-		$cacheEntry = $this->getCache()->get($path);
209
-		if ($cacheEntry instanceof CacheEntry) {
210
-			return $cacheEntry->getData();
211
-		} else {
212
-			return false;
213
-		}
214
-	}
215
-
216
-	/**
217
-	 * Override this method if you need a different unique resource identifier for your object storage implementation.
218
-	 * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
219
-	 * You may need a mapping table to store your URN if it cannot be generated from the fileid.
220
-	 *
221
-	 * @param int $fileId the fileid
222
-	 * @return null|string the unified resource name used to identify the object
223
-	 */
224
-	protected function getURN($fileId) {
225
-		if (is_numeric($fileId)) {
226
-			return $this->objectPrefix . $fileId;
227
-		}
228
-		return null;
229
-	}
230
-
231
-	public function opendir($path) {
232
-		$path = $this->normalizePath($path);
233
-
234
-		try {
235
-			$files = array();
236
-			$folderContents = $this->getCache()->getFolderContents($path);
237
-			foreach ($folderContents as $file) {
238
-				$files[] = $file['name'];
239
-			}
240
-
241
-			return IteratorDirectory::wrap($files);
242
-		} catch (\Exception $e) {
243
-			$this->logger->logException($e);
244
-			return false;
245
-		}
246
-	}
247
-
248
-	public function filetype($path) {
249
-		$path = $this->normalizePath($path);
250
-		$stat = $this->stat($path);
251
-		if ($stat) {
252
-			if ($stat['mimetype'] === 'httpd/unix-directory') {
253
-				return 'dir';
254
-			}
255
-			return 'file';
256
-		} else {
257
-			return false;
258
-		}
259
-	}
260
-
261
-	public function fopen($path, $mode) {
262
-		$path = $this->normalizePath($path);
263
-
264
-		if (strrpos($path, '.') !== false) {
265
-			$ext = substr($path, strrpos($path, '.'));
266
-		} else {
267
-			$ext = '';
268
-		}
269
-
270
-		switch ($mode) {
271
-			case 'r':
272
-			case 'rb':
273
-				$stat = $this->stat($path);
274
-				if (is_array($stat)) {
275
-					try {
276
-						return $this->objectStore->readObject($this->getURN($stat['fileid']));
277
-					} catch (\Exception $ex) {
278
-						$this->logger->logException($ex, [
279
-							'app' => 'objectstore',
280
-							'message' => 'Count not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
281
-						]);
282
-						return false;
283
-					}
284
-				} else {
285
-					return false;
286
-				}
287
-			case 'w':
288
-			case 'wb':
289
-			case 'w+':
290
-			case 'wb+':
291
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
292
-				$handle = fopen($tmpFile, $mode);
293
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
294
-					$this->writeBack($tmpFile, $path);
295
-				});
296
-			case 'a':
297
-			case 'ab':
298
-			case 'r+':
299
-			case 'a+':
300
-			case 'x':
301
-			case 'x+':
302
-			case 'c':
303
-			case 'c+':
304
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
305
-				if ($this->file_exists($path)) {
306
-					$source = $this->fopen($path, 'r');
307
-					file_put_contents($tmpFile, $source);
308
-				}
309
-				$handle = fopen($tmpFile, $mode);
310
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
311
-					$this->writeBack($tmpFile, $path);
312
-				});
313
-		}
314
-		return false;
315
-	}
316
-
317
-	public function file_exists($path) {
318
-		$path = $this->normalizePath($path);
319
-		return (bool)$this->stat($path);
320
-	}
321
-
322
-	public function rename($source, $target) {
323
-		$source = $this->normalizePath($source);
324
-		$target = $this->normalizePath($target);
325
-		$this->remove($target);
326
-		$this->getCache()->move($source, $target);
327
-		$this->touch(dirname($target));
328
-		return true;
329
-	}
330
-
331
-	public function getMimeType($path) {
332
-		$path = $this->normalizePath($path);
333
-		$stat = $this->stat($path);
334
-		if (is_array($stat)) {
335
-			return $stat['mimetype'];
336
-		} else {
337
-			return false;
338
-		}
339
-	}
340
-
341
-	public function touch($path, $mtime = null) {
342
-		if (is_null($mtime)) {
343
-			$mtime = time();
344
-		}
345
-
346
-		$path = $this->normalizePath($path);
347
-		$dirName = dirname($path);
348
-		$parentExists = $this->is_dir($dirName);
349
-		if (!$parentExists) {
350
-			return false;
351
-		}
352
-
353
-		$stat = $this->stat($path);
354
-		if (is_array($stat)) {
355
-			// update existing mtime in db
356
-			$stat['mtime'] = $mtime;
357
-			$this->getCache()->update($stat['fileid'], $stat);
358
-		} else {
359
-			try {
360
-				//create a empty file, need to have at least on char to make it
361
-				// work with all object storage implementations
362
-				$this->file_put_contents($path, ' ');
363
-				$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
364
-				$stat = array(
365
-					'etag' => $this->getETag($path),
366
-					'mimetype' => $mimeType,
367
-					'size' => 0,
368
-					'mtime' => $mtime,
369
-					'storage_mtime' => $mtime,
370
-					'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
371
-				);
372
-				$this->getCache()->put($path, $stat);
373
-			} catch (\Exception $ex) {
374
-				$this->logger->logException($ex, [
375
-					'app' => 'objectstore',
376
-					'message' => 'Could not create object for ' . $path,
377
-				]);
378
-				throw $ex;
379
-			}
380
-		}
381
-		return true;
382
-	}
383
-
384
-	public function writeBack($tmpFile, $path) {
385
-		$stat = $this->stat($path);
386
-		if (empty($stat)) {
387
-			// create new file
388
-			$stat = array(
389
-				'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
390
-			);
391
-		}
392
-		// update stat with new data
393
-		$mTime = time();
394
-		$stat['size'] = filesize($tmpFile);
395
-		$stat['mtime'] = $mTime;
396
-		$stat['storage_mtime'] = $mTime;
397
-
398
-		// run path based detection first, to use file extension because $tmpFile is only a random string
399
-		$mimetypeDetector = \OC::$server->getMimeTypeDetector();
400
-		$mimetype = $mimetypeDetector->detectPath($path);
401
-		if ($mimetype === 'application/octet-stream') {
402
-			$mimetype = $mimetypeDetector->detect($tmpFile);
403
-		}
404
-
405
-		$stat['mimetype'] = $mimetype;
406
-		$stat['etag'] = $this->getETag($path);
407
-
408
-		$fileId = $this->getCache()->put($path, $stat);
409
-		try {
410
-			//upload to object storage
411
-			$this->objectStore->writeObject($this->getURN($fileId), fopen($tmpFile, 'r'));
412
-		} catch (\Exception $ex) {
413
-			$this->getCache()->remove($path);
414
-			$this->logger->logException($ex, [
415
-				'app' => 'objectstore',
416
-				'message' => 'Could not create object ' . $this->getURN($fileId) . ' for ' . $path,
417
-			]);
418
-			throw $ex; // make this bubble up
419
-		}
420
-	}
421
-
422
-	/**
423
-	 * external changes are not supported, exclusive access to the object storage is assumed
424
-	 *
425
-	 * @param string $path
426
-	 * @param int $time
427
-	 * @return false
428
-	 */
429
-	public function hasUpdated($path, $time) {
430
-		return false;
431
-	}
432
-
433
-	public function needsPartFile() {
434
-		return false;
435
-	}
34
+    /**
35
+     * @var \OCP\Files\ObjectStore\IObjectStore $objectStore
36
+     */
37
+    protected $objectStore;
38
+    /**
39
+     * @var string $id
40
+     */
41
+    protected $id;
42
+    /**
43
+     * @var \OC\User\User $user
44
+     */
45
+    protected $user;
46
+
47
+    private $objectPrefix = 'urn:oid:';
48
+
49
+    private $logger;
50
+
51
+    public function __construct($params) {
52
+        if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
53
+            $this->objectStore = $params['objectstore'];
54
+        } else {
55
+            throw new \Exception('missing IObjectStore instance');
56
+        }
57
+        if (isset($params['storageid'])) {
58
+            $this->id = 'object::store:' . $params['storageid'];
59
+        } else {
60
+            $this->id = 'object::store:' . $this->objectStore->getStorageId();
61
+        }
62
+        if (isset($params['objectPrefix'])) {
63
+            $this->objectPrefix = $params['objectPrefix'];
64
+        }
65
+        //initialize cache with root directory in cache
66
+        if (!$this->is_dir('/')) {
67
+            $this->mkdir('/');
68
+        }
69
+
70
+        $this->logger = \OC::$server->getLogger();
71
+    }
72
+
73
+    public function mkdir($path) {
74
+        $path = $this->normalizePath($path);
75
+
76
+        if ($this->file_exists($path)) {
77
+            return false;
78
+        }
79
+
80
+        $mTime = time();
81
+        $data = [
82
+            'mimetype' => 'httpd/unix-directory',
83
+            'size' => 0,
84
+            'mtime' => $mTime,
85
+            'storage_mtime' => $mTime,
86
+            'permissions' => \OCP\Constants::PERMISSION_ALL,
87
+        ];
88
+        if ($path === '') {
89
+            //create root on the fly
90
+            $data['etag'] = $this->getETag('');
91
+            $this->getCache()->put('', $data);
92
+            return true;
93
+        } else {
94
+            // if parent does not exist, create it
95
+            $parent = $this->normalizePath(dirname($path));
96
+            $parentType = $this->filetype($parent);
97
+            if ($parentType === false) {
98
+                if (!$this->mkdir($parent)) {
99
+                    // something went wrong
100
+                    return false;
101
+                }
102
+            } else if ($parentType === 'file') {
103
+                // parent is a file
104
+                return false;
105
+            }
106
+            // finally create the new dir
107
+            $mTime = time(); // update mtime
108
+            $data['mtime'] = $mTime;
109
+            $data['storage_mtime'] = $mTime;
110
+            $data['etag'] = $this->getETag($path);
111
+            $this->getCache()->put($path, $data);
112
+            return true;
113
+        }
114
+    }
115
+
116
+    /**
117
+     * @param string $path
118
+     * @return string
119
+     */
120
+    private function normalizePath($path) {
121
+        $path = trim($path, '/');
122
+        //FIXME why do we sometimes get a path like 'files//username'?
123
+        $path = str_replace('//', '/', $path);
124
+
125
+        // dirname('/folder') returns '.' but internally (in the cache) we store the root as ''
126
+        if (!$path || $path === '.') {
127
+            $path = '';
128
+        }
129
+
130
+        return $path;
131
+    }
132
+
133
+    /**
134
+     * Object Stores use a NoopScanner because metadata is directly stored in
135
+     * the file cache and cannot really scan the filesystem. The storage passed in is not used anywhere.
136
+     *
137
+     * @param string $path
138
+     * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
139
+     * @return \OC\Files\ObjectStore\NoopScanner
140
+     */
141
+    public function getScanner($path = '', $storage = null) {
142
+        if (!$storage) {
143
+            $storage = $this;
144
+        }
145
+        if (!isset($this->scanner)) {
146
+            $this->scanner = new NoopScanner($storage);
147
+        }
148
+        return $this->scanner;
149
+    }
150
+
151
+    public function getId() {
152
+        return $this->id;
153
+    }
154
+
155
+    public function rmdir($path) {
156
+        $path = $this->normalizePath($path);
157
+
158
+        if (!$this->is_dir($path)) {
159
+            return false;
160
+        }
161
+
162
+        $this->rmObjects($path);
163
+
164
+        $this->getCache()->remove($path);
165
+
166
+        return true;
167
+    }
168
+
169
+    private function rmObjects($path) {
170
+        $children = $this->getCache()->getFolderContents($path);
171
+        foreach ($children as $child) {
172
+            if ($child['mimetype'] === 'httpd/unix-directory') {
173
+                $this->rmObjects($child['path']);
174
+            } else {
175
+                $this->unlink($child['path']);
176
+            }
177
+        }
178
+    }
179
+
180
+    public function unlink($path) {
181
+        $path = $this->normalizePath($path);
182
+        $stat = $this->stat($path);
183
+
184
+        if ($stat && isset($stat['fileid'])) {
185
+            if ($stat['mimetype'] === 'httpd/unix-directory') {
186
+                return $this->rmdir($path);
187
+            }
188
+            try {
189
+                $this->objectStore->deleteObject($this->getURN($stat['fileid']));
190
+            } catch (\Exception $ex) {
191
+                if ($ex->getCode() !== 404) {
192
+                    $this->logger->logException($ex, [
193
+                        'app' => 'objectstore',
194
+                        'message' => 'Could not delete object ' . $this->getURN($stat['fileid']) . ' for ' . $path,
195
+                    ]);
196
+                    return false;
197
+                }
198
+                //removing from cache is ok as it does not exist in the objectstore anyway
199
+            }
200
+            $this->getCache()->remove($path);
201
+            return true;
202
+        }
203
+        return false;
204
+    }
205
+
206
+    public function stat($path) {
207
+        $path = $this->normalizePath($path);
208
+        $cacheEntry = $this->getCache()->get($path);
209
+        if ($cacheEntry instanceof CacheEntry) {
210
+            return $cacheEntry->getData();
211
+        } else {
212
+            return false;
213
+        }
214
+    }
215
+
216
+    /**
217
+     * Override this method if you need a different unique resource identifier for your object storage implementation.
218
+     * The default implementations just appends the fileId to 'urn:oid:'. Make sure the URN is unique over all users.
219
+     * You may need a mapping table to store your URN if it cannot be generated from the fileid.
220
+     *
221
+     * @param int $fileId the fileid
222
+     * @return null|string the unified resource name used to identify the object
223
+     */
224
+    protected function getURN($fileId) {
225
+        if (is_numeric($fileId)) {
226
+            return $this->objectPrefix . $fileId;
227
+        }
228
+        return null;
229
+    }
230
+
231
+    public function opendir($path) {
232
+        $path = $this->normalizePath($path);
233
+
234
+        try {
235
+            $files = array();
236
+            $folderContents = $this->getCache()->getFolderContents($path);
237
+            foreach ($folderContents as $file) {
238
+                $files[] = $file['name'];
239
+            }
240
+
241
+            return IteratorDirectory::wrap($files);
242
+        } catch (\Exception $e) {
243
+            $this->logger->logException($e);
244
+            return false;
245
+        }
246
+    }
247
+
248
+    public function filetype($path) {
249
+        $path = $this->normalizePath($path);
250
+        $stat = $this->stat($path);
251
+        if ($stat) {
252
+            if ($stat['mimetype'] === 'httpd/unix-directory') {
253
+                return 'dir';
254
+            }
255
+            return 'file';
256
+        } else {
257
+            return false;
258
+        }
259
+    }
260
+
261
+    public function fopen($path, $mode) {
262
+        $path = $this->normalizePath($path);
263
+
264
+        if (strrpos($path, '.') !== false) {
265
+            $ext = substr($path, strrpos($path, '.'));
266
+        } else {
267
+            $ext = '';
268
+        }
269
+
270
+        switch ($mode) {
271
+            case 'r':
272
+            case 'rb':
273
+                $stat = $this->stat($path);
274
+                if (is_array($stat)) {
275
+                    try {
276
+                        return $this->objectStore->readObject($this->getURN($stat['fileid']));
277
+                    } catch (\Exception $ex) {
278
+                        $this->logger->logException($ex, [
279
+                            'app' => 'objectstore',
280
+                            'message' => 'Count not get object ' . $this->getURN($stat['fileid']) . ' for file ' . $path,
281
+                        ]);
282
+                        return false;
283
+                    }
284
+                } else {
285
+                    return false;
286
+                }
287
+            case 'w':
288
+            case 'wb':
289
+            case 'w+':
290
+            case 'wb+':
291
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
292
+                $handle = fopen($tmpFile, $mode);
293
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
294
+                    $this->writeBack($tmpFile, $path);
295
+                });
296
+            case 'a':
297
+            case 'ab':
298
+            case 'r+':
299
+            case 'a+':
300
+            case 'x':
301
+            case 'x+':
302
+            case 'c':
303
+            case 'c+':
304
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
305
+                if ($this->file_exists($path)) {
306
+                    $source = $this->fopen($path, 'r');
307
+                    file_put_contents($tmpFile, $source);
308
+                }
309
+                $handle = fopen($tmpFile, $mode);
310
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
311
+                    $this->writeBack($tmpFile, $path);
312
+                });
313
+        }
314
+        return false;
315
+    }
316
+
317
+    public function file_exists($path) {
318
+        $path = $this->normalizePath($path);
319
+        return (bool)$this->stat($path);
320
+    }
321
+
322
+    public function rename($source, $target) {
323
+        $source = $this->normalizePath($source);
324
+        $target = $this->normalizePath($target);
325
+        $this->remove($target);
326
+        $this->getCache()->move($source, $target);
327
+        $this->touch(dirname($target));
328
+        return true;
329
+    }
330
+
331
+    public function getMimeType($path) {
332
+        $path = $this->normalizePath($path);
333
+        $stat = $this->stat($path);
334
+        if (is_array($stat)) {
335
+            return $stat['mimetype'];
336
+        } else {
337
+            return false;
338
+        }
339
+    }
340
+
341
+    public function touch($path, $mtime = null) {
342
+        if (is_null($mtime)) {
343
+            $mtime = time();
344
+        }
345
+
346
+        $path = $this->normalizePath($path);
347
+        $dirName = dirname($path);
348
+        $parentExists = $this->is_dir($dirName);
349
+        if (!$parentExists) {
350
+            return false;
351
+        }
352
+
353
+        $stat = $this->stat($path);
354
+        if (is_array($stat)) {
355
+            // update existing mtime in db
356
+            $stat['mtime'] = $mtime;
357
+            $this->getCache()->update($stat['fileid'], $stat);
358
+        } else {
359
+            try {
360
+                //create a empty file, need to have at least on char to make it
361
+                // work with all object storage implementations
362
+                $this->file_put_contents($path, ' ');
363
+                $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
364
+                $stat = array(
365
+                    'etag' => $this->getETag($path),
366
+                    'mimetype' => $mimeType,
367
+                    'size' => 0,
368
+                    'mtime' => $mtime,
369
+                    'storage_mtime' => $mtime,
370
+                    'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
371
+                );
372
+                $this->getCache()->put($path, $stat);
373
+            } catch (\Exception $ex) {
374
+                $this->logger->logException($ex, [
375
+                    'app' => 'objectstore',
376
+                    'message' => 'Could not create object for ' . $path,
377
+                ]);
378
+                throw $ex;
379
+            }
380
+        }
381
+        return true;
382
+    }
383
+
384
+    public function writeBack($tmpFile, $path) {
385
+        $stat = $this->stat($path);
386
+        if (empty($stat)) {
387
+            // create new file
388
+            $stat = array(
389
+                'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
390
+            );
391
+        }
392
+        // update stat with new data
393
+        $mTime = time();
394
+        $stat['size'] = filesize($tmpFile);
395
+        $stat['mtime'] = $mTime;
396
+        $stat['storage_mtime'] = $mTime;
397
+
398
+        // run path based detection first, to use file extension because $tmpFile is only a random string
399
+        $mimetypeDetector = \OC::$server->getMimeTypeDetector();
400
+        $mimetype = $mimetypeDetector->detectPath($path);
401
+        if ($mimetype === 'application/octet-stream') {
402
+            $mimetype = $mimetypeDetector->detect($tmpFile);
403
+        }
404
+
405
+        $stat['mimetype'] = $mimetype;
406
+        $stat['etag'] = $this->getETag($path);
407
+
408
+        $fileId = $this->getCache()->put($path, $stat);
409
+        try {
410
+            //upload to object storage
411
+            $this->objectStore->writeObject($this->getURN($fileId), fopen($tmpFile, 'r'));
412
+        } catch (\Exception $ex) {
413
+            $this->getCache()->remove($path);
414
+            $this->logger->logException($ex, [
415
+                'app' => 'objectstore',
416
+                'message' => 'Could not create object ' . $this->getURN($fileId) . ' for ' . $path,
417
+            ]);
418
+            throw $ex; // make this bubble up
419
+        }
420
+    }
421
+
422
+    /**
423
+     * external changes are not supported, exclusive access to the object storage is assumed
424
+     *
425
+     * @param string $path
426
+     * @param int $time
427
+     * @return false
428
+     */
429
+    public function hasUpdated($path, $time) {
430
+        return false;
431
+    }
432
+
433
+    public function needsPartFile() {
434
+        return false;
435
+    }
436 436
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/File.php 1 patch
Indentation   +533 added lines, -533 removed lines patch added patch discarded remove patch
@@ -65,538 +65,538 @@
 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
-				\OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR);
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
-						\OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: '  . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', \OCP\Util::ERROR);
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
+                \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR);
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
+                        \OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: '  . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', \OCP\Util::ERROR);
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
-			$res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
336
-			if ($res === false) {
337
-				throw new ServiceUnavailable("Could not open file");
338
-			}
339
-			return $res;
340
-		} catch (GenericEncryptionException $e) {
341
-			// returning 503 will allow retry of the operation at a later point in time
342
-			throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
343
-		} catch (StorageNotAvailableException $e) {
344
-			throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
345
-		} catch (ForbiddenException $ex) {
346
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
347
-		} catch (LockedException $e) {
348
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
349
-		}
350
-	}
351
-
352
-	/**
353
-	 * Delete the current file
354
-	 *
355
-	 * @throws Forbidden
356
-	 * @throws ServiceUnavailable
357
-	 */
358
-	public function delete() {
359
-		if (!$this->info->isDeletable()) {
360
-			throw new Forbidden();
361
-		}
362
-
363
-		try {
364
-			if (!$this->fileView->unlink($this->path)) {
365
-				// assume it wasn't possible to delete due to permissions
366
-				throw new Forbidden();
367
-			}
368
-		} catch (StorageNotAvailableException $e) {
369
-			throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
370
-		} catch (ForbiddenException $ex) {
371
-			throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
372
-		} catch (LockedException $e) {
373
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
374
-		}
375
-	}
376
-
377
-	/**
378
-	 * Returns the mime-type for a file
379
-	 *
380
-	 * If null is returned, we'll assume application/octet-stream
381
-	 *
382
-	 * @return string
383
-	 */
384
-	public function getContentType() {
385
-		$mimeType = $this->info->getMimetype();
386
-
387
-		// PROPFIND needs to return the correct mime type, for consistency with the web UI
388
-		if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
389
-			return $mimeType;
390
-		}
391
-		return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
392
-	}
393
-
394
-	/**
395
-	 * @return array|false
396
-	 */
397
-	public function getDirectDownload() {
398
-		if (\OCP\App::isEnabled('encryption')) {
399
-			return [];
400
-		}
401
-		/** @var \OCP\Files\Storage $storage */
402
-		list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
403
-		if (is_null($storage)) {
404
-			return [];
405
-		}
406
-
407
-		return $storage->getDirectDownload($internalPath);
408
-	}
409
-
410
-	/**
411
-	 * @param resource $data
412
-	 * @return null|string
413
-	 * @throws Exception
414
-	 * @throws BadRequest
415
-	 * @throws NotImplemented
416
-	 * @throws ServiceUnavailable
417
-	 */
418
-	private function createFileChunked($data) {
419
-		list($path, $name) = \Sabre\Uri\split($this->path);
420
-
421
-		$info = \OC_FileChunking::decodeName($name);
422
-		if (empty($info)) {
423
-			throw new NotImplemented('Invalid chunk name');
424
-		}
425
-
426
-		$chunk_handler = new \OC_FileChunking($info);
427
-		$bytesWritten = $chunk_handler->store($info['index'], $data);
428
-
429
-		//detect aborted upload
430
-		if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
431
-			if (isset($_SERVER['CONTENT_LENGTH'])) {
432
-				$expected = (int) $_SERVER['CONTENT_LENGTH'];
433
-				if ($bytesWritten !== $expected) {
434
-					$chunk_handler->remove($info['index']);
435
-					throw new BadRequest(
436
-						'expected filesize ' . $expected . ' got ' . $bytesWritten);
437
-				}
438
-			}
439
-		}
440
-
441
-		if ($chunk_handler->isComplete()) {
442
-			list($storage,) = $this->fileView->resolvePath($path);
443
-			$needsPartFile = $this->needsPartFile($storage);
444
-			$partFile = null;
445
-
446
-			$targetPath = $path . '/' . $info['name'];
447
-			/** @var \OC\Files\Storage\Storage $targetStorage */
448
-			list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
449
-
450
-			$exists = $this->fileView->file_exists($targetPath);
451
-
452
-			try {
453
-				$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
454
-
455
-				$this->emitPreHooks($exists, $targetPath);
456
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
457
-				/** @var \OC\Files\Storage\Storage $targetStorage */
458
-				list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
459
-
460
-				if ($needsPartFile) {
461
-					// we first assembly the target file as a part file
462
-					$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
463
-					/** @var \OC\Files\Storage\Storage $targetStorage */
464
-					list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
465
-
466
-
467
-					$chunk_handler->file_assemble($partStorage, $partInternalPath);
468
-
469
-					// here is the final atomic rename
470
-					$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
471
-					$fileExists = $targetStorage->file_exists($targetInternalPath);
472
-					if ($renameOkay === false || $fileExists === false) {
473
-						\OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR);
474
-						// only delete if an error occurred and the target file was already created
475
-						if ($fileExists) {
476
-							// set to null to avoid double-deletion when handling exception
477
-							// stray part file
478
-							$partFile = null;
479
-							$targetStorage->unlink($targetInternalPath);
480
-						}
481
-						$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
482
-						throw new Exception('Could not rename part file assembled from chunks');
483
-					}
484
-				} else {
485
-					// assemble directly into the final file
486
-					$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
487
-				}
488
-
489
-				// allow sync clients to send the mtime along in a header
490
-				if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
491
-					$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
492
-					if ($targetStorage->touch($targetInternalPath, $mtime)) {
493
-						$this->header('X-OC-MTime: accepted');
494
-					}
495
-				}
496
-
497
-				// since we skipped the view we need to scan and emit the hooks ourselves
498
-				$targetStorage->getUpdater()->update($targetInternalPath);
499
-
500
-				$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
501
-
502
-				$this->emitPostHooks($exists, $targetPath);
503
-
504
-				// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
505
-				$info = $this->fileView->getFileInfo($targetPath);
506
-
507
-				if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
508
-					$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
509
-					$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
510
-				} else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
511
-					$this->fileView->putFileInfo($this->path, ['checksum' => '']);
512
-				}
513
-
514
-				$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
515
-
516
-				return $info->getEtag();
517
-			} catch (\Exception $e) {
518
-				if ($partFile !== null) {
519
-					$targetStorage->unlink($targetInternalPath);
520
-				}
521
-				$this->convertToSabreException($e);
522
-			}
523
-		}
524
-
525
-		return null;
526
-	}
527
-
528
-	/**
529
-	 * Returns whether a part file is needed for the given storage
530
-	 * or whether the file can be assembled/uploaded directly on the
531
-	 * target storage.
532
-	 *
533
-	 * @param \OCP\Files\Storage $storage
534
-	 * @return bool true if the storage needs part file handling
535
-	 */
536
-	private function needsPartFile($storage) {
537
-		// TODO: in the future use ChunkHandler provided by storage
538
-		return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
539
-			!$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
540
-			$storage->needsPartFile();
541
-	}
542
-
543
-	/**
544
-	 * Convert the given exception to a SabreException instance
545
-	 *
546
-	 * @param \Exception $e
547
-	 *
548
-	 * @throws \Sabre\DAV\Exception
549
-	 */
550
-	private function convertToSabreException(\Exception $e) {
551
-		if ($e instanceof \Sabre\DAV\Exception) {
552
-			throw $e;
553
-		}
554
-		if ($e instanceof NotPermittedException) {
555
-			// a more general case - due to whatever reason the content could not be written
556
-			throw new Forbidden($e->getMessage(), 0, $e);
557
-		}
558
-		if ($e instanceof ForbiddenException) {
559
-			// the path for the file was forbidden
560
-			throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
561
-		}
562
-		if ($e instanceof EntityTooLargeException) {
563
-			// the file is too big to be stored
564
-			throw new EntityTooLarge($e->getMessage(), 0, $e);
565
-		}
566
-		if ($e instanceof InvalidContentException) {
567
-			// the file content is not permitted
568
-			throw new UnsupportedMediaType($e->getMessage(), 0, $e);
569
-		}
570
-		if ($e instanceof InvalidPathException) {
571
-			// the path for the file was not valid
572
-			// TODO: find proper http status code for this case
573
-			throw new Forbidden($e->getMessage(), 0, $e);
574
-		}
575
-		if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
576
-			// the file is currently being written to by another process
577
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
578
-		}
579
-		if ($e instanceof GenericEncryptionException) {
580
-			// returning 503 will allow retry of the operation at a later point in time
581
-			throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
582
-		}
583
-		if ($e instanceof StorageNotAvailableException) {
584
-			throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
585
-		}
586
-
587
-		throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
588
-	}
589
-
590
-	/**
591
-	 * Get the checksum for this file
592
-	 *
593
-	 * @return string
594
-	 */
595
-	public function getChecksum() {
596
-		return $this->info->getChecksum();
597
-	}
598
-
599
-	protected function header($string) {
600
-		\header($string);
601
-	}
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
+            $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
336
+            if ($res === false) {
337
+                throw new ServiceUnavailable("Could not open file");
338
+            }
339
+            return $res;
340
+        } catch (GenericEncryptionException $e) {
341
+            // returning 503 will allow retry of the operation at a later point in time
342
+            throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
343
+        } catch (StorageNotAvailableException $e) {
344
+            throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
345
+        } catch (ForbiddenException $ex) {
346
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
347
+        } catch (LockedException $e) {
348
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
349
+        }
350
+    }
351
+
352
+    /**
353
+     * Delete the current file
354
+     *
355
+     * @throws Forbidden
356
+     * @throws ServiceUnavailable
357
+     */
358
+    public function delete() {
359
+        if (!$this->info->isDeletable()) {
360
+            throw new Forbidden();
361
+        }
362
+
363
+        try {
364
+            if (!$this->fileView->unlink($this->path)) {
365
+                // assume it wasn't possible to delete due to permissions
366
+                throw new Forbidden();
367
+            }
368
+        } catch (StorageNotAvailableException $e) {
369
+            throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
370
+        } catch (ForbiddenException $ex) {
371
+            throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry());
372
+        } catch (LockedException $e) {
373
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
374
+        }
375
+    }
376
+
377
+    /**
378
+     * Returns the mime-type for a file
379
+     *
380
+     * If null is returned, we'll assume application/octet-stream
381
+     *
382
+     * @return string
383
+     */
384
+    public function getContentType() {
385
+        $mimeType = $this->info->getMimetype();
386
+
387
+        // PROPFIND needs to return the correct mime type, for consistency with the web UI
388
+        if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
389
+            return $mimeType;
390
+        }
391
+        return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
392
+    }
393
+
394
+    /**
395
+     * @return array|false
396
+     */
397
+    public function getDirectDownload() {
398
+        if (\OCP\App::isEnabled('encryption')) {
399
+            return [];
400
+        }
401
+        /** @var \OCP\Files\Storage $storage */
402
+        list($storage, $internalPath) = $this->fileView->resolvePath($this->path);
403
+        if (is_null($storage)) {
404
+            return [];
405
+        }
406
+
407
+        return $storage->getDirectDownload($internalPath);
408
+    }
409
+
410
+    /**
411
+     * @param resource $data
412
+     * @return null|string
413
+     * @throws Exception
414
+     * @throws BadRequest
415
+     * @throws NotImplemented
416
+     * @throws ServiceUnavailable
417
+     */
418
+    private function createFileChunked($data) {
419
+        list($path, $name) = \Sabre\Uri\split($this->path);
420
+
421
+        $info = \OC_FileChunking::decodeName($name);
422
+        if (empty($info)) {
423
+            throw new NotImplemented('Invalid chunk name');
424
+        }
425
+
426
+        $chunk_handler = new \OC_FileChunking($info);
427
+        $bytesWritten = $chunk_handler->store($info['index'], $data);
428
+
429
+        //detect aborted upload
430
+        if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
431
+            if (isset($_SERVER['CONTENT_LENGTH'])) {
432
+                $expected = (int) $_SERVER['CONTENT_LENGTH'];
433
+                if ($bytesWritten !== $expected) {
434
+                    $chunk_handler->remove($info['index']);
435
+                    throw new BadRequest(
436
+                        'expected filesize ' . $expected . ' got ' . $bytesWritten);
437
+                }
438
+            }
439
+        }
440
+
441
+        if ($chunk_handler->isComplete()) {
442
+            list($storage,) = $this->fileView->resolvePath($path);
443
+            $needsPartFile = $this->needsPartFile($storage);
444
+            $partFile = null;
445
+
446
+            $targetPath = $path . '/' . $info['name'];
447
+            /** @var \OC\Files\Storage\Storage $targetStorage */
448
+            list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
449
+
450
+            $exists = $this->fileView->file_exists($targetPath);
451
+
452
+            try {
453
+                $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
454
+
455
+                $this->emitPreHooks($exists, $targetPath);
456
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
457
+                /** @var \OC\Files\Storage\Storage $targetStorage */
458
+                list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath);
459
+
460
+                if ($needsPartFile) {
461
+                    // we first assembly the target file as a part file
462
+                    $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
463
+                    /** @var \OC\Files\Storage\Storage $targetStorage */
464
+                    list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile);
465
+
466
+
467
+                    $chunk_handler->file_assemble($partStorage, $partInternalPath);
468
+
469
+                    // here is the final atomic rename
470
+                    $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
471
+                    $fileExists = $targetStorage->file_exists($targetInternalPath);
472
+                    if ($renameOkay === false || $fileExists === false) {
473
+                        \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR);
474
+                        // only delete if an error occurred and the target file was already created
475
+                        if ($fileExists) {
476
+                            // set to null to avoid double-deletion when handling exception
477
+                            // stray part file
478
+                            $partFile = null;
479
+                            $targetStorage->unlink($targetInternalPath);
480
+                        }
481
+                        $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
482
+                        throw new Exception('Could not rename part file assembled from chunks');
483
+                    }
484
+                } else {
485
+                    // assemble directly into the final file
486
+                    $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
487
+                }
488
+
489
+                // allow sync clients to send the mtime along in a header
490
+                if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
491
+                    $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
492
+                    if ($targetStorage->touch($targetInternalPath, $mtime)) {
493
+                        $this->header('X-OC-MTime: accepted');
494
+                    }
495
+                }
496
+
497
+                // since we skipped the view we need to scan and emit the hooks ourselves
498
+                $targetStorage->getUpdater()->update($targetInternalPath);
499
+
500
+                $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
501
+
502
+                $this->emitPostHooks($exists, $targetPath);
503
+
504
+                // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
505
+                $info = $this->fileView->getFileInfo($targetPath);
506
+
507
+                if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
508
+                    $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
509
+                    $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
510
+                } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') {
511
+                    $this->fileView->putFileInfo($this->path, ['checksum' => '']);
512
+                }
513
+
514
+                $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
515
+
516
+                return $info->getEtag();
517
+            } catch (\Exception $e) {
518
+                if ($partFile !== null) {
519
+                    $targetStorage->unlink($targetInternalPath);
520
+                }
521
+                $this->convertToSabreException($e);
522
+            }
523
+        }
524
+
525
+        return null;
526
+    }
527
+
528
+    /**
529
+     * Returns whether a part file is needed for the given storage
530
+     * or whether the file can be assembled/uploaded directly on the
531
+     * target storage.
532
+     *
533
+     * @param \OCP\Files\Storage $storage
534
+     * @return bool true if the storage needs part file handling
535
+     */
536
+    private function needsPartFile($storage) {
537
+        // TODO: in the future use ChunkHandler provided by storage
538
+        return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') &&
539
+            !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') &&
540
+            $storage->needsPartFile();
541
+    }
542
+
543
+    /**
544
+     * Convert the given exception to a SabreException instance
545
+     *
546
+     * @param \Exception $e
547
+     *
548
+     * @throws \Sabre\DAV\Exception
549
+     */
550
+    private function convertToSabreException(\Exception $e) {
551
+        if ($e instanceof \Sabre\DAV\Exception) {
552
+            throw $e;
553
+        }
554
+        if ($e instanceof NotPermittedException) {
555
+            // a more general case - due to whatever reason the content could not be written
556
+            throw new Forbidden($e->getMessage(), 0, $e);
557
+        }
558
+        if ($e instanceof ForbiddenException) {
559
+            // the path for the file was forbidden
560
+            throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e);
561
+        }
562
+        if ($e instanceof EntityTooLargeException) {
563
+            // the file is too big to be stored
564
+            throw new EntityTooLarge($e->getMessage(), 0, $e);
565
+        }
566
+        if ($e instanceof InvalidContentException) {
567
+            // the file content is not permitted
568
+            throw new UnsupportedMediaType($e->getMessage(), 0, $e);
569
+        }
570
+        if ($e instanceof InvalidPathException) {
571
+            // the path for the file was not valid
572
+            // TODO: find proper http status code for this case
573
+            throw new Forbidden($e->getMessage(), 0, $e);
574
+        }
575
+        if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) {
576
+            // the file is currently being written to by another process
577
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
578
+        }
579
+        if ($e instanceof GenericEncryptionException) {
580
+            // returning 503 will allow retry of the operation at a later point in time
581
+            throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e);
582
+        }
583
+        if ($e instanceof StorageNotAvailableException) {
584
+            throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e);
585
+        }
586
+
587
+        throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e);
588
+    }
589
+
590
+    /**
591
+     * Get the checksum for this file
592
+     *
593
+     * @return string
594
+     */
595
+    public function getChecksum() {
596
+        return $this->info->getChecksum();
597
+    }
598
+
599
+    protected function header($string) {
600
+        \header($string);
601
+    }
602 602
 }
Please login to merge, or discard this patch.