cmdlet refactor

This commit is contained in:
2026-05-04 18:41:01 -07:00
parent 3ce339b3c1
commit 24f983473f
44 changed files with 1320 additions and 309 deletions
+1 -1
View File
@@ -392,7 +392,7 @@ def _dispatch_alldebrid_magnet_search(
config: Dict[str, Any],
) -> None:
try:
from cmdlet.search_file import CMDLET as _SEARCH_FILE_CMDLET
from cmdlet.file.search import CMDLET as _SEARCH_FILE_CMDLET
exec_fn = getattr(_SEARCH_FILE_CMDLET, "exec", None)
if callable(exec_fn):
+45
View File
@@ -549,6 +549,8 @@ class FTP(Provider):
if not local_path.exists() or not local_path.is_file():
raise FileNotFoundError(f"File not found: {local_path}")
pipe_obj = kwargs.get("pipe_obj")
settings = self._resolve_settings(
instance_name=str(kwargs.get("instance") or kwargs.get("store") or "").strip() or None,
require_explicit=bool(kwargs.get("instance") or kwargs.get("store")),
@@ -569,6 +571,19 @@ class FTP(Provider):
ftp = self._connect(settings=settings)
try:
self._ensure_directory(ftp, remote_dir, base_path=str(settings.get("base_path") or "/"))
# FTP duplicate check is filename-based at the destination directory.
# If the exact filename already exists remotely, skip re-upload.
if self._remote_filename_exists(ftp, remote_dir, remote_name):
try:
if pipe_obj is not None:
if not isinstance(getattr(pipe_obj, "extra", None), dict):
pipe_obj.extra = {}
pipe_obj.extra["upload_duplicate"] = True
pipe_obj.extra["upload_duplicate_rule"] = "filename"
pipe_obj.extra["upload_duplicate_target"] = remote_path
except Exception:
pass
return self._build_url(remote_path, settings=settings)
with local_path.open("rb") as handle:
ftp.storbinary(f"STOR {remote_path}", handle)
finally:
@@ -930,6 +945,36 @@ class FTP(Provider):
if not self._is_directory(ftp, partial):
raise
def _remote_filename_exists(self, ftp: ftplib.FTP, remote_dir: str, filename: str) -> bool:
target_name = str(filename or "").strip()
if not target_name:
return False
normalized_dir = self._normalize_remote_path(remote_dir, default=self._base_path)
try:
for name, facts in ftp.mlsd(normalized_dir):
_ = facts
if str(name or "").strip() == target_name:
return True
except Exception:
pass
try:
entries = ftp.nlst(normalized_dir)
except Exception:
entries = []
for entry in entries or []:
entry_text = str(entry or "").strip().rstrip("/")
if not entry_text:
continue
entry_name = posixpath.basename(entry_text)
if entry_name == target_name:
return True
return False
def _item_metadata(self, item: Any, *, pipe_obj: Any = None) -> Dict[str, Any]:
metadata: Dict[str, Any] = {}
for source in (item, pipe_obj):
+14 -14
View File
@@ -2842,7 +2842,7 @@ local function _start_screenshot_store_save(store, out_path, tags)
if screenshot_url == '' or not screenshot_url:match('^https?://') then
screenshot_url = ''
end
local cmd = 'add-file -store ' .. quote_pipeline_arg(store)
local cmd = 'file -add -store ' .. quote_pipeline_arg(store)
.. ' -path ' .. quote_pipeline_arg(out_path)
if screenshot_url ~= '' then
cmd = cmd .. ' -url ' .. quote_pipeline_arg(screenshot_url)
@@ -2854,7 +2854,7 @@ local function _start_screenshot_store_save(store, out_path, tags)
local tag_suffix = (#tag_list > 0) and (' | tags: ' .. tostring(#tag_list)) or ''
if #tag_list > 0 then
local tag_string = table.concat(tag_list, ',')
cmd = cmd .. ' | add-tag ' .. quote_pipeline_arg(tag_string)
cmd = cmd .. ' | tag -add ' .. quote_pipeline_arg(tag_string)
end
local queue_target = is_named_store and ('store ' .. store) or 'folder'
@@ -5539,7 +5539,7 @@ local function _start_download_flow_for_current()
end
ensure_mpv_ipc_server()
local pipeline_cmd = 'get-file -store ' .. quote_pipeline_arg(store_hash.store) .. ' -query ' .. quote_pipeline_arg('hash:' .. store_hash.hash) .. ' -path ' .. quote_pipeline_arg(folder)
local pipeline_cmd = 'file -get -store ' .. quote_pipeline_arg(store_hash.store) .. ' -query ' .. quote_pipeline_arg('hash:' .. store_hash.hash) .. ' -path ' .. quote_pipeline_arg(folder)
_queue_pipeline_in_repl(
pipeline_cmd,
'Queued in REPL: store copy',
@@ -5835,9 +5835,9 @@ mp.register_script_message('medios-download-pick-store', function(json)
end
local clip_suffix = clip_range ~= '' and (' [' .. clip_range .. ']') or ''
local pipeline_cmd = 'download-file -url ' .. quote_pipeline_arg(url)
local pipeline_cmd = 'file -download -url ' .. quote_pipeline_arg(url)
.. ' -query ' .. quote_pipeline_arg(query)
.. ' | add-file -store ' .. quote_pipeline_arg(store)
.. ' | file -add -store ' .. quote_pipeline_arg(store)
_set_selected_store(store)
_queue_pipeline_in_repl(
@@ -5901,9 +5901,9 @@ mp.register_script_message('medios-download-pick-path', function()
end
local clip_suffix = clip_range ~= '' and (' [' .. clip_range .. ']') or ''
local pipeline_cmd = 'download-file -url ' .. quote_pipeline_arg(url)
local pipeline_cmd = 'file -download -url ' .. quote_pipeline_arg(url)
.. ' -query ' .. quote_pipeline_arg(query)
.. ' | add-file -path ' .. quote_pipeline_arg(folder)
.. ' | file -add -path ' .. quote_pipeline_arg(folder)
_queue_pipeline_in_repl(
pipeline_cmd,
@@ -6137,7 +6137,7 @@ function M.delete_current_file()
local seed = {{path = path}}
M.run_pipeline('delete-file', seed, function(_, err)
M.run_pipeline('file -delete', seed, function(_, err)
if err then
mp.osd_message('Delete failed: ' .. tostring(err), 3)
return
@@ -6302,17 +6302,17 @@ local function _start_trim_with_range(range)
_lua_log('trim: building store file pipeline (original from store)')
if selected_store then
pipeline_cmd =
'get-tag -emit -store ' .. quote_pipeline_arg(store_hash.store) ..
'tag -get -emit -store ' .. quote_pipeline_arg(store_hash.store) ..
' -query ' .. quote_pipeline_arg('hash:' .. store_hash.hash) ..
' | add-file -path ' .. quote_pipeline_arg(output_path) ..
' | file -add -path ' .. quote_pipeline_arg(output_path) ..
' -store "' .. selected_store .. '"' ..
' | add-relationship -store "' .. selected_store .. '"' ..
' -to-hash ' .. quote_pipeline_arg(store_hash.hash)
else
pipeline_cmd =
'get-tag -emit -store ' .. quote_pipeline_arg(store_hash.store) ..
'tag -get -emit -store ' .. quote_pipeline_arg(store_hash.store) ..
' -query ' .. quote_pipeline_arg('hash:' .. store_hash.hash) ..
' | add-file -path ' .. quote_pipeline_arg(output_path) ..
' | file -add -path ' .. quote_pipeline_arg(output_path) ..
' -store "' .. store_hash.store .. '"' ..
' | add-relationship -store "' .. store_hash.store .. '"' ..
' -to-hash ' .. quote_pipeline_arg(store_hash.hash)
@@ -6321,9 +6321,9 @@ local function _start_trim_with_range(range)
-- Local file: save to selected store if available
_lua_log('trim: local file pipeline (not from store)')
if selected_store then
_lua_log('trim: building add-file command to selected_store=' .. selected_store)
_lua_log('trim: building file -add command to selected_store=' .. selected_store)
-- Don't add title if empty - the file path will be used as title by default
pipeline_cmd = 'add-file -path ' .. quote_pipeline_arg(output_path) ..
pipeline_cmd = 'file -add -path ' .. quote_pipeline_arg(output_path) ..
' -store "' .. selected_store .. '"'
_lua_log('trim: pipeline_cmd=' .. pipeline_cmd)
else
+3 -3
View File
@@ -470,11 +470,11 @@ class MPV:
def _q(s: str) -> str:
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
pipeline = f"download-file -url {_q(url)} -query {_q(f'format:{fmt}')}"
pipeline = f"file -download -url {_q(url)} -query {_q(f'format:{fmt}')}"
if store:
pipeline += f" | add-file -instance {_q(store)}"
pipeline += f" | file -add -instance {_q(store)}"
else:
pipeline += f" | add-file -path {_q(path or '')}"
pipeline += f" | file -add -path {_q(path or '')}"
try:
from TUI.pipeline_runner import PipelineRunner # noqa: WPS433
+37
View File
@@ -582,6 +582,8 @@ class SCP(Provider):
if not local_path.exists() or not local_path.is_file():
raise FileNotFoundError(f"File not found: {local_path}")
pipe_obj = kwargs.get("pipe_obj")
settings = self._resolve_settings(
instance_name=str(kwargs.get("instance") or kwargs.get("store") or "").strip() or None,
require_explicit=bool(kwargs.get("instance") or kwargs.get("store")),
@@ -609,8 +611,30 @@ class SCP(Provider):
if not self._is_sftp_negotiation_error(exc):
raise
self._ensure_directory_via_ssh(ssh, remote_dir)
if self._remote_filename_exists_via_ssh(ssh, remote_path):
try:
if pipe_obj is not None:
if not isinstance(getattr(pipe_obj, "extra", None), dict):
pipe_obj.extra = {}
pipe_obj.extra["upload_duplicate"] = True
pipe_obj.extra["upload_duplicate_rule"] = "filename"
pipe_obj.extra["upload_duplicate_target"] = remote_path
except Exception:
pass
return self._build_url(remote_path, settings=settings)
else:
self._ensure_directory(sftp, remote_dir, base_path=str(settings.get("base_path") or "/"))
if self._remote_filename_exists(sftp, remote_path):
try:
if pipe_obj is not None:
if not isinstance(getattr(pipe_obj, "extra", None), dict):
pipe_obj.extra = {}
pipe_obj.extra["upload_duplicate"] = True
pipe_obj.extra["upload_duplicate_rule"] = "filename"
pipe_obj.extra["upload_duplicate_target"] = remote_path
except Exception:
pass
return self._build_url(remote_path, settings=settings)
scp_client = self._open_scp(ssh)
scp_client.put(str(local_path), remote_path=remote_path)
finally:
@@ -620,6 +644,19 @@ class SCP(Provider):
return self._build_url(remote_path, settings=settings)
def _remote_filename_exists(self, sftp: Any, remote_path: str) -> bool:
try:
sftp.stat(remote_path)
return True
except Exception:
return False
def _remote_filename_exists_via_ssh(self, ssh: Any, remote_path: str) -> bool:
normalized = self._normalize_remote_path(remote_path, default=self._base_path)
quoted_path = shlex.quote(normalized)
status, _, _ = self._run_ssh_command(ssh, f"test -e {quoted_path}")
return status == 0
def _run_test_connection(self) -> Dict[str, Any]:
settings = self._resolve_settings()
if not settings.get("host"):