mdify-cli 3.0.8__tar.gz → 3.1.0__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.
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/PKG-INFO +1 -1
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/__init__.py +1 -1
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/cli.py +63 -12
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify_cli.egg-info/PKG-INFO +1 -1
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/pyproject.toml +1 -1
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/LICENSE +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/README.md +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/assets/mdify.png +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/__main__.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/container.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/docling_client.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/formatting.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/ssh/__init__.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/ssh/client.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/ssh/models.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/ssh/remote_container.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify/ssh/transfer.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify_cli.egg-info/SOURCES.txt +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify_cli.egg-info/dependency_links.txt +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify_cli.egg-info/entry_points.txt +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify_cli.egg-info/requires.txt +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/mdify_cli.egg-info/top_level.txt +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/setup.cfg +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/tests/test_cli.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/tests/test_container.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/tests/test_docling_client.py +0 -0
- {mdify_cli-3.0.8 → mdify_cli-3.1.0}/tests/test_ssh_client.py +0 -0
|
@@ -8,12 +8,14 @@ is lightweight and has no ML dependencies.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
|
+
import asyncio
|
|
11
12
|
import json
|
|
12
13
|
import os
|
|
13
14
|
import platform
|
|
14
15
|
import shutil
|
|
15
16
|
import subprocess
|
|
16
17
|
import sys
|
|
18
|
+
import tempfile
|
|
17
19
|
import threading
|
|
18
20
|
import time
|
|
19
21
|
from pathlib import Path
|
|
@@ -1247,7 +1249,8 @@ def main_async_remote(args) -> int:
|
|
|
1247
1249
|
if isinstance(exc, SSHConnectionError):
|
|
1248
1250
|
return True
|
|
1249
1251
|
msg = str(exc).lower()
|
|
1250
|
-
|
|
1252
|
+
# Errno 32 = Broken pipe, Errno 54 = Connection reset by peer
|
|
1253
|
+
return any(x in msg for x in ["broken pipe", "connection closed", "connection reset", "errno 32", "errno 54", "ssh connection"])
|
|
1251
1254
|
|
|
1252
1255
|
try:
|
|
1253
1256
|
for idx, input_file in enumerate(files_to_convert, 1):
|
|
@@ -1336,7 +1339,10 @@ def main_async_remote(args) -> int:
|
|
|
1336
1339
|
while conversion_attempt < 3 and not conversion_success:
|
|
1337
1340
|
try:
|
|
1338
1341
|
if conversion_attempt > 0 and not args.quiet:
|
|
1339
|
-
|
|
1342
|
+
# Exponential backoff: 2s, 4s, 8s
|
|
1343
|
+
backoff_delay = 2 ** conversion_attempt
|
|
1344
|
+
print(f" ↻ Conversion retry {conversion_attempt} (waiting {backoff_delay}s for server recovery)...", file=sys.stderr)
|
|
1345
|
+
await asyncio.sleep(backoff_delay)
|
|
1340
1346
|
|
|
1341
1347
|
conversion_output, _, conv_code = await ssh_client.run_command(convert_cmd, timeout=remote_conversion_timeout)
|
|
1342
1348
|
|
|
@@ -1344,19 +1350,39 @@ def main_async_remote(args) -> int:
|
|
|
1344
1350
|
conversion_success = True
|
|
1345
1351
|
break
|
|
1346
1352
|
else:
|
|
1347
|
-
|
|
1353
|
+
# Non-zero exit code - fail without retry for non-connection errors
|
|
1354
|
+
break
|
|
1348
1355
|
except Exception as conv_exc:
|
|
1349
|
-
|
|
1356
|
+
is_conn_err = is_connection_error(conv_exc)
|
|
1357
|
+
if is_conn_err and conversion_attempt < 2:
|
|
1350
1358
|
conversion_attempt += 1
|
|
1351
1359
|
if not args.quiet:
|
|
1352
|
-
|
|
1360
|
+
# Exponential backoff: 5s, 10s
|
|
1361
|
+
backoff_delay = 5 * conversion_attempt
|
|
1362
|
+
print(f" ↻ Connection reset during conversion. Reconnecting in {backoff_delay}s...", file=sys.stderr)
|
|
1363
|
+
|
|
1364
|
+
await asyncio.sleep(backoff_delay)
|
|
1365
|
+
|
|
1353
1366
|
try:
|
|
1354
1367
|
await ssh_client.disconnect()
|
|
1355
1368
|
except Exception:
|
|
1356
1369
|
pass
|
|
1357
|
-
|
|
1370
|
+
|
|
1371
|
+
# Reconnect with retry
|
|
1372
|
+
try:
|
|
1373
|
+
await ssh_client.connect()
|
|
1374
|
+
except Exception:
|
|
1375
|
+
if not args.quiet:
|
|
1376
|
+
print(f" ⚠ Reconnection failed: retrying...", file=sys.stderr)
|
|
1377
|
+
continue
|
|
1358
1378
|
else:
|
|
1359
|
-
|
|
1379
|
+
# Either not a connection error, or we've exhausted retries
|
|
1380
|
+
if not args.quiet:
|
|
1381
|
+
print(f" [DEBUG] Breaking loop: not conn_err or exhausted retries", file=sys.stderr)
|
|
1382
|
+
if conversion_attempt >= 2 and is_conn_err:
|
|
1383
|
+
if not args.quiet:
|
|
1384
|
+
print(f" ↻ Connection error on final retry attempt", file=sys.stderr)
|
|
1385
|
+
break
|
|
1360
1386
|
|
|
1361
1387
|
if not conversion_success:
|
|
1362
1388
|
print(f" ✗ Failed: Conversion failed after {conversion_attempt} attempt(s)", file=sys.stderr)
|
|
@@ -1396,12 +1422,37 @@ def main_async_remote(args) -> int:
|
|
|
1396
1422
|
# Ultimate fallback
|
|
1397
1423
|
markdown_content = conversion_output
|
|
1398
1424
|
|
|
1399
|
-
# Write markdown content to
|
|
1400
|
-
|
|
1401
|
-
|
|
1425
|
+
# Write markdown content to local temp file first, then upload via SFTP
|
|
1426
|
+
# (Piping large content through SSH here-documents can crash the connection)
|
|
1427
|
+
content_size_kb = len(markdown_content) / 1024
|
|
1428
|
+
if not args.quiet:
|
|
1429
|
+
print(f" {color.cyan('Writing')} {content_size_kb:.1f}KB markdown via SFTP...", file=sys.stderr)
|
|
1402
1430
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1431
|
+
try:
|
|
1432
|
+
# Write to temporary local file
|
|
1433
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as temp_file:
|
|
1434
|
+
temp_file.write(markdown_content)
|
|
1435
|
+
temp_path = temp_file.name
|
|
1436
|
+
|
|
1437
|
+
# Upload via SFTP (more reliable for large files)
|
|
1438
|
+
await transfer_manager.upload_file(
|
|
1439
|
+
local_path=temp_path,
|
|
1440
|
+
remote_path=remote_output_path,
|
|
1441
|
+
overwrite=True,
|
|
1442
|
+
compress=False,
|
|
1443
|
+
)
|
|
1444
|
+
|
|
1445
|
+
# Cleanup temp file
|
|
1446
|
+
try:
|
|
1447
|
+
os.unlink(temp_path)
|
|
1448
|
+
except Exception:
|
|
1449
|
+
pass
|
|
1450
|
+
|
|
1451
|
+
if not args.quiet:
|
|
1452
|
+
print(f" {color.green('✓')} Markdown written", file=sys.stderr)
|
|
1453
|
+
except Exception as write_exc:
|
|
1454
|
+
if not args.quiet:
|
|
1455
|
+
print(f" ✗ Failed to write markdown: {write_exc}", file=sys.stderr)
|
|
1405
1456
|
failed += 1
|
|
1406
1457
|
break
|
|
1407
1458
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|