|
| 1 | +""" |
| 2 | +cs3resource.py |
| 3 | +
|
| 4 | +Authors: Rasmus Welander, Diogo Castro, Giuseppe Lo Presti. |
| 5 | + |
| 6 | +Last updated: 29/07/2024 |
| 7 | +""" |
| 8 | + |
| 9 | +import cs3.storage.provider.v1beta1.resources_pb2 as cs3spr |
| 10 | + |
| 11 | + |
| 12 | +class Resource: |
| 13 | + """Class to handle CS3 resources, the class can be initialized with an absolute path, |
| 14 | + a relative path or an opaque fileid as the "file" parameter (required). |
| 15 | +
|
| 16 | + Absolute path example: `/path/to/file` |
| 17 | + Relative path example: `<parent_opaque_id>/<base_filename>` or `<parent_opaque_id>/<path>/<to>/<file>` |
| 18 | + Opaque fileid example: `<opaque_file_id>` |
| 19 | +
|
| 20 | + The endpoint attribute contains the storage_id and space_id (always optional) separated by a `$` character, |
| 21 | + this is optional if the file is an absolute path. |
| 22 | +
|
| 23 | + endpoint example: `storage_id` or `storage_id$space_id` |
| 24 | +
|
| 25 | + """ |
| 26 | + |
| 27 | + def __init__( |
| 28 | + self, |
| 29 | + abs_path: str | None = None, |
| 30 | + rel_path: str | None = None, |
| 31 | + opaque_id: str | None = None, |
| 32 | + parent_id: str | None = None, |
| 33 | + storage_id: str | None = None, |
| 34 | + space_id: str | None = None, |
| 35 | + ) -> None: |
| 36 | + """ |
| 37 | + initializes the Resource class, either abs_path, rel_path or opaque_id is required |
| 38 | + and with rel_path the parent_id is also required, the rest parameters are fully optional. |
| 39 | +
|
| 40 | +
|
| 41 | + :param abs_path: absolute path (semi-optional) |
| 42 | + :param rel_path: relative path (semi-optional) |
| 43 | + :param parent_id: parent id (semi-optional) |
| 44 | + :param opaque_id: opaque id (semi-optional) |
| 45 | + :param storage_id: storage id (optional) |
| 46 | + :param space_id: space id (optional) |
| 47 | + """ |
| 48 | + self._abs_path: str | None = abs_path |
| 49 | + self._rel_path: str | None = rel_path |
| 50 | + self._parent_id: str | None = parent_id |
| 51 | + self._opaque_id: str | None = opaque_id |
| 52 | + self._space_id: str | None = space_id |
| 53 | + self._storage_id: str | None = storage_id |
| 54 | + |
| 55 | + @classmethod |
| 56 | + def from_file_ref_and_endpoint(cls, file: str, endpoint: str | None = None) -> "Resource": |
| 57 | + """ |
| 58 | + Extracts the attributes from the file and endpoint and returns a resource. |
| 59 | +
|
| 60 | + :param file: The file reference |
| 61 | + :param endpoint: The storage id and space id (optional) |
| 62 | + :return: Resource object |
| 63 | + """ |
| 64 | + |
| 65 | + abs_path = None |
| 66 | + rel_path = None |
| 67 | + opaque_id = None |
| 68 | + parent_id = None |
| 69 | + storage_id = None |
| 70 | + space_id = None |
| 71 | + |
| 72 | + if file.startswith("/"): |
| 73 | + # assume we have an absolute path |
| 74 | + abs_path = file |
| 75 | + else: |
| 76 | + # try splitting endpoint |
| 77 | + parts = endpoint.split("$", 2) |
| 78 | + storage_id = parts[0] |
| 79 | + if len(parts) == 2: |
| 80 | + space_id = parts[1] |
| 81 | + if file.find("/") > 0: |
| 82 | + # assume we have an relative path, |
| 83 | + parent_id = file[: file.find("/")] |
| 84 | + rel_path = file[file.find("/") :] |
| 85 | + else: |
| 86 | + # assume we have an opaque fileid |
| 87 | + opaque_id = file |
| 88 | + return cls(abs_path, rel_path, opaque_id, parent_id, storage_id, space_id) |
| 89 | + |
| 90 | + @property |
| 91 | + def ref(self) -> cs3spr.Reference: |
| 92 | + """ |
| 93 | + Generates a CS3 reference for a given resource, covering the following cases: |
| 94 | + absolute path, relative hybrid path, fully opaque fileid. |
| 95 | +
|
| 96 | + :return: The cs3 reference. |
| 97 | + May throw ValueError (Invalid Resource) |
| 98 | + """ |
| 99 | + if self._abs_path: |
| 100 | + return cs3spr.Reference(path=self._abs_path) |
| 101 | + if self._rel_path: |
| 102 | + return cs3spr.Reference( |
| 103 | + resource_id=cs3spr.ResourceId( |
| 104 | + storage_id=self._storage_id, |
| 105 | + space_id=self._space_id, |
| 106 | + opaque_id=self._parent_id, |
| 107 | + ), |
| 108 | + path="." + self._rel_path, |
| 109 | + ) |
| 110 | + if self._opaque_id: |
| 111 | + return cs3spr.Reference( |
| 112 | + resource_id=cs3spr.ResourceId( |
| 113 | + storage_id=self._storage_id, |
| 114 | + space_id=self._space_id, |
| 115 | + opaque_id=self._opaque_id, |
| 116 | + ), |
| 117 | + path=".", |
| 118 | + ) |
| 119 | + raise ValueError("Invalid Resource") |
| 120 | + |
| 121 | + def recreate_endpoint_and_file(self) -> dict: |
| 122 | + """ |
| 123 | + Recreates the endpoint and file reference from the given resource |
| 124 | +
|
| 125 | + :return: (dict) {"file": fileref, "endpoint": endpoint} |
| 126 | + May throw ValueError (invalid resource) |
| 127 | + """ |
| 128 | + endpoint = self._storage_id |
| 129 | + if self._space_id: |
| 130 | + endpoint += f"${self._space_id}" |
| 131 | + if self._abs_path: |
| 132 | + return {"file": self._abs_path, "endpoint": endpoint} |
| 133 | + if self._parent_id and self._rel_path: |
| 134 | + return {"file": f"{self._parent_id}{self._rel_path}", "endpoint": endpoint} |
| 135 | + if self._opaque_id: |
| 136 | + return {"file": self._opaque_id, "endpoint": endpoint} |
| 137 | + raise ValueError("Invalid Resource") |
| 138 | + |
| 139 | + @classmethod |
| 140 | + def from_cs3_ref(cls, reference: cs3spr.Reference) -> "Resource": |
| 141 | + """ |
| 142 | + Alternate constructor that reverses a CS3 reference to obtain a resource.ß |
| 143 | +
|
| 144 | + :param reference: The CS3 reference. |
| 145 | + :return: Resource object. |
| 146 | + May throw ValueError (Invalid reference) |
| 147 | + """ |
| 148 | + rel_path = None |
| 149 | + opaque_id = None |
| 150 | + parent_id = None |
| 151 | + storage_id = None |
| 152 | + space_id = None |
| 153 | + |
| 154 | + if reference.path and reference.path.startswith("/"): |
| 155 | + # It's an absolute path, we can return straight away |
| 156 | + return Resource(abs_path=reference.path) |
| 157 | + elif reference.resource_id and reference.resource_id.storage_id: |
| 158 | + storage_id = reference.resource_id.storage_id |
| 159 | + if reference.resource_id.space_id: |
| 160 | + space_id = reference.resource_id.space_id |
| 161 | + if reference.path and len(reference.path) > 1: |
| 162 | + # It's a relative path (remove the "." in the relative path) |
| 163 | + rel_path = reference.path[1:] |
| 164 | + # The opaque_id is a parent id since it's a relative pathß |
| 165 | + parent_id = reference.resource_id.opaque_id |
| 166 | + else: |
| 167 | + opaque_id = reference.resource_id.opaque_id |
| 168 | + return Resource( |
| 169 | + abs_path=None, |
| 170 | + rel_path=rel_path, |
| 171 | + opaque_id=opaque_id, |
| 172 | + parent_id=parent_id, |
| 173 | + storage_id=storage_id, |
| 174 | + space_id=space_id, |
| 175 | + ) |
| 176 | + raise ValueError("Invalid CS3 reference") |
| 177 | + |
| 178 | + # It is possible that the same resource is different if abs_path is used in one |
| 179 | + # and the other is using opaque_id for example. |
| 180 | + def __eq__(self, other): |
| 181 | + """redefine the equality operator to compare two resources""" |
| 182 | + if isinstance(other, Resource): |
| 183 | + return ( |
| 184 | + self._abs_path == other._abs_path |
| 185 | + and self._rel_path == other._rel_path |
| 186 | + and self._parent_id == other._parent_id |
| 187 | + and self._opaque_id == other._opaque_id |
| 188 | + and self._space_id == other._space_id |
| 189 | + and self._storage_id == other._storage_id |
| 190 | + ) |
| 191 | + return False |
| 192 | + |
| 193 | + def get_file_ref_str(self): |
| 194 | + """ |
| 195 | + Generates a string from the file ref, '<type>="fileref">' |
| 196 | +
|
| 197 | + :return: str '<type>="fileref">' |
| 198 | + """ |
| 199 | + if self._abs_path: |
| 200 | + return f'absolute_path="{self._abs_path}"' |
| 201 | + elif self._rel_path: |
| 202 | + return f'relative_path="{self._parent_id}/{self._rel_path}"' |
| 203 | + elif self._opaque_id: |
| 204 | + return f'opaque_id="{self._opaque_id}"' |
0 commit comments