|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Nextcloud - Gallery |
|
4
|
|
|
* |
|
5
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
|
6
|
|
|
* later. See the COPYING file. |
|
7
|
|
|
* |
|
8
|
|
|
* @author Olivier Paroz <[email protected]> |
|
9
|
|
|
* |
|
10
|
|
|
* @copyright Olivier Paroz 2017 |
|
11
|
|
|
*/ |
|
12
|
|
|
|
|
13
|
|
|
namespace OCA\Gallery\Service; |
|
14
|
|
|
|
|
15
|
|
|
use OCP\Files\File; |
|
16
|
|
|
use OCP\Files\Folder; |
|
17
|
|
|
use OCP\Files\Node; |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* Contains various methods to retrieve information from the filesystem |
|
21
|
|
|
* |
|
22
|
|
|
* @package OCA\Gallery\Service |
|
23
|
|
|
*/ |
|
24
|
|
|
abstract class FilesService extends Service { |
|
25
|
|
|
|
|
26
|
|
|
/** @var int */ |
|
27
|
|
|
protected $virtualRootLevel = null; |
|
28
|
|
|
/** @var string[] */ |
|
29
|
|
|
protected $features; |
|
30
|
|
|
/** @var string */ |
|
31
|
|
|
protected $ignoreAlbum = '.nomedia'; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* Retrieves all files and sub-folders contained in a folder |
|
35
|
|
|
* |
|
36
|
|
|
* If we can't find anything in the current folder, we throw an exception as there is no point |
|
37
|
|
|
* in doing any more work, but if we're looking at a sub-folder, we return an empty array so |
|
38
|
|
|
* that it can be simply ignored |
|
39
|
|
|
* |
|
40
|
|
|
* @param Folder $folder |
|
41
|
|
|
* @param int $subDepth |
|
42
|
|
|
* |
|
43
|
|
|
* @return array |
|
44
|
|
|
*/ |
|
45
|
|
|
protected function getNodes($folder, $subDepth) { |
|
46
|
|
|
try { |
|
47
|
|
|
$nodes = $folder->getDirectoryListing(); |
|
48
|
|
|
} catch (\Exception $exception) { |
|
49
|
|
|
$nodes = $this->recoverFromGetNodesError($subDepth, $exception); |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
return $nodes; |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* Determines if the files are hosted locally (shared or not) and can be used by the preview |
|
57
|
|
|
* system |
|
58
|
|
|
* |
|
59
|
|
|
* isMounted() doesn't include externally hosted shares, so we need to exclude those from the |
|
60
|
|
|
* non-mounted nodes |
|
61
|
|
|
* |
|
62
|
|
|
* @param Node $node |
|
63
|
|
|
* |
|
64
|
|
|
* @return bool |
|
65
|
|
|
*/ |
|
66
|
|
|
protected function isAllowedAndAvailable($node) { |
|
67
|
|
|
try { |
|
68
|
|
|
return $node && $this->isAllowed($node) && $this->isAvailable($node); |
|
69
|
|
|
} catch (\Exception $exception) { |
|
70
|
|
|
$message = 'The folder is not available: ' . $exception->getMessage(); |
|
71
|
|
|
$this->logger->error($message); |
|
72
|
|
|
|
|
73
|
|
|
return false; |
|
74
|
|
|
} |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* Returns the node type, either 'dir' or 'file' |
|
79
|
|
|
* |
|
80
|
|
|
* If there is a problem, we return an empty string so that the node can be ignored |
|
81
|
|
|
* |
|
82
|
|
|
* @param Node $node |
|
83
|
|
|
* |
|
84
|
|
|
* @return string |
|
85
|
|
|
*/ |
|
86
|
|
|
protected function getNodeType($node) { |
|
87
|
|
|
try { |
|
88
|
|
|
$nodeType = $node->getType(); |
|
89
|
|
|
} catch (\Exception $exception) { |
|
90
|
|
|
return ''; |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
return $nodeType; |
|
94
|
|
|
} |
|
95
|
|
|
|
|
96
|
|
|
/** |
|
97
|
|
|
* Returns various information about a node |
|
98
|
|
|
* |
|
99
|
|
|
* @param Node|File|Folder $node |
|
100
|
|
|
* |
|
101
|
|
|
* @return array<string,int|string|bool|array<string,int|string>> |
|
102
|
|
|
*/ |
|
103
|
|
|
protected function getNodeData($node) { |
|
104
|
|
|
$imagePath = $this->environment->getPathFromVirtualRoot($node); |
|
105
|
|
|
$nodeId = $node->getId(); |
|
106
|
|
|
$mTime = $node->getMTime(); |
|
107
|
|
|
$etag = $node->getEtag(); |
|
108
|
|
|
$size = $node->getSize(); |
|
109
|
|
|
$sharedWithUser = $node->isShared(); |
|
110
|
|
|
$ownerData = $this->getOwnerData($node); |
|
111
|
|
|
$permissions = $node->getPermissions(); |
|
112
|
|
|
$exif = $this->getEXIF('data'.$node->getPath()); |
|
113
|
|
|
|
|
114
|
|
|
//$this->logger->debug("Image path : {var1}", ['var1' => $imagePath]); |
|
|
|
|
|
|
115
|
|
|
|
|
116
|
|
|
return $this->formatNodeData( |
|
117
|
|
|
$imagePath, $nodeId, $mTime, $etag, $size, $sharedWithUser, $ownerData, $permissions, $exif |
|
118
|
|
|
); |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* Returns specific EXIF data for given path (null if directory) |
|
123
|
|
|
* |
|
124
|
|
|
* @param string $path |
|
125
|
|
|
* |
|
126
|
|
|
* @return null|array<string,int|string> |
|
|
|
|
|
|
127
|
|
|
*/ |
|
128
|
|
|
protected function getEXIF($path) { |
|
129
|
|
|
if (is_dir($path)) { |
|
130
|
|
|
return null; |
|
131
|
|
|
} |
|
132
|
|
|
$taken = null; |
|
133
|
|
|
$data = @exif_read_data($path, 'EXIF'); |
|
134
|
|
|
if ($data) { |
|
135
|
|
|
$value = @$data['DateTimeOriginal']; // -> 'date taken' field |
|
136
|
|
|
if ($value) { |
|
137
|
|
|
$date = date_parse_from_format('Y:m:d H:i:s', $value); |
|
138
|
|
|
$taken = mktime( |
|
139
|
|
|
$date['hour'], $date['minute'], $date['second'], |
|
140
|
|
|
$date['month'], $date['day'], $date['year'] |
|
141
|
|
|
); |
|
142
|
|
|
} |
|
143
|
|
|
} |
|
144
|
|
|
return ['taken_date' => ($taken ? $taken : 0)]; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* Returns various information about a folder |
|
149
|
|
|
* |
|
150
|
|
|
* @param Folder $node |
|
151
|
|
|
* |
|
152
|
|
|
* @return array<string,int|string|bool|array<string,int|string>> |
|
153
|
|
|
*/ |
|
154
|
|
|
protected function getFolderData($node) { |
|
155
|
|
|
$folderData = $this->getNodeData($node); |
|
156
|
|
|
$folderData['freespace'] = $node->getFreeSpace(); |
|
157
|
|
|
|
|
158
|
|
|
return $folderData; |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* Returns the node if it's a folder we have access to |
|
163
|
|
|
* |
|
164
|
|
|
* @param Folder $node |
|
165
|
|
|
* @param string $nodeType |
|
166
|
|
|
* |
|
167
|
|
|
* @return array|Folder |
|
168
|
|
|
*/ |
|
169
|
|
|
protected function getAllowedSubFolder($node, $nodeType) { |
|
170
|
|
|
if ($nodeType === 'dir') { |
|
171
|
|
|
/** @var Folder $node */ |
|
172
|
|
|
if (!$node->nodeExists($this->ignoreAlbum)) { |
|
173
|
|
|
return [$node]; |
|
174
|
|
|
} |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
return []; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** |
|
181
|
|
|
* Determines if we've reached the root folder |
|
182
|
|
|
* |
|
183
|
|
|
* @param Folder $folder |
|
184
|
|
|
* @param int $level |
|
185
|
|
|
* |
|
186
|
|
|
* @return bool |
|
187
|
|
|
*/ |
|
188
|
|
|
protected function isRootFolder($folder, $level) { |
|
189
|
|
|
$isRootFolder = false; |
|
190
|
|
|
$rootFolder = $this->environment->getVirtualRootFolder(); |
|
191
|
|
|
if ($folder->getPath() === $rootFolder->getPath()) { |
|
192
|
|
|
$isRootFolder = true; |
|
193
|
|
|
} |
|
194
|
|
|
$virtualRootFolder = $this->environment->getPathFromVirtualRoot($folder); |
|
195
|
|
|
if (empty($virtualRootFolder)) { |
|
196
|
|
|
$this->virtualRootLevel = $level; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
return $isRootFolder; |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
/** |
|
203
|
|
|
* Throws an exception if this problem occurs in the current folder, otherwise just ignores the |
|
204
|
|
|
* sub-folder |
|
205
|
|
|
* |
|
206
|
|
|
* @param int $subDepth |
|
207
|
|
|
* @param \Exception $exception |
|
208
|
|
|
* |
|
209
|
|
|
* @return array |
|
210
|
|
|
* @throws NotFoundServiceException |
|
211
|
|
|
*/ |
|
212
|
|
|
private function recoverFromGetNodesError($subDepth, $exception) { |
|
213
|
|
|
if ($subDepth === 0) { |
|
214
|
|
|
throw new NotFoundServiceException($exception->getMessage()); |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
return []; |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
/** |
|
221
|
|
|
* Determines if we can consider the node mounted locally or if it's been authorised to be |
|
222
|
|
|
* scanned |
|
223
|
|
|
* |
|
224
|
|
|
* @param Node $node |
|
225
|
|
|
* |
|
226
|
|
|
* @return bool |
|
227
|
|
|
*/ |
|
228
|
|
|
private function isAllowed($node) { |
|
229
|
|
|
$allowed = true; |
|
230
|
|
|
if ($this->isExternalShare($node)) { |
|
231
|
|
|
$allowed = $this->isExternalShareAllowed(); |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
if ($node->isMounted()) { |
|
235
|
|
|
$mount = $node->getMountPoint(); |
|
236
|
|
|
$allowed = $mount && $mount->getOption('previews', true); |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
return $allowed; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* Determines if the node is available, as in readable |
|
244
|
|
|
* |
|
245
|
|
|
* @todo Test to see by how much using file_exists slows things down |
|
246
|
|
|
* |
|
247
|
|
|
* @param Node $node |
|
248
|
|
|
* |
|
249
|
|
|
* @return bool |
|
250
|
|
|
*/ |
|
251
|
|
|
private function isAvailable($node) { |
|
252
|
|
|
return $node->isReadable(); |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
/** |
|
256
|
|
|
* Determines if the user has allowed the use of external shares |
|
257
|
|
|
* |
|
258
|
|
|
* @return bool |
|
259
|
|
|
*/ |
|
260
|
|
|
private function isExternalShareAllowed() { |
|
261
|
|
|
$rootFolder = $this->environment->getVirtualRootFolder(); |
|
262
|
|
|
|
|
263
|
|
|
return ($this->isExternalShare($rootFolder) |
|
|
|
|
|
|
264
|
|
|
|| in_array('external_shares', $this->features)); |
|
265
|
|
|
} |
|
266
|
|
|
|
|
267
|
|
|
/** |
|
268
|
|
|
* Determines if the node is a share which is hosted externally |
|
269
|
|
|
* |
|
270
|
|
|
* |
|
271
|
|
|
* @param Node $node |
|
272
|
|
|
* |
|
273
|
|
|
* @return bool |
|
274
|
|
|
*/ |
|
275
|
|
|
private function isExternalShare($node) { |
|
276
|
|
|
$sid = explode( |
|
277
|
|
|
':', |
|
278
|
|
|
$node->getStorage() |
|
279
|
|
|
->getId() |
|
280
|
|
|
); |
|
281
|
|
|
|
|
282
|
|
|
return ($sid[0] === 'shared' && $sid[2][0] !== '/'); |
|
283
|
|
|
} |
|
284
|
|
|
|
|
285
|
|
|
/** |
|
286
|
|
|
* Returns what we known about the owner of a node |
|
287
|
|
|
* |
|
288
|
|
|
* @param Node $node |
|
289
|
|
|
* |
|
290
|
|
|
* @return null|array<string,int|string> |
|
|
|
|
|
|
291
|
|
|
*/ |
|
292
|
|
|
private function getOwnerData($node) { |
|
293
|
|
|
$owner = $node->getOwner(); |
|
294
|
|
|
$ownerData = []; |
|
295
|
|
|
if ($owner) { |
|
296
|
|
|
$ownerData = [ |
|
297
|
|
|
'uid' => $owner->getUID(), |
|
298
|
|
|
'displayname' => $owner->getDisplayName() |
|
299
|
|
|
]; |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
return $ownerData; |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
/** |
|
306
|
|
|
* Returns an array containing information about a node |
|
307
|
|
|
* |
|
308
|
|
|
* @param string $imagePath |
|
309
|
|
|
* @param int $nodeId |
|
310
|
|
|
* @param int $mTime |
|
311
|
|
|
* @param string $etag |
|
312
|
|
|
* @param int $size |
|
313
|
|
|
* @param bool $sharedWithUser |
|
314
|
|
|
* @param array <string,int|string> $ownerData |
|
315
|
|
|
* @param int $permissions |
|
316
|
|
|
* |
|
317
|
|
|
* @return array |
|
318
|
|
|
*/ |
|
319
|
|
|
private function formatNodeData( |
|
320
|
|
|
$imagePath, $nodeId, $mTime, $etag, $size, $sharedWithUser, $ownerData, $permissions, $exif |
|
321
|
|
|
) { |
|
322
|
|
|
return [ |
|
323
|
|
|
'path' => $imagePath, |
|
324
|
|
|
'nodeid' => $nodeId, |
|
325
|
|
|
'mtime' => $mTime, |
|
326
|
|
|
'etag' => $etag, |
|
327
|
|
|
'size' => $size, |
|
328
|
|
|
'sharedwithuser' => $sharedWithUser, |
|
329
|
|
|
'owner' => $ownerData, |
|
330
|
|
|
'permissions' => $permissions, |
|
331
|
|
|
'exif' => $exif |
|
332
|
|
|
]; |
|
333
|
|
|
} |
|
334
|
|
|
|
|
335
|
|
|
} |
|
336
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.