hippius 0.2.4__py3-none-any.whl → 0.2.5__py3-none-any.whl
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.4.dist-info → hippius-0.2.5.dist-info}/METADATA +1 -1
- hippius-0.2.5.dist-info/RECORD +17 -0
- hippius_sdk/__init__.py +21 -10
- hippius_sdk/cli.py +11 -0
- hippius_sdk/cli_handlers.py +256 -48
- hippius_sdk/cli_parser.py +20 -0
- hippius_sdk/cli_rich.py +8 -2
- hippius_sdk/client.py +5 -3
- hippius_sdk/errors.py +77 -0
- hippius_sdk/ipfs.py +237 -297
- hippius_sdk/ipfs_core.py +209 -9
- hippius_sdk/substrate.py +101 -14
- hippius-0.2.4.dist-info/RECORD +0 -16
- {hippius-0.2.4.dist-info → hippius-0.2.5.dist-info}/WHEEL +0 -0
- {hippius-0.2.4.dist-info → hippius-0.2.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
hippius_sdk/__init__.py,sha256=LbBsuMIYPBStrXmKVlPxdE9qCwqFNaj9FTiHkWNZ1Zw,1391
|
2
|
+
hippius_sdk/cli.py,sha256=_puHEeryJ5yDPgFJi0xcvMJl7Jlsa6-5K9EITOwSdQw,17821
|
3
|
+
hippius_sdk/cli_assets.py,sha256=V3MX63QTiex6mCp0VDXQJ7cagm5v1s4xtsu8c1O4G_k,371
|
4
|
+
hippius_sdk/cli_handlers.py,sha256=GmedWU9nGTA7DJKBnGnXQB7rhAfcl8ucbx5Hc05P_ZA,113964
|
5
|
+
hippius_sdk/cli_parser.py,sha256=fU4kIeNM4an6FtyVX13-u33MmO70PrNzvmPe2ZtPf-0,19576
|
6
|
+
hippius_sdk/cli_rich.py,sha256=_jTBYMdHi2--fIVwoeNi-EtkdOb6Zy_O2TUiGvU3O7s,7324
|
7
|
+
hippius_sdk/client.py,sha256=eYURsq_so3WlEt_JY_u7J0iECFVOKDf5vsnGyR9Kngw,16974
|
8
|
+
hippius_sdk/config.py,sha256=sCWD2remLa-FodvxC2O45tiNSJD7gzv9idIStX9sF_k,21240
|
9
|
+
hippius_sdk/errors.py,sha256=LScJJmawVAx7aRzqqQguYSkf9iazSjEQEBNlD_GXZ6Y,1589
|
10
|
+
hippius_sdk/ipfs.py,sha256=nO1EwxJCvmdEkNviWOKu0g0fv9qqCOkhKMmyzaHyf9g,71047
|
11
|
+
hippius_sdk/ipfs_core.py,sha256=sZhwWF6gYoYANyf-NC5tFAzJVrmVTp9TnIsqcIFqI-E,15759
|
12
|
+
hippius_sdk/substrate.py,sha256=HqR2-_9njZZ5UCKgiaGr5L5TGQ_wtj7oyA3sA5sGGyE,47525
|
13
|
+
hippius_sdk/utils.py,sha256=Ur7P_7iVnXXYvbg7a0aVrdN_8NkVxjhdngn8NzR_zpc,7066
|
14
|
+
hippius-0.2.5.dist-info/METADATA,sha256=w82vZdB717Z7lrlAp8tzeatZI0c30yWNP9ozcMRoXck,29992
|
15
|
+
hippius-0.2.5.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
16
|
+
hippius-0.2.5.dist-info/entry_points.txt,sha256=b1lo60zRXmv1ud-c5BC-cJcAfGE5FD4qM_nia6XeQtM,98
|
17
|
+
hippius-0.2.5.dist-info/RECORD,,
|
hippius_sdk/__init__.py
CHANGED
@@ -3,19 +3,30 @@ Hippius SDK - Python interface for Hippius blockchain storage
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from hippius_sdk.client import HippiusClient
|
6
|
-
from hippius_sdk.config import (
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
from hippius_sdk.config import (
|
7
|
+
decrypt_seed_phrase,
|
8
|
+
delete_account,
|
9
|
+
encrypt_seed_phrase,
|
10
|
+
get_account_address,
|
11
|
+
get_active_account,
|
12
|
+
get_all_config,
|
13
|
+
get_config_value,
|
14
|
+
get_encryption_key,
|
15
|
+
get_seed_phrase,
|
16
|
+
initialize_from_env,
|
17
|
+
list_accounts,
|
18
|
+
load_config,
|
19
|
+
reset_config,
|
20
|
+
save_config,
|
21
|
+
set_active_account,
|
22
|
+
set_config_value,
|
23
|
+
set_encryption_key,
|
24
|
+
set_seed_phrase,
|
25
|
+
)
|
15
26
|
from hippius_sdk.ipfs import IPFSClient
|
16
27
|
from hippius_sdk.utils import format_cid, format_size, hex_to_ipfs_cid
|
17
28
|
|
18
|
-
__version__ = "0.2.
|
29
|
+
__version__ = "0.2.5"
|
19
30
|
__all__ = [
|
20
31
|
"HippiusClient",
|
21
32
|
"IPFSClient",
|
hippius_sdk/cli.py
CHANGED
@@ -113,6 +113,16 @@ def main():
|
|
113
113
|
encrypt = True if args.encrypt else (False if args.no_encrypt else None)
|
114
114
|
decrypt = True if args.decrypt else (False if args.no_decrypt else None)
|
115
115
|
|
116
|
+
# For erasure-code specifically, the password may have been requested and cached already
|
117
|
+
# We need to preserve it if it was set by handle_erasure_code
|
118
|
+
if (
|
119
|
+
args.command == "erasure-code"
|
120
|
+
and hasattr(client.substrate_client, "_seed_phrase")
|
121
|
+
and client.substrate_client._seed_phrase
|
122
|
+
):
|
123
|
+
# Password has already been handled by the command handler
|
124
|
+
pass
|
125
|
+
|
116
126
|
# Handle commands with the helper function
|
117
127
|
if args.command == "download":
|
118
128
|
return run_async_handler(
|
@@ -151,6 +161,7 @@ def main():
|
|
151
161
|
args.dir_path,
|
152
162
|
miner_ids,
|
153
163
|
encrypt=encrypt,
|
164
|
+
publish=not args.no_publish if hasattr(args, "no_publish") else True,
|
154
165
|
)
|
155
166
|
|
156
167
|
elif args.command == "credits":
|
hippius_sdk/cli_handlers.py
CHANGED
@@ -9,6 +9,7 @@ import asyncio
|
|
9
9
|
import base64
|
10
10
|
import getpass
|
11
11
|
import json
|
12
|
+
import math
|
12
13
|
import os
|
13
14
|
import tempfile
|
14
15
|
import time
|
@@ -44,6 +45,12 @@ from hippius_sdk.cli_rich import (
|
|
44
45
|
success,
|
45
46
|
warning,
|
46
47
|
)
|
48
|
+
from hippius_sdk.errors import (
|
49
|
+
HippiusAlreadyDeletedError,
|
50
|
+
HippiusFailedIPFSUnpin,
|
51
|
+
HippiusFailedSubstrateDelete,
|
52
|
+
HippiusMetadataError,
|
53
|
+
)
|
47
54
|
|
48
55
|
try:
|
49
56
|
import nacl.secret
|
@@ -147,6 +154,12 @@ async def handle_download(
|
|
147
154
|
f"Size: [bold cyan]{result['size_bytes']:,}[/bold cyan] bytes ([bold cyan]{result['size_formatted']}[/bold cyan])",
|
148
155
|
]
|
149
156
|
|
157
|
+
# Add details about content type
|
158
|
+
if result.get("is_directory", False):
|
159
|
+
details.append("[bold green]Content type: Directory[/bold green]")
|
160
|
+
else:
|
161
|
+
details.append("[bold blue]Content type: File[/bold blue]")
|
162
|
+
|
150
163
|
if result.get("decrypted"):
|
151
164
|
details.append("[bold yellow]File was decrypted during download[/bold yellow]")
|
152
165
|
|
@@ -358,6 +371,7 @@ async def handle_store_dir(
|
|
358
371
|
dir_path: str,
|
359
372
|
miner_ids: Optional[List[str]] = None,
|
360
373
|
encrypt: Optional[bool] = None,
|
374
|
+
publish: bool = True,
|
361
375
|
) -> int:
|
362
376
|
"""Handle the store directory command"""
|
363
377
|
if not os.path.exists(dir_path):
|
@@ -417,12 +431,44 @@ async def handle_store_dir(
|
|
417
431
|
updater = asyncio.create_task(update_progress())
|
418
432
|
|
419
433
|
try:
|
434
|
+
# Upload info message based on publish flag
|
435
|
+
if not publish:
|
436
|
+
upload_info.append(
|
437
|
+
"[bold yellow]Publishing: Disabled (local upload only)[/bold yellow]"
|
438
|
+
)
|
439
|
+
log(
|
440
|
+
"\nUpload will be local only - not publishing to blockchain or pinning to IPFS"
|
441
|
+
)
|
442
|
+
else:
|
443
|
+
upload_info.append(
|
444
|
+
"[bold green]Publishing: Enabled (publishing to blockchain)[/bold green]"
|
445
|
+
)
|
446
|
+
|
447
|
+
# Display updated upload information panel
|
448
|
+
print_panel("\n".join(upload_info), title="Directory Upload Operation")
|
449
|
+
|
420
450
|
# Use the store_directory method
|
421
451
|
result = await client.ipfs_client.upload_directory(
|
422
452
|
dir_path=dir_path,
|
423
453
|
encrypt=encrypt,
|
424
454
|
)
|
425
455
|
|
456
|
+
# Skip publishing to blockchain if publish is False
|
457
|
+
if not publish:
|
458
|
+
# Remove any blockchain-related data from result to ensure we don't try to use it
|
459
|
+
if "transaction_hash" in result:
|
460
|
+
del result["transaction_hash"]
|
461
|
+
else:
|
462
|
+
# If we want to publish, make sure files are pinned globally
|
463
|
+
for file_info in result.get("files", []):
|
464
|
+
if "cid" in file_info:
|
465
|
+
try:
|
466
|
+
await client.ipfs_client.publish_global(file_info["cid"])
|
467
|
+
except Exception as e:
|
468
|
+
warning(
|
469
|
+
f"Failed to publish file {file_info['name']} globally: {str(e)}"
|
470
|
+
)
|
471
|
+
|
426
472
|
# Complete the progress
|
427
473
|
progress.update(task, completed=100)
|
428
474
|
# Cancel the updater task
|
@@ -461,11 +507,15 @@ async def handle_store_dir(
|
|
461
507
|
["Index", "Filename", "CID"],
|
462
508
|
)
|
463
509
|
|
464
|
-
# If we stored in the marketplace
|
465
|
-
if "transaction_hash" in result:
|
510
|
+
# If publishing is enabled and we stored in the marketplace
|
511
|
+
if publish and "transaction_hash" in result:
|
466
512
|
log(
|
467
513
|
f"\nStored in marketplace. Transaction hash: [bold]{result['transaction_hash']}[/bold]"
|
468
514
|
)
|
515
|
+
elif not publish:
|
516
|
+
log(
|
517
|
+
"\n[yellow]Files were uploaded locally only. No blockchain publication or IPFS pinning.[/yellow]"
|
518
|
+
)
|
469
519
|
|
470
520
|
return 0
|
471
521
|
|
@@ -991,6 +1041,36 @@ async def handle_erasure_code(
|
|
991
1041
|
)
|
992
1042
|
return 1
|
993
1043
|
|
1044
|
+
# Request password early if we're going to publish to the blockchain
|
1045
|
+
if publish and client.substrate_client._seed_phrase is None:
|
1046
|
+
# First check if we have an encrypted seed phrase that will require a password
|
1047
|
+
config = load_config()
|
1048
|
+
account_name = client.substrate_client._account_name or get_active_account()
|
1049
|
+
|
1050
|
+
if account_name and account_name in config["substrate"].get("accounts", {}):
|
1051
|
+
account_data = config["substrate"]["accounts"][account_name]
|
1052
|
+
is_encoded = account_data.get("seed_phrase_encoded", False)
|
1053
|
+
|
1054
|
+
if is_encoded:
|
1055
|
+
warning("Wallet password will be required for publishing to blockchain")
|
1056
|
+
password = getpass.getpass(
|
1057
|
+
"Enter password to decrypt seed phrase: \n\n"
|
1058
|
+
)
|
1059
|
+
|
1060
|
+
# Store the password in client for later use
|
1061
|
+
client.substrate_client._seed_phrase_password = password
|
1062
|
+
|
1063
|
+
# Pre-authenticate to ensure the password is correct
|
1064
|
+
try:
|
1065
|
+
seed_phrase = decrypt_seed_phrase(password, account_name)
|
1066
|
+
if not seed_phrase:
|
1067
|
+
error("Failed to decrypt seed phrase. Incorrect password?")
|
1068
|
+
return 1
|
1069
|
+
client.substrate_client._seed_phrase = seed_phrase
|
1070
|
+
except Exception as e:
|
1071
|
+
error(f"Error decrypting seed phrase: {e}")
|
1072
|
+
return 1
|
1073
|
+
|
994
1074
|
# Get file size
|
995
1075
|
file_size = os.path.getsize(file_path)
|
996
1076
|
file_name = os.path.basename(file_path)
|
@@ -1020,11 +1100,18 @@ async def handle_erasure_code(
|
|
1020
1100
|
|
1021
1101
|
chunk_size = new_chunk_size
|
1022
1102
|
|
1103
|
+
# Calculate total number of chunks that will be created
|
1104
|
+
total_original_chunks = max(1, int(math.ceil(file_size / chunk_size)))
|
1105
|
+
total_encoded_chunks = total_original_chunks * m
|
1106
|
+
estimated_size_per_chunk = min(chunk_size, file_size / total_original_chunks)
|
1107
|
+
|
1023
1108
|
# Create parameter information panel
|
1024
1109
|
param_info = [
|
1025
1110
|
f"File: [bold]{file_name}[/bold] ([bold cyan]{file_size / 1024 / 1024:.2f} MB[/bold cyan])",
|
1026
1111
|
f"Parameters: k=[bold]{k}[/bold], m=[bold]{m}[/bold] (need {k} of {m} chunks to reconstruct)",
|
1027
1112
|
f"Chunk size: [bold cyan]{chunk_size / 1024 / 1024:.6f} MB[/bold cyan]",
|
1113
|
+
f"Total chunks to be created: [bold yellow]{total_encoded_chunks}[/bold yellow] ({total_original_chunks} original chunks × {m} encoded chunks each)",
|
1114
|
+
f"Estimated storage required: [bold magenta]{(total_encoded_chunks * estimated_size_per_chunk) / (1024 * 1024):.2f} MB[/bold magenta]",
|
1028
1115
|
]
|
1029
1116
|
|
1030
1117
|
# Add encryption status
|
@@ -1394,89 +1481,210 @@ async def handle_reconstruct(
|
|
1394
1481
|
|
1395
1482
|
|
1396
1483
|
async def handle_delete(client: HippiusClient, cid: str, force: bool = False) -> int:
|
1397
|
-
"""Handle the delete command"""
|
1398
|
-
info(f"Preparing to delete
|
1484
|
+
"""Handle the delete command for files or directories"""
|
1485
|
+
info(f"Preparing to delete content with CID: [bold cyan]{cid}[/bold cyan]")
|
1486
|
+
|
1487
|
+
# First check if this is a directory
|
1488
|
+
try:
|
1489
|
+
exists_result = await client.exists(cid)
|
1490
|
+
if not exists_result["exists"]:
|
1491
|
+
error(f"CID [bold cyan]{cid}[/bold cyan] not found on IPFS")
|
1492
|
+
return 1
|
1493
|
+
except Exception as e:
|
1494
|
+
warning(f"Error checking if CID exists: {e}")
|
1399
1495
|
|
1400
1496
|
if not force:
|
1401
|
-
warning("This will cancel storage and remove the
|
1497
|
+
warning("This will cancel storage and remove the content from the marketplace.")
|
1402
1498
|
confirm = input("Continue? (y/n): ").strip().lower()
|
1403
1499
|
if confirm != "y":
|
1404
1500
|
log("Deletion cancelled", style="yellow")
|
1405
1501
|
return 0
|
1406
1502
|
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
if result.get("success"):
|
1411
|
-
success("File successfully deleted")
|
1503
|
+
# Show spinner during deletion
|
1504
|
+
with console.status("[cyan]Deleting content...[/cyan]", spinner="dots") as status:
|
1505
|
+
result = await client.delete_file(cid)
|
1412
1506
|
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
f"Transaction hash: [bold]{result['transaction_hash']}[/bold]"
|
1417
|
-
)
|
1507
|
+
# Display results
|
1508
|
+
is_directory = result.get("is_directory", False)
|
1509
|
+
child_files = result.get("child_files", [])
|
1418
1510
|
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
"
|
1423
|
-
"
|
1424
|
-
" available through other nodes that pinned it",
|
1511
|
+
if is_directory:
|
1512
|
+
# Directory deletion
|
1513
|
+
details = [
|
1514
|
+
f"Successfully deleted directory: [bold cyan]{cid}[/bold cyan]",
|
1515
|
+
f"Child files unpinned: [bold]{len(child_files)}[/bold]",
|
1425
1516
|
]
|
1426
1517
|
|
1427
|
-
|
1428
|
-
|
1518
|
+
# If there are child files, show them in a table
|
1519
|
+
if child_files:
|
1520
|
+
table_data = []
|
1521
|
+
for i, file in enumerate(
|
1522
|
+
child_files[:10], 1
|
1523
|
+
): # Limit to first 10 files if many
|
1524
|
+
table_data.append(
|
1525
|
+
{
|
1526
|
+
"Index": str(i),
|
1527
|
+
"Filename": file.get("name", "unknown"),
|
1528
|
+
"CID": file.get("cid", "unknown"),
|
1529
|
+
}
|
1530
|
+
)
|
1429
1531
|
|
1430
|
-
|
1532
|
+
if len(child_files) > 10:
|
1533
|
+
table_data.append(
|
1534
|
+
{
|
1535
|
+
"Index": "...",
|
1536
|
+
"Filename": f"({len(child_files) - 10} more files)",
|
1537
|
+
"CID": "...",
|
1538
|
+
}
|
1539
|
+
)
|
1431
1540
|
|
1432
|
-
|
1541
|
+
print_table(
|
1542
|
+
"Unpinned Child Files", table_data, ["Index", "Filename", "CID"]
|
1543
|
+
)
|
1433
1544
|
else:
|
1434
|
-
|
1545
|
+
# Regular file deletion
|
1546
|
+
details = [f"Successfully deleted file: [bold cyan]{cid}[/bold cyan]"]
|
1547
|
+
|
1548
|
+
if "duration_seconds" in result.get("timing", {}):
|
1549
|
+
details.append(
|
1550
|
+
f"Deletion completed in [bold green]{result['timing']['duration_seconds']:.2f}[/bold green] seconds"
|
1551
|
+
)
|
1552
|
+
|
1553
|
+
print_panel("\n".join(details), title="Deletion Complete")
|
1554
|
+
|
1555
|
+
# Create an informative panel with notes
|
1556
|
+
notes = [
|
1557
|
+
"1. The content is now unpinned from the marketplace",
|
1558
|
+
"2. The CID may still resolve temporarily until garbage collection occurs",
|
1559
|
+
"3. If the content was published to the global IPFS network, it may still be",
|
1560
|
+
" available through other nodes that pinned it",
|
1561
|
+
]
|
1562
|
+
|
1563
|
+
print_panel("\n".join(notes), title="Important Notes")
|
1564
|
+
|
1565
|
+
return 0
|
1435
1566
|
|
1436
1567
|
|
1437
1568
|
async def handle_ec_delete(
|
1438
1569
|
client: HippiusClient, metadata_cid: str, force: bool = False
|
1439
1570
|
) -> int:
|
1440
|
-
"""Handle the
|
1441
|
-
info(
|
1442
|
-
f"Preparing to delete erasure-coded file with metadata CID: [bold cyan]{metadata_cid}[/bold cyan]"
|
1443
|
-
)
|
1571
|
+
"""Handle the erasure-code delete command"""
|
1444
1572
|
|
1573
|
+
# Create a stylish header with the CID
|
1574
|
+
info(f"Preparing to delete erasure-coded file with metadata CID:")
|
1575
|
+
print_panel(f"[bold cyan]{metadata_cid}[/bold cyan]", title="Metadata CID")
|
1576
|
+
|
1577
|
+
# Confirm the deletion if not forced
|
1445
1578
|
if not force:
|
1446
|
-
|
1447
|
-
|
1579
|
+
warning_text = [
|
1580
|
+
"This will cancel the storage of this file on the Hippius blockchain.",
|
1581
|
+
"The file metadata will be removed from blockchain storage tracking.",
|
1582
|
+
"[dim]Note: Only the metadata CID will be canceled; contents may remain on IPFS.[/dim]",
|
1583
|
+
]
|
1584
|
+
print_panel("\n".join(warning_text), title="Warning")
|
1585
|
+
|
1586
|
+
confirm = input("Continue with deletion? (y/n): ").strip().lower()
|
1448
1587
|
if confirm != "y":
|
1449
1588
|
log("Deletion cancelled", style="yellow")
|
1450
1589
|
return 0
|
1451
1590
|
|
1452
1591
|
try:
|
1592
|
+
# First, pre-authenticate the client to get any password prompts out of the way
|
1593
|
+
# This accesses the substrate client to trigger authentication
|
1594
|
+
if not client.substrate_client._keypair:
|
1595
|
+
client.substrate_client._ensure_keypair()
|
1596
|
+
|
1597
|
+
# Now we can show the spinner after any password prompts
|
1453
1598
|
info("Deleting erasure-coded file from marketplace...")
|
1454
|
-
result = await client.delete_ec_file(metadata_cid)
|
1455
1599
|
|
1456
|
-
|
1457
|
-
|
1600
|
+
# Create a more detailed spinner with phases
|
1601
|
+
with console.status(
|
1602
|
+
"[cyan]Processing file metadata and chunks...[/cyan]", spinner="dots"
|
1603
|
+
) as status:
|
1604
|
+
try:
|
1605
|
+
# Use the specialized delete method that now throws specific exceptions
|
1606
|
+
await client.delete_ec_file(metadata_cid)
|
1458
1607
|
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
details.append(f"Deleted [bold]{chunks_deleted}[/bold] chunks")
|
1608
|
+
# If we get here, deletion was successful
|
1609
|
+
deletion_success = True
|
1610
|
+
already_deleted = False
|
1463
1611
|
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1612
|
+
except HippiusAlreadyDeletedError:
|
1613
|
+
# Special case - already deleted
|
1614
|
+
deletion_success = False
|
1615
|
+
already_deleted = True
|
1616
|
+
|
1617
|
+
except HippiusFailedSubstrateDelete as e:
|
1618
|
+
# Blockchain deletion failed
|
1619
|
+
error(f"Blockchain storage cancellation failed: {e}")
|
1620
|
+
return 1
|
1621
|
+
|
1622
|
+
except HippiusFailedIPFSUnpin as e:
|
1623
|
+
# IPFS unpinning failed, but blockchain deletion succeeded
|
1624
|
+
warning(
|
1625
|
+
f"Note: Some IPFS operations failed, but blockchain storage was successfully canceled"
|
1626
|
+
)
|
1627
|
+
# Consider this a success for the user since the more important blockchain part worked
|
1628
|
+
deletion_success = True
|
1629
|
+
already_deleted = False
|
1630
|
+
|
1631
|
+
except HippiusMetadataError as e:
|
1632
|
+
# Metadata parsing failed, but we can still continue
|
1633
|
+
warning(
|
1634
|
+
f"Note: Metadata file was corrupted, but blockchain storage was successfully canceled"
|
1467
1635
|
)
|
1636
|
+
# Consider this a success for the user since the blockchain part worked
|
1637
|
+
deletion_success = True
|
1638
|
+
already_deleted = False
|
1468
1639
|
|
1469
|
-
|
1640
|
+
except Exception as e:
|
1641
|
+
# Handle any unexpected errors
|
1642
|
+
error(f"Unexpected error: {e}")
|
1643
|
+
return 1
|
1470
1644
|
|
1645
|
+
# Show the result
|
1646
|
+
if deletion_success:
|
1647
|
+
# Create a success panel
|
1648
|
+
success_panel = [
|
1649
|
+
"[bold green]✓[/bold green] Metadata CID canceled from blockchain storage",
|
1650
|
+
f"[dim]This file is no longer tracked for storage payments[/dim]",
|
1651
|
+
"",
|
1652
|
+
"[dim]To purge file data completely:[/dim]",
|
1653
|
+
"• Individual chunks may still exist on IPFS and nodes",
|
1654
|
+
"• For complete deletion, all chunks should be unpinned manually",
|
1655
|
+
]
|
1656
|
+
print_panel(
|
1657
|
+
"\n".join(success_panel), title="Storage Cancellation Successful"
|
1658
|
+
)
|
1659
|
+
return 0
|
1660
|
+
elif already_deleted:
|
1661
|
+
# Create a panel for the already deleted case
|
1662
|
+
already_panel = [
|
1663
|
+
"[bold yellow]![/bold yellow] This file has already been deleted from storage",
|
1664
|
+
"[dim]The CID was not found in the blockchain storage registry[/dim]",
|
1665
|
+
"",
|
1666
|
+
"This is expected if:",
|
1667
|
+
"• You previously deleted this file",
|
1668
|
+
"• The file was deleted by another process",
|
1669
|
+
"• The file was never stored in the first place",
|
1670
|
+
]
|
1671
|
+
print_panel("\n".join(already_panel), title="Already Deleted")
|
1672
|
+
# Return 0 since this is not an error condition
|
1471
1673
|
return 0
|
1472
1674
|
else:
|
1473
|
-
error
|
1474
|
-
|
1475
|
-
|
1675
|
+
# Create an error panel for all other failures
|
1676
|
+
error_panel = [
|
1677
|
+
"[bold red]×[/bold red] File not found in blockchain storage",
|
1678
|
+
"[dim]The metadata CID was not found in the blockchain storage registry[/dim]",
|
1679
|
+
"",
|
1680
|
+
"Possible reasons:",
|
1681
|
+
"• The CID may be incorrect",
|
1682
|
+
"• You may not be the owner of this file",
|
1683
|
+
]
|
1684
|
+
print_panel("\n".join(error_panel), title="Storage Cancellation Failed")
|
1476
1685
|
return 1
|
1477
|
-
|
1478
1686
|
except Exception as e:
|
1479
|
-
error(f"
|
1687
|
+
error(f"Error deleting erasure-coded file: {e}")
|
1480
1688
|
return 1
|
1481
1689
|
|
1482
1690
|
|
hippius_sdk/cli_parser.py
CHANGED
@@ -197,12 +197,32 @@ def add_storage_commands(subparsers):
|
|
197
197
|
"store", help="Upload a file to IPFS and store it on Substrate"
|
198
198
|
)
|
199
199
|
store_parser.add_argument("file_path", help="Path to file to upload")
|
200
|
+
store_parser.add_argument(
|
201
|
+
"--publish",
|
202
|
+
action="store_true",
|
203
|
+
help="Publish file to IPFS and store on the blockchain (default)",
|
204
|
+
)
|
205
|
+
store_parser.add_argument(
|
206
|
+
"--no-publish",
|
207
|
+
action="store_true",
|
208
|
+
help="Don't publish file to IPFS or store on the blockchain (local only)",
|
209
|
+
)
|
200
210
|
|
201
211
|
# Store directory command
|
202
212
|
store_dir_parser = subparsers.add_parser(
|
203
213
|
"store-dir", help="Upload a directory to IPFS and store all files on Substrate"
|
204
214
|
)
|
205
215
|
store_dir_parser.add_argument("dir_path", help="Path to directory to upload")
|
216
|
+
store_dir_parser.add_argument(
|
217
|
+
"--publish",
|
218
|
+
action="store_true",
|
219
|
+
help="Publish all files to IPFS and store on the blockchain (default)",
|
220
|
+
)
|
221
|
+
store_dir_parser.add_argument(
|
222
|
+
"--no-publish",
|
223
|
+
action="store_true",
|
224
|
+
help="Don't publish files to IPFS or store on the blockchain (local only)",
|
225
|
+
)
|
206
226
|
|
207
227
|
# Pinning status command
|
208
228
|
pinning_status_parser = subparsers.add_parser(
|
hippius_sdk/cli_rich.py
CHANGED
@@ -6,8 +6,14 @@ from typing import Any, Dict, List, Optional, Union
|
|
6
6
|
|
7
7
|
from rich.console import Console
|
8
8
|
from rich.panel import Panel
|
9
|
-
from rich.progress import (
|
10
|
-
|
9
|
+
from rich.progress import (
|
10
|
+
BarColumn,
|
11
|
+
Progress,
|
12
|
+
SpinnerColumn,
|
13
|
+
TextColumn,
|
14
|
+
TimeElapsedColumn,
|
15
|
+
TimeRemainingColumn,
|
16
|
+
)
|
11
17
|
from rich.table import Table
|
12
18
|
from rich.text import Text
|
13
19
|
|
hippius_sdk/client.py
CHANGED
@@ -148,10 +148,11 @@ class HippiusClient:
|
|
148
148
|
) -> Dict[str, Any]:
|
149
149
|
"""
|
150
150
|
Download a file from IPFS with optional decryption.
|
151
|
+
Supports downloading directories - in that case, a directory structure will be created.
|
151
152
|
|
152
153
|
Args:
|
153
154
|
cid: Content Identifier (CID) of the file to download
|
154
|
-
output_path: Path where the downloaded file will be saved
|
155
|
+
output_path: Path where the downloaded file/directory will be saved
|
155
156
|
decrypt: Whether to decrypt the file (overrides default)
|
156
157
|
|
157
158
|
Returns:
|
@@ -162,6 +163,7 @@ class HippiusClient:
|
|
162
163
|
- size_formatted: Human-readable file size
|
163
164
|
- elapsed_seconds: Time taken for the download
|
164
165
|
- decrypted: Whether the file was decrypted
|
166
|
+
- is_directory: Whether the download was a directory
|
165
167
|
|
166
168
|
Raises:
|
167
169
|
requests.RequestException: If the download fails
|
@@ -436,7 +438,7 @@ class HippiusClient:
|
|
436
438
|
metadata_cid: str,
|
437
439
|
cancel_from_blockchain: bool = True,
|
438
440
|
parallel_limit: int = 20,
|
439
|
-
) ->
|
441
|
+
) -> bool:
|
440
442
|
"""
|
441
443
|
Delete an erasure-coded file, including all its chunks in parallel.
|
442
444
|
|
@@ -446,7 +448,7 @@ class HippiusClient:
|
|
446
448
|
parallel_limit: Maximum number of concurrent deletion operations
|
447
449
|
|
448
450
|
Returns:
|
449
|
-
|
451
|
+
True or false if failed.
|
450
452
|
|
451
453
|
Raises:
|
452
454
|
RuntimeError: If deletion fails completely
|