diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index b240498c..f9189ba2 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -17,6 +17,13 @@ * mimeType: string, * hasPreview: bool, * reason: string, + * etag: string, + * mtime: int, + * size: int|float, + * permissions: int, + * ownerId: ?string, + * ownerDisplayName: ?string, + * shareAttributes: list, * } * * @psalm-suppress UnusedClass diff --git a/lib/Service/RecommendedFile.php b/lib/Service/RecommendedFile.php index a3600965..838a285e 100644 --- a/lib/Service/RecommendedFile.php +++ b/lib/Service/RecommendedFile.php @@ -10,6 +10,9 @@ namespace OCA\Recommendations\Service; use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\Storage\ISharedStorage; +use OCP\Share\IAttributes; use ResponseDefinitions; /** @@ -82,6 +85,36 @@ public function jsonSerialize() { 'mimeType' => $this->node->getMimetype(), 'hasPreview' => $this->hasPreview(), 'reason' => $this->getReason(), + 'etag' => $this->node->getEtag(), + 'mtime' => $this->node->getMTime(), + 'size' => $this->node->getSize(), + 'permissions' => $this->node->getPermissions(), + 'ownerId' => $this->node->getOwner()?->getUID(), + 'ownerDisplayName' => $this->node->getOwner()?->getDisplayName(), + 'shareAttributes' => $this->getShareAttributes(), ]; } + + /** + * What the share the file was received through allows, as WebDAV gives it + * in `share-attributes`: a share can forbid downloading it, for one. + * + * @return list + */ + private function getShareAttributes(): array { + try { + $storage = $this->node->getStorage(); + } catch (NotFoundException) { + return []; + } + if (!$storage->instanceOfStorage(ISharedStorage::class)) { + return []; + } + /** @var ISharedStorage $storage */ + $attributes = $storage->getShare()->getAttributes(); + if (!$attributes instanceof IAttributes) { + return []; + } + return array_values($attributes->toArray()); + } } diff --git a/openapi.json b/openapi.json index 75ee8d37..50d175cc 100644 --- a/openapi.json +++ b/openapi.json @@ -70,7 +70,14 @@ "extension", "mimeType", "hasPreview", - "reason" + "reason", + "etag", + "mtime", + "size", + "permissions", + "ownerId", + "ownerDisplayName", + "shareAttributes" ], "properties": { "id": { @@ -97,6 +104,59 @@ }, "reason": { "type": "string" + }, + "etag": { + "type": "string" + }, + "mtime": { + "type": "integer", + "format": "int64" + }, + "size": { + "anyOf": [ + { + "type": "integer", + "format": "int64" + }, + { + "type": "number", + "format": "double" + } + ] + }, + "permissions": { + "type": "integer", + "format": "int64" + }, + "ownerId": { + "type": "string", + "nullable": true + }, + "ownerDisplayName": { + "type": "string", + "nullable": true + }, + "shareAttributes": { + "type": "array", + "items": { + "type": "object", + "required": [ + "scope", + "key", + "value" + ], + "properties": { + "scope": { + "type": "string" + }, + "key": { + "type": "string" + }, + "value": { + "type": "object" + } + } + } } } } diff --git a/tests/Unit/Service/RecommendedFileTest.php b/tests/Unit/Service/RecommendedFileTest.php new file mode 100644 index 00000000..7d7851fe --- /dev/null +++ b/tests/Unit/Service/RecommendedFileTest.php @@ -0,0 +1,75 @@ +createMock(IUser::class); + $owner->method('getUID')->willReturn('bob'); + $owner->method('getDisplayName')->willReturn('Bob'); + + $this->node = $this->createMock(File::class); + $this->node->method('getId')->willReturn(42); + $this->node->method('getName')->willReturn('beach.jpg'); + $this->node->method('getExtension')->willReturn('jpg'); + $this->node->method('getMimetype')->willReturn('image/jpeg'); + $this->node->method('getEtag')->willReturn('abc123'); + $this->node->method('getMTime')->willReturn(1760000000); + $this->node->method('getSize')->willReturn(2048); + $this->node->method('getPermissions')->willReturn(27); + $this->node->method('getOwner')->willReturn($owner); + } + + // What WebDAV says of the file, for the viewer to build the same node + public function testDescribesTheFileAsWebDavDoes(): void { + $this->node->method('getStorage')->willReturn($this->createMock(IStorage::class)); + + $file = (new RecommendedFile('/Photos', $this->node, 1760000100, 'recently-edited'))->jsonSerialize(); + + $this->assertSame('abc123', $file['etag']); + $this->assertSame(1760000000, $file['mtime']); + $this->assertSame(2048, $file['size']); + $this->assertSame(27, $file['permissions']); + $this->assertSame('bob', $file['ownerId']); + $this->assertSame('Bob', $file['ownerDisplayName']); + $this->assertSame([], $file['shareAttributes']); + } + + // A share that forbids downloading says so, as `share-attributes` does + public function testGivesWhatTheShareItCameThroughAllows(): void { + $forbidden = [['scope' => 'permissions', 'key' => 'download', 'value' => false]]; + $attributes = $this->createMock(IAttributes::class); + $attributes->method('toArray')->willReturn($forbidden); + $share = $this->createMock(IShare::class); + $share->method('getAttributes')->willReturn($attributes); + $storage = $this->createMock(ISharedStorage::class); + $storage->method('instanceOfStorage')->with(ISharedStorage::class)->willReturn(true); + $storage->method('getShare')->willReturn($share); + $this->node->method('getStorage')->willReturn($storage); + + $file = (new RecommendedFile('/Photos', $this->node, 1760000100, 'recently-shared'))->jsonSerialize(); + + $this->assertSame($forbidden, $file['shareAttributes']); + } +}