Skip to content
This repository was archived by the owner on Mar 26, 2024. It is now read-only.

Commit d4b2bb7

Browse files
committed
media/create: add /create endpoint
See matrix-org/matrix-spec-proposals#2246 for details Signed-off-by: Sumner Evans <[email protected]>
1 parent 6615ea4 commit d4b2bb7

File tree

5 files changed

+140
-1
lines changed

5 files changed

+140
-1
lines changed

synapse/media/media_repository.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(self, hs: "HomeServer"):
7878
self.store = hs.get_datastores().main
7979
self.max_upload_size = hs.config.media.max_upload_size
8080
self.max_image_pixels = hs.config.media.max_image_pixels
81+
self.unused_expiration_time = hs.config.media.unused_expiration_time
8182

8283
Thumbnailer.set_limits(self.max_image_pixels)
8384

@@ -183,6 +184,28 @@ def mark_recently_accessed(self, server_name: Optional[str], media_id: str) -> N
183184
else:
184185
self.recently_accessed_locals.add(media_id)
185186

187+
@trace
188+
async def create_media_id(self, auth_user: UserID) -> Tuple[str, int]:
189+
"""Create and store a media ID for a local user and return the MXC URI and its
190+
expiration.
191+
Args:
192+
auth_user: The user_id of the uploader
193+
Returns:
194+
A tuple containing the MXC URI of the stored content and the timestamp at
195+
which the MXC URI expires.
196+
"""
197+
media_id = random_string(24)
198+
now = self.clock.time_msec()
199+
# After the configured amount of time, don't allow the upload to start.
200+
unused_expires_at = now + self.unused_expiration_time
201+
await self.store.store_local_media_id(
202+
media_id=media_id,
203+
time_now_ms=now,
204+
user_id=auth_user,
205+
unused_expires_at=unused_expires_at,
206+
)
207+
return f"mxc://{self.server_name}/{media_id}", unused_expires_at
208+
186209
@trace
187210
async def create_content(
188211
self,

synapse/rest/media/create_resource.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright 2023 Beeper Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
import re
17+
from typing import TYPE_CHECKING
18+
19+
from synapse.api.ratelimiting import Ratelimiter
20+
from synapse.http.server import DirectServeJsonResource, respond_with_json
21+
from synapse.http.site import SynapseRequest
22+
23+
if TYPE_CHECKING:
24+
from synapse.media.media_repository import MediaRepository
25+
from synapse.server import HomeServer
26+
27+
logger = logging.getLogger(__name__)
28+
29+
30+
class CreateResource(DirectServeJsonResource):
31+
PATTERNS = [re.compile("/_matrix/media/v1/create")]
32+
isLeaf = True
33+
34+
def __init__(self, hs: "HomeServer", media_repo: "MediaRepository"):
35+
super().__init__()
36+
37+
self.media_repo = media_repo
38+
self.clock = hs.get_clock()
39+
self.auth = hs.get_auth()
40+
41+
# A rate limiter for creating new media IDs.
42+
self._create_media_rate_limiter = Ratelimiter(
43+
store=hs.get_datastores().main,
44+
clock=self.clock,
45+
cfg=hs.config.ratelimiting.rc_media_create,
46+
)
47+
48+
async def _async_render_OPTIONS(self, request: SynapseRequest) -> None:
49+
respond_with_json(request, 200, {}, send_cors=True)
50+
51+
async def _async_render_POST(self, request: SynapseRequest) -> None:
52+
requester = await self.auth.get_user_by_req(request)
53+
54+
# If the create media requests for the user are over the limit, drop them.
55+
await self._create_media_rate_limiter.ratelimit(requester)
56+
57+
content_uri, unused_expires_at = await self.media_repo.create_media_id(
58+
requester.user
59+
)
60+
61+
logger.info(
62+
"Created Media URI %r that if unused will expire at %d",
63+
content_uri,
64+
unused_expires_at,
65+
)
66+
respond_with_json(
67+
request,
68+
200,
69+
{
70+
"content_uri": content_uri,
71+
"unused_expires_at": unused_expires_at,
72+
},
73+
send_cors=True,
74+
)

synapse/rest/media/media_repository_resource.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from synapse.http.server import HttpServer, JsonResource
1919

2020
from .config_resource import MediaConfigResource
21+
from .create_resource import CreateResource
2122
from .download_resource import DownloadResource
2223
from .preview_url_resource import PreviewUrlResource
2324
from .thumbnail_resource import ThumbnailResource
@@ -91,7 +92,7 @@ def register_servlets(http_server: HttpServer, hs: "HomeServer") -> None:
9192

9293
# Note that many of these should not exist as v1 endpoints, but empirically
9394
# a lot of traffic still goes to them.
94-
95+
CreateResource(hs, media_repo).register(http_server)
9596
UploadResource(hs, media_repo).register(http_server)
9697
DownloadResource(hs, media_repo).register(http_server)
9798
ThumbnailResource(hs, media_repo, media_repo.media_storage).register(

synapse/storage/databases/main/media_repository.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,25 @@ def _get_local_media_ids_txn(txn: LoggingTransaction) -> List[str]:
330330
"get_local_media_ids", _get_local_media_ids_txn
331331
)
332332

333+
@trace
334+
async def store_local_media_id(
335+
self,
336+
media_id: str,
337+
time_now_ms: int,
338+
user_id: UserID,
339+
unused_expires_at: int,
340+
) -> None:
341+
await self.db_pool.simple_insert(
342+
"local_media_repository",
343+
{
344+
"media_id": media_id,
345+
"created_ts": time_now_ms,
346+
"user_id": user_id.to_string(),
347+
"unused_expires_at": unused_expires_at,
348+
},
349+
desc="store_local_media_id",
350+
)
351+
333352
@trace
334353
async def store_local_media(
335354
self,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* Copyright 2023 Beeper Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
-- Add new colums to the `local_media_repository` to keep track of when the
17+
-- media ID must be used by. This is to support async uploads (see MSC2246).
18+
19+
ALTER TABLE local_media_repository
20+
ADD COLUMN unused_expires_at BIGINT DEFAULT NULL;
21+
22+
CREATE INDEX CONCURRENTLY ON local_media_repository (unused_expires_at);

0 commit comments

Comments
 (0)