dfdf
This commit is contained in:
@@ -6,7 +6,14 @@ import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import http.server
|
||||
from urllib.parse import quote
|
||||
import webbrowser
|
||||
from urllib.parse import urljoin
|
||||
from urllib.request import pathname2url
|
||||
|
||||
import pipeline as ctx
|
||||
from . import _shared as sh
|
||||
@@ -56,7 +63,7 @@ class Get_File(sh.Cmdlet):
|
||||
output_path = parsed.get("path")
|
||||
output_name = parsed.get("name")
|
||||
|
||||
debug(f"[get-file] file_hash={file_hash[:12] if file_hash else None}... store_name={store_name}")
|
||||
debug(f"[get-file] file_hash={file_hash} store_name={store_name}")
|
||||
|
||||
if not file_hash:
|
||||
log("Error: No file hash provided (pipe an item or use -query \"hash:<sha256>\")")
|
||||
@@ -83,7 +90,7 @@ class Get_File(sh.Cmdlet):
|
||||
debug(f"[get-file] Getting metadata for hash...")
|
||||
metadata = backend.get_metadata(file_hash)
|
||||
if not metadata:
|
||||
log(f"Error: File metadata not found for hash {file_hash[:12]}...")
|
||||
log(f"Error: File metadata not found for hash {file_hash}")
|
||||
return 1
|
||||
debug(f"[get-file] Metadata retrieved: title={metadata.get('title')}, ext={metadata.get('ext')}")
|
||||
|
||||
@@ -104,7 +111,7 @@ class Get_File(sh.Cmdlet):
|
||||
return text
|
||||
return ""
|
||||
|
||||
debug(f"[get-file] Calling backend.get_file({file_hash[:12]}...)")
|
||||
debug(f"[get-file] Calling backend.get_file({file_hash})")
|
||||
|
||||
# Get file from backend (may return Path or URL string depending on backend)
|
||||
source_path = backend.get_file(file_hash)
|
||||
@@ -135,7 +142,7 @@ class Get_File(sh.Cmdlet):
|
||||
source_path = Path(source_path)
|
||||
|
||||
if not source_path or not source_path.exists():
|
||||
log(f"Error: Backend could not retrieve file for hash {file_hash[:12]}...")
|
||||
log(f"Error: Backend could not retrieve file for hash {file_hash}")
|
||||
return 1
|
||||
|
||||
# Folder store UX: without -path, just open the file in the default app.
|
||||
@@ -202,6 +209,18 @@ class Get_File(sh.Cmdlet):
|
||||
def _open_file_default(self, path: Path) -> None:
|
||||
"""Open a local file in the OS default application."""
|
||||
try:
|
||||
suffix = str(path.suffix or "").lower()
|
||||
if sys.platform.startswith("win"):
|
||||
# On Windows, file associations for common media types can point at
|
||||
# editors (Paint/VS Code). Prefer opening a localhost URL.
|
||||
if self._open_local_file_in_browser_via_http(path):
|
||||
return
|
||||
|
||||
if suffix in {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".tif", ".tiff", ".svg"}:
|
||||
# Use default web browser for images.
|
||||
if self._open_image_in_default_browser(path):
|
||||
return
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
os.startfile(str(path)) # type: ignore[attr-defined]
|
||||
return
|
||||
@@ -211,6 +230,122 @@ class Get_File(sh.Cmdlet):
|
||||
subprocess.Popen(["xdg-open", str(path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except Exception as exc:
|
||||
log(f"Error opening file: {exc}", file=sys.stderr)
|
||||
|
||||
def _open_local_file_in_browser_via_http(self, file_path: Path) -> bool:
|
||||
"""Serve a single local file via localhost HTTP and open in browser.
|
||||
|
||||
This avoids Windows file-association issues (e.g., PNG -> Paint, HTML -> VS Code).
|
||||
The server is bound to 127.0.0.1 on an ephemeral port and is shut down after
|
||||
a timeout.
|
||||
"""
|
||||
try:
|
||||
resolved = file_path.resolve()
|
||||
directory = resolved.parent
|
||||
filename = resolved.name
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
class OneFileHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *handler_args, **handler_kwargs):
|
||||
super().__init__(*handler_args, directory=str(directory), **handler_kwargs)
|
||||
|
||||
def log_message(self, format: str, *args) -> None: # noqa: A003
|
||||
# Keep normal output clean.
|
||||
return
|
||||
|
||||
def do_GET(self) -> None: # noqa: N802
|
||||
if self.path in {"/", ""}:
|
||||
self.path = "/" + filename
|
||||
return super().do_GET()
|
||||
|
||||
if self.path == "/" + filename or self.path == "/" + quote(filename):
|
||||
return super().do_GET()
|
||||
|
||||
self.send_error(404)
|
||||
|
||||
def do_HEAD(self) -> None: # noqa: N802
|
||||
if self.path in {"/", ""}:
|
||||
self.path = "/" + filename
|
||||
return super().do_HEAD()
|
||||
|
||||
if self.path == "/" + filename or self.path == "/" + quote(filename):
|
||||
return super().do_HEAD()
|
||||
|
||||
self.send_error(404)
|
||||
|
||||
try:
|
||||
httpd = http.server.ThreadingHTTPServer(("127.0.0.1", 0), OneFileHandler)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
port = httpd.server_address[1]
|
||||
url = f"http://127.0.0.1:{port}/{quote(filename)}"
|
||||
|
||||
# Run server in the background.
|
||||
server_thread = threading.Thread(target=httpd.serve_forever, kwargs={"poll_interval": 0.2}, daemon=True)
|
||||
server_thread.start()
|
||||
|
||||
# Auto-shutdown after a timeout to avoid lingering servers.
|
||||
def shutdown_later() -> None:
|
||||
time.sleep(10 * 60)
|
||||
try:
|
||||
httpd.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
httpd.server_close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=shutdown_later, daemon=True).start()
|
||||
|
||||
try:
|
||||
debug(f"[get-file] Opening via localhost: {url}")
|
||||
return bool(webbrowser.open(url))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _open_image_in_default_browser(self, image_path: Path) -> bool:
|
||||
"""Open an image file in the user's default web browser.
|
||||
|
||||
We intentionally avoid opening the image path directly on Windows because
|
||||
file associations may point to editors/viewers (e.g., Paint). Instead we
|
||||
generate a tiny HTML wrapper and open that (HTML is typically associated
|
||||
with the default browser).
|
||||
"""
|
||||
try:
|
||||
resolved = image_path.resolve()
|
||||
image_url = urljoin("file:", pathname2url(str(resolved)))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# Create a stable wrapper filename to reduce temp-file spam.
|
||||
wrapper_path = Path(tempfile.gettempdir()) / f"medeia-open-image-{resolved.stem}.html"
|
||||
try:
|
||||
wrapper_path.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"<!doctype html>",
|
||||
"<meta charset=\"utf-8\">",
|
||||
f"<title>{resolved.name}</title>",
|
||||
"<style>html,body{margin:0;padding:0;background:#000}img{display:block;max-width:100vw;max-height:100vh;margin:auto}</style>",
|
||||
f"<img src=\"{image_url}\" alt=\"{resolved.name}\">",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# Prefer localhost server when possible (reliable on Windows).
|
||||
if self._open_local_file_in_browser_via_http(image_path):
|
||||
return True
|
||||
|
||||
wrapper_url = wrapper_path.as_uri()
|
||||
try:
|
||||
return bool(webbrowser.open(wrapper_url))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _sanitize_filename(self, name: str) -> str:
|
||||
"""Sanitize filename by removing invalid characters."""
|
||||
|
||||
Reference in New Issue
Block a user