dfd
This commit is contained in:
343
MPV/LUA/main.lua
343
MPV/LUA/main.lua
@@ -4,7 +4,7 @@ local msg = require 'mp.msg'
|
||||
|
||||
local M = {}
|
||||
|
||||
local MEDEIA_LUA_VERSION = '2026-03-22.1'
|
||||
local MEDEIA_LUA_VERSION = '2026-03-22.3'
|
||||
|
||||
-- Expose a tiny breadcrumb for debugging which script version is loaded.
|
||||
pcall(mp.set_property, 'user-data/medeia-lua-version', MEDEIA_LUA_VERSION)
|
||||
@@ -750,6 +750,121 @@ local _refresh_current_store_url_status
|
||||
local _skip_next_store_check_url = ''
|
||||
local _pick_folder_windows
|
||||
|
||||
function M._load_store_choices_direct_async(cb)
|
||||
cb = cb or function() end
|
||||
|
||||
local refresh_state = M._store_direct_refresh_state or { inflight = false, callbacks = {} }
|
||||
M._store_direct_refresh_state = refresh_state
|
||||
|
||||
if refresh_state.inflight then
|
||||
table.insert(refresh_state.callbacks, cb)
|
||||
_lua_log('stores: direct config load join existing request')
|
||||
return
|
||||
end
|
||||
|
||||
local python = _resolve_python_exe(false)
|
||||
if not python or python == '' then
|
||||
cb(nil, 'no python executable available')
|
||||
return
|
||||
end
|
||||
|
||||
local repo_root = _detect_repo_root()
|
||||
if not repo_root or repo_root == '' then
|
||||
cb(nil, 'repo root not found')
|
||||
return
|
||||
end
|
||||
|
||||
local function finish(resp, err)
|
||||
local callbacks = refresh_state.callbacks or {}
|
||||
refresh_state.callbacks = {}
|
||||
refresh_state.inflight = false
|
||||
for _, pending_cb in ipairs(callbacks) do
|
||||
pcall(pending_cb, resp, err)
|
||||
end
|
||||
end
|
||||
|
||||
local bootstrap = table.concat({
|
||||
'import json, os, sys',
|
||||
'root = sys.argv[1]',
|
||||
'if root:',
|
||||
' os.chdir(root)',
|
||||
' sys.path.insert(0, root) if root not in sys.path else None',
|
||||
'from SYS.logger import set_thread_stream',
|
||||
'set_thread_stream(sys.stderr)',
|
||||
'from SYS.config import load_config',
|
||||
'from Store.registry import list_configured_backend_names',
|
||||
'config = load_config()',
|
||||
'choices = list_configured_backend_names(config) or []',
|
||||
'sys.stdout.write(json.dumps({"choices": choices}, ensure_ascii=False))',
|
||||
}, '\n')
|
||||
|
||||
refresh_state.inflight = true
|
||||
refresh_state.callbacks = { cb }
|
||||
_lua_log('stores: spawning direct config scan python=' .. tostring(python) .. ' root=' .. tostring(repo_root))
|
||||
|
||||
mp.command_native_async(
|
||||
{
|
||||
name = 'subprocess',
|
||||
args = { python, '-c', bootstrap, repo_root },
|
||||
capture_stdout = true,
|
||||
capture_stderr = true,
|
||||
playback_only = false,
|
||||
},
|
||||
function(success, result, err)
|
||||
if not success then
|
||||
finish(nil, tostring(err or 'subprocess failed'))
|
||||
return
|
||||
end
|
||||
|
||||
if type(result) ~= 'table' then
|
||||
finish(nil, 'invalid subprocess result')
|
||||
return
|
||||
end
|
||||
|
||||
local status = tonumber(result.status or 0) or 0
|
||||
local stdout = trim(tostring(result.stdout or ''))
|
||||
local stderr = trim(tostring(result.stderr or ''))
|
||||
if status ~= 0 then
|
||||
local detail = stderr
|
||||
if detail == '' then
|
||||
detail = tostring(result.error or ('direct store scan exited with status ' .. tostring(status)))
|
||||
end
|
||||
finish(nil, detail)
|
||||
return
|
||||
end
|
||||
|
||||
if stdout == '' then
|
||||
local detail = stderr
|
||||
if detail == '' then
|
||||
detail = 'direct store scan returned no output'
|
||||
end
|
||||
finish(nil, detail)
|
||||
return
|
||||
end
|
||||
|
||||
local ok, resp = pcall(utils.parse_json, stdout)
|
||||
if ok and type(resp) == 'string' then
|
||||
ok, resp = pcall(utils.parse_json, resp)
|
||||
end
|
||||
if not ok or type(resp) ~= 'table' then
|
||||
local stdout_preview = stdout
|
||||
if #stdout_preview > 200 then
|
||||
stdout_preview = stdout_preview:sub(1, 200) .. '...'
|
||||
end
|
||||
local stderr_preview = stderr
|
||||
if #stderr_preview > 200 then
|
||||
stderr_preview = stderr_preview:sub(1, 200) .. '...'
|
||||
end
|
||||
_lua_log('stores: direct config parse failed stdout=' .. tostring(stdout_preview) .. ' stderr=' .. tostring(stderr_preview))
|
||||
finish(nil, 'failed to parse direct store scan output')
|
||||
return
|
||||
end
|
||||
|
||||
finish(resp, nil)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
local function _normalize_store_name(store)
|
||||
store = trim(tostring(store or ''))
|
||||
store = store:gsub('^"', ''):gsub('"$', '')
|
||||
@@ -2715,7 +2830,34 @@ end
|
||||
|
||||
_pick_folder_windows = function()
|
||||
-- Native folder picker via PowerShell + WinForms.
|
||||
local ps = [[Add-Type -AssemblyName System.Windows.Forms; $d = New-Object System.Windows.Forms.FolderBrowserDialog; $d.Description = 'Select download folder'; $d.ShowNewFolderButton = $true; if ($d.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $d.SelectedPath }]]
|
||||
local ps = [[
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
$owner = New-Object System.Windows.Forms.Form
|
||||
$owner.Text = 'medeia-folder-owner'
|
||||
$owner.StartPosition = [System.Windows.Forms.FormStartPosition]::Manual
|
||||
$owner.Location = New-Object System.Drawing.Point(-32000, -32000)
|
||||
$owner.Size = New-Object System.Drawing.Size(1, 1)
|
||||
$owner.ShowInTaskbar = $false
|
||||
$owner.TopMost = $true
|
||||
$owner.Opacity = 0
|
||||
$owner.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedToolWindow
|
||||
$owner.Add_Shown({ $owner.Activate() })
|
||||
$null = $owner.Show()
|
||||
$d = New-Object System.Windows.Forms.FolderBrowserDialog
|
||||
$d.Description = 'Select download folder'
|
||||
$d.ShowNewFolderButton = $true
|
||||
try {
|
||||
if ($d.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) {
|
||||
$d.SelectedPath
|
||||
}
|
||||
} finally {
|
||||
$d.Dispose()
|
||||
$owner.Close()
|
||||
$owner.Dispose()
|
||||
}
|
||||
]]
|
||||
local res = utils.subprocess({
|
||||
-- Hide the PowerShell console window (dialog still shows).
|
||||
args = { 'powershell', '-NoProfile', '-WindowStyle', 'Hidden', '-STA', '-ExecutionPolicy', 'Bypass', '-Command', ps },
|
||||
@@ -2763,108 +2905,61 @@ _refresh_store_cache = function(timeout_seconds, on_complete)
|
||||
local prev_key = _store_names_key(_cached_store_names)
|
||||
local had_previous = prev_count > 0
|
||||
|
||||
local cached_json = mp.get_property('user-data/medeia-store-choices-cached')
|
||||
_lua_log('stores: cache_read cached_json=' .. tostring(cached_json) .. ' len=' .. tostring(cached_json and #cached_json or 0))
|
||||
local function apply_store_choices(resp, source)
|
||||
if not resp or type(resp) ~= 'table' or type(resp.choices) ~= 'table' then
|
||||
_lua_log('stores: ' .. tostring(source) .. ' result missing choices table; resp_type=' .. tostring(type(resp)))
|
||||
return false
|
||||
end
|
||||
|
||||
if cached_json and cached_json ~= '' then
|
||||
local function handle_cached(resp)
|
||||
if not resp or type(resp) ~= 'table' or type(resp.choices) ~= 'table' then
|
||||
_lua_log('stores: cache_parse result missing choices table; resp_type=' .. tostring(type(resp)))
|
||||
return false
|
||||
local out = {}
|
||||
for _, v in ipairs(resp.choices) do
|
||||
local name = trim(tostring(v or ''))
|
||||
if name ~= '' then
|
||||
out[#out + 1] = name
|
||||
end
|
||||
|
||||
local out = {}
|
||||
for _, v in ipairs(resp.choices) do
|
||||
local name = trim(tostring(v or ''))
|
||||
if name ~= '' then
|
||||
out[#out + 1] = name
|
||||
end
|
||||
end
|
||||
if #out == 0 and had_previous then
|
||||
_lua_log('stores: ignoring empty cache payload; keeping previous store list')
|
||||
if type(on_complete) == 'function' then
|
||||
on_complete(true, false)
|
||||
end
|
||||
return true
|
||||
end
|
||||
_cached_store_names = out
|
||||
_store_cache_loaded = (#out > 0) or _store_cache_loaded
|
||||
local preview = ''
|
||||
if #out > 0 then
|
||||
preview = table.concat(out, ', ')
|
||||
end
|
||||
_lua_log('stores: loaded ' .. tostring(#out) .. ' stores from cache: ' .. tostring(preview))
|
||||
end
|
||||
if #out == 0 and had_previous then
|
||||
_lua_log('stores: ignoring empty ' .. tostring(source) .. ' payload; keeping previous store list')
|
||||
if type(on_complete) == 'function' then
|
||||
on_complete(true, _store_names_key(out) ~= prev_key)
|
||||
on_complete(true, false)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local ok, cached_resp = pcall(utils.parse_json, cached_json)
|
||||
_lua_log('stores: cache_parse ok=' .. tostring(ok) .. ' resp_type=' .. tostring(type(cached_resp)))
|
||||
if ok then
|
||||
if type(cached_resp) == 'string' then
|
||||
_lua_log('stores: cache_parse returned string, trying again...')
|
||||
ok, cached_resp = pcall(utils.parse_json, cached_resp)
|
||||
_lua_log('stores: cache_parse retry ok=' .. tostring(ok) .. ' resp_type=' .. tostring(type(cached_resp)))
|
||||
end
|
||||
if ok then
|
||||
if handle_cached(cached_resp) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
_lua_log('stores: cache_parse failed ok=' .. tostring(ok) .. ' resp=' .. tostring(cached_resp))
|
||||
_cached_store_names = out
|
||||
_store_cache_loaded = (#out > 0) or _store_cache_loaded
|
||||
|
||||
local payload = utils.format_json({ choices = out })
|
||||
if type(payload) == 'string' and payload ~= '' then
|
||||
pcall(mp.set_property, 'user-data/medeia-store-choices-cached', payload)
|
||||
end
|
||||
else
|
||||
_lua_log('stores: cache_empty cached_json=' .. tostring(cached_json))
|
||||
|
||||
local preview = ''
|
||||
if #out > 0 then
|
||||
preview = table.concat(out, ', ')
|
||||
end
|
||||
_lua_log('stores: loaded ' .. tostring(#out) .. ' stores from ' .. tostring(source) .. ': ' .. tostring(preview))
|
||||
if type(on_complete) == 'function' then
|
||||
on_complete(true, _store_names_key(out) ~= prev_key)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if not _is_pipeline_helper_ready() then
|
||||
if not _store_cache_retry_pending then
|
||||
_store_cache_retry_pending = true
|
||||
_lua_log('stores: helper not ready; deferring dynamic store refresh')
|
||||
attempt_start_pipeline_helper_async(function(success)
|
||||
_lua_log('stores: deferred helper start success=' .. tostring(success))
|
||||
end)
|
||||
mp.add_timeout(1.0, function()
|
||||
_store_cache_retry_pending = false
|
||||
_refresh_store_cache(timeout_seconds, on_complete)
|
||||
end)
|
||||
else
|
||||
_lua_log('stores: helper not ready; refresh already deferred')
|
||||
local function request_helper_store_choices()
|
||||
if not _is_pipeline_helper_ready() then
|
||||
_lua_log('stores: helper not ready; skipping helper store refresh fallback')
|
||||
if type(on_complete) == 'function' then
|
||||
on_complete(false, false)
|
||||
end
|
||||
return false
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
_lua_log('stores: requesting store-choices via helper (fallback)')
|
||||
_run_helper_request_async({ op = 'store-choices' }, math.max(timeout_seconds or 0, 6.0), function(resp, err)
|
||||
local success = false
|
||||
local changed = false
|
||||
if resp and resp.success and type(resp.choices) == 'table' then
|
||||
local out = {}
|
||||
for _, v in ipairs(resp.choices) do
|
||||
local name = trim(tostring(v or ''))
|
||||
if name ~= '' then
|
||||
out[#out + 1] = name
|
||||
end
|
||||
_lua_log('stores: requesting store-choices via helper fallback')
|
||||
_run_helper_request_async({ op = 'store-choices' }, math.max(timeout_seconds or 0, 6.0), function(resp, err)
|
||||
if apply_store_choices(resp, 'helper fallback') then
|
||||
return
|
||||
end
|
||||
if #out == 0 and had_previous then
|
||||
_lua_log('stores: helper returned empty store list; keeping previous store list')
|
||||
success = true
|
||||
changed = false
|
||||
else
|
||||
_cached_store_names = out
|
||||
_store_cache_loaded = (#out > 0) or _store_cache_loaded
|
||||
local preview = ''
|
||||
if #out > 0 then
|
||||
preview = table.concat(out, ', ')
|
||||
end
|
||||
_lua_log('stores: loaded ' .. tostring(#out) .. ' stores via helper request: ' .. tostring(preview))
|
||||
success = true
|
||||
changed = (#out ~= prev_count) or (_store_names_key(out) ~= prev_key)
|
||||
end
|
||||
else
|
||||
|
||||
_lua_log(
|
||||
'stores: failed to load store choices via helper; success='
|
||||
.. tostring(resp and resp.success or false)
|
||||
@@ -2875,11 +2970,51 @@ _refresh_store_cache = function(timeout_seconds, on_complete)
|
||||
.. ' error='
|
||||
.. tostring(resp and resp.error or err or '')
|
||||
)
|
||||
if type(on_complete) == 'function' then
|
||||
on_complete(false, false)
|
||||
end
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
local cached_json = mp.get_property('user-data/medeia-store-choices-cached')
|
||||
_lua_log('stores: cache_read cached_json=' .. tostring(cached_json) .. ' len=' .. tostring(cached_json and #cached_json or 0))
|
||||
|
||||
if cached_json and cached_json ~= '' then
|
||||
local ok, cached_resp = pcall(utils.parse_json, cached_json)
|
||||
_lua_log('stores: cache_parse ok=' .. tostring(ok) .. ' resp_type=' .. tostring(type(cached_resp)))
|
||||
if ok then
|
||||
if type(cached_resp) == 'string' then
|
||||
_lua_log('stores: cache_parse returned string, trying again...')
|
||||
ok, cached_resp = pcall(utils.parse_json, cached_resp)
|
||||
_lua_log('stores: cache_parse retry ok=' .. tostring(ok) .. ' resp_type=' .. tostring(type(cached_resp)))
|
||||
end
|
||||
if ok then
|
||||
if apply_store_choices(cached_resp, 'cache') then
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
_lua_log('stores: cache_parse failed ok=' .. tostring(ok) .. ' resp=' .. tostring(cached_resp))
|
||||
end
|
||||
if type(on_complete) == 'function' then
|
||||
on_complete(success, changed)
|
||||
end
|
||||
end)
|
||||
else
|
||||
_lua_log('stores: cache_empty cached_json=' .. tostring(cached_json))
|
||||
end
|
||||
|
||||
if not _store_cache_retry_pending then
|
||||
_store_cache_retry_pending = true
|
||||
M._load_store_choices_direct_async(function(resp, err)
|
||||
_store_cache_retry_pending = false
|
||||
if apply_store_choices(resp, 'direct config') then
|
||||
return
|
||||
end
|
||||
|
||||
_lua_log('stores: direct config load failed error=' .. tostring(err or 'unknown'))
|
||||
request_helper_store_choices()
|
||||
end)
|
||||
else
|
||||
_lua_log('stores: direct config refresh already pending')
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -3271,22 +3406,8 @@ _refresh_current_store_url_status = function(reason)
|
||||
|
||||
local generation = _begin_current_store_url_status(store, url, 'pending')
|
||||
if not _is_pipeline_helper_ready() then
|
||||
_lua_log('store-check: helper not ready; deferring reason=' .. tostring(reason) .. ' store=' .. tostring(store))
|
||||
attempt_start_pipeline_helper_async(function(success)
|
||||
if _current_store_url_status.generation ~= generation then
|
||||
return
|
||||
end
|
||||
if not success then
|
||||
_set_current_store_url_status(store, url, 'error', 'helper not running')
|
||||
return
|
||||
end
|
||||
mp.add_timeout(0.1, function()
|
||||
if _current_store_url_status.generation ~= generation then
|
||||
return
|
||||
end
|
||||
_refresh_current_store_url_status((reason ~= '' and (reason .. '-retry')) or 'retry')
|
||||
end)
|
||||
end)
|
||||
_lua_log('store-check: helper not ready; skipping lookup reason=' .. tostring(reason) .. ' store=' .. tostring(store))
|
||||
_set_current_store_url_status(store, url, 'idle')
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user