huy
This commit is contained in:
414
MPV/LUA/main.lua
414
MPV/LUA/main.lua
@@ -42,6 +42,7 @@ local LOAD_URL_MENU_TYPE = 'medios_load_url'
|
|||||||
|
|
||||||
local DOWNLOAD_FORMAT_MENU_TYPE = 'medios_download_pick_format'
|
local DOWNLOAD_FORMAT_MENU_TYPE = 'medios_download_pick_format'
|
||||||
local DOWNLOAD_STORE_MENU_TYPE = 'medios_download_pick_store'
|
local DOWNLOAD_STORE_MENU_TYPE = 'medios_download_pick_store'
|
||||||
|
local SCREENSHOT_TAG_MENU_TYPE = 'medeia_screenshot_tags'
|
||||||
|
|
||||||
-- Menu types for the command submenu and trim prompt
|
-- Menu types for the command submenu and trim prompt
|
||||||
local CMD_MENU_TYPE = 'medios_cmd_menu'
|
local CMD_MENU_TYPE = 'medios_cmd_menu'
|
||||||
@@ -386,6 +387,12 @@ local function _path_exists(path)
|
|||||||
return utils.file_info(path) ~= nil
|
return utils.file_info(path) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function _normalize_fs_path(path)
|
||||||
|
path = trim(tostring(path or ''))
|
||||||
|
path = path:gsub('^"+', ''):gsub('"+$', '')
|
||||||
|
return trim(path)
|
||||||
|
end
|
||||||
|
|
||||||
local function _build_python_candidates(configured_python, prefer_no_console)
|
local function _build_python_candidates(configured_python, prefer_no_console)
|
||||||
local candidates = {}
|
local candidates = {}
|
||||||
local seen = {}
|
local seen = {}
|
||||||
@@ -585,6 +592,9 @@ end
|
|||||||
|
|
||||||
-- Forward declaration (defined later) used by helper auto-start.
|
-- Forward declaration (defined later) used by helper auto-start.
|
||||||
local _resolve_python_exe
|
local _resolve_python_exe
|
||||||
|
local _refresh_store_cache
|
||||||
|
local _uosc_open_list_picker
|
||||||
|
local _run_pipeline_detached
|
||||||
|
|
||||||
local _cached_store_names = {}
|
local _cached_store_names = {}
|
||||||
local _store_cache_loaded = false
|
local _store_cache_loaded = false
|
||||||
@@ -830,18 +840,15 @@ local function attempt_start_pipeline_helper_async(callback)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local cwd = _detect_repo_root()
|
|
||||||
local cwd_arg = cwd ~= '' and cwd or nil
|
|
||||||
|
|
||||||
local args = { python, '-m', 'MPV.pipeline_helper', '--ipc', get_mpv_ipc_path(), '--timeout', '30' }
|
local args = { python, '-m', 'MPV.pipeline_helper', '--ipc', get_mpv_ipc_path(), '--timeout', '30' }
|
||||||
_lua_log('attempt_start_pipeline_helper_async: spawning helper python=' .. tostring(python) .. ' cwd=' .. tostring(cwd_arg or ''))
|
_lua_log('attempt_start_pipeline_helper_async: spawning helper python=' .. tostring(python))
|
||||||
|
|
||||||
-- Spawn detached; don't wait for it here (async).
|
-- Spawn detached; don't wait for it here (async).
|
||||||
local ok, result, detail = _run_subprocess_command({ name = 'subprocess', args = args, cwd = cwd_arg, detach = true })
|
local ok, result, detail = _run_subprocess_command({ name = 'subprocess', args = args, detach = true })
|
||||||
_lua_log('attempt_start_pipeline_helper_async: detached spawn result ' .. tostring(detail or ''))
|
_lua_log('attempt_start_pipeline_helper_async: detached spawn result ' .. tostring(detail or ''))
|
||||||
if not ok then
|
if not ok then
|
||||||
_lua_log('attempt_start_pipeline_helper_async: detached spawn failed, retrying blocking')
|
_lua_log('attempt_start_pipeline_helper_async: detached spawn failed, retrying blocking')
|
||||||
ok, result, detail = _run_subprocess_command({ name = 'subprocess', args = args, cwd = cwd_arg })
|
ok, result, detail = _run_subprocess_command({ name = 'subprocess', args = args })
|
||||||
_lua_log('attempt_start_pipeline_helper_async: blocking spawn result ' .. tostring(detail or ''))
|
_lua_log('attempt_start_pipeline_helper_async: blocking spawn result ' .. tostring(detail or ''))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -913,6 +920,7 @@ local function _run_helper_request_async(req, timeout_seconds, cb)
|
|||||||
|
|
||||||
local function done(resp, err)
|
local function done(resp, err)
|
||||||
local err_text = err and tostring(err) or ''
|
local err_text = err and tostring(err) or ''
|
||||||
|
local quiet = type(req) == 'table' and req.quiet and true or false
|
||||||
local is_timeout = err_text:find('timeout waiting response', 1, true) ~= nil
|
local is_timeout = err_text:find('timeout waiting response', 1, true) ~= nil
|
||||||
local retry_count = type(req) == 'table' and tonumber(req._retry or 0) or 0
|
local retry_count = type(req) == 'table' and tonumber(req._retry or 0) or 0
|
||||||
local is_retryable = is_timeout and type(req) == 'table'
|
local is_retryable = is_timeout and type(req) == 'table'
|
||||||
@@ -938,7 +946,11 @@ local function _run_helper_request_async(req, timeout_seconds, cb)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if err then
|
if err then
|
||||||
|
if quiet then
|
||||||
|
_lua_log('ipc-async: done id=' .. tostring(id) .. ' unavailable ' .. tostring(label))
|
||||||
|
else
|
||||||
_lua_log('ipc-async: done id=' .. tostring(id) .. ' ERROR: ' .. tostring(err))
|
_lua_log('ipc-async: done id=' .. tostring(id) .. ' ERROR: ' .. tostring(err))
|
||||||
|
end
|
||||||
else
|
else
|
||||||
_lua_log('ipc-async: done id=' .. tostring(id) .. ' success=' .. tostring(resp and resp.success))
|
_lua_log('ipc-async: done id=' .. tostring(id) .. ' success=' .. tostring(resp and resp.success))
|
||||||
end
|
end
|
||||||
@@ -1044,88 +1056,16 @@ local function _run_helper_request_async(req, timeout_seconds, cb)
|
|||||||
ensure_helper_and_send()
|
ensure_helper_and_send()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _run_helper_request_response(req, timeout_seconds)
|
local function run_pipeline_via_ipc_async(pipeline_cmd, seeds, timeout_seconds, cb)
|
||||||
-- Legacy synchronous wrapper for compatibility with run_pipeline_via_ipc_response.
|
|
||||||
-- TODO: Migrate all callers to async _run_helper_request_async and remove this.
|
|
||||||
_last_ipc_error = ''
|
|
||||||
if not ensure_pipeline_helper_running() then
|
|
||||||
local rv = tostring(mp.get_property(PIPELINE_READY_PROP) or mp.get_property_native(PIPELINE_READY_PROP) or '')
|
|
||||||
_lua_log('ipc: helper not ready (ready=' .. rv .. '); attempting request anyway')
|
|
||||||
_last_ipc_error = 'helper not ready'
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
-- Best-effort wait for heartbeat, but do not hard-fail the request.
|
|
||||||
local deadline = mp.get_time() + 1.5
|
|
||||||
while mp.get_time() < deadline do
|
|
||||||
if _is_pipeline_helper_ready() then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
mp.wait_event(0.05)
|
|
||||||
end
|
|
||||||
if not _is_pipeline_helper_ready() then
|
|
||||||
local rv = tostring(mp.get_property(PIPELINE_READY_PROP) or mp.get_property_native(PIPELINE_READY_PROP) or '')
|
|
||||||
_lua_log('ipc: proceeding without helper heartbeat; ready=' .. rv)
|
|
||||||
_last_ipc_error = 'helper heartbeat missing (ready=' .. rv .. ')'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(req) ~= 'table' then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local id = tostring(req.id or '')
|
|
||||||
if id == '' then
|
|
||||||
id = tostring(math.floor(mp.get_time() * 1000)) .. '-' .. tostring(math.random(100000, 999999))
|
|
||||||
req.id = id
|
|
||||||
end
|
|
||||||
|
|
||||||
local label = ''
|
|
||||||
if req.op then
|
|
||||||
label = 'op=' .. tostring(req.op)
|
|
||||||
elseif req.pipeline then
|
|
||||||
label = 'cmd=' .. tostring(req.pipeline)
|
|
||||||
else
|
|
||||||
label = '(unknown)'
|
|
||||||
end
|
|
||||||
_lua_log('ipc: send request id=' .. tostring(id) .. ' ' .. label)
|
|
||||||
|
|
||||||
local req_json = utils.format_json(req)
|
|
||||||
_last_ipc_last_req_json = req_json
|
|
||||||
mp.set_property(PIPELINE_RESP_PROP, '')
|
|
||||||
mp.set_property(PIPELINE_REQ_PROP, req_json)
|
|
||||||
-- Read-back for debugging: confirms MPV accepted the property write.
|
|
||||||
local echoed = mp.get_property(PIPELINE_REQ_PROP) or ''
|
|
||||||
if echoed == '' then
|
|
||||||
_lua_log('ipc: WARNING request property echoed empty after set')
|
|
||||||
end
|
|
||||||
|
|
||||||
local deadline = mp.get_time() + (timeout_seconds or 5)
|
|
||||||
while mp.get_time() < deadline do
|
|
||||||
local resp_json = mp.get_property(PIPELINE_RESP_PROP)
|
|
||||||
if resp_json and resp_json ~= '' then
|
|
||||||
_last_ipc_last_resp_json = resp_json
|
|
||||||
local ok, resp = pcall(utils.parse_json, resp_json)
|
|
||||||
if ok and resp and resp.id == id then
|
|
||||||
_lua_log('ipc: got response id=' .. tostring(id) .. ' success=' .. tostring(resp.success))
|
|
||||||
return resp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
mp.wait_event(0.05)
|
|
||||||
end
|
|
||||||
|
|
||||||
_lua_log('ipc: timeout waiting response; ' .. label)
|
|
||||||
_last_ipc_error = 'timeout waiting response (' .. label .. ')'
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- IPC helper: return the whole response object (stdout/stderr/error/table)
|
|
||||||
local function run_pipeline_via_ipc_response(pipeline_cmd, seeds, timeout_seconds)
|
|
||||||
local req = { pipeline = pipeline_cmd }
|
local req = { pipeline = pipeline_cmd }
|
||||||
if seeds then
|
if seeds then
|
||||||
req.seeds = seeds
|
req.seeds = seeds
|
||||||
end
|
end
|
||||||
return _run_helper_request_response(req, timeout_seconds)
|
_run_helper_request_async(req, timeout_seconds, function(resp, err)
|
||||||
|
if type(cb) == 'function' then
|
||||||
|
cb(resp, err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _url_can_direct_load(url)
|
local function _url_can_direct_load(url)
|
||||||
@@ -1330,30 +1270,18 @@ local function _check_store_for_existing_url(store, url, cb)
|
|||||||
local needle = tostring(needles[idx])
|
local needle = tostring(needles[idx])
|
||||||
idx = idx + 1
|
idx = idx + 1
|
||||||
local query = 'url:' .. needle
|
local query = 'url:' .. needle
|
||||||
local pipeline_cmd = 'search-file -query ' .. quote_pipeline_arg(query) .. ' | output-json'
|
|
||||||
|
|
||||||
_lua_log('store-check: probing global query=' .. tostring(query))
|
_lua_log('store-check: probing global query=' .. tostring(query))
|
||||||
_run_helper_request_async({ pipeline = pipeline_cmd }, 4.0, function(resp, err)
|
_run_helper_request_async({ op = 'url-exists', data = { url = url, needles = { needle } }, quiet = true }, 2.5, function(resp, err)
|
||||||
if resp and resp.success then
|
if resp and resp.success then
|
||||||
local output = trim(tostring(resp.stdout or ''))
|
local data = resp.data
|
||||||
if output == '' then
|
if type(data) ~= 'table' or #data == 0 then
|
||||||
run_next(nil)
|
run_next(nil)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, data = pcall(utils.parse_json, output)
|
|
||||||
if ok and type(data) == 'table' then
|
|
||||||
if #data > 0 then
|
|
||||||
cb(data, nil, needle)
|
cb(data, nil, needle)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
run_next(nil)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
run_next('malformed JSON response')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local details = trim(tostring(err or ''))
|
local details = trim(tostring(err or ''))
|
||||||
if details == '' and type(resp) == 'table' then
|
if details == '' and type(resp) == 'table' then
|
||||||
@@ -1556,6 +1484,160 @@ local function _strip_title_extension(title, path)
|
|||||||
return title
|
return title
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local _pending_screenshot = nil
|
||||||
|
|
||||||
|
local function _normalize_tag_list(value)
|
||||||
|
local tags = {}
|
||||||
|
local seen = {}
|
||||||
|
|
||||||
|
local function add_tag(text)
|
||||||
|
text = trim(tostring(text or ''))
|
||||||
|
if text == '' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local key = text:lower()
|
||||||
|
if seen[key] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
seen[key] = true
|
||||||
|
tags[#tags + 1] = text
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(value) == 'table' then
|
||||||
|
for _, item in ipairs(value) do
|
||||||
|
add_tag(item)
|
||||||
|
end
|
||||||
|
return tags
|
||||||
|
end
|
||||||
|
|
||||||
|
local text = tostring(value or '')
|
||||||
|
for token in text:gmatch('[^,;\r\n]+') do
|
||||||
|
add_tag(token)
|
||||||
|
end
|
||||||
|
return tags
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _start_screenshot_store_save(store, out_path, tags)
|
||||||
|
store = _normalize_store_name(store)
|
||||||
|
out_path = _normalize_fs_path(out_path)
|
||||||
|
if store == '' or out_path == '' then
|
||||||
|
mp.osd_message('Screenshot upload failed: invalid store or path', 5)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local tag_list = _normalize_tag_list(tags)
|
||||||
|
local cmd = 'add-file -store ' .. quote_pipeline_arg(store)
|
||||||
|
local seeds = { { path = out_path } }
|
||||||
|
if #tag_list > 0 then
|
||||||
|
seeds[1].tag = tag_list
|
||||||
|
end
|
||||||
|
_set_selected_store(store)
|
||||||
|
|
||||||
|
if _run_pipeline_detached(cmd, function(_, err)
|
||||||
|
mp.osd_message('Screenshot upload failed to start: ' .. tostring(err or 'unknown'), 5)
|
||||||
|
end, seeds) then
|
||||||
|
local tag_suffix = (#tag_list > 0) and (' | tags: ' .. tostring(#tag_list)) or ''
|
||||||
|
mp.osd_message('Screenshot saved to store: ' .. store .. tag_suffix, 3)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
mp.osd_message('Screenshot upload failed to start', 5)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _commit_pending_screenshot(tags)
|
||||||
|
if type(_pending_screenshot) ~= 'table' or not _pending_screenshot.path or not _pending_screenshot.store then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local store = tostring(_pending_screenshot.store or '')
|
||||||
|
local out_path = tostring(_pending_screenshot.path or '')
|
||||||
|
_pending_screenshot = nil
|
||||||
|
_start_screenshot_store_save(store, out_path, tags)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _apply_screenshot_tag_query(query)
|
||||||
|
_commit_pending_screenshot(_normalize_tag_list(query))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _open_screenshot_tag_prompt(store, out_path)
|
||||||
|
store = _normalize_store_name(store)
|
||||||
|
out_path = _normalize_fs_path(out_path)
|
||||||
|
if store == '' or out_path == '' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
_pending_screenshot = { store = store, path = out_path }
|
||||||
|
|
||||||
|
if not ensure_uosc_loaded() then
|
||||||
|
_commit_pending_screenshot(nil)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local menu_data = {
|
||||||
|
type = SCREENSHOT_TAG_MENU_TYPE,
|
||||||
|
title = 'Screenshot tags',
|
||||||
|
search_style = 'palette',
|
||||||
|
search_debounce = 'submit',
|
||||||
|
on_search = { 'script-message-to', mp.get_script_name(), 'medeia-image-screenshot-tags-search' },
|
||||||
|
footnote = 'Optional comma-separated tags. Press Enter to save, or choose Save without tags.',
|
||||||
|
items = {
|
||||||
|
{
|
||||||
|
title = 'Save without tags',
|
||||||
|
hint = 'Skip optional tags',
|
||||||
|
value = { 'script-message-to', mp.get_script_name(), 'medeia-image-screenshot-tags-event', utils.format_json({ query = '' }) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mp.commandv('script-message-to', 'uosc', 'open-menu', utils.format_json(menu_data))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _open_store_picker_for_pending_screenshot()
|
||||||
|
if type(_pending_screenshot) ~= 'table' or not _pending_screenshot.path then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function build_items()
|
||||||
|
local selected = _get_selected_store()
|
||||||
|
local items = {}
|
||||||
|
|
||||||
|
if type(_cached_store_names) == 'table' and #_cached_store_names > 0 then
|
||||||
|
for _, name in ipairs(_cached_store_names) do
|
||||||
|
name = trim(tostring(name or ''))
|
||||||
|
if name ~= '' then
|
||||||
|
items[#items + 1] = {
|
||||||
|
title = name,
|
||||||
|
hint = (selected ~= '' and name == selected) and 'Current store' or '',
|
||||||
|
active = (selected ~= '' and name == selected) and true or false,
|
||||||
|
value = { 'script-message-to', mp.get_script_name(), 'medeia-image-screenshot-pick-store', utils.format_json({ store = name }) },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
items[#items + 1] = {
|
||||||
|
title = 'No stores found',
|
||||||
|
hint = 'Configure stores in config.conf',
|
||||||
|
selectable = false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
_uosc_open_list_picker(DOWNLOAD_STORE_MENU_TYPE, 'Save screenshot', build_items())
|
||||||
|
|
||||||
|
mp.add_timeout(0.05, function()
|
||||||
|
if type(_pending_screenshot) ~= 'table' or not _pending_screenshot.path then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
_refresh_store_cache(1.5, function(success, changed)
|
||||||
|
if success and changed then
|
||||||
|
_uosc_open_list_picker(DOWNLOAD_STORE_MENU_TYPE, 'Save screenshot', build_items())
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
local function _capture_screenshot()
|
local function _capture_screenshot()
|
||||||
local function _format_time_label(seconds)
|
local function _format_time_label(seconds)
|
||||||
local total = math.max(0, math.floor(tonumber(seconds or 0) or 0))
|
local total = math.max(0, math.floor(tonumber(seconds or 0) or 0))
|
||||||
@@ -1585,8 +1667,12 @@ local function _capture_screenshot()
|
|||||||
local safe_title = _sanitize_filename_component(raw_title)
|
local safe_title = _sanitize_filename_component(raw_title)
|
||||||
|
|
||||||
local filename = safe_title .. '_' .. label .. '.png'
|
local filename = safe_title .. '_' .. label .. '.png'
|
||||||
local temp_dir = mp.get_property('user-data/medeia-config-temp') or os.getenv('TEMP') or os.getenv('TMP') or '/tmp'
|
local temp_dir = _normalize_fs_path(mp.get_property('user-data/medeia-config-temp'))
|
||||||
|
if temp_dir == '' then
|
||||||
|
temp_dir = _normalize_fs_path(os.getenv('TEMP') or os.getenv('TMP') or '/tmp')
|
||||||
|
end
|
||||||
local out_path = utils.join_path(temp_dir, filename)
|
local out_path = utils.join_path(temp_dir, filename)
|
||||||
|
out_path = _normalize_fs_path(out_path)
|
||||||
|
|
||||||
local function do_screenshot(mode)
|
local function do_screenshot(mode)
|
||||||
mode = mode or 'video'
|
mode = mode or 'video'
|
||||||
@@ -1611,28 +1697,35 @@ local function _capture_screenshot()
|
|||||||
end
|
end
|
||||||
|
|
||||||
_ensure_selected_store_loaded()
|
_ensure_selected_store_loaded()
|
||||||
local selected_store = _get_selected_store()
|
|
||||||
selected_store = trim(tostring(selected_store or ''))
|
local function dispatch_screenshot_save()
|
||||||
selected_store = selected_store:gsub('^\"', ''):gsub('\"$', '')
|
local store_count = (type(_cached_store_names) == 'table') and #_cached_store_names or 0
|
||||||
|
local selected_store = _normalize_store_name(_get_selected_store())
|
||||||
|
|
||||||
|
if store_count > 1 then
|
||||||
|
_pending_screenshot = { path = out_path }
|
||||||
|
_open_store_picker_for_pending_screenshot()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if selected_store == '' and store_count == 1 then
|
||||||
|
selected_store = _normalize_store_name(_cached_store_names[1])
|
||||||
|
end
|
||||||
|
|
||||||
if selected_store == '' then
|
if selected_store == '' then
|
||||||
mp.osd_message('Select a store first (Store button)', 2)
|
mp.osd_message('Select a store first (Store button)', 2)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
mp.osd_message('Saving screenshot...', 1)
|
_open_screenshot_tag_prompt(selected_store, out_path)
|
||||||
|
end
|
||||||
|
|
||||||
-- optimization: use persistent pipeline helper instead of spawning new python process
|
if not _store_cache_loaded then
|
||||||
-- escape paths for command line
|
_refresh_store_cache(1.5, function()
|
||||||
local cmd = 'add-file -store "' .. selected_store .. '" -path "' .. out_path .. '"'
|
dispatch_screenshot_save()
|
||||||
|
end)
|
||||||
local resp = run_pipeline_via_ipc_response(cmd, nil, 10)
|
|
||||||
|
|
||||||
if resp and resp.success then
|
|
||||||
mp.osd_message('Screenshot saved to store: ' .. selected_store, 3)
|
|
||||||
else
|
else
|
||||||
local err = (resp and resp.error) or (resp and resp.stderr) or 'IPC error'
|
dispatch_screenshot_save()
|
||||||
mp.osd_message('Screenshot upload failed: ' .. tostring(err), 5)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1640,6 +1733,36 @@ mp.register_script_message('medeia-image-screenshot', function()
|
|||||||
_capture_screenshot()
|
_capture_screenshot()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('medeia-image-screenshot-pick-store', function(json)
|
||||||
|
if type(_pending_screenshot) ~= 'table' or not _pending_screenshot.path then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local ok, ev = pcall(utils.parse_json, json)
|
||||||
|
if not ok or type(ev) ~= 'table' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local store = _normalize_store_name(ev.store)
|
||||||
|
if store == '' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local out_path = tostring(_pending_screenshot.path or '')
|
||||||
|
_open_screenshot_tag_prompt(store, out_path)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('medeia-image-screenshot-tags-search', function(query)
|
||||||
|
_apply_screenshot_tag_query(query)
|
||||||
|
end)
|
||||||
|
|
||||||
|
mp.register_script_message('medeia-image-screenshot-tags-event', function(json)
|
||||||
|
local ok, ev = pcall(utils.parse_json, json)
|
||||||
|
if not ok or type(ev) ~= 'table' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
_apply_screenshot_tag_query(ev.query)
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
local CLIP_MARKER_SLOT_COUNT = 2
|
local CLIP_MARKER_SLOT_COUNT = 2
|
||||||
local clip_markers = {}
|
local clip_markers = {}
|
||||||
@@ -2061,7 +2184,7 @@ local function _run_pipeline_request_async(pipeline_cmd, seeds, timeout_seconds,
|
|||||||
_run_helper_request_async(req, timeout_seconds or 30, cb)
|
_run_helper_request_async(req, timeout_seconds or 30, cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _refresh_store_cache(timeout_seconds, on_complete)
|
_refresh_store_cache = function(timeout_seconds, on_complete)
|
||||||
ensure_mpv_ipc_server()
|
ensure_mpv_ipc_server()
|
||||||
|
|
||||||
local prev_count = (type(_cached_store_names) == 'table') and #_cached_store_names or 0
|
local prev_count = (type(_cached_store_names) == 'table') and #_cached_store_names or 0
|
||||||
@@ -2174,7 +2297,7 @@ local function _refresh_store_cache(timeout_seconds, on_complete)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _uosc_open_list_picker(menu_type, title, items)
|
_uosc_open_list_picker = function(menu_type, title, items)
|
||||||
local menu_data = {
|
local menu_data = {
|
||||||
type = menu_type,
|
type = menu_type,
|
||||||
title = title,
|
title = title,
|
||||||
@@ -2606,7 +2729,7 @@ _refresh_current_store_url_status = function(reason)
|
|||||||
local err_text = trim(tostring(err or ''))
|
local err_text = trim(tostring(err or ''))
|
||||||
if err_text ~= '' then
|
if err_text ~= '' then
|
||||||
_set_current_store_url_status(store, url, 'error', err_text, 0, needle)
|
_set_current_store_url_status(store, url, 'error', err_text, 0, needle)
|
||||||
_lua_log('store-check: failed err=' .. tostring(err_text))
|
_lua_log('store-check: lookup unavailable')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -3281,10 +3404,6 @@ local function _run_pipeline_cli_detached(pipeline_cmd, seeds)
|
|||||||
args = args,
|
args = args,
|
||||||
detach = true,
|
detach = true,
|
||||||
}
|
}
|
||||||
local repo_root = _detect_repo_root()
|
|
||||||
if repo_root ~= '' then
|
|
||||||
cmd.cwd = repo_root
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, result, detail = _run_subprocess_command(cmd)
|
local ok, result, detail = _run_subprocess_command(cmd)
|
||||||
if ok then
|
if ok then
|
||||||
@@ -3296,11 +3415,11 @@ local function _run_pipeline_cli_detached(pipeline_cmd, seeds)
|
|||||||
return false, detail or _describe_subprocess_result(result)
|
return false, detail or _describe_subprocess_result(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _run_pipeline_detached(pipeline_cmd, on_failure)
|
_run_pipeline_detached = function(pipeline_cmd, on_failure, seeds)
|
||||||
if not pipeline_cmd or pipeline_cmd == '' then
|
if not pipeline_cmd or pipeline_cmd == '' then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
local ok, detail = _run_pipeline_cli_detached(pipeline_cmd, nil)
|
local ok, detail = _run_pipeline_cli_detached(pipeline_cmd, seeds)
|
||||||
if ok then
|
if ok then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -3313,7 +3432,7 @@ local function _run_pipeline_detached(pipeline_cmd, on_failure)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
_run_helper_request_async({ op = 'run-detached', data = { pipeline = pipeline_cmd } }, 1.0, function(resp, err)
|
_run_helper_request_async({ op = 'run-detached', data = { pipeline = pipeline_cmd, seeds = seeds } }, 1.0, function(resp, err)
|
||||||
if resp and resp.success then
|
if resp and resp.success then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -3826,11 +3945,26 @@ end
|
|||||||
-- Helper to run pipeline and parse JSON output
|
-- Helper to run pipeline and parse JSON output
|
||||||
function M.run_pipeline_json(pipeline_cmd, seeds, cb)
|
function M.run_pipeline_json(pipeline_cmd, seeds, cb)
|
||||||
cb = cb or function() end
|
cb = cb or function() end
|
||||||
if not pipeline_cmd:match('output%-json$') then
|
pipeline_cmd = trim(tostring(pipeline_cmd or ''))
|
||||||
pipeline_cmd = pipeline_cmd .. ' | output-json'
|
if pipeline_cmd == '' then
|
||||||
|
cb(nil, 'empty pipeline command')
|
||||||
|
return
|
||||||
end
|
end
|
||||||
M.run_pipeline(pipeline_cmd, seeds, function(output, err)
|
|
||||||
if output then
|
ensure_mpv_ipc_server()
|
||||||
|
|
||||||
|
local lower_cmd = pipeline_cmd:lower()
|
||||||
|
local is_mpv_load = lower_cmd:match('%.mpv%s+%-url') ~= nil
|
||||||
|
local timeout_seconds = is_mpv_load and 45 or 30
|
||||||
|
_run_pipeline_request_async(pipeline_cmd, seeds, timeout_seconds, function(resp, err)
|
||||||
|
if resp and resp.success then
|
||||||
|
if type(resp.data) == 'table' then
|
||||||
|
cb(resp.data, nil)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local output = trim(tostring(resp.stdout or ''))
|
||||||
|
if output ~= '' then
|
||||||
local ok, data = pcall(utils.parse_json, output)
|
local ok, data = pcall(utils.parse_json, output)
|
||||||
if ok then
|
if ok then
|
||||||
cb(data, nil)
|
cb(data, nil)
|
||||||
@@ -3840,7 +3974,20 @@ function M.run_pipeline_json(pipeline_cmd, seeds, cb)
|
|||||||
cb(nil, 'malformed JSON response')
|
cb(nil, 'malformed JSON response')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
cb(nil, err)
|
|
||||||
|
cb({}, nil)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local details = err or ''
|
||||||
|
if details == '' and type(resp) == 'table' then
|
||||||
|
if resp.error and tostring(resp.error) ~= '' then
|
||||||
|
details = tostring(resp.error)
|
||||||
|
elseif resp.stderr and tostring(resp.stderr) ~= '' then
|
||||||
|
details = tostring(resp.stderr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cb(nil, details ~= '' and details or 'unknown')
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4081,11 +4228,9 @@ local function _start_trim_with_range(range)
|
|||||||
_lua_log('trim: final upload_cmd=' .. pipeline_cmd)
|
_lua_log('trim: final upload_cmd=' .. pipeline_cmd)
|
||||||
_lua_log('trim: === CALLING PIPELINE HELPER FOR UPLOAD ===')
|
_lua_log('trim: === CALLING PIPELINE HELPER FOR UPLOAD ===')
|
||||||
|
|
||||||
-- Optimization: use persistent pipeline helper
|
run_pipeline_via_ipc_async(pipeline_cmd, nil, 60, function(response, err)
|
||||||
local response = run_pipeline_via_ipc_response(pipeline_cmd, nil, 60)
|
|
||||||
|
|
||||||
if not response then
|
if not response then
|
||||||
response = { success = false, error = "Timeout or IPC error" }
|
response = { success = false, error = err or 'Timeout or IPC error' }
|
||||||
end
|
end
|
||||||
|
|
||||||
_lua_log('trim: api response success=' .. tostring(response.success))
|
_lua_log('trim: api response success=' .. tostring(response.success))
|
||||||
@@ -4105,10 +4250,11 @@ local function _start_trim_with_range(range)
|
|||||||
mp.osd_message(msg, 5)
|
mp.osd_message(msg, 5)
|
||||||
_lua_log('trim: SUCCESS - ' .. msg)
|
_lua_log('trim: SUCCESS - ' .. msg)
|
||||||
else
|
else
|
||||||
local err_msg = response.error or response.stderr or 'unknown error'
|
local err_msg = err or response.error or response.stderr or 'unknown error'
|
||||||
mp.osd_message('Upload failed: ' .. err_msg, 5)
|
mp.osd_message('Upload failed: ' .. err_msg, 5)
|
||||||
_lua_log('trim: upload FAILED - ' .. err_msg)
|
_lua_log('trim: upload FAILED - ' .. err_msg)
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.open_trim_prompt()
|
function M.open_trim_prompt()
|
||||||
|
|||||||
14
MPV/lyric.py
14
MPV/lyric.py
@@ -1079,6 +1079,7 @@ class _PlaybackState:
|
|||||||
fetch_attempt_at: float = 0.0
|
fetch_attempt_at: float = 0.0
|
||||||
cache_wait_key: Optional[str] = None
|
cache_wait_key: Optional[str] = None
|
||||||
cache_wait_started_at: float = 0.0
|
cache_wait_started_at: float = 0.0
|
||||||
|
cache_wait_next_probe_at: float = 0.0
|
||||||
|
|
||||||
def clear(self, client: MPVIPCClient, *, clear_hash: bool = True) -> None:
|
def clear(self, client: MPVIPCClient, *, clear_hash: bool = True) -> None:
|
||||||
"""Reset backend resolution and clean up any active OSD / external subtitle.
|
"""Reset backend resolution and clean up any active OSD / external subtitle.
|
||||||
@@ -1096,6 +1097,7 @@ class _PlaybackState:
|
|||||||
self.times = []
|
self.times = []
|
||||||
self.cache_wait_key = None
|
self.cache_wait_key = None
|
||||||
self.cache_wait_started_at = 0.0
|
self.cache_wait_started_at = 0.0
|
||||||
|
self.cache_wait_next_probe_at = 0.0
|
||||||
if self.loaded_key is not None:
|
if self.loaded_key is not None:
|
||||||
_osd_clear_and_restore(client)
|
_osd_clear_and_restore(client)
|
||||||
self.loaded_key = None
|
self.loaded_key = None
|
||||||
@@ -1244,6 +1246,7 @@ def run_auto_overlay(
|
|||||||
state.key = None
|
state.key = None
|
||||||
state.cache_wait_key = None
|
state.cache_wait_key = None
|
||||||
state.cache_wait_started_at = 0.0
|
state.cache_wait_started_at = 0.0
|
||||||
|
state.cache_wait_next_probe_at = 0.0
|
||||||
|
|
||||||
if store_override and (not hash_override or hash_override == state.file_hash):
|
if store_override and (not hash_override or hash_override == state.file_hash):
|
||||||
reg = _make_registry()
|
reg = _make_registry()
|
||||||
@@ -1390,15 +1393,21 @@ def run_auto_overlay(
|
|||||||
if state.cache_wait_key != state.key:
|
if state.cache_wait_key != state.key:
|
||||||
state.cache_wait_key = state.key
|
state.cache_wait_key = state.key
|
||||||
state.cache_wait_started_at = now
|
state.cache_wait_started_at = now
|
||||||
|
state.cache_wait_next_probe_at = 0.0
|
||||||
|
elif state.cache_wait_next_probe_at > now:
|
||||||
|
time.sleep(max(0.05, min(0.5, state.cache_wait_next_probe_at - now)))
|
||||||
|
continue
|
||||||
pending = is_notes_prefetch_pending(state.store_name, state.file_hash)
|
pending = is_notes_prefetch_pending(state.store_name, state.file_hash)
|
||||||
waited_s = max(0.0, now - float(state.cache_wait_started_at or now))
|
waited_s = max(0.0, now - float(state.cache_wait_started_at or now))
|
||||||
|
|
||||||
if pending and waited_s < pending_wait_s:
|
if pending and waited_s < pending_wait_s:
|
||||||
time.sleep(min(max(poll_s, 0.05), 0.2))
|
state.cache_wait_next_probe_at = now + max(0.2, min(0.5, pending_wait_s - waited_s))
|
||||||
|
time.sleep(max(0.05, min(0.5, state.cache_wait_next_probe_at - now)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if waited_s < cache_wait_s:
|
if waited_s < cache_wait_s:
|
||||||
time.sleep(min(max(poll_s, 0.05), 0.2))
|
state.cache_wait_next_probe_at = now + max(0.2, min(0.5, cache_wait_s - waited_s))
|
||||||
|
time.sleep(max(0.05, min(0.5, state.cache_wait_next_probe_at - now)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1413,6 +1422,7 @@ def run_auto_overlay(
|
|||||||
|
|
||||||
state.cache_wait_key = None
|
state.cache_wait_key = None
|
||||||
state.cache_wait_started_at = 0.0
|
state.cache_wait_started_at = 0.0
|
||||||
|
state.cache_wait_next_probe_at = 0.0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_log(
|
_log(
|
||||||
|
|||||||
@@ -112,10 +112,32 @@ def _start_ready_heartbeat(ipc_path: str, stop_event: threading.Event) -> thread
|
|||||||
return thread
|
return thread
|
||||||
|
|
||||||
|
|
||||||
def _run_pipeline(pipeline_text: str, *, seeds: Any = None) -> Dict[str, Any]:
|
def _run_pipeline(
|
||||||
|
pipeline_text: str,
|
||||||
|
*,
|
||||||
|
seeds: Any = None,
|
||||||
|
json_output: bool = False,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
# Import after sys.path fix.
|
# Import after sys.path fix.
|
||||||
from TUI.pipeline_runner import PipelineRunner # noqa: WPS433
|
from TUI.pipeline_runner import PipelineRunner # noqa: WPS433
|
||||||
|
|
||||||
|
def _json_safe(value: Any) -> Any:
|
||||||
|
if value is None or isinstance(value, (str, int, float, bool)):
|
||||||
|
return value
|
||||||
|
if isinstance(value, dict):
|
||||||
|
out: Dict[str, Any] = {}
|
||||||
|
for key, item in value.items():
|
||||||
|
out[str(key)] = _json_safe(item)
|
||||||
|
return out
|
||||||
|
if isinstance(value, (list, tuple, set)):
|
||||||
|
return [_json_safe(item) for item in value]
|
||||||
|
if hasattr(value, "to_dict") and callable(getattr(value, "to_dict")):
|
||||||
|
try:
|
||||||
|
return _json_safe(value.to_dict())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return str(value)
|
||||||
|
|
||||||
def _table_to_payload(table: Any) -> Optional[Dict[str, Any]]:
|
def _table_to_payload(table: Any) -> Optional[Dict[str, Any]]:
|
||||||
if table is None:
|
if table is None:
|
||||||
return None
|
return None
|
||||||
@@ -190,12 +212,20 @@ def _run_pipeline(pipeline_text: str, *, seeds: Any = None) -> Dict[str, Any]:
|
|||||||
except Exception:
|
except Exception:
|
||||||
table_payload = None
|
table_payload = None
|
||||||
|
|
||||||
|
data_payload = None
|
||||||
|
if json_output:
|
||||||
|
try:
|
||||||
|
data_payload = _json_safe(getattr(result, "emitted", None) or [])
|
||||||
|
except Exception:
|
||||||
|
data_payload = []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": bool(result.success),
|
"success": bool(result.success),
|
||||||
"stdout": result.stdout or "",
|
"stdout": result.stdout or "",
|
||||||
"stderr": result.stderr or "",
|
"stderr": result.stderr or "",
|
||||||
"error": result.error,
|
"error": result.error,
|
||||||
"table": table_payload,
|
"table": table_payload,
|
||||||
|
"data": data_payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -372,6 +402,112 @@ def _run_op(op: str, data: Any) -> Dict[str, Any]:
|
|||||||
"choices": [],
|
"choices": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if op_name in {"url-exists",
|
||||||
|
"url_exists",
|
||||||
|
"find-url",
|
||||||
|
"find_url"}:
|
||||||
|
try:
|
||||||
|
from Store import Store # noqa: WPS433
|
||||||
|
|
||||||
|
cfg = load_config() or {}
|
||||||
|
storage = Store(config=cfg, suppress_debug=True)
|
||||||
|
|
||||||
|
raw_needles: list[str] = []
|
||||||
|
if isinstance(data, dict):
|
||||||
|
maybe_needles = data.get("needles")
|
||||||
|
if isinstance(maybe_needles, (list, tuple, set)):
|
||||||
|
for item in maybe_needles:
|
||||||
|
text = str(item or "").strip()
|
||||||
|
if text and text not in raw_needles:
|
||||||
|
raw_needles.append(text)
|
||||||
|
elif isinstance(maybe_needles, str):
|
||||||
|
text = maybe_needles.strip()
|
||||||
|
if text:
|
||||||
|
raw_needles.append(text)
|
||||||
|
|
||||||
|
if not raw_needles:
|
||||||
|
text = str(data.get("url") or "").strip()
|
||||||
|
if text:
|
||||||
|
raw_needles.append(text)
|
||||||
|
|
||||||
|
if not raw_needles:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": "",
|
||||||
|
"error": "Missing url",
|
||||||
|
"table": None,
|
||||||
|
"data": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
matches: list[dict[str, Any]] = []
|
||||||
|
seen_keys: set[str] = set()
|
||||||
|
|
||||||
|
for backend_name in storage.list_backends() or []:
|
||||||
|
try:
|
||||||
|
backend = storage[backend_name]
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
search_fn = getattr(backend, "search", None)
|
||||||
|
if not callable(search_fn):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for needle in raw_needles:
|
||||||
|
query = f"url:{needle}"
|
||||||
|
try:
|
||||||
|
results = backend.search(
|
||||||
|
query,
|
||||||
|
limit=1,
|
||||||
|
minimal=True,
|
||||||
|
url_only=True,
|
||||||
|
) or []
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for item in results:
|
||||||
|
if hasattr(item, "to_dict") and callable(getattr(item, "to_dict")):
|
||||||
|
try:
|
||||||
|
item = item.to_dict()
|
||||||
|
except Exception:
|
||||||
|
item = {"title": str(item)}
|
||||||
|
elif not isinstance(item, dict):
|
||||||
|
item = {"title": str(item)}
|
||||||
|
|
||||||
|
payload = dict(item)
|
||||||
|
payload.setdefault("store", str(backend_name))
|
||||||
|
payload.setdefault("needle", str(needle))
|
||||||
|
|
||||||
|
key = str(payload.get("hash") or payload.get("url") or payload.get("title") or needle).strip().lower()
|
||||||
|
if key in seen_keys:
|
||||||
|
continue
|
||||||
|
seen_keys.add(key)
|
||||||
|
matches.append(payload)
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
break
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
break
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": "",
|
||||||
|
"error": None,
|
||||||
|
"table": None,
|
||||||
|
"data": matches,
|
||||||
|
}
|
||||||
|
except Exception as exc:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"stdout": "",
|
||||||
|
"stderr": "",
|
||||||
|
"error": f"url-exists failed: {type(exc).__name__}: {exc}",
|
||||||
|
"table": None,
|
||||||
|
"data": [],
|
||||||
|
}
|
||||||
|
|
||||||
# Provide yt-dlp format list for a URL (for MPV "Change format" menu).
|
# Provide yt-dlp format list for a URL (for MPV "Change format" menu).
|
||||||
# Returns a ResultTable-like payload so the Lua UI can render without running cmdlets.
|
# Returns a ResultTable-like payload so the Lua UI can render without running cmdlets.
|
||||||
if op_name in {"ytdlp-formats",
|
if op_name in {"ytdlp-formats",
|
||||||
@@ -1084,6 +1220,7 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|||||||
data = req.get("data")
|
data = req.get("data")
|
||||||
pipeline_text = str(req.get("pipeline") or "").strip()
|
pipeline_text = str(req.get("pipeline") or "").strip()
|
||||||
seeds = req.get("seeds")
|
seeds = req.get("seeds")
|
||||||
|
json_output = bool(req.get("json") or req.get("output_json"))
|
||||||
|
|
||||||
if not req_id:
|
if not req_id:
|
||||||
continue
|
continue
|
||||||
@@ -1122,7 +1259,11 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|||||||
req_id=req_id,
|
req_id=req_id,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
run = _run_pipeline(pipeline_text, seeds=seeds)
|
run = _run_pipeline(
|
||||||
|
pipeline_text,
|
||||||
|
seeds=seeds,
|
||||||
|
json_output=json_output,
|
||||||
|
)
|
||||||
|
|
||||||
resp = {
|
resp = {
|
||||||
"id": req_id,
|
"id": req_id,
|
||||||
@@ -1133,6 +1274,7 @@ def main(argv: Optional[list[str]] = None) -> int:
|
|||||||
""),
|
""),
|
||||||
"error": run.get("error"),
|
"error": run.get("error"),
|
||||||
"table": run.get("table"),
|
"table": run.get("table"),
|
||||||
|
"data": run.get("data"),
|
||||||
}
|
}
|
||||||
if "choices" in run:
|
if "choices" in run:
|
||||||
resp["choices"] = run.get("choices")
|
resp["choices"] = run.get("choices")
|
||||||
|
|||||||
@@ -527,15 +527,72 @@ def get_config_all() -> Dict[str, Any]:
|
|||||||
|
|
||||||
# Worker Management Methods for medios.db
|
# Worker Management Methods for medios.db
|
||||||
|
|
||||||
|
def _worker_db_connect(timeout: float = 0.75) -> sqlite3.Connection:
|
||||||
|
conn = sqlite3.connect(
|
||||||
|
str(DB_PATH),
|
||||||
|
timeout=timeout,
|
||||||
|
check_same_thread=False,
|
||||||
|
)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
try:
|
||||||
|
busy_ms = max(1, int(timeout * 1000))
|
||||||
|
conn.execute(f"PRAGMA busy_timeout = {busy_ms}")
|
||||||
|
conn.execute("PRAGMA journal_mode=WAL")
|
||||||
|
conn.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
except sqlite3.Error:
|
||||||
|
pass
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
def _worker_db_execute(
|
||||||
|
query: str,
|
||||||
|
params: tuple = (),
|
||||||
|
*,
|
||||||
|
fetch: Optional[str] = None,
|
||||||
|
timeout: float = 0.75,
|
||||||
|
retries: int = 1,
|
||||||
|
) -> Any:
|
||||||
|
attempts = 0
|
||||||
|
while True:
|
||||||
|
conn: Optional[sqlite3.Connection] = None
|
||||||
|
try:
|
||||||
|
conn = _worker_db_connect(timeout=timeout)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute(query, params)
|
||||||
|
if fetch == "one":
|
||||||
|
result = cursor.fetchone()
|
||||||
|
elif fetch == "all":
|
||||||
|
result = cursor.fetchall()
|
||||||
|
else:
|
||||||
|
result = cursor.rowcount
|
||||||
|
conn.commit()
|
||||||
|
return result
|
||||||
|
finally:
|
||||||
|
cursor.close()
|
||||||
|
except sqlite3.OperationalError as exc:
|
||||||
|
msg = str(exc).lower()
|
||||||
|
if "locked" in msg and attempts < retries:
|
||||||
|
attempts += 1
|
||||||
|
time.sleep(0.05 * attempts)
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if conn is not None:
|
||||||
|
try:
|
||||||
|
conn.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def insert_worker(worker_id: str, worker_type: str, title: str = "", description: str = "") -> bool:
|
def insert_worker(worker_id: str, worker_type: str, title: str = "", description: str = "") -> bool:
|
||||||
try:
|
try:
|
||||||
db.execute(
|
_worker_db_execute(
|
||||||
"INSERT INTO workers (id, type, title, description, status) VALUES (?, ?, ?, ?, 'running')",
|
"INSERT INTO workers (id, type, title, description, status) VALUES (?, ?, ?, ?, 'running')",
|
||||||
(worker_id, worker_type, title, description)
|
(worker_id, worker_type, title, description),
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception("Failed to insert worker %s: %s", worker_id, exc)
|
logger.warning("Failed to insert worker %s: %s", worker_id, exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_worker(worker_id: str, **kwargs) -> bool:
|
def update_worker(worker_id: str, **kwargs) -> bool:
|
||||||
@@ -559,20 +616,20 @@ def update_worker(worker_id: str, **kwargs) -> bool:
|
|||||||
vals.append(worker_id)
|
vals.append(worker_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.execute(query, tuple(vals))
|
_worker_db_execute(query, tuple(vals))
|
||||||
return True
|
return True
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception("Failed to update worker %s: %s", worker_id, exc)
|
logger.warning("Failed to update worker %s: %s", worker_id, exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def append_worker_stdout(worker_id: str, content: str, channel: str = 'stdout'):
|
def append_worker_stdout(worker_id: str, content: str, channel: str = 'stdout'):
|
||||||
try:
|
try:
|
||||||
db.execute(
|
_worker_db_execute(
|
||||||
"INSERT INTO worker_stdout (worker_id, channel, content) VALUES (?, ?, ?)",
|
"INSERT INTO worker_stdout (worker_id, channel, content) VALUES (?, ?, ?)",
|
||||||
(worker_id, channel, content)
|
(worker_id, channel, content),
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception("Failed to append worker stdout for %s", worker_id)
|
logger.warning("Failed to append worker stdout for %s: %s", worker_id, exc)
|
||||||
|
|
||||||
def get_worker_stdout(worker_id: str, channel: Optional[str] = None) -> str:
|
def get_worker_stdout(worker_id: str, channel: Optional[str] = None) -> str:
|
||||||
query = "SELECT content FROM worker_stdout WHERE worker_id = ?"
|
query = "SELECT content FROM worker_stdout WHERE worker_id = ?"
|
||||||
@@ -582,20 +639,30 @@ def get_worker_stdout(worker_id: str, channel: Optional[str] = None) -> str:
|
|||||||
params.append(channel)
|
params.append(channel)
|
||||||
query += " ORDER BY timestamp ASC"
|
query += " ORDER BY timestamp ASC"
|
||||||
|
|
||||||
rows = db.fetchall(query, tuple(params))
|
rows = _worker_db_execute(query, tuple(params), fetch="all") or []
|
||||||
return "\n".join(row['content'] for row in rows)
|
return "\n".join(row['content'] for row in rows)
|
||||||
|
|
||||||
def get_active_workers() -> List[Dict[str, Any]]:
|
def get_active_workers() -> List[Dict[str, Any]]:
|
||||||
rows = db.fetchall("SELECT * FROM workers WHERE status = 'running' ORDER BY created_at DESC")
|
rows = _worker_db_execute(
|
||||||
|
"SELECT * FROM workers WHERE status = 'running' ORDER BY created_at DESC",
|
||||||
|
fetch="all",
|
||||||
|
) or []
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
def get_worker(worker_id: str) -> Optional[Dict[str, Any]]:
|
def get_worker(worker_id: str) -> Optional[Dict[str, Any]]:
|
||||||
row = db.fetchone("SELECT * FROM workers WHERE id = ?", (worker_id,))
|
row = _worker_db_execute(
|
||||||
|
"SELECT * FROM workers WHERE id = ?",
|
||||||
|
(worker_id,),
|
||||||
|
fetch="one",
|
||||||
|
)
|
||||||
return dict(row) if row else None
|
return dict(row) if row else None
|
||||||
|
|
||||||
def expire_running_workers(older_than_seconds: int = 300, status: str = 'error', reason: str = 'timeout') -> int:
|
def expire_running_workers(older_than_seconds: int = 300, status: str = 'error', reason: str = 'timeout') -> int:
|
||||||
# SQLITE doesn't have a simple way to do DATETIME - INTERVAL, so we'll use strftime/unixepoch if available
|
# SQLITE doesn't have a simple way to do DATETIME - INTERVAL, so we'll use strftime/unixepoch if available
|
||||||
# or just do regular update for all running ones for now as a simple fallback
|
# or just do regular update for all running ones for now as a simple fallback
|
||||||
query = f"UPDATE workers SET status = ?, error_message = ? WHERE status = 'running'"
|
query = f"UPDATE workers SET status = ?, error_message = ? WHERE status = 'running'"
|
||||||
db.execute(query, (status, reason))
|
try:
|
||||||
|
_worker_db_execute(query, (status, reason), timeout=0.5, retries=0)
|
||||||
|
except Exception:
|
||||||
|
return 0
|
||||||
return 0 # We don't easily get the rowcount from db.execute right now
|
return 0 # We don't easily get the rowcount from db.execute right now
|
||||||
|
|||||||
@@ -684,6 +684,30 @@ class HydrusNetwork(Store):
|
|||||||
continue
|
continue
|
||||||
return ids_out, hashes_out
|
return ids_out, hashes_out
|
||||||
|
|
||||||
|
def _fetch_search_metadata(
|
||||||
|
*,
|
||||||
|
file_ids: Optional[Sequence[Any]] = None,
|
||||||
|
hashes: Optional[Sequence[Any]] = None,
|
||||||
|
include_tags: bool = True,
|
||||||
|
include_urls: bool = True,
|
||||||
|
include_mime: bool = True,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
try:
|
||||||
|
payload = client.fetch_file_metadata(
|
||||||
|
file_ids=file_ids,
|
||||||
|
hashes=hashes,
|
||||||
|
include_service_keys_to_tags=include_tags,
|
||||||
|
include_file_url=include_urls,
|
||||||
|
include_duration=False,
|
||||||
|
include_size=True,
|
||||||
|
include_mime=include_mime,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
metadata = payload.get("metadata", []) if isinstance(payload, dict) else []
|
||||||
|
return metadata if isinstance(metadata, list) else []
|
||||||
|
|
||||||
def _iter_url_filtered_metadata(
|
def _iter_url_filtered_metadata(
|
||||||
url_value: str | None,
|
url_value: str | None,
|
||||||
want_any: bool,
|
want_any: bool,
|
||||||
@@ -927,6 +951,55 @@ class HydrusNetwork(Store):
|
|||||||
|
|
||||||
return metas_out[:fetch_limit]
|
return metas_out[:fetch_limit]
|
||||||
|
|
||||||
|
def _cap_metadata_candidates(
|
||||||
|
file_ids_in: list[int],
|
||||||
|
hashes_in: list[str],
|
||||||
|
*,
|
||||||
|
requested_limit: Any,
|
||||||
|
freeform_mode: bool = False,
|
||||||
|
fallback_scan: bool = False,
|
||||||
|
) -> tuple[list[int], list[str]]:
|
||||||
|
"""Cap metadata hydration to a sane subset of Hydrus hits.
|
||||||
|
|
||||||
|
Hydrus native tag search is fast, but fetching metadata for every
|
||||||
|
matched file can explode for broad queries. Keep the native search,
|
||||||
|
but only hydrate a bounded working set and let downstream filtering
|
||||||
|
stop once enough display rows are collected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
base_limit = int(requested_limit or 100)
|
||||||
|
except Exception:
|
||||||
|
base_limit = 100
|
||||||
|
if base_limit <= 0:
|
||||||
|
base_limit = 100
|
||||||
|
|
||||||
|
hydrate_limit = base_limit
|
||||||
|
if freeform_mode:
|
||||||
|
hydrate_limit = max(hydrate_limit * 4, 200)
|
||||||
|
if fallback_scan:
|
||||||
|
hydrate_limit = max(hydrate_limit * 2, 200)
|
||||||
|
hydrate_limit = min(hydrate_limit, 1000)
|
||||||
|
|
||||||
|
ids_out = list(file_ids_in or [])
|
||||||
|
hashes_out = list(hashes_in or [])
|
||||||
|
total_candidates = len(ids_out) + len(hashes_out)
|
||||||
|
if total_candidates <= hydrate_limit:
|
||||||
|
return ids_out, hashes_out
|
||||||
|
|
||||||
|
debug(
|
||||||
|
f"{prefix} limiting metadata hydration to {hydrate_limit} of {total_candidates} candidate(s)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if ids_out:
|
||||||
|
ids_out = ids_out[:hydrate_limit]
|
||||||
|
remaining = max(0, hydrate_limit - len(ids_out))
|
||||||
|
hashes_out = hashes_out[:remaining] if remaining > 0 else []
|
||||||
|
else:
|
||||||
|
hashes_out = hashes_out[:hydrate_limit]
|
||||||
|
|
||||||
|
return ids_out, hashes_out
|
||||||
|
|
||||||
query_lower = query.lower().strip()
|
query_lower = query.lower().strip()
|
||||||
|
|
||||||
# Support `ext:<value>` anywhere in the query. We filter results by the
|
# Support `ext:<value>` anywhere in the query. We filter results by the
|
||||||
@@ -1172,7 +1245,7 @@ class HydrusNetwork(Store):
|
|||||||
payloads.append(
|
payloads.append(
|
||||||
client.search_files(
|
client.search_files(
|
||||||
tags=title_predicates,
|
tags=title_predicates,
|
||||||
return_hashes=True,
|
return_hashes=False,
|
||||||
return_file_ids=True,
|
return_file_ids=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1187,7 +1260,7 @@ class HydrusNetwork(Store):
|
|||||||
payloads.append(
|
payloads.append(
|
||||||
client.search_files(
|
client.search_files(
|
||||||
tags=[f"title:{query_lower}*"],
|
tags=[f"title:{query_lower}*"],
|
||||||
return_hashes=True,
|
return_hashes=False,
|
||||||
return_file_ids=True,
|
return_file_ids=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1198,7 +1271,7 @@ class HydrusNetwork(Store):
|
|||||||
payloads.append(
|
payloads.append(
|
||||||
client.search_files(
|
client.search_files(
|
||||||
tags=freeform_predicates,
|
tags=freeform_predicates,
|
||||||
return_hashes=True,
|
return_hashes=False,
|
||||||
return_file_ids=True,
|
return_file_ids=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1206,15 +1279,12 @@ class HydrusNetwork(Store):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
id_set: set[int] = set()
|
id_set: set[int] = set()
|
||||||
hash_set: set[str] = set()
|
|
||||||
for payload in payloads:
|
for payload in payloads:
|
||||||
ids_part, hashes_part = _extract_search_ids(payload)
|
ids_part, _ = _extract_search_ids(payload)
|
||||||
for fid in ids_part:
|
for fid in ids_part:
|
||||||
id_set.add(fid)
|
id_set.add(fid)
|
||||||
for hh in hashes_part:
|
|
||||||
hash_set.add(hh)
|
|
||||||
file_ids = list(id_set)
|
file_ids = list(id_set)
|
||||||
hashes = list(hash_set)
|
hashes = []
|
||||||
else:
|
else:
|
||||||
if not tags:
|
if not tags:
|
||||||
debug(f"{prefix} 0 result(s)")
|
debug(f"{prefix} 0 result(s)")
|
||||||
@@ -1222,10 +1292,11 @@ class HydrusNetwork(Store):
|
|||||||
|
|
||||||
search_result = client.search_files(
|
search_result = client.search_files(
|
||||||
tags=tags,
|
tags=tags,
|
||||||
return_hashes=True,
|
return_hashes=False,
|
||||||
return_file_ids=True
|
return_file_ids=True
|
||||||
)
|
)
|
||||||
file_ids, hashes = _extract_search_ids(search_result)
|
file_ids, _ = _extract_search_ids(search_result)
|
||||||
|
hashes = []
|
||||||
|
|
||||||
# Fast path: ext-only search. Avoid fetching metadata for an unbounded
|
# Fast path: ext-only search. Avoid fetching metadata for an unbounded
|
||||||
# system:everything result set; fetch in chunks until we have enough.
|
# system:everything result set; fetch in chunks until we have enough.
|
||||||
@@ -1242,21 +1313,13 @@ class HydrusNetwork(Store):
|
|||||||
if len(results) >= limit:
|
if len(results) >= limit:
|
||||||
break
|
break
|
||||||
chunk = file_ids[start:start + chunk_size]
|
chunk = file_ids[start:start + chunk_size]
|
||||||
try:
|
metas = _fetch_search_metadata(
|
||||||
payload = client.fetch_file_metadata(
|
|
||||||
file_ids=chunk,
|
file_ids=chunk,
|
||||||
include_service_keys_to_tags=True,
|
include_tags=True,
|
||||||
include_file_url=True,
|
include_urls=True,
|
||||||
include_duration=True,
|
|
||||||
include_size=True,
|
|
||||||
include_mime=True,
|
include_mime=True,
|
||||||
)
|
)
|
||||||
except Exception:
|
if not metas:
|
||||||
continue
|
|
||||||
metas = payload.get("metadata",
|
|
||||||
[]) if isinstance(payload,
|
|
||||||
dict) else []
|
|
||||||
if not isinstance(metas, list):
|
|
||||||
continue
|
continue
|
||||||
for meta in metas:
|
for meta in metas:
|
||||||
if len(results) >= limit:
|
if len(results) >= limit:
|
||||||
@@ -1312,26 +1375,27 @@ class HydrusNetwork(Store):
|
|||||||
debug(f"{prefix} 0 result(s)")
|
debug(f"{prefix} 0 result(s)")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
file_ids, hashes = _cap_metadata_candidates(
|
||||||
|
file_ids,
|
||||||
|
hashes,
|
||||||
|
requested_limit=limit,
|
||||||
|
freeform_mode=freeform_union_search,
|
||||||
|
)
|
||||||
|
|
||||||
if file_ids:
|
if file_ids:
|
||||||
metadata = client.fetch_file_metadata(
|
metadata_list = _fetch_search_metadata(
|
||||||
file_ids=file_ids,
|
file_ids=file_ids,
|
||||||
include_service_keys_to_tags=True,
|
include_tags=True,
|
||||||
include_file_url=True,
|
include_urls=True,
|
||||||
include_duration=True,
|
|
||||||
include_size=True,
|
|
||||||
include_mime=True,
|
include_mime=True,
|
||||||
)
|
)
|
||||||
metadata_list = metadata.get("metadata", [])
|
|
||||||
elif hashes:
|
elif hashes:
|
||||||
metadata = client.fetch_file_metadata(
|
metadata_list = _fetch_search_metadata(
|
||||||
hashes=hashes,
|
hashes=hashes,
|
||||||
include_service_keys_to_tags=True,
|
include_tags=True,
|
||||||
include_file_url=True,
|
include_urls=True,
|
||||||
include_duration=True,
|
|
||||||
include_size=True,
|
|
||||||
include_mime=True,
|
include_mime=True,
|
||||||
)
|
)
|
||||||
metadata_list = metadata.get("metadata", [])
|
|
||||||
else:
|
else:
|
||||||
metadata_list = []
|
metadata_list = []
|
||||||
|
|
||||||
@@ -1341,31 +1405,34 @@ class HydrusNetwork(Store):
|
|||||||
try:
|
try:
|
||||||
search_result = client.search_files(
|
search_result = client.search_files(
|
||||||
tags=["system:everything"],
|
tags=["system:everything"],
|
||||||
return_hashes=True,
|
return_hashes=False,
|
||||||
return_file_ids=True,
|
return_file_ids=True,
|
||||||
)
|
)
|
||||||
file_ids, hashes = _extract_search_ids(search_result)
|
file_ids, _ = _extract_search_ids(search_result)
|
||||||
|
hashes = []
|
||||||
|
|
||||||
|
file_ids, hashes = _cap_metadata_candidates(
|
||||||
|
file_ids,
|
||||||
|
hashes,
|
||||||
|
requested_limit=limit,
|
||||||
|
freeform_mode=True,
|
||||||
|
fallback_scan=True,
|
||||||
|
)
|
||||||
|
|
||||||
if file_ids:
|
if file_ids:
|
||||||
metadata = client.fetch_file_metadata(
|
metadata_list = _fetch_search_metadata(
|
||||||
file_ids=file_ids,
|
file_ids=file_ids,
|
||||||
include_service_keys_to_tags=True,
|
include_tags=True,
|
||||||
include_file_url=True,
|
include_urls=True,
|
||||||
include_duration=True,
|
|
||||||
include_size=True,
|
|
||||||
include_mime=True,
|
include_mime=True,
|
||||||
)
|
)
|
||||||
metadata_list = metadata.get("metadata", [])
|
|
||||||
elif hashes:
|
elif hashes:
|
||||||
metadata = client.fetch_file_metadata(
|
metadata_list = _fetch_search_metadata(
|
||||||
hashes=hashes,
|
hashes=hashes,
|
||||||
include_service_keys_to_tags=True,
|
include_tags=True,
|
||||||
include_file_url=True,
|
include_urls=True,
|
||||||
include_duration=True,
|
|
||||||
include_size=True,
|
|
||||||
include_mime=True,
|
include_mime=True,
|
||||||
)
|
)
|
||||||
metadata_list = metadata.get("metadata", [])
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,60 @@ class _WorkerLogger:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _truncate_worker_text(value: Any, max_len: int = 120) -> str:
|
||||||
|
text = str(value or "").strip()
|
||||||
|
if len(text) <= max_len:
|
||||||
|
return text
|
||||||
|
if max_len <= 3:
|
||||||
|
return text[:max_len]
|
||||||
|
return f"{text[:max_len - 3].rstrip()}..."
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_worker_result(item: Dict[str, Any]) -> str:
|
||||||
|
title = (
|
||||||
|
item.get("title")
|
||||||
|
or item.get("name")
|
||||||
|
or item.get("path")
|
||||||
|
or item.get("url")
|
||||||
|
or item.get("hash")
|
||||||
|
or "Result"
|
||||||
|
)
|
||||||
|
details: list[str] = []
|
||||||
|
|
||||||
|
store_val = str(item.get("store") or item.get("source") or "").strip()
|
||||||
|
if store_val:
|
||||||
|
details.append(store_val)
|
||||||
|
|
||||||
|
ext_val = str(item.get("ext") or item.get("mime") or "").strip()
|
||||||
|
if ext_val:
|
||||||
|
details.append(ext_val)
|
||||||
|
|
||||||
|
hash_val = str(
|
||||||
|
item.get("hash") or item.get("file_hash") or item.get("hash_hex") or ""
|
||||||
|
).strip()
|
||||||
|
if hash_val:
|
||||||
|
details.append(hash_val[:12])
|
||||||
|
|
||||||
|
suffix = f" [{' | '.join(details)}]" if details else ""
|
||||||
|
return f"- {_truncate_worker_text(title)}{suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_worker_results(results: Sequence[Dict[str, Any]], preview_limit: int = 8) -> str:
|
||||||
|
count = len(results)
|
||||||
|
lines = [f"{count} result(s)"]
|
||||||
|
if count <= 0:
|
||||||
|
return lines[0]
|
||||||
|
|
||||||
|
for item in results[:preview_limit]:
|
||||||
|
lines.append(_summarize_worker_result(item))
|
||||||
|
|
||||||
|
remaining = count - min(count, preview_limit)
|
||||||
|
if remaining > 0:
|
||||||
|
lines.append(f"... {remaining} more")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
class search_file(Cmdlet):
|
class search_file(Cmdlet):
|
||||||
"""Class-based search-file cmdlet for searching storage backends."""
|
"""Class-based search-file cmdlet for searching storage backends."""
|
||||||
|
|
||||||
@@ -1127,7 +1181,7 @@ class search_file(Cmdlet):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
append_worker_stdout(worker_id, json.dumps([], indent=2))
|
append_worker_stdout(worker_id, _summarize_worker_results([]))
|
||||||
update_worker(worker_id, status="completed")
|
update_worker(worker_id, status="completed")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -1195,7 +1249,7 @@ class search_file(Cmdlet):
|
|||||||
ctx.set_current_stage_table(table)
|
ctx.set_current_stage_table(table)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
append_worker_stdout(worker_id, json.dumps(results_list, indent=2))
|
append_worker_stdout(worker_id, _summarize_worker_results(results_list))
|
||||||
update_worker(worker_id, status="completed")
|
update_worker(worker_id, status="completed")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -1498,7 +1552,7 @@ class search_file(Cmdlet):
|
|||||||
if not results:
|
if not results:
|
||||||
log(f"No results found for query: {query}", file=sys.stderr)
|
log(f"No results found for query: {query}", file=sys.stderr)
|
||||||
try:
|
try:
|
||||||
append_worker_stdout(worker_id, json.dumps([], indent=2))
|
append_worker_stdout(worker_id, _summarize_worker_results([]))
|
||||||
update_worker(worker_id, status="completed")
|
update_worker(worker_id, status="completed")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -1534,7 +1588,7 @@ class search_file(Cmdlet):
|
|||||||
ctx.set_current_stage_table(table)
|
ctx.set_current_stage_table(table)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
append_worker_stdout(worker_id, json.dumps(results_list, indent=2))
|
append_worker_stdout(worker_id, _summarize_worker_results(results_list))
|
||||||
update_worker(worker_id, status="completed")
|
update_worker(worker_id, status="completed")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -2034,8 +2088,7 @@ class search_file(Cmdlet):
|
|||||||
ctx.set_last_result_table(table, results_list)
|
ctx.set_last_result_table(table, results_list)
|
||||||
db.append_worker_stdout(
|
db.append_worker_stdout(
|
||||||
worker_id,
|
worker_id,
|
||||||
json.dumps(results_list,
|
_summarize_worker_results(results_list)
|
||||||
indent=2)
|
|
||||||
)
|
)
|
||||||
db.update_worker_status(worker_id, "completed")
|
db.update_worker_status(worker_id, "completed")
|
||||||
return 0
|
return 0
|
||||||
@@ -2047,7 +2100,7 @@ class search_file(Cmdlet):
|
|||||||
ctx.set_last_result_table_preserve_history(table, [])
|
ctx.set_last_result_table_preserve_history(table, [])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
db.append_worker_stdout(worker_id, json.dumps([], indent=2))
|
db.append_worker_stdout(worker_id, _summarize_worker_results([]))
|
||||||
db.update_worker_status(worker_id, "completed")
|
db.update_worker_status(worker_id, "completed")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -2259,8 +2312,7 @@ class search_file(Cmdlet):
|
|||||||
ctx.set_last_result_table(table, results_list)
|
ctx.set_last_result_table(table, results_list)
|
||||||
db.append_worker_stdout(
|
db.append_worker_stdout(
|
||||||
worker_id,
|
worker_id,
|
||||||
json.dumps(results_list,
|
_summarize_worker_results(results_list)
|
||||||
indent=2)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log("No results found", file=sys.stderr)
|
log("No results found", file=sys.stderr)
|
||||||
@@ -2270,7 +2322,7 @@ class search_file(Cmdlet):
|
|||||||
ctx.set_last_result_table_preserve_history(table, [])
|
ctx.set_last_result_table_preserve_history(table, [])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
db.append_worker_stdout(worker_id, json.dumps([], indent=2))
|
db.append_worker_stdout(worker_id, _summarize_worker_results([]))
|
||||||
|
|
||||||
db.update_worker_status(worker_id, "completed")
|
db.update_worker_status(worker_id, "completed")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
Reference in New Issue
Block a user