|
18 | 18 |
|
19 | 19 | from multiformats import CID, multicodec |
20 | 20 | from . import unixfsv1 |
| 21 | +from .car import read_car |
21 | 22 |
|
22 | 23 | import logging |
23 | 24 |
|
@@ -69,20 +70,30 @@ def __str__(self): |
69 | 70 | return f"GW({self.url})" |
70 | 71 |
|
71 | 72 | async def info(self, path, session): |
72 | | - res = await self.get(path, session, headers={"Accept": "application/vnd.ipld.raw"}, params={"format": "raw"}) |
| 73 | + res = await self.get(path, session, headers={"Accept": "application/vnd.ipld.car"}, params={"format": "car", "dag-scope": "block"}) |
73 | 74 | self._raise_not_found_for_status(res, path) |
74 | | - cid = CID.decode(res.headers["X-Ipfs-Roots"].split(",")[-1]) |
| 75 | + |
| 76 | + roots = res.headers["X-Ipfs-Roots"].split(",") |
| 77 | + if len(roots) != len(path.split("/")): |
| 78 | + raise FileNotFoundError(path) |
| 79 | + |
| 80 | + cid = CID.decode(roots[-1]) |
75 | 81 | resdata = await res.read() |
76 | 82 |
|
| 83 | + _, blocks = read_car(resdata) # roots should be ignored by https://specs.ipfs.tech/http-gateways/trustless-gateway/ |
| 84 | + blocks = {cid: data for cid, data, _ in blocks} |
| 85 | + block = blocks[cid] |
| 86 | + |
77 | 87 | if cid.codec == RawCodec: |
78 | 88 | return { |
79 | 89 | "name": path, |
80 | 90 | "CID": str(cid), |
81 | 91 | "type": "file", |
82 | | - "size": len(resdata), |
| 92 | + "size": len(block), |
83 | 93 | } |
84 | 94 | elif cid.codec == DagPbCodec: |
85 | | - node = unixfsv1.PBNode.loads(resdata) |
| 95 | + |
| 96 | + node = unixfsv1.PBNode.loads(block) |
86 | 97 | data = unixfsv1.Data.loads(node.Data) |
87 | 98 | if data.Type == unixfsv1.DataType.Raw: |
88 | 99 | raise FileNotFoundError(path) # this is not a file, it's only a part of it |
@@ -133,12 +144,20 @@ async def iter_chunked(self, path, session, chunk_size): |
133 | 144 | yield size, res.content.iter_chunked(chunk_size) |
134 | 145 |
|
135 | 146 | async def ls(self, path, session, detail=False): |
136 | | - res = await self.get(path, session, headers={"Accept": "application/vnd.ipld.raw"}, params={"format": "raw"}) |
| 147 | + res = await self.get(path, session, headers={"Accept": "application/vnd.ipld.car"}, params={"format": "car", "dag-scope": "block"}) |
137 | 148 | self._raise_not_found_for_status(res, path) |
138 | | - resdata = await res.read() |
139 | | - cid = CID.decode(res.headers["X-Ipfs-Roots"].split(",")[-1]) |
| 149 | + roots = res.headers["X-Ipfs-Roots"].split(",") |
| 150 | + if len(roots) != len(path.split("/")): |
| 151 | + raise FileNotFoundError(path) |
| 152 | + |
| 153 | + cid = CID.decode(roots[-1]) |
140 | 154 | assert cid.codec == DagPbCodec, "this is not a directory" |
141 | | - node = unixfsv1.PBNode.loads(resdata) |
| 155 | + |
| 156 | + resdata = await res.read() |
| 157 | + |
| 158 | + _, blocks = read_car(resdata) # roots should be ignored by https://specs.ipfs.tech/http-gateways/trustless-gateway/ |
| 159 | + blocks = {cid: data for cid, data, _ in blocks} |
| 160 | + node = unixfsv1.PBNode.loads(blocks[cid]) |
142 | 161 | data = unixfsv1.Data.loads(node.Data) |
143 | 162 | if data.Type != unixfsv1.DataType.Directory: |
144 | 163 | # TODO: we might need support for HAMTShard here (for large directories) |
@@ -180,13 +199,17 @@ def gateway_from_file(gateway_path, protocol="ipfs"): |
180 | 199 |
|
181 | 200 |
|
182 | 201 | @lru_cache |
183 | | -def get_gateway(protocol="ipfs"): |
| 202 | +def get_gateway(protocol="ipfs", gateway_addr=None): |
184 | 203 | """ |
185 | 204 | Get IPFS gateway according to IPIP-280 |
186 | 205 |
|
187 | 206 | see: https://github.com/ipfs/specs/pull/280 |
188 | 207 | """ |
189 | 208 |
|
| 209 | + if gateway_addr: |
| 210 | + logger.debug("using IPFS gateway as specified via function argument: %s", gateway_addr) |
| 211 | + return AsyncIPFSGateway(gateway_addr, protocol) |
| 212 | + |
190 | 213 | # IPFS_GATEWAY environment variable should override everything |
191 | 214 | ipfs_gateway = os.environ.get("IPFS_GATEWAY", "") |
192 | 215 | if ipfs_gateway: |
@@ -263,19 +286,20 @@ class AsyncIPFSFileSystem(AsyncFileSystem): |
263 | 286 | sep = "/" |
264 | 287 | protocol = "ipfs" |
265 | 288 |
|
266 | | - def __init__(self, asynchronous=False, loop=None, client_kwargs=None, **storage_options): |
| 289 | + def __init__(self, asynchronous=False, loop=None, client_kwargs=None, gateway_addr=None, **storage_options): |
267 | 290 | super().__init__(self, asynchronous=asynchronous, loop=loop, **storage_options) |
268 | 291 | self._session = None |
269 | 292 |
|
270 | 293 | self.client_kwargs = client_kwargs or {} |
271 | 294 | self.get_client = get_client |
| 295 | + self.gateway_addr = gateway_addr |
272 | 296 |
|
273 | 297 | if not asynchronous: |
274 | 298 | sync(self.loop, self.set_session) |
275 | 299 |
|
276 | 300 | @property |
277 | 301 | def gateway(self): |
278 | | - return get_gateway(self.protocol) |
| 302 | + return get_gateway(self.protocol, gateway_addr=self.gateway_addr) |
279 | 303 |
|
280 | 304 | @staticmethod |
281 | 305 | def close_session(loop, session): |
|
0 commit comments