Skip to content

Commit 74cdd1e

Browse files
committed
Replace generic avatar system with specific Talk endpoints
The generic avatar system will not be included in Nextcloud 21, so for the time being Talk needs to provide its own endpoints for room avatars. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
1 parent 0eae410 commit 74cdd1e

File tree

5 files changed

+265
-8
lines changed

5 files changed

+265
-8
lines changed

appinfo/routes.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,5 +592,33 @@
592592
'apiVersion' => 'v1',
593593
],
594594
],
595+
596+
/**
597+
* Room avatar
598+
*/
599+
[
600+
'name' => 'RoomAvatar#getAvatar',
601+
'url' => '/api/{apiVersion}/avatar/{roomToken}/{size}',
602+
'verb' => 'GET',
603+
'requirements' => [
604+
'apiVersion' => 'v3',
605+
],
606+
],
607+
[
608+
'name' => 'RoomAvatar#setAvatar',
609+
'url' => '/api/{apiVersion}/avatar/{roomToken}',
610+
'verb' => 'POST',
611+
'requirements' => [
612+
'apiVersion' => 'v3',
613+
],
614+
],
615+
[
616+
'name' => 'RoomAvatar#deleteAvatar',
617+
'url' => '/api/{apiVersion}/avatar/{roomToken}',
618+
'verb' => 'DELETE',
619+
'requirements' => [
620+
'apiVersion' => 'v3',
621+
],
622+
],
595623
],
596624
];

lib/AppInfo/Application.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
2727
use OCA\Talk\Activity\Listener as ActivityListener;
2828
use OCA\Talk\Avatar\Listener as AvatarListener;
29-
use OCA\Talk\Avatar\RoomAvatarProvider;
3029
use OCA\Talk\Capabilities;
3130
use OCA\Talk\Chat\Changelog\Listener as ChangelogListener;
3231
use OCA\Talk\Chat\ChatManager;
@@ -104,8 +103,6 @@ public function register(IRegistrationContext $context): void {
104103
$context->registerSearchProvider(MessageSearch::class);
105104

106105
$context->registerDashboardWidget(TalkWidget::class);
107-
108-
$context->registerAvatarProvider('room', RoomAvatarProvider::class);
109106
}
110107

111108
public function boot(IBootContext $context): void {

lib/Avatar/RoomAvatarProvider.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@
3333
use OCP\Files\IAppData;
3434
use OCP\Files\NotFoundException;
3535
use OCP\IAvatar;
36-
use OCP\IAvatarProvider;
3736
use OCP\IL10N;
3837
use Psr\Log\LoggerInterface;
3938

40-
class RoomAvatarProvider implements IAvatarProvider {
39+
class RoomAvatarProvider {
4140

4241
/** @var IAppData */
4342
private $appData;
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2020, Daniel Calviño Sánchez (danxuliu@gmail.com)
7+
*
8+
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\Talk\Controller;
28+
29+
use OCA\Talk\Avatar\RoomAvatarProvider;
30+
use OCP\AppFramework\OCSController;
31+
use OCP\AppFramework\Http;
32+
use OCP\AppFramework\Http\DataResponse;
33+
use OCP\AppFramework\Http\FileDisplayResponse;
34+
use OCP\AppFramework\Http\Response;
35+
use OCP\Files\NotFoundException;
36+
use OCP\IL10N;
37+
use OCP\Image;
38+
use OCP\IRequest;
39+
use Psr\Log\LoggerInterface;
40+
41+
class RoomAvatarController extends OCSController {
42+
43+
/** @var IL10N */
44+
protected $l;
45+
46+
/** @var LoggerInterface */
47+
protected $logger;
48+
49+
/** @var RoomAvatarProvider */
50+
protected $roomAvatarProvider;
51+
52+
public function __construct($appName,
53+
IRequest $request,
54+
IL10N $l10n,
55+
LoggerInterface $logger,
56+
RoomAvatarProvider $roomAvatarProvider) {
57+
parent::__construct($appName, $request);
58+
59+
$this->l = $l10n;
60+
$this->logger = $logger;
61+
$this->roomAvatarProvider = $roomAvatarProvider;
62+
}
63+
64+
/**
65+
* @PublicPage
66+
*
67+
* @param string $roomToken
68+
* @param int $size
69+
* @return DataResponse|FileDisplayResponse
70+
*/
71+
public function getAvatar(string $roomToken, int $size): Response {
72+
$size = $this->sanitizeSize($size);
73+
74+
try {
75+
$avatar = $this->roomAvatarProvider->getAvatar($roomToken);
76+
} catch (\InvalidArgumentException $e) {
77+
return new DataResponse([], Http::STATUS_NOT_FOUND);
78+
}
79+
80+
if (!$this->roomAvatarProvider->canBeAccessedByCurrentUser($avatar)) {
81+
return new DataResponse([], Http::STATUS_NOT_FOUND);
82+
}
83+
84+
try {
85+
$avatarFile = $avatar->getFile($size);
86+
$response = new FileDisplayResponse(
87+
$avatarFile,
88+
Http::STATUS_OK,
89+
[
90+
'Content-Type' => $avatarFile->getMimeType(),
91+
'X-NC-IsCustomAvatar' => $avatar->isCustomAvatar() ? '1' : '0',
92+
]
93+
);
94+
} catch (NotFoundException $e) {
95+
return new DataResponse([], Http::STATUS_NOT_FOUND);
96+
}
97+
98+
$cache = $this->roomAvatarProvider->getCacheTimeToLive($avatar);
99+
if ($cache !== null) {
100+
$response->cacheFor($cache);
101+
}
102+
103+
return $response;
104+
}
105+
106+
/**
107+
* Returns the closest value to the predefined set of sizes
108+
*
109+
* @param int $size the size to sanitize
110+
* @return int the sanitized size
111+
*/
112+
private function sanitizeSize(int $size): int {
113+
$validSizes = [64, 128, 256, 512];
114+
115+
if ($size < $validSizes[0]) {
116+
return $validSizes[0];
117+
}
118+
119+
if ($size > $validSizes[count($validSizes) - 1]) {
120+
return $validSizes[count($validSizes) - 1];
121+
}
122+
123+
for ($i = 0; $i < count($validSizes) - 1; $i++) {
124+
if ($size >= $validSizes[$i] && $size <= $validSizes[$i + 1]) {
125+
$middlePoint = ($validSizes[$i] + $validSizes[$i + 1]) / 2;
126+
if ($size < $middlePoint) {
127+
return $validSizes[$i];
128+
}
129+
return $validSizes[$i + 1];
130+
}
131+
}
132+
133+
return $size;
134+
}
135+
136+
/**
137+
* @PublicPage
138+
*
139+
* @param string $roomToken
140+
* @return DataResponse
141+
*/
142+
public function setAvatar(string $roomToken): DataResponse {
143+
$files = $this->request->getUploadedFile('files');
144+
145+
if (is_null($files)) {
146+
return new DataResponse(
147+
['data' => ['message' => $this->l->t('No file provided')]],
148+
Http::STATUS_BAD_REQUEST
149+
);
150+
}
151+
152+
if (
153+
$files['error'][0] !== 0 ||
154+
!is_uploaded_file($files['tmp_name'][0]) ||
155+
\OC\Files\Filesystem::isFileBlacklisted($files['tmp_name'][0])
156+
) {
157+
return new DataResponse(
158+
['data' => ['message' => $this->l->t('Invalid file provided')]],
159+
Http::STATUS_BAD_REQUEST
160+
);
161+
}
162+
163+
if ($files['size'][0] > 20 * 1024 * 1024) {
164+
return new DataResponse(
165+
['data' => ['message' => $this->l->t('File is too big')]],
166+
Http::STATUS_BAD_REQUEST
167+
);
168+
}
169+
170+
$content = file_get_contents($files['tmp_name'][0]);
171+
unlink($files['tmp_name'][0]);
172+
173+
$image = new Image();
174+
$image->loadFromData($content);
175+
176+
try {
177+
$avatar = $this->roomAvatarProvider->getAvatar($roomToken);
178+
} catch (\InvalidArgumentException $e) {
179+
return new DataResponse([], Http::STATUS_NOT_FOUND);
180+
}
181+
182+
if (!$this->roomAvatarProvider->canBeModifiedByCurrentUser($avatar)) {
183+
return new DataResponse([], Http::STATUS_NOT_FOUND);
184+
}
185+
186+
try {
187+
$avatar->set($image);
188+
return new DataResponse(
189+
['status' => 'success']
190+
);
191+
} catch (\OC\NotSquareException $e) {
192+
return new DataResponse(
193+
['data' => ['message' => $this->l->t('Crop is not square')]],
194+
Http::STATUS_BAD_REQUEST
195+
);
196+
} catch (\Exception $e) {
197+
$this->logger->error('Error when setting avatar', ['app' => 'core', 'exception' => $e]);
198+
return new DataResponse(
199+
['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]],
200+
Http::STATUS_BAD_REQUEST
201+
);
202+
}
203+
}
204+
205+
/**
206+
* @PublicPage
207+
*
208+
* @param string $roomToken
209+
* @return DataResponse
210+
*/
211+
public function deleteAvatar(string $roomToken): DataResponse {
212+
try {
213+
$avatar = $this->roomAvatarProvider->getAvatar($roomToken);
214+
} catch (\InvalidArgumentException $e) {
215+
return new DataResponse([], Http::STATUS_NOT_FOUND);
216+
}
217+
218+
if (!$this->roomAvatarProvider->canBeModifiedByCurrentUser($avatar)) {
219+
return new DataResponse([], Http::STATUS_NOT_FOUND);
220+
}
221+
222+
try {
223+
$avatar->remove();
224+
return new DataResponse();
225+
} catch (\Exception $e) {
226+
$this->logger->error('Error when deleting avatar', ['app' => 'core', 'exception' => $e]);
227+
return new DataResponse(
228+
['data' => ['message' => $this->l->t('An error occurred. Please contact your admin.')]],
229+
Http::STATUS_BAD_REQUEST
230+
);
231+
}
232+
}
233+
}

tests/integration/features/bootstrap/AvatarTrait.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public function userGetsAvatarForRoomWithSize(string $user, string $identifier,
7474
*/
7575
public function userGetsAvatarForRoomWithSizeWith(string $user, string $identifier, string $size, string $statusCode) {
7676
$this->setCurrentUser($user);
77-
$this->sendRequest('GET', '/core/avatar/room/' . FeatureContext::getTokenForIdentifier($identifier) . '/' . $size, null);
77+
$this->sendRequest('GET', '/apps/spreed/api/v3/avatar/' . FeatureContext::getTokenForIdentifier($identifier) . '/' . $size, null);
7878
$this->assertStatusCode($this->response, $statusCode);
7979

8080
if ($statusCode !== '200') {
@@ -107,7 +107,7 @@ public function userSetsAvatarForRoomFromFileWith(string $user, string $identifi
107107
$file = \GuzzleHttp\Psr7\stream_for(fopen($source, 'r'));
108108

109109
$this->setCurrentUser($user);
110-
$this->sendRequest('POST', '/core/avatar/room/' . FeatureContext::getTokenForIdentifier($identifier),
110+
$this->sendRequest('POST', '/apps/spreed/api/v3/avatar/' . FeatureContext::getTokenForIdentifier($identifier),
111111
[
112112
'multipart' => [
113113
[
@@ -138,7 +138,7 @@ public function userDeletesAvatarForRoom(string $user, string $identifier) {
138138
*/
139139
public function userDeletesAvatarForRoomWith(string $user, string $identifier, string $statusCode) {
140140
$this->setCurrentUser($user);
141-
$this->sendRequest('DELETE', '/core/avatar/room/' . FeatureContext::getTokenForIdentifier($identifier), null);
141+
$this->sendRequest('DELETE', '/apps/spreed/api/v3/avatar/' . FeatureContext::getTokenForIdentifier($identifier), null);
142142
$this->assertStatusCode($this->response, $statusCode);
143143
}
144144

0 commit comments

Comments
 (0)