|
66 | 66 | from .s3_client import S3MetaData, StorageS3Client
|
67 | 67 | from .s3_utils import S3TransferDataCB, update_task_progress
|
68 | 68 | from .settings import Settings
|
69 |
| -from .simcore_s3_dsm_utils import expand_directory, get_simcore_directory |
| 69 | +from .simcore_s3_dsm_utils import ( |
| 70 | + expand_directory, |
| 71 | + get_directory_file_id, |
| 72 | + get_simcore_directory, |
| 73 | +) |
70 | 74 | from .utils import (
|
71 | 75 | convert_db_to_model,
|
72 | 76 | download_to_file_or_raise,
|
@@ -383,37 +387,91 @@ async def complete_file_upload(
|
383 | 387 | async def create_file_download_link(
|
384 | 388 | self, user_id: UserID, file_id: StorageFileID, link_type: LinkType
|
385 | 389 | ) -> AnyUrl:
|
| 390 | + """ |
| 391 | + Cases: |
| 392 | + 1. the file_id maps 1:1 to `file_meta_data` (e.g. it is not a file inside a directory) |
| 393 | + 2. the file_id represents a file inside a directory (its root path maps 1:1 to a `file_meta_data` defined as a directory) |
| 394 | +
|
| 395 | + 3. Raises FileNotFoundError if the file does not exist |
| 396 | + 4. Raises FileAccessRightError if the user does not have access to the file |
| 397 | + """ |
386 | 398 | async with self.engine.acquire() as conn:
|
387 |
| - can: AccessRights | None = await get_file_access_rights( |
388 |
| - conn, user_id, file_id |
| 399 | + directory_file_id: SimcoreS3FileID | None = await get_directory_file_id( |
| 400 | + conn, cast(SimcoreS3FileID, file_id) |
389 | 401 | )
|
390 |
| - if not can.read: |
391 |
| - # NOTE: this is tricky. A user with read access can download and data! |
392 |
| - # If write permission would be required, then shared projects as views cannot |
393 |
| - # recover data in nodes (e.g. jupyter cannot pull work data) |
394 |
| - # |
395 |
| - raise FileAccessRightError(access_right="read", file_id=file_id) |
396 |
| - |
397 |
| - fmd = await db_file_meta_data.get( |
398 |
| - conn, parse_obj_as(SimcoreS3FileID, file_id) |
| 402 | + return ( |
| 403 | + await self._get_link_for_directory_fmd( |
| 404 | + conn, user_id, directory_file_id, file_id, link_type |
| 405 | + ) |
| 406 | + if directory_file_id |
| 407 | + else await self._get_link_for_file_fmd( |
| 408 | + conn, user_id, file_id, link_type |
| 409 | + ) |
399 | 410 | )
|
400 |
| - if not is_file_entry_valid(fmd): |
401 |
| - # try lazy update |
402 |
| - fmd = await self._update_database_from_storage(conn, fmd) |
403 | 411 |
|
| 412 | + @staticmethod |
| 413 | + async def __ensure_read_access_rights( |
| 414 | + conn: SAConnection, user_id: UserID, storage_file_id: StorageFileID |
| 415 | + ) -> None: |
| 416 | + can: AccessRights | None = await get_file_access_rights( |
| 417 | + conn, user_id, storage_file_id |
| 418 | + ) |
| 419 | + if not can.read: |
| 420 | + # NOTE: this is tricky. A user with read access can download and data! |
| 421 | + # If write permission would be required, then shared projects as views cannot |
| 422 | + # recover data in nodes (e.g. jupyter cannot pull work data) |
| 423 | + # |
| 424 | + raise FileAccessRightError(access_right="read", file_id=storage_file_id) |
| 425 | + |
| 426 | + async def __get_link( |
| 427 | + self, s3_file_id: SimcoreS3FileID, link_type: LinkType |
| 428 | + ) -> AnyUrl: |
404 | 429 | link: AnyUrl = parse_obj_as(
|
405 | 430 | AnyUrl,
|
406 |
| - f"s3://{self.simcore_bucket_name}/{urllib.parse.quote(fmd.object_name)}", |
| 431 | + f"s3://{self.simcore_bucket_name}/{urllib.parse.quote(s3_file_id)}", |
407 | 432 | )
|
408 | 433 | if link_type == LinkType.PRESIGNED:
|
409 | 434 | link = await get_s3_client(self.app).create_single_presigned_download_link(
|
410 | 435 | self.simcore_bucket_name,
|
411 |
| - fmd.object_name, |
| 436 | + s3_file_id, |
412 | 437 | self.settings.STORAGE_DEFAULT_PRESIGNED_LINK_EXPIRATION_SECONDS,
|
413 | 438 | )
|
414 | 439 |
|
415 | 440 | return link
|
416 | 441 |
|
| 442 | + async def _get_link_for_file_fmd( |
| 443 | + self, |
| 444 | + conn: SAConnection, |
| 445 | + user_id: UserID, |
| 446 | + file_id: StorageFileID, |
| 447 | + link_type: LinkType, |
| 448 | + ) -> AnyUrl: |
| 449 | + # 1. the file_id maps 1:1 to `file_meta_data` |
| 450 | + await self.__ensure_read_access_rights(conn, user_id, file_id) |
| 451 | + |
| 452 | + fmd = await db_file_meta_data.get(conn, parse_obj_as(SimcoreS3FileID, file_id)) |
| 453 | + if not is_file_entry_valid(fmd): |
| 454 | + # try lazy update |
| 455 | + fmd = await self._update_database_from_storage(conn, fmd) |
| 456 | + |
| 457 | + return await self.__get_link(fmd.object_name, link_type) |
| 458 | + |
| 459 | + async def _get_link_for_directory_fmd( |
| 460 | + self, |
| 461 | + conn: SAConnection, |
| 462 | + user_id: UserID, |
| 463 | + directory_file_id: SimcoreS3FileID, |
| 464 | + file_id: StorageFileID, |
| 465 | + link_type: LinkType, |
| 466 | + ) -> AnyUrl: |
| 467 | + # 2. the file_id represents a file inside a directory |
| 468 | + await self.__ensure_read_access_rights(conn, user_id, directory_file_id) |
| 469 | + if not await get_s3_client(self.app).file_exists( |
| 470 | + self.simcore_bucket_name, s3_object=f"{file_id}" |
| 471 | + ): |
| 472 | + raise S3KeyNotFoundError(key=file_id, bucket=self.simcore_bucket_name) |
| 473 | + return await self.__get_link(parse_obj_as(SimcoreS3FileID, file_id), link_type) |
| 474 | + |
417 | 475 | async def delete_file(self, user_id: UserID, file_id: StorageFileID):
|
418 | 476 | async with self.engine.acquire() as conn, conn.begin():
|
419 | 477 | can: AccessRights | None = await get_file_access_rights(
|
|
0 commit comments