hippius 0.2.5__tar.gz → 0.2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hippius-0.2.5 → hippius-0.2.7}/PKG-INFO +1 -1
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/__init__.py +1 -1
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/cli.py +10 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/cli_handlers.py +279 -20
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/cli_parser.py +26 -3
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/ipfs.py +21 -10
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/ipfs_core.py +23 -17
- {hippius-0.2.5 → hippius-0.2.7}/pyproject.toml +1 -1
- {hippius-0.2.5 → hippius-0.2.7}/README.md +0 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/cli_assets.py +0 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/cli_rich.py +0 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/client.py +0 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/config.py +0 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/errors.py +0 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/substrate.py +0 -0
- {hippius-0.2.5 → hippius-0.2.7}/hippius_sdk/utils.py +0 -0
@@ -152,6 +152,7 @@ def main():
|
|
152
152
|
args.file_path,
|
153
153
|
miner_ids,
|
154
154
|
encrypt=encrypt,
|
155
|
+
publish=not args.no_publish if hasattr(args, "no_publish") else True,
|
155
156
|
)
|
156
157
|
|
157
158
|
elif args.command == "store-dir":
|
@@ -235,6 +236,15 @@ def main():
|
|
235
236
|
force=args.force if hasattr(args, "force") else False,
|
236
237
|
)
|
237
238
|
|
239
|
+
elif args.command == "pin":
|
240
|
+
return run_async_handler(
|
241
|
+
cli_handlers.handle_pin,
|
242
|
+
client,
|
243
|
+
args.cid,
|
244
|
+
publish=not args.no_publish if hasattr(args, "no_publish") else True,
|
245
|
+
miner_ids=miner_ids,
|
246
|
+
)
|
247
|
+
|
238
248
|
elif args.command == "ec-delete":
|
239
249
|
return run_async_handler(
|
240
250
|
cli_handlers.handle_ec_delete,
|
@@ -51,6 +51,7 @@ from hippius_sdk.errors import (
|
|
51
51
|
HippiusFailedSubstrateDelete,
|
52
52
|
HippiusMetadataError,
|
53
53
|
)
|
54
|
+
from hippius_sdk.substrate import FileInput
|
54
55
|
|
55
56
|
try:
|
56
57
|
import nacl.secret
|
@@ -246,6 +247,7 @@ async def handle_store(
|
|
246
247
|
file_path: str,
|
247
248
|
miner_ids: Optional[List[str]] = None,
|
248
249
|
encrypt: Optional[bool] = None,
|
250
|
+
publish: bool = True,
|
249
251
|
) -> int:
|
250
252
|
"""Handle the store command (upload file to IPFS and store on Substrate)"""
|
251
253
|
if not os.path.exists(file_path):
|
@@ -256,6 +258,16 @@ async def handle_store(
|
|
256
258
|
error(f"[bold]{file_path}[/bold] is not a file")
|
257
259
|
return 1
|
258
260
|
|
261
|
+
# If publishing is enabled, ensure we have a valid substrate client by accessing it
|
262
|
+
# This will trigger password prompts if needed right at the beginning
|
263
|
+
if publish and hasattr(client, "substrate_client") and client.substrate_client:
|
264
|
+
try:
|
265
|
+
# Force keypair initialization - this will prompt for password if needed
|
266
|
+
_ = client.substrate_client._ensure_keypair()
|
267
|
+
except Exception as e:
|
268
|
+
warning(f"Failed to initialize blockchain client: {str(e)}")
|
269
|
+
warning("Will continue with upload but blockchain publishing may fail")
|
270
|
+
|
259
271
|
# Get file size for display
|
260
272
|
file_size = os.path.getsize(file_path)
|
261
273
|
file_name = os.path.basename(file_path)
|
@@ -282,6 +294,19 @@ async def handle_store(
|
|
282
294
|
"[bold yellow]Encryption: Using default setting[/bold yellow]"
|
283
295
|
)
|
284
296
|
|
297
|
+
# Add publishing status
|
298
|
+
if not publish:
|
299
|
+
upload_info.append(
|
300
|
+
"[bold yellow]Publishing: Disabled (local upload only)[/bold yellow]"
|
301
|
+
)
|
302
|
+
log(
|
303
|
+
"\nUpload will be local only - not publishing to blockchain or pinning to IPFS"
|
304
|
+
)
|
305
|
+
else:
|
306
|
+
upload_info.append(
|
307
|
+
"[bold green]Publishing: Enabled (publishing to blockchain)[/bold green]"
|
308
|
+
)
|
309
|
+
|
285
310
|
# Parse miner IDs if provided
|
286
311
|
miner_id_list = None
|
287
312
|
if miner_ids:
|
@@ -318,13 +343,51 @@ async def handle_store(
|
|
318
343
|
updater = asyncio.create_task(update_progress())
|
319
344
|
|
320
345
|
try:
|
321
|
-
# Use the
|
346
|
+
# Use the upload_file method to get the CID
|
322
347
|
result = await client.upload_file(
|
323
348
|
file_path=file_path,
|
324
349
|
encrypt=encrypt,
|
325
|
-
# miner_ids=miner_id_list
|
326
350
|
)
|
327
351
|
|
352
|
+
# If publishing is enabled, store on blockchain
|
353
|
+
if publish and result.get("cid"):
|
354
|
+
try:
|
355
|
+
# Pin and publish the file globally
|
356
|
+
# First pin in IPFS (essential step for publishing)
|
357
|
+
await client.ipfs_client.pin(result["cid"])
|
358
|
+
|
359
|
+
# Then publish globally to make available across network
|
360
|
+
publish_result = await client.ipfs_client.publish_global(
|
361
|
+
result["cid"]
|
362
|
+
)
|
363
|
+
|
364
|
+
log(
|
365
|
+
"\n[green]File has been pinned to IPFS and published to the network[/green]"
|
366
|
+
)
|
367
|
+
|
368
|
+
# Add gateway URL to the result for use in output
|
369
|
+
if "cid" in result:
|
370
|
+
result[
|
371
|
+
"gateway_url"
|
372
|
+
] = f"{client.ipfs_client.gateway}/ipfs/{result['cid']}"
|
373
|
+
|
374
|
+
# Store on blockchain if miners are provided
|
375
|
+
if miner_ids:
|
376
|
+
# Create a file input for blockchain storage
|
377
|
+
file_input = FileInput(
|
378
|
+
file_hash=result["cid"], file_name=file_name
|
379
|
+
)
|
380
|
+
|
381
|
+
# Submit storage request
|
382
|
+
tx_hash = await client.substrate_client.storage_request(
|
383
|
+
files=[file_input], miner_ids=miner_id_list
|
384
|
+
)
|
385
|
+
|
386
|
+
# Add transaction hash to result
|
387
|
+
result["transaction_hash"] = tx_hash
|
388
|
+
except Exception as e:
|
389
|
+
warning(f"Failed to publish file globally: {str(e)}")
|
390
|
+
|
328
391
|
progress.update(task, completed=100)
|
329
392
|
updater.cancel()
|
330
393
|
|
@@ -336,16 +399,24 @@ async def handle_store(
|
|
336
399
|
f"IPFS CID: [bold cyan]{result['cid']}[/bold cyan]",
|
337
400
|
]
|
338
401
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
402
|
+
# Always add the gateway URL
|
403
|
+
gateway_url = result.get("gateway_url")
|
404
|
+
if not gateway_url and "cid" in result:
|
405
|
+
gateway_url = f"{client.ipfs_client.gateway}/ipfs/{result['cid']}"
|
406
|
+
|
407
|
+
if gateway_url:
|
408
|
+
success_info.append(f"Gateway URL: [link]{gateway_url}[/link]")
|
343
409
|
|
344
410
|
if result.get("encrypted"):
|
345
411
|
success_info.append(
|
346
412
|
"[bold yellow]File was encrypted during upload[/bold yellow]"
|
347
413
|
)
|
348
414
|
|
415
|
+
if not publish:
|
416
|
+
success_info.append(
|
417
|
+
"[bold yellow]File was uploaded locally only (not published to blockchain)[/bold yellow]"
|
418
|
+
)
|
419
|
+
|
349
420
|
print_panel("\n".join(success_info), title="Upload Successful")
|
350
421
|
|
351
422
|
# If we stored in the marketplace
|
@@ -382,6 +453,16 @@ async def handle_store_dir(
|
|
382
453
|
error(f"[bold]{dir_path}[/bold] is not a directory")
|
383
454
|
return 1
|
384
455
|
|
456
|
+
# If publishing is enabled, ensure we have a valid substrate client by accessing it
|
457
|
+
# This will trigger password prompts if needed right at the beginning
|
458
|
+
if publish and hasattr(client, "substrate_client") and client.substrate_client:
|
459
|
+
try:
|
460
|
+
# Force keypair initialization - this will prompt for password if needed
|
461
|
+
_ = client.substrate_client._ensure_keypair()
|
462
|
+
except Exception as e:
|
463
|
+
warning(f"Failed to initialize blockchain client: {str(e)}")
|
464
|
+
warning("Will continue with upload but blockchain publishing may fail")
|
465
|
+
|
385
466
|
# Upload information panel
|
386
467
|
upload_info = [f"Directory: [bold]{dir_path}[/bold]"]
|
387
468
|
|
@@ -460,14 +541,62 @@ async def handle_store_dir(
|
|
460
541
|
del result["transaction_hash"]
|
461
542
|
else:
|
462
543
|
# If we want to publish, make sure files are pinned globally
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
544
|
+
try:
|
545
|
+
# Add gateway URL to the result for use in output
|
546
|
+
if "cid" in result:
|
547
|
+
result[
|
548
|
+
"gateway_url"
|
549
|
+
] = f"{client.ipfs_client.gateway}/ipfs/{result['cid']}"
|
550
|
+
|
551
|
+
# Pin and publish the directory root CID globally
|
552
|
+
# First pin in IPFS (essential step for publishing)
|
553
|
+
await client.ipfs_client.pin(result["cid"])
|
554
|
+
|
555
|
+
# Then publish globally to make available across network
|
556
|
+
await client.ipfs_client.publish_global(result["cid"])
|
557
|
+
|
558
|
+
log(
|
559
|
+
"\n[green]Directory has been pinned to IPFS and published to the network[/green]"
|
560
|
+
)
|
561
|
+
|
562
|
+
# Also pin and publish individual files if available
|
563
|
+
for file_info in result.get("files", []):
|
564
|
+
if "cid" in file_info:
|
565
|
+
try:
|
566
|
+
# Pin each file to ensure availability
|
567
|
+
await client.ipfs_client.pin(file_info["cid"])
|
568
|
+
|
569
|
+
# Then publish globally
|
570
|
+
await client.ipfs_client.publish_global(
|
571
|
+
file_info["cid"]
|
572
|
+
)
|
573
|
+
except Exception as e:
|
574
|
+
warning(
|
575
|
+
f"Failed to publish file {file_info['name']} globally: {str(e)}"
|
576
|
+
)
|
577
|
+
|
578
|
+
# Store on blockchain if miners are provided - this is what requires a password
|
579
|
+
if (
|
580
|
+
miner_ids
|
581
|
+
and hasattr(client, "substrate_client")
|
582
|
+
and client.substrate_client
|
583
|
+
):
|
584
|
+
# Create a file input for blockchain storage
|
585
|
+
file_input = FileInput(
|
586
|
+
file_hash=result["cid"],
|
587
|
+
file_name=os.path.basename(dir_path),
|
588
|
+
)
|
589
|
+
|
590
|
+
# This will prompt for a password if needed
|
591
|
+
tx_hash = await client.substrate_client.storage_request(
|
592
|
+
files=[file_input], miner_ids=miner_id_list
|
593
|
+
)
|
594
|
+
|
595
|
+
# Add transaction hash to result
|
596
|
+
result["transaction_hash"] = tx_hash
|
597
|
+
|
598
|
+
except Exception as e:
|
599
|
+
warning(f"Failed to publish directory globally: {str(e)}")
|
471
600
|
|
472
601
|
# Complete the progress
|
473
602
|
progress.update(task, completed=100)
|
@@ -482,9 +611,23 @@ async def handle_store_dir(
|
|
482
611
|
f"Directory CID: [bold cyan]{result['cid']}[/bold cyan]",
|
483
612
|
]
|
484
613
|
|
485
|
-
|
614
|
+
# Always add the gateway URL
|
615
|
+
gateway_url = result.get("gateway_url")
|
616
|
+
if not gateway_url and "cid" in result:
|
617
|
+
gateway_url = f"{client.ipfs_client.gateway}/ipfs/{result['cid']}"
|
618
|
+
|
619
|
+
if gateway_url:
|
620
|
+
success_info.append(f"Gateway URL: [link]{gateway_url}[/link]")
|
621
|
+
|
622
|
+
# Add encryption and publish status to success info
|
623
|
+
if result.get("encrypted"):
|
624
|
+
success_info.append(
|
625
|
+
"[bold yellow]Directory was encrypted during upload[/bold yellow]"
|
626
|
+
)
|
627
|
+
|
628
|
+
if not publish:
|
486
629
|
success_info.append(
|
487
|
-
|
630
|
+
"[bold yellow]Directory was uploaded locally only (not published to blockchain)[/bold yellow]"
|
488
631
|
)
|
489
632
|
|
490
633
|
print_panel("\n".join(success_info), title="Directory Upload Successful")
|
@@ -508,10 +651,15 @@ async def handle_store_dir(
|
|
508
651
|
)
|
509
652
|
|
510
653
|
# If publishing is enabled and we stored in the marketplace
|
511
|
-
if publish
|
512
|
-
|
513
|
-
|
514
|
-
|
654
|
+
if publish:
|
655
|
+
# We only include transaction hash stuff if we actually created a blockchain transaction
|
656
|
+
if "transaction_hash" in result:
|
657
|
+
log(
|
658
|
+
f"\nStored in marketplace. Transaction hash: [bold]{result['transaction_hash']}[/bold]"
|
659
|
+
)
|
660
|
+
else:
|
661
|
+
# If publish is true but no transaction hash, just indicate files were published to IPFS
|
662
|
+
log("\n[green]Directory was published to IPFS network.[/green]")
|
515
663
|
elif not publish:
|
516
664
|
log(
|
517
665
|
"\n[yellow]Files were uploaded locally only. No blockchain publication or IPFS pinning.[/yellow]"
|
@@ -1565,6 +1713,117 @@ async def handle_delete(client: HippiusClient, cid: str, force: bool = False) ->
|
|
1565
1713
|
return 0
|
1566
1714
|
|
1567
1715
|
|
1716
|
+
async def handle_pin(
|
1717
|
+
client: HippiusClient, cid: str, publish: bool = True, miner_ids=None
|
1718
|
+
) -> int:
|
1719
|
+
"""Handle the pin command to pin a CID to IPFS and optionally publish to blockchain"""
|
1720
|
+
from rich.panel import Panel
|
1721
|
+
|
1722
|
+
# First check if this CID exists
|
1723
|
+
try:
|
1724
|
+
exists_result = await client.exists(cid)
|
1725
|
+
if not exists_result["exists"]:
|
1726
|
+
error(f"CID [bold cyan]{cid}[/bold cyan] not found on IPFS")
|
1727
|
+
return 1
|
1728
|
+
except Exception as e:
|
1729
|
+
warning(f"Error checking if CID exists: {e}")
|
1730
|
+
return 1
|
1731
|
+
|
1732
|
+
# Create operation title based on publish flag
|
1733
|
+
if publish:
|
1734
|
+
info(
|
1735
|
+
f"Preparing to pin and publish content with CID: [bold cyan]{cid}[/bold cyan]"
|
1736
|
+
)
|
1737
|
+
operation_title = "Pin & Publish Operation"
|
1738
|
+
else:
|
1739
|
+
info(f"Preparing to pin content with CID: [bold cyan]{cid}[/bold cyan]")
|
1740
|
+
operation_title = "Pin Operation"
|
1741
|
+
|
1742
|
+
# Display operation details
|
1743
|
+
operation_details = [
|
1744
|
+
f"CID: [bold cyan]{cid}[/bold cyan]",
|
1745
|
+
f"Publishing to blockchain: {'Enabled' if publish else 'Disabled'}",
|
1746
|
+
]
|
1747
|
+
print_panel("\n".join(operation_details), title=operation_title)
|
1748
|
+
|
1749
|
+
# Need to authenticate if publishing to blockchain
|
1750
|
+
if publish:
|
1751
|
+
try:
|
1752
|
+
# Ensure we have a keypair for substrate operations
|
1753
|
+
_ = client.substrate_client._ensure_keypair()
|
1754
|
+
except Exception as e:
|
1755
|
+
warning(f"Failed to initialize blockchain client: {str(e)}")
|
1756
|
+
warning("Will continue with pinning but blockchain publishing may fail")
|
1757
|
+
|
1758
|
+
# Show spinner during pinning
|
1759
|
+
with console.status(
|
1760
|
+
"[cyan]Pinning content to IPFS...[/cyan]", spinner="dots"
|
1761
|
+
) as status:
|
1762
|
+
try:
|
1763
|
+
# Pin the content to IPFS
|
1764
|
+
pin_result = await client.ipfs_client.pin(cid)
|
1765
|
+
|
1766
|
+
if not pin_result.get("success", False):
|
1767
|
+
error(
|
1768
|
+
f"Failed to pin content: {pin_result.get('message', 'Unknown error')}"
|
1769
|
+
)
|
1770
|
+
return 1
|
1771
|
+
|
1772
|
+
# If publishing to blockchain, do that now
|
1773
|
+
if publish:
|
1774
|
+
status.update("[cyan]Publishing content to blockchain...[/cyan]")
|
1775
|
+
|
1776
|
+
# Create a FileInput object for the substrate client
|
1777
|
+
from hippius_sdk.substrate import FileInput
|
1778
|
+
|
1779
|
+
file_input = FileInput(file_hash=cid, file_name=f"pinned_{cid}")
|
1780
|
+
|
1781
|
+
# Submit the storage request
|
1782
|
+
tx_hash = await client.substrate_client.storage_request(
|
1783
|
+
files=[file_input], miner_ids=miner_ids
|
1784
|
+
)
|
1785
|
+
|
1786
|
+
# Create result panel with blockchain details
|
1787
|
+
gateway_url = f"{client.ipfs_client.gateway}/ipfs/{cid}"
|
1788
|
+
panel_details = [
|
1789
|
+
f"Successfully pinned and published: [bold cyan]{cid}[/bold cyan]",
|
1790
|
+
f"Gateway URL: [bold cyan]{gateway_url}[/bold cyan]",
|
1791
|
+
f"Transaction hash: [bold green]{tx_hash}[/bold green]",
|
1792
|
+
"\nThis content is now:",
|
1793
|
+
"1. Pinned to your IPFS node",
|
1794
|
+
"2. Published to the IPFS network",
|
1795
|
+
"3. Stored on the Hippius blockchain",
|
1796
|
+
]
|
1797
|
+
console.print(
|
1798
|
+
Panel(
|
1799
|
+
"\n".join(panel_details),
|
1800
|
+
title="Operation Complete",
|
1801
|
+
border_style="green",
|
1802
|
+
)
|
1803
|
+
)
|
1804
|
+
else:
|
1805
|
+
# Just pinning, no blockchain publishing
|
1806
|
+
gateway_url = f"{client.ipfs_client.gateway}/ipfs/{cid}"
|
1807
|
+
panel_details = [
|
1808
|
+
f"Successfully pinned: [bold cyan]{cid}[/bold cyan]",
|
1809
|
+
f"Gateway URL: [bold cyan]{gateway_url}[/bold cyan]",
|
1810
|
+
"\nThis content is now pinned to your IPFS node.",
|
1811
|
+
"It will remain available as long as your node is running.",
|
1812
|
+
]
|
1813
|
+
console.print(
|
1814
|
+
Panel(
|
1815
|
+
"\n".join(panel_details),
|
1816
|
+
title="Pinning Complete",
|
1817
|
+
border_style="green",
|
1818
|
+
)
|
1819
|
+
)
|
1820
|
+
|
1821
|
+
return 0
|
1822
|
+
except Exception as e:
|
1823
|
+
error(f"Error during operation: {e}")
|
1824
|
+
return 1
|
1825
|
+
|
1826
|
+
|
1568
1827
|
async def handle_ec_delete(
|
1569
1828
|
client: HippiusClient, metadata_cid: str, force: bool = False
|
1570
1829
|
) -> int:
|
@@ -63,13 +63,19 @@ examples:
|
|
63
63
|
|
64
64
|
# Erasure code without publishing to global IPFS network
|
65
65
|
hippius erasure-code large_file.avi --no-publish
|
66
|
-
|
66
|
+
|
67
67
|
# Reconstruct an erasure-coded file
|
68
68
|
hippius reconstruct QmMetadataHash reconstructed_file.mp4
|
69
|
-
|
69
|
+
|
70
|
+
# Pin a CID to IPFS and publish to blockchain
|
71
|
+
hippius pin QmHash
|
72
|
+
|
73
|
+
# Pin a CID to IPFS without publishing to blockchain
|
74
|
+
hippius pin QmHash --no-publish
|
75
|
+
|
70
76
|
# Delete a file from IPFS and marketplace
|
71
77
|
hippius delete QmHash
|
72
|
-
|
78
|
+
|
73
79
|
# Delete an erasure-coded file and all its chunks
|
74
80
|
hippius ec-delete QmMetadataHash
|
75
81
|
""",
|
@@ -250,6 +256,23 @@ def add_storage_commands(subparsers):
|
|
250
256
|
help="Delete without confirmation prompt",
|
251
257
|
)
|
252
258
|
|
259
|
+
# Pin command
|
260
|
+
pin_parser = subparsers.add_parser(
|
261
|
+
"pin",
|
262
|
+
help="Pin a CID to IPFS and publish to blockchain",
|
263
|
+
)
|
264
|
+
pin_parser.add_argument("cid", help="CID to pin")
|
265
|
+
pin_parser.add_argument(
|
266
|
+
"--publish",
|
267
|
+
action="store_true",
|
268
|
+
help="Publish file to IPFS and store on the blockchain (default)",
|
269
|
+
)
|
270
|
+
pin_parser.add_argument(
|
271
|
+
"--no-publish",
|
272
|
+
action="store_true",
|
273
|
+
help="Don't publish file to blockchain (local pinning only)",
|
274
|
+
)
|
275
|
+
|
253
276
|
# Keygen command
|
254
277
|
keygen_parser = subparsers.add_parser(
|
255
278
|
"keygen", help="Generate an encryption key for secure file storage"
|
@@ -443,6 +443,7 @@ class IPFSClient:
|
|
443
443
|
output_path: str,
|
444
444
|
decrypt: Optional[bool] = None,
|
445
445
|
max_retries: int = 3,
|
446
|
+
skip_directory_check: bool = False,
|
446
447
|
) -> Dict[str, Any]:
|
447
448
|
"""
|
448
449
|
Download a file from IPFS with optional decryption.
|
@@ -453,6 +454,7 @@ class IPFSClient:
|
|
453
454
|
output_path: Path where the downloaded file/directory will be saved
|
454
455
|
decrypt: Whether to decrypt the file (overrides default)
|
455
456
|
max_retries: Maximum number of retry attempts (default: 3)
|
457
|
+
skip_directory_check: If True, skips directory check (treats as file)
|
456
458
|
|
457
459
|
Returns:
|
458
460
|
Dict[str, Any]: Dictionary containing download results:
|
@@ -470,15 +472,17 @@ class IPFSClient:
|
|
470
472
|
"""
|
471
473
|
start_time = time.time()
|
472
474
|
|
473
|
-
#
|
475
|
+
# Skip directory check if requested (important for erasure code chunks)
|
474
476
|
is_directory = False
|
475
|
-
|
476
|
-
#
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
477
|
+
if not skip_directory_check:
|
478
|
+
# Use the improved ls function to properly detect directories
|
479
|
+
try:
|
480
|
+
# The ls function now properly detects directories
|
481
|
+
ls_result = await self.client.ls(cid)
|
482
|
+
is_directory = ls_result.get("is_directory", False)
|
483
|
+
except Exception:
|
484
|
+
# If ls fails, we'll proceed as if it's a file
|
485
|
+
pass
|
482
486
|
|
483
487
|
# If it's a directory, handle it differently
|
484
488
|
if is_directory:
|
@@ -530,7 +534,10 @@ class IPFSClient:
|
|
530
534
|
else:
|
531
535
|
download_path = output_path
|
532
536
|
|
533
|
-
|
537
|
+
# Pass the skip_directory_check parameter to the core client
|
538
|
+
await self.client.download_file(
|
539
|
+
cid, download_path, skip_directory_check=skip_directory_check
|
540
|
+
)
|
534
541
|
download_success = True
|
535
542
|
|
536
543
|
if not download_success:
|
@@ -1225,8 +1232,12 @@ class IPFSClient:
|
|
1225
1232
|
async def download_chunk(cid, path, chunk_info):
|
1226
1233
|
async with encoded_chunks_semaphore:
|
1227
1234
|
try:
|
1235
|
+
# Always skip directory check for erasure code chunks
|
1228
1236
|
await self.download_file(
|
1229
|
-
cid,
|
1237
|
+
cid,
|
1238
|
+
path,
|
1239
|
+
max_retries=max_retries,
|
1240
|
+
skip_directory_check=True,
|
1230
1241
|
)
|
1231
1242
|
|
1232
1243
|
# Read chunk data
|
@@ -219,7 +219,9 @@ class AsyncIPFSClient:
|
|
219
219
|
except httpx.HTTPError:
|
220
220
|
return False
|
221
221
|
|
222
|
-
async def download_file(
|
222
|
+
async def download_file(
|
223
|
+
self, cid: str, output_path: str, skip_directory_check: bool = False
|
224
|
+
) -> str:
|
223
225
|
"""
|
224
226
|
Download content from IPFS to a file.
|
225
227
|
If the CID is a directory, it will create a directory and download all files.
|
@@ -227,19 +229,22 @@ class AsyncIPFSClient:
|
|
227
229
|
Args:
|
228
230
|
cid: Content identifier
|
229
231
|
output_path: Path where to save the file/directory
|
232
|
+
skip_directory_check: If True, skip directory check (useful for erasure code chunks)
|
230
233
|
|
231
234
|
Returns:
|
232
235
|
Path to the saved file/directory
|
233
236
|
"""
|
234
|
-
#
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
237
|
+
# Skip directory check if requested (useful for erasure code chunks)
|
238
|
+
if not skip_directory_check:
|
239
|
+
# First, check if this is a directory using the improved ls function
|
240
|
+
try:
|
241
|
+
ls_result = await self.ls(cid)
|
242
|
+
if ls_result.get("is_directory", False):
|
243
|
+
# It's a directory, use the get command to download it properly
|
244
|
+
return await self.download_directory_with_get(cid, output_path)
|
245
|
+
except Exception:
|
246
|
+
# If ls check fails, continue with regular file download
|
247
|
+
pass
|
243
248
|
|
244
249
|
# If we reached here, treat it as a regular file
|
245
250
|
try:
|
@@ -251,13 +256,14 @@ class AsyncIPFSClient:
|
|
251
256
|
f.write(content)
|
252
257
|
return output_path
|
253
258
|
except Exception as e:
|
254
|
-
#
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
259
|
+
# Only try directory fallback if not skipping directory check
|
260
|
+
if not skip_directory_check:
|
261
|
+
try:
|
262
|
+
return await self.download_directory_with_get(cid, output_path)
|
263
|
+
except Exception:
|
264
|
+
pass
|
265
|
+
# Raise the original error
|
266
|
+
raise e
|
261
267
|
|
262
268
|
async def download_directory(self, cid: str, output_path: str) -> str:
|
263
269
|
"""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|